多进程编程

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

一、复制进程映像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机制,但是为了在任何时刻保证只有一个进程访问该数据,所以一般讲共享内存和信号量一起使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值