Linux操作系统|【Linux】进程间通信(匿名管道、命名管道、共享内存)


进程间通信

  • 引言
  • 进程通信
  • 进程间通信发展
  • 进程间通信分类
  • 管道
    • 匿名管道
    • 命名管道
    • 共享内存
  • 信号量

引言 通信的本质是传递数据,是互相的。
进程间不能直接相互传递数据,进程具有独立性,所有的数据操作,都会发生写时拷贝,一定要通过中间媒介的方式来进行通信。
进程间通信的本质:让不同的进程先看到同一份资源(内存空间)。
进程通信 目的:
数据传输:一个进程需要将它的数据发送给另一个进程。
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
进程间通信发展 1.管道
2.System V进程间通信
3.POSIX进程间通信
进程间通信分类 【Linux操作系统|【Linux】进程间通信(匿名管道、命名管道、共享内存)】管道:
1.匿名管道pipe
2.命名管道
System V IPC
1.System V消息内存
2.System V共享内存
3.System V信号量
POSIX IPC
1.消息队列
2.共享内存
3.信号量
4.互斥量
5.条件变量
6.读写锁
管道 Linux操作系统|【Linux】进程间通信(匿名管道、命名管道、共享内存)
文章图片

匿名管道 供具有血缘关系的进程,进行进程间通信。(常见于父子)
管道只能进行单向数据通信。
因此,父子进程关闭不需要的文件描述符,来达到构建单向通信的信道的目的。
Linux操作系统|【Linux】进程间通信(匿名管道、命名管道、共享内存)
文章图片

为什么曾经要打开?
不打开读写,子进程拿到的文件打开方式必定和父进程一样,无法通信。
建立管道代码:
#include #include #include #include #include int main() { int pipe_fd[2] = {0}; if(pipe(pipe_fd) < 0) { perror("pipe"); return 1; } printf("%d", %d\n",pipe_fd[0],pipe_fd[1]); pid_t id = fork(); if(id<0) { perror("fork"); return 2; } else if(id == 0) { //关闭读 close(pipe_fd[0]); const char *msg = "hello parent ,i am child"; int count =5; while(count) { write(pipe_fd[1],msg,strlen(msg)); sleep(1); count--; } close(pipe_fd[1]); exit(0); } else { //关闭写 close(pipe_fd[1]); char buffer[64]; while(1) { buffer[0] = 0; ssize_t size = read(pipe_fd[0],buffer,sizeof(buffer) - 1); if(size>0) { buffer[size] = 0; printf("parent get message from child# %s\n",buffer); } else if(size == 0) { printf("pipe file close,child quit!\n"); break; } else { //........... } } int status = 0; if(waitpid(id,&status,0)>0) { printf("child quit,wait success!\n"); } close(pipe_fd[0]); } return 0; }

进程间同步:
如果管道里面没有消息,父进程(读端)在等待,等管道内部有数据就绪(子进程写入)。
如果管道里面写端已经写满了。继续写入是不能写的,它在等待,等待管道内部有空闲空间(父进程读走)。
管道本身就是一个文件。
管道的特性:
1.管道自带同步机制!
2.管道是单向通信的!
3.管道是面向字节流的!
4.管道只能保证是具有血缘关系的进程进行通信,常用于父子进程。
5.管可以保证一定程度的数据读取的原子性!
进程退出,曾经打开的文件也会被关掉。管道也是文件,管道的生命周期随进程!
读取关闭,一直写,毫无意义。本质就是在浪费系统资源,写进程会立马被OS终止掉!
Linux操作系统|【Linux】进程间通信(匿名管道、命名管道、共享内存)
文章图片

命名管道 不相关的进程之间进行进程间通信叫做命名管道。
匿名管道:父子共享文件的特征
命名管道:文件路径具有唯一性,让进程看到同一个文件
共享内存 进程间通信的本质:要先让不同的进程看到同一份资源!
1.OS申请一块物理内存空间
2.OS将该内存映射进对应进程的共享区中(堆栈之间)
3.OS可以将映射之后的虚拟地址返回给用户
1.申请共享内存
2.进程1和进程2分别挂接对应的共享内存到自己的地址空间(共享区)
3.双方就看到了同一份资源!即可以进行正常通信了!
操作系统内部提供了通信机制的(IPC)ipc模块
查看共享内存:ipcs -m
所有的ipc资源都是随内核的,不随进程。
删除共享内存:ipcrm -m shmid号
//创建key key_t k = ftok(PATH_NAME,PROJ_ID); //申请共享内存 int shmid = shmget(k,SIZE,IPC_CREAT|IPC_EXCL); //共享内存如果不存在,创建;如果存在,出错返回。 //释放共享内存 shmctl(shmid,IPC_RMID,NULL); //将当前进程和共享内存进行关联 char* start = (char*)shmat(shmid,NULL,0); //将当前进程和共享内存去关联 shmdt(start);

1.共享内存的生命周期随OS
2.共享内存不提供任何同步与互斥的操作,双方彼此独立
3.共享内存是所有的进程间通信中,速度最快的
共享内存的大小:系统在分配shm的时候,是按照4kb为基本单位的。
key:是一个用户层生成的唯一键值,核心作用是为了区分“唯一性”,不能用来进行IPC资源的操作!
shmid:是一个系统给我们返回的IPC资源标识符,用来进行操作IPC资源。
类比:
key,文件的inode号
shmid,文件的fd
信号量 1.让进程看到同一份资源(内存空间),这份资源叫做临界资源。
2.进程内的所有代码,不是全部的代码都在访问临界资源,而是只有一部分在访问。可能造成数据不一致问题的是这部分少量的代码。(临界区代码)
3.为了避免数据不一致,保护临界资源,需要对临界区代码进行某种保护。(方法叫做互斥)
4.互斥:一部分空间任何时候,有且只能有一个进程在进行访问。串行化的执行(锁,二元信号量)
5.加锁和解锁是有对应的代码的。本质:对临界区进行加锁和解锁,完成互斥操作。
信号量:本质是一个计数器。(用来描述临界资源中,资源数目的计数器)
申请信号量:P操作(计算器减减)
释放信号量:V操作 (计数器加加)
PV操作的伪代码:
P: begin: Lock(); if(count<=0) { goto begin; } else { count--; } Unlock(); //内部访问临界资源。V: Lock(); count++; Unlock();

1.多个进程不能操作同一个count值
2.信号量是保护临界资源的安全性
信号量本身就是一个临界资源。
1.信号量,多进程环境下,如何保证信号量被多个进程看到?
semget,semctl,ftok() ---->Key ----->IPC
2.如果信号量的计数器的值是:1
则为1,0两种结果,二元信号量---->就是一种互斥语义。

    推荐阅读