多进程编程

来源:互联网 发布:xp系统分区软件 编辑:程序博客网 时间:2024/06/01 09:00

多进程编程包括以下内容:

一、复制进程映像fork系统调用和替换进程映像的系统调用。

二、僵尸进程以及如何处理僵尸进程;

三、进程间的通信机制(inter-process Communication),最简单的是管道;

四、3种systemV进程间的通信方式:信号量,消息队列,和共享内存;

(1)fork系统调用

1、创建新进程的系统调用是fork pid_t fork(void),该函数调用一次返回俩次,在父进程中返回的是子进程的PID,在子进程中返回的是0,所以可以根据返回值判断是父进程还是子进程;

2、fork函数是复制当前进程,在内核中创建一个新的进程表项,这个进程表项的一些属性和父进程相同,堆指针,栈指针以及局部变量,也有一些许多属性被赋予了新的值,比如说子进程的PPID变成原来进程的PID,原来进程的信。信号处理函数在子进程中失效;

3、子进程会复制父进程的代码,也会复制进程的数据,复制数据采用的是写时拷贝。

4、此外,在父进程中打开的文件描述符在默认子进程也是打开的,这些文件描述符的引用计数加1。

(2)僵尸进程

有2种使子进程变成僵尸进程情况:1、在子进程退出之后,父进程读取他的退出状态之前,我们称子进程为僵尸进程;2、在子进程还未退出,父进程结束,或者异常终止,这样的子进程是僵尸进程;

有俩个函数:pid_t  wait(int *stat_loc); pid_t  waitpid(pid_t  pid,int *stat_loc,int options),这俩个函数在父进程中调用,用来等待子进程的结束,并获取子进程返回信息。

1、pid_t   wait(int * stat_loc) 是一个阻塞函数,只有该进程的某一个子进程结束的时候才返回,返回结束子进程的PID,并且把该子进程的退出状态返回给stat_loc参数指向内存中,

2、waitpid(),是一个非阻塞函数,他可以等待指定PID的子进程的退出,如果此时PID是-1,那么它就和wait函数一样等待任意一个子进程的结束,之所以说他是一个非阻塞的函数,是因为有一个参数options参数,如果该参数指定为WNOHANG,在子进程未结束或者异常终止的时候,他立即返回0,但是如果子进程确实正常退出,他就返回子进程的PID,

我们知道为了提高程序的效率,我们希望的是在事情已经发生的情况下调用非阻塞函数,所以我们最好在子进程退出之后调用waitpid,那么我们如何判断一个子进程/退出了呢?SIGCHLD,在子进程结束时候,他给父进程发送一个SIGCHLD信号,我们可以在父进程中捕获此信号,然后在对子进程处理,彻底结束子进程。

(2)PIPE(管道)

管道能在父,子进程间传递数据,利用的是fork调用之后俩个管道文件描述符(fd[1] ,fd[0])都保持打开,一对这样的文件描述符只能保障父子进程间一个方向的数据传输,父 ,子进程必须有一个关闭fd[0],另一个关闭fd[1].显然要实现父子进程之间的双向数据传输,就必须使用俩个管道,否则只能实现单项数据的传输。socketpair创建的管道,是双向的管道,数据既可在fd[0]读,也可以在fd[1]读,但是pipe只能在fd[0]读,fd[1]写,socketpair创建的管道,fd[1]也可以被用读,fd[0]也可以写。

#include<sys/types.h> #include<sys/socket.h>#include<unistd.h>#include<iostream>#include<stdio.h>#include<stdlib.h>#include<string.h>using namespace std;int main(){    int ret;    pid_t pid;    int fd[2];    char buf[256];//    ret = socketpair(AF_UNIX,SOCK_STREAM,0,fd); //socketpair俩边都可以    pipe(fd);    pid =fork();    if(pid == 0)    {       // close(fd[0]);       //read(fd[1],buf,256);//这样无法读出数据       char *str="hello world";       close(fd[0]);        write(fd[1],str,strlen(str));//这样才可以;        printf("chid:%s\n",buf);    }    else if(pid > 0)    {    // char *str="hello word";    // close(fd[1]);    // write(fd[0],str,strlen(str));    char buf[256]={0};    close(fd[1]);    read(fd[0],buf,256);    printf("%s\n",buf);    }}

管道缓存区一有空闲区域,写进程就会试图向管道写入数据,如果读进程不读走管道缓存区的数据,那么写一直被阻塞等待,在写管道时,如果要求写的字节数小于等于PIPE_BUF,则多个进程对同一个管道的写操作不会较错进行。


(3)信号量

1、信号量可以确保在任意时刻关键代码段(临界区)只有一个进程在访问。

2、信号量可以取自然数值,并且只支持俩种操作(等待,信号)也就是(p,v)操作。他有一组函数分别是senget,semopt,semctl

3、原子操作:不会被线程调度机制打断的操作,这种操作一旦开始执行,就一直运行到结束,中间不会切换到任何线程。

4、为什么不适用一个普通的变量来模拟二进制信号量,因为所有的高级语言都没有一个原子操作可以同时完成来个不走:检查变量是否为true/false,如果是在将他更改为false/true;

5、semget用来获取一个信号量集,或者获取一个已经存在的信号量集;semopt对信号量的操作实际是修改这些内核变量的修改。有一些重要的内核变量(unsigned short semval,unsigned short semzcnt ,unsigned short semncnt,pid_t  semid);semop(int sem_id,struct sembuf* sem_ops,size_t num_sem_ops);struct sembuf{unsigned short int sem_num;short int sem_op;short int sem_falg};

sem_op 和sem_flag按照如下方式来影响semop:当

(1)sem_op > 0,semop将被操作的信号的值sem_value增加semop.如果设置了SEM_UNDO,则系统更新semadj变量(会跟踪进程对信号量的修改)当进程退出时取消正在进行的semop的操作;

(2)当sem_op=0;如果semvalue是0,则调用成功立即返回。如果信号量的值不为0,如果说设置了sem_falg = IPC_NOWAIT,则sem_op立即返回并设置error.如果未指定标志位,则semop被投入睡眠,直到有以下三件事发生:第一,信号量的值semvalue变为0;第二,被操作的信号量所在的信号量集被进程移除,调用被信号中断。

(3)sem_op < 0,进行的是P操作,如果当前的信号量的绝对值大于等于sem_op,那么调用进程获得该信号量。如果信号量的值小于sem_op的值,调用进程将会被挂起,直到信号量的值大于等于了sem_op的值。

(4)消息队列

消息队列可以在俩个进程之间进行数据的传送,但是他不像管道那样,只能先进先被接收端收,它的数据被 打包为一组结构体msgbuf;

struct msgbuf

{

long mytype;

char mtext[512];

};

根据mytype确定接受哪先数据:
当mstype>0时,接收端接受消息队列中的第一个mutype的消息;

当mstype=0时,接收端接受消息队列中第一个消息。

当mstype<0时,读取消息队列中第一个比mstype绝对值小的那个消息。

(5)共享内存

共享内存是最高效的IPC机制,但是为了在任何时刻保证只有一个进程访问该数据,所以一般讲共享内存和信号量一起使用。

原创粉丝点击