您的位置:首页 > 运维架构 > Linux

Linux多进程编程

2015-06-29 13:46 585 查看
操作系统中核心的概念就是进程:这是对正在运行程序的一个抽象。

一个进程就是某种类型的一个活动,它有程序、输入、输出、以及状态。单个处理器可以被若干进程共享,它使用某种调度算法进行进程的调度。注意:如果一个程序运行了两遍,就是两个进程。

进程创建 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函数)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: