Linux多进程编程

来源:互联网 发布:gcc怎么用windows 编辑:程序博客网 时间:2024/06/05 19:23

操作系统中核心的概念就是进程:这是对正在运行程序的一个抽象。
一个进程就是某种类型的一个活动,它有程序、输入、输出、以及状态。单个处理器可以被若干进程共享,它使用某种调度算法进行进程的调度。注意:如果一个程序运行了两遍,就是两个进程。


进程创建 fork

#include <sys/types.h>#include <unistd.h>pid_t fork(void);返回:每次调用返回2次,父进程中返回子进程PID,在子进程中返回0,出错返回-1
        fork函数复制当前进程,在内核进程表中创建一个新的进程表项。新的进程表项去多属性和原进程的相同,比如堆指针、栈指针和标志寄存器的值。也有许多新的属性被更改,比如该进程的PPID设置为远进程的PID,信号为徒被清除(远近程设置的信号处理函数不再对新进程起作用)


exec系列函数

#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章 多进程编程/管道

        2、网络编程API-中 (高级I/O函数)

0 0
原创粉丝点击