博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
进程间通信(一)
阅读量:4870 次
发布时间:2019-06-11

本文共 3695 字,大约阅读时间需要 12 分钟。

1.进程是一种单执行流,每个进程都私有/独占一份系统资源,代码段共享但是数据段不共享,子进程写数据时会触发写实拷贝,从而保证资源独享。总的来说 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到 。
 所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间 拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信 如图所示:
 

 

进程间通信(IPC,InterProcess Communication)的方式:
1.管道:匿名管道、命名管道
2. :消息队列、信号量、共享内存
XSI IPC,依托标识符和键来实现的,如同管道靠文件描述符来实现一样。
XSI IPC使用一般步骤:
1)IPC对象进程内部用标识符identifier,进程外部标识用key
2)首先用semget,shmget,msgget等函数根据key创建或获取IPC对象的identifier
3)然后根据identifier用semctrl,shmctrl等控制函数做某些修改,这步可选
4)最后用各个IPC特有操作函数如semop,shmat等函数操作)
 

 

 
 
 
匿名管道(pipe)
调⽤用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后 通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向 管道 的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe函数调用成功返回0,调用失败返回-1。

 

 

1. 父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。 
2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。 
3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信
 
 
int Test1()         //Test Creat IPC Fun "pipe()"
{
           int _pipe[2];   //two file describe sign
           int ret = pipe(_pipe);
           if (ret == -1)
          {
                   perror( "create pipe error" );
                    return 1;
          }
          pid_t id = fork();
           if (id < 0)
          {
                   perror( "create fork error" );
                    return 1;
          }
           else if (id == 0)         //child only write,father only read
          { //child
                   close(_pipe[0]);
                    int i=0;
                    char * msg = "hello world" ;
                    while (i<5)
                   {
                             write(_pipe[1],msg,strlen(msg));
                             sleep(1);
                             ++i;
                   }
          }
           else
          { //father
                   close(_pipe[1]);
                    int i=0;
                    char buf[1024];
                    while (i<5)
                   {
                             read(_pipe[0],buf, sizeof (buf)-1);
                             printf( "%s\n" ,buf);
                             ++i;
                   }
          }
           return 0;
}
 
使用管道有一些限制:
两个进程通过一个管道只能实现单向通信。比如上面的例子,父进程写子进程读,如果有时候也需要子进程写父进程读,就必须另开一个管道。(原因:
管道是一种半双工方式,即对于进程来说,要么只能读管道,要么只能写管道。不允许对管道又读又写)<半双工数据传输允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输>
 
管道的读写端通过打开的文件描述符来传递,因此要通信的两个进程必须从它们的公共祖先 那⾥里继承管道文件描述符。
父进程fork两次,把文件描述符传给两个子进程,然后两个子进程之间通信, 总之需要通过fork传递文件描述符使两个进程都能访问同一管道,它们才能通信。也就是说,管道通信是需要进程之间有血缘关系。
 
⽤用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):  
1. 如果所有指向管道写端的文件描述符都关闭了(管道写端的引⽤用计数等于0),而仍然有进程 从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像 读到文件 末尾一样。
2. 如果有指向管道写端的文件描述符没关闭(管道写端的引用计数⼤大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回 
3. 如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。
4. 如果有指向管道读端的文件描述符没关闭(管道读端的引⽤用计数⼤大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。
 
 
 
 
命名管道(FIFO) 文件系统中的路径名是全局的,各进程都可以访问,因此可以⽤用文件系统中的路径名来标识一 个IPC通道。  命名管道也被称为FIFO文件,它是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但是它的行为却和之前所讲的没有名字的管道(匿名管道)类似。
由于Linux中所有的事物都可被视为文件,所以对命名管道的使用也就变得与文件操作非常的统一,也使它的使用非常方便,同时我们也可以像平常的文件名一样在命令中使用。 创建命名管道 我们可以使用两下函数之一来创建一个命名管道,他们的原型如下:
#include <sys/types.h>  
#include <sys/stat.h>   
int mkfifo(const char *filename, mode_t mode);  
int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0);  
这两个函数都能创建一个FIFO文件,注意是创建一个真实存在于文件系统中的文件, filename指定了文件名,而mode则指定了文件的读写权限。
mknod是比较老的函数,而使 用mkfifo函数更加简单和规范,所以建议在可能的情况下,尽量使⽤用mkfifo而不是mknod。
 
 
//client.c
int main()
{
           int fp = open(_PATH_,O_RDONLY);
           if (fp < 0)
          {
                   perror( "open file fail" );
                    return -1;
          }
           char buf[_SIZE_];
          memset(buf, '\0' ,sizeof (buf));
           while (1)
          {
                    int ret = read(fp,buf,sizeof (buf)-1);
                    if (ret <= 0)
                   {
                             perror( "read fail of read over" );
                              return -1;
                   }
                   printf( "%s\n" ,buf);
                    if (strncmp(buf,"quit" ,4) == 0)
                              break ;
          }
          close(fp);
           return 0;
}
 
 
 
//server.c
int main()
{
           int ret = mkfifo(_PATH_,0644);
           if (ret == -1)
          {
                   perror( "mkfifo" );
                    return -1;
          }
 
           int fp = open(_PATH_,O_WRONLY);
           if (fp == -1)
          {
                   perror( "open file" );
                    return -2;
          }
           char buf[_SIZE_];
          memset(buf, '\0' ,sizeof (buf));
           while (1)
          {
                   scanf( "%s" ,buf);
                    int ret = write(fp,buf,strlen(buf)+1);
           //       sleep(5);
                    if (ret <= 0)
                   {
                             perror( "write error" );
                              return -3;
                   }
                    if (strncmp(buf,"quit" ,4) == 0)
                              break ;
          }
          close(fp);
           return 0;
}
 

转载于:https://www.cnblogs.com/shihaochangeworld/p/5706747.html

你可能感兴趣的文章
OC基础9:预处理程序
查看>>
uva 11181 - Probability|Given
查看>>
【 D3.js 入门系列 --- 9.6 】 生产的包图
查看>>
request.getparameter和 request.getattribute的差别
查看>>
Bulk Insert命令具体
查看>>
Redhat Linux下的python版本号升级
查看>>
多功能检測按键-3 按键扫描 单按 长按 多个按键 响应方式
查看>>
mysql 用户管理 pymysql模块
查看>>
exit,_exit,wait,waitpid
查看>>
VUE2开发实战——搜索功能
查看>>
Codeforces997D Cycles in product 【FFT】【树形DP】
查看>>
基于Linux系统--web环境搭建
查看>>
gridview 根据条件更改链接的可用和颜色
查看>>
10.26会议记录
查看>>
SpringBoot用SpringAOP实现页面访问日志功能
查看>>
C# 中的"yield"使用
查看>>
(27)zabbix自定义图表Graph
查看>>
学生和老师思考问题角度的区别
查看>>
通过反射,给对象之间赋值
查看>>
Unity2.0学习笔记-Unity2.0基础-如何配置Unity2.0容器-设计时配置
查看>>