操作系统中核心的概念就是进程:这是对正在运行程序的一个抽象。
一个进程就是某种类型的一个活动,它有程序、输入、输出、以及状态。单个处理器可以被若干进程共享,它使用某种调度算法进行进程的调度。注意:如果一个程序运行了两遍,就是两个进程。
#include <sys/types.h> #include <unistd.h> pid_t fork(void); 返回:每次调用返回2次,父进程中返回子进程PID,在子进程中返回0,出错返回-1fork函数复制当前进程,在内核进程表中创建一个新的进程表项。新的进程表项去多属性和原进程的相同,比如堆指针、栈指针和标志寄存器的值。也有许多新的属性被更改,比如该进程的PPID设置为远进程的PID,信号为徒被清除(远近程设置的信号处理函数不再对新进程起作用)
#include <unistd.h> extern char **environ; 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[]);
path参数指定可执行文件的完整路径,file参数可以接收文件名,该文件的具体位置则在环境变量中PATH中寻找。arg接受可变参数,argv接受参数数组,他们都会被传递给新程序的main函数。envp用于设置新程序的环境变量,如果未设置,则新程序将使用environ指定的环境变量。
一般情况下,exec函数是不返回的,除非出错。出错时返回-1,并设置errno,调用成功后原程序中调用exec之后的代码都不会执行,因为此时源程序已经被exec的参数指定的程序完全替换了(代码和数据)。
exec函数不会关闭源程序中打开的文件描述符,除非该文件描述符被设置了SOCK_CLOEXEC
对于多进程程序而言,父进程一般需要跟踪子进程的退出状态。因此,当子进程结束运行时,内核不会立即释放该进程的进程表表项,以满足父进程后续对该子进程退出信息的查询。在子进程结束运行之后,父进程读取器退出状态之前,我们称该子进程处于僵尸态。若父进程结束或者异常终止,而子进程继续运行,此时子进程的PPID将被系统设置为1,即init进程。init进程接管了该子进程,并等待它结束。
子进程停留在僵尸态,占据内核资源,这是绝对不允许的,毕竟内核资源有限。下面这对函数在父进程中调用,以等待子进程结束,并获取子进程的返回信息,从而避免了僵尸进程的产生,或者使子进程的僵尸态立即结束:
#include<sys/types.h> #include<sys/wait.h> pid_t wait(int*status); pid_twaitpid(pid_t pid, int *status, int options);
wait函数将阻塞进程,直到该进程的某个子进程结束运行为止。它返回结束运行的子进程的PID,并将子进程的退出状态信息存储于status参数指向的内存中。
waitpid只等待由pid参数指定的子进程,如果pid取值为-1,则和wait函数相同,即等待任意一个子进程结束。options的参数可以控制waitpid的行为,当该参数取值为WNOHANG时,waitpid将是非阻塞的:如果pid指定的目标子进程还没有结束或意外终止,则waitpid立即返回0;如果目标子进程确实正常退出了,则waitpid返回该子进程的PID。失败返回-1,并设置errno。
要在事件已经发生的情况下执行非阻塞调用才能提高程序的效率。对waitpid函数而言,我们最好在某个子进程退出之后再调用它。那么父进程从何得知某个子进程已经退出了?这正是SIGCHLD信号的用途。当一个进程结束时,它将给其父进程发送一个SIGCHLD信号。因此,我们可以在父进程中捕获SIGCHLD信号,并在信号处理函数中调用waitpid函数以彻底结束一个子进程。如下所示:
static void handle_child( int sig ) { pid_t pid; int stat; while((pid = waitpid(-1, &stat, WNOHANG)) > 0) { /* 对结束的子进程进行善后处理 */ } }
管道是父子进程之间通信的常用手段,管道能在父、子进程之间传递数据,利用的是fork调用之后两个管道文件描述符(fd[0]和fd[1])都保持打开。
管道有pipe函数创建,此时管道是单向的,pipe函数讲解见:网络编程API-中 (高级I/O函数)
管道程序示例:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #define err_sys(msg) do { perror(msg); exit(-1); } while(0) int main(void) { int pipefd[2]; pid_t pid; if(pipe(pipefd) < 0) err_sys("pipe"); if((pid= fork()) < 0) err_sys("fork"); else if(pid == 0) { char buf[10] = {0}; close(pipefd[1]); read(pipefd[0], buf, sizeof(buf)); //当管道中没有数据时,read会阻塞 printf("In child process:\n"); printf(" %s\n", buf); exit(0); } else { close(pipefd[0]); sleep(1); write(pipefd[1], "hello", strlen("hello")); wait(NULL); } return 0; }
参考:
1、《Linux高性能服务器编程》 9章 多进程编程/管道
原文地址:http://blog.csdn.net/u012796139/article/details/46681393