Linux多进程编程

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


进程创建 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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值