Linux内核编程——进程控制、线程控制、锁机制内核编程论坛
1. 过程控制
进程 ID
每个进程都有一个唯一的进程ID,用非负整数表示。进程ID是可重复使用的,当一个进程终止时,它的进程ID也会被其他进程使用。
除了进程ID之外,每个进程还有一些其他可以获取的标识符。
#includepid_t getpid(void); --返回调用进程的进程IDpid_t getppid(void); --返回调用进程的父进程IDuid_t getuid(void); --返回调用进程的实际用户IDuid_t geteuid(void); --返回调用进程的有效用户IDgid_t getgid(void); --返回调用进程的实际组IDgid_t getegid(void); --返回调用进程的有效组ID
*上述get类型的函数也有对应的set函数,比如setuid()、setgid()等。
控制流程的常用函数
1.创建新进程(子进程)--fork/vfork
#includepid_t fork(void);返回:若成功,子进程返回0,父进程返回子进程ID。若失败,返回-1
fork函数被调用一次,但返回两次:子进程返回0,父进程返回子进程的进程ID。
子进程可以调用getppid()函数来获取父进程的进程ID。
由于子进程是父进程的副本,因此子进程可以获得父进程的数据空间、堆和堆栈的副本。
#includepid_t vfork(void);返回:若成功,子进程返回0,父进程返回子进程ID。若失败,返回-1。
vfork与fork的区别在于,vfork不会将父进程的地址空间完全复制到子进程。
vfork创建子进程后,子进程先运行,子进程调用exec或者exit后,父进程开始被调度运行。
2. 暂停进程--wait/waitpid/waitid
#includepid_t wait(int *statloc);pid_t waitpid(pid_t pid, int *statloc, int options);--pid参数取值:pid==-1,等待任一子进程,此时的waitpid()与wait()等效pid>0,等待“进程ID==pid”的子进程pid==0,等待“组ID==调用进程组ID”的子进程pid<-1,等待“组ID==pid的绝对值”的子进程返回:若成功,返回进程ID。若失败,返回0或-1。
父进程调用wait/waitpid之后,将会被挂起,直到子进程终止并返回退出状态给父进程。
父进程也可以使用wait/waitpid来检查子进程是否已经运行完毕。
waitpid与wait的区别:
1.wait返回任意终止的子进程的状态,waitpid可以指定等待的具体进程。
2.waitpid提供了wait的非阻塞版本,可以非阻塞地获取子进程的状态。
3.waitpid可以通过WUNTRACED和WCONTINUED选项支持作业控制。
3.终止进程--exit
#includevoid exit(int status)--status: 进程退出码,主要返回给父进程无返回
4.在进程中启动另一个进程并退出--exec函数族
int execl(const char *path, const char *arg, ...);int execlp(const char *file, const char *arg, ...);int execle(const char *path, const char *arg, ..., char * const envp[]);int execv(const char *path, char *const argv[]);int execvp(const char *file, char *const argv[]);int execve(const char *path, char *const argv[], char *const envp[]);返回:若成功,不做返回。若失败,返回-1。
第一个参数,file与path的区别:当参数为path时,传入的数据是路径名;当参数为file时,传入的数据是可执行文件名。
l:表示以逐个列出的方式传入参数(execl、execle、execlp)
v:表示通过构造指针数组的方式传入参数(execv,execve,execvp)
e:可以传递新的进程环境变量(execle、execve)
p:可执行文件搜索方式为文件名(execlp、execvp)
exec 可以根据指定的文件名或者目录名找到可执行文件,并替换原进程的数据段、代码段和堆栈,exec 执行完之后,原进程中除进程ID之外的内容全部被新进程替换。
5.在进程中运行shell命令--system
#includeint system(const char *cmdstring)
系统调用fork生成子进程,子进程执行shell脚本cmdstring,命令执行完毕后返回到原来调用的父进程。
system函数在系统中的实现:system函数在执行时,会调用fork、execve、waitpid等函数。
int system(const char * cmdstring){pid_t pid;int status;if(cmdstring == NULL){return (1);}if((pid = fork())<0){status = -1;}else if(pid == 0){execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);_exit(127); //exec只在执行失败时才返回原有的进程}else{while(waitpid(pid, &status, 0) < 0){if(errno != EINTR){status = -1;break;}}}return status; //如果waitpid成功,则返回子进程的退出状态}
如果 fork()、exec() 和 waitpid() 都成功,system() 将返回 shell 的终止状态。
代码示例:
#include#include#include#includeusing namespace std;#define NUM_THREADS 5void *wait(void *t) {int i;long tid;tid = (long)t;sleep(1);cout << "Sleeping in thread " << endl;cout << "Thread with id : " << tid << " ...exiting " << endl;pthread_exit(NULL);}int main () {int rc;int i;pthread_t threads[NUM_THREADS];pthread_attr_t attr;void *status;// Initialize and set thread joinablepthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);for( i = 0; i < NUM_THREADS; i++ ) {cout << "main() : creating thread, " << i << endl;rc = pthread_create(&threads[i], &attr, wait, (void *)i );if (rc) {cout << "Error:unable to create thread," << rc << endl;exit(-1);}}// free attribute and wait for the other threadspthread_attr_destroy(&attr);for( i = 0; i < NUM_THREADS; i++ ) {rc = pthread_join(threads[i], &status);if (rc) {cout << "Error:unable to join," << rc << endl;exit(-1);}cout << "Main: completed thread id :" << i ;cout << " exiting with status :" << status << endl;}cout << "Main: program exiting." << endl;pthread_exit(NULL);}
二、线程控制:
同一个进程的所有线程都可以访问该进程的组件,例如文件描述符、内存和进程中的代码。
由于同一个进程内的所有线程共享相同的内存,所以可以方便的在线程间同步信息,而不需要IPC机制。
线程控制编码实现:
POSIX 版本的线程模块--pthread
存储线程属性的结构——pthread_attr_t
typedef struct{__detachstate; --两种状态:detachable和joinable__schedpolicy; --线程的调度策略__schedparam; --线程的调度优先级__inheritsched; --线程的继承策略__scope; --线程的作用域......} pthread_attr_t;
1.获取并比较线程ID--pthread_self/pthread_equal
就像每个进程都有一个进程ID一样,每个线程也有一个对应的线程ID——线程标识符。进程ID的数据类型为pid_t,线程ID的数据类型为pthread_t。
比较两个线程ID是否相等函数--pthread_equal:
#includeint pthread_equal(pthread_t tid1, pthread_t tid2)返回:若相等,返回非0数值。否则,返回0。
获取线程自身的线程ID的函数——pthread_self:
#includepthread_t pthread_self(void)返回:调用线程的线程ID
2.创建线程--pthread_create
#includeint pthread_create(pthread_t *tid,const pthread_attr_t *attr,void * (*start_routine)(void *),void *arg);void * (*start_routine)--指向要线程化的函数的指针返回:若成功,返回0。否则,返回错误编号
进程中的主线程由exec执行创建,其余线程由pthread_create创建。
如果新线程创建成功,则可以通过tid指针返回其线程ID。
pthread_attr_t类型的attr指针指向线程的属性,比如线程优先级,堆栈大小等,如果需要使用默认属性可以将attr参数设置为空指针。
当在线程中封装一个函数func的时候,arg参数用于给函数传递参数。
3.线程终止--pthread_exit/pthread_join/pthread_cancel
如果进程中的任何线程调用 exit、_Exit 或 _exit,整个进程将终止。
有三种方法可以允许单个线程退出而不终止整个进程:
(1)启动线程的函数返回时,返回值为线程的退出码(pthread_create的第三个参数)
(2)该线程被同一进程内的其他线程取消(pthread_cancel)
(3)线程调用pthread_exit
#includevoid pthread_exit(void *status);不向调用者返回
status 是一个void*类型的指针,可以指向任意类型的值,status指向的数据将作为线程退出时的返回值。
pthread_exit相当于执行“线程退出+返回”。
#includeint pthread_join(pthread_t tid, void **status);返回:若成功,返回0。否则,返回错误编号
类似于wait/waitpid,进程被挂起以等待其子进程结束。在线程场景中,可以使用pthread_join来挂起一个线程以等待另一个线程结束并获取其退出状态。
等待线程的退出状态将存储在状态指针指向的位置。
仅当线程未分离(pthread_detach)时,才可以使用pthread_join从另一个线程获取线程id和返回值。
#includeint pthread_cancel(pthread_t tid);返回:若成功,返回0。否则,返回错误编号
仅仅因为一个线程成功地从另一个线程接收到取消信号并不意味着它将终止。
如果线程在收到Cancel信号后终止,则相当于调用“pthread_exit(PTHREAD_CANCELED);”。
4. 分离线程--pthread_detach
#includeint pthread_detach(pthread_t tid);返回:若成功,返回0。否则,返回错误编号
该函数主要用于线程终止后,清理剩余的资源。
当分离线程终止时,该线程的存储资源将被立即回收。
该函数会将线程与主线程的分支分离,当线程结束时,它的退出状态不是被其他线程通过pthread_join获取,而是由线程主动释放。
代码示例:
#include#include#include#include#include#ifndef NUM_THREADS#define NUM_THREADS 8#endifvoid *printHello(void *threadid) {long tid;tid = (long)threadid;printf("Hello from thread %ld, pthread ID - %lu\n", tid, pthread_self());return NULL;}int main(int argc, char const *argv[]) {pthread_t threads[NUM_THREADS];int rc;long t;for (t = 0; t < NUM_THREADS; t++) {rc = pthread_create(&threads[t], NULL, printHello, (void *)t);if (rc) {printf("ERORR; return code from pthread_create() is %d\n", rc);exit(EXIT_FAILURE);}}int ret;for (t = 0; t < NUM_THREADS; t++) {void *retval;ret = pthread_join(threads[t], &retval);if (retval == PTHREAD_CANCELED)printf("The thread was canceled - ");elseprintf("Returned value %d - ", (int)retval);}pthread_exit(NULL);}

3. 锁定机制
1. 互斥锁
互斥锁确保每次只有一个线程可以访问指定的数据。
互斥锁的用法:访问共享资源前先锁定资源,访问完成后再解锁资源。
当一个线程对某个共享资源添加互斥锁之后,其他试图对该共享资源添加互斥锁的线程将会被阻塞,直到当前资源的锁被释放。
使用互斥锁,一次只有一个线程可以访问共享资源。
数据类型:
pthread_mutex_t(使用前必须初始化)
常用功能:
初始化互斥锁——pthread_mutex_init
销毁互斥锁--pthread_mutex_destroy
添加互斥锁--pthread_mutex_lock
释放互斥锁--pthread_mutex_unlock
限时互斥锁
--pthread_mutex_timedlock
加互斥锁(非阻塞模式)
锁:
#includeint pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);int pthread_mutex_destroy(pthread_mutex_t *mutex);pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
#includeint pthread_mutex_lock(pthread_mutex_t *mutex);int pthread_mutex_trylock(pthread_mutex_t *mutex);int pthread_mutex_unlock(pthread_mutex_t *mutex)
*带有“try”关键字的函数,表示如果尝试失败,则返回错误信息,然后再次尝试,。。非阻塞模式。
2.读写锁
读写锁有三种状态:
1. 读取模式下的锁定状态
2. 写模式下的锁定状态
3. 解锁状态
写模式每次只允许一个线程加锁,但是读模式允许多个线程同时加锁。
锁定写模式后,其他尝试锁定写模式或读模式的线程将被阻塞。
锁定读模式后,其他尝试锁定写模式的线程将被阻塞,而锁定读模式的线程将获得访问权限。
在读模式锁定之后,如果有线程试图锁定写模式,那么后续的读模式锁定线程将会被阻塞,这样可以防止读模式锁被长时间占用。
当共享资源的读取次数远大于写入次数时,可以考虑使用读写锁。
写入模式与互斥锁非常相似,每次只有一个线程可以访问它。
数据类型:
锁机制
常用功能:
初始化读写锁--pthread_rwlock_init
销毁读写锁--pthread_rwlock_destroy
读模式锁--pthread_rwlock_rdlock
写模式锁--pthread_rwlock_wrlock
解锁--pthread_rwlock_unlock
限时读锁--pthread_rwlock_timedrdlock
限时写锁--pthread_rwlock_timedwrlock
#includeint pthread_rwlock_init(pthread_rwlock_t *rwlock,const pthread_rwlockattr_t *attr);int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;
#includeint pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
3. 自旋锁
自旋锁与互斥锁的区别在于:自旋锁机制会一直等待直到获取锁,而不会因阻塞导致线程休眠。
如果对共享资源进行短时间锁定,建议使用自旋锁。
数据类型:
锁
常用功能:
初始化自旋锁--pthread_spin_init
销毁自旋锁--pthread_spin_destroy
自旋锁锁定--pthread_spin_lock
锁
自旋锁解锁--pthread_spin_unlock
#includeint pthread_spin_init(pthread_spinlock_t *lock int pshared);int pthread_spin_destroy(pthread_spinlock_t *lock);
#includeint pthread_spin_lock(pthread_spinlock_t *lock);int pthread_spin_trylock(pthread_spinlock_t *lock);int pthread_spin_unlock(pthread_spinlock_t *lock);
4. 条件变量
条件变量和互斥锁配合使用,互斥锁提供互斥机制,条件变量提供信号机制。
条件变量用于阻塞线程。当条件不满足时,线程解锁互斥锁并等待条件满足。当条件满足时,线程重新锁定互斥锁。
条件变量相当于在互斥锁上增加了一个if-else,只有满足if条件的时候才允许加锁。
数据类型:
pthread_cond_t
常用功能:
初始化条件变量--pthread_cond_init
销毁条件变量--pthread_cond_destroy
使用条件变量阻塞当前线程,直到条件满足
--pthread_cond_wait
pthread_cond_timedwait
解除线程阻塞
--pthread_cond_信号
pthread_cond_broadcast
(这里的信号并非操作系统里的“SIGXXX”信号)
#includeint pthread_cond_init(pthread_cond_t *cond,const pthread_condattr_t *attr);int pthread_cond_destroy(pthread_cond_t *cond);pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
#includeint pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex,const struct timespec *abstime);
#includeint pthread_cond_signal(pthread_cond_t *cond);int pthread_cond_broadcast(pthread_cond_t *cond);
代码示例:
#include#include#include#define NUM_THREADS 5typedef struct _thread_data_t {int tid;double stuff;} thread_data_t;/* shared data between threads */double shared_x;pthread_mutex_t lock_x;void *thr_func(void *arg) {thread_data_t *data = (thread_data_t *)arg;printf("hello from thr_func, thread id: %d\n", data->tid);/* get mutex before modifying and printing shared_x */pthread_mutex_lock(&lock_x);shared_x += data->stuff;printf("x = %f\n", shared_x);pthread_mutex_unlock(&lock_x);pthread_exit(NULL);}int main(int argc, char **argv) {pthread_t thr[NUM_THREADS];int i, rc;/* create a thread_data_t argument array */thread_data_t thr_data[NUM_THREADS];/* initialize shared data */shared_x = 0;/* initialize pthread mutex protecting "shared_x" */pthread_mutex_init(&lock_x, NULL);/* create threads */for (i = 0; i < NUM_THREADS; ++i) {thr_data[i].tid = i;thr_data[i].stuff = (i + 1) * NUM_THREADS;if ((rc = pthread_create(&thr[i], NULL, thr_func, &thr_data[i]))) {fprintf(stderr, "error: pthread_create, rc: %d\n", rc);return EXIT_FAILURE;}}/* block until all threads complete */for (i = 0; i < NUM_THREADS; ++i) {pthread_join(thr[i], NULL);}return EXIT_SUCCESS;}
*本次代码分享不提供运行结果,建议使用GDB工具进行调试分析,收益会更大。
进程原语与线程原语的比较:
参考教程:
UNIX 环境高级编程,第 3 版
UNIX 网络编程卷 1:套接字网络 API 第 3 版
UNIX网络编程第2卷:进程间通信第二版