Bootstrap

【Linux】进程间通信

目录

一、为什么要进行进程间通信

二、进程间通信分类

三、管道

 1、管道与文件

2、管道读写规则

3、匿名管道

4、命名管道

5、匿名管道与命名管道的区别

四、共享内存


一、为什么要进行进程间通信

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止 时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另 一个进程的所有陷入和异常,并能够及时知道它的状态改变。

二、进程间通信分类

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

三、管道

管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。

 1、管道与文件

在Linux系统中,一切皆文件。管道也是一种文件,管道的原理就是两个进程打开两个相同的文件,一个以写方式打开,将信息写入管道;一个以读方式打开,从管道中读取信息。

PCB中管道读写端的描述符和文件描述符共用一套系统。

2、管道读写规则

  • 当没有数据可读时
  • O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
  • O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
  • 当管道满的时候
  • O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
  • O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
  • 如果所有管道写端对应的文件描述符被关闭,则read返回0
  • 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

3、匿名管道

匿名管道不需要真的创建一个管道文件,直接复用文件系统中的系统级缓冲区来构筑信道进行通信。但因为没有实体文件,只有两个具有亲代关系的进程可以建立匿名管道,因此其使用具有局限性。

函数调用接口如下:

#include <unistd.h>

int pipe(int pipefd[2]);
// 参数为输出型参数,返回值分别为管道输入输出端口的fd号

实例代码如下:

anonymousPipe · 梁羽赫/Code_in_linux - 码云 - 开源中国 (gitee.com)icon-default.png?t=N7T8https://gitee.com/yuhe-liang/code_in_linux/tree/master/8.anonymousPipe 利用匿名管道创建的进程池:

ProcessPool · 梁羽赫/Code_in_linux - 码云 - 开源中国 (gitee.com)icon-default.png?t=N7T8https://gitee.com/yuhe-liang/code_in_linux/tree/master/9.ProcessPool

4、命名管道

 命名管道需要创建一个实体文件,不同的进程通过访问该文件而建立联系,进行通信。

函数调用接口如下:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode); 
//按指定的权限掩码在指定路径下创建管道文件
//后续使用时按照文件的方法使用即可(open、write函数)

int unlink(const char *pathname);
//删除命名管道

实例代码如下:

fifo · 梁羽赫/Code_in_linux - 码云 - 开源中国 (gitee.com)icon-default.png?t=N7T8https://gitee.com/yuhe-liang/code_in_linux/tree/master/10.fifo

5、匿名管道与命名管道的区别

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完 成之后,它们具有相同的语义。

四、共享内存

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据

 函数调用接口如下:

shmget函数

功能:用来创建共享内存
原型
     int shmget(key_t key, size_t size, int shmflg);
参数
     key:这个共享内存段名字
     size:共享内存大小
     shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

ftok函数

功能:用来生成共享内存的key(不是必须使用此函数,使用此函数可避免key重复)
原型
     key_t ftok(const char *pathname, int proj_id);
参数
     pathname:路径字符串
     proj_id:用户设定的项目编号

shmat函数

功能:将共享内存段连接到进程地址空间
原型
     void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
     shmid: 共享内存标识
     shmaddr:指定连接的地址
     shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
说明
    shmaddr为NULL,核心自动选择一个地址
    shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
    shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
    shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

shmdt函数

功能:将共享内存段与当前进程脱离
原型
     int shmdt(const void *shmaddr);
参数
     shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

功能:用于控制共享内存
原型
     int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
     shmid:由shmget返回的共享内存标识码
     cmd:将要采取的动作(有三个可取值)
     buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

实例代码如下:

Shm · 梁羽赫/Code_in_linux - 码云 - 开源中国 (gitee.com)icon-default.png?t=N7T8https://gitee.com/yuhe-liang/code_in_linux/tree/master/11.Shm

;