操作系统 --- 进程通信 IPC Inter Process Communication
为什么需要进程通信
进程通信的方法
pipeline
什么是pipeline
- pipe是一块内存,被视为"virual file" (实际不是file 不在文件系统中, 而是存在于内存之中的一块缓冲区)
- pipe是单向通信,pipe的一端连接输出进程(read end),一端连接输入进程(write end),
- read end和write end是两个file descriptor(数据类型是int)
- 一般会创建一个int数组: fd[2], fd[0]代表read end, fd[1]代表write end
- 主要用于父进程和子进程之间,或者有共同祖先的两个进程, 因为需要父进程创建pipe
- 进程结束后,pipe被自动释放
shell中的pipe
- 在shell中 “ | ” 代表pipeline
- 可以将 “|" 前面的commond处理完的数据传给后面的command
创建过程
- 一个进程创建完pipe之后,write end和read end都在同一进程里
- 然后这个进程fork一个子进程,此时子进程也同时拥有write end和read end
- 父进程关掉其中一端,子进程关掉另外一端,实现单向通信
pipe的同步机制
- 管道自带同步互斥机制:
管道的内核实现:fs/pipe.c ,主要通过内核的锁以及等待队列等机制实现
- 管道的write操作会阻塞进程
当内存缓冲区已满或被读进程锁定,会阻塞write操作
当所有数据被写入管道时write操作才会结束
- 管道的read操作会阻塞进程
当读进程被阻塞时会形成等待队列,并让等待队列进入休眠
当所有子进程关闭管道的写入端的操作时会停止阻塞
父进程的写入端操作关闭时,父进程的读操作会停止阻塞
- 当所有读取端和写入端都关闭后,管道才能被销毁
————————————————
版权声明:本文为CSDN博主「z_stand」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Z_Stand/article/details/100806493
实现pipe通信 — system call: pipe()
父进程和子进程之间
- 创建一个进程A,然后创建pipe
- 进程A fork一个进程B
- 进程A关闭write end
- 进程B关闭read end
实现两个子进程之间的pipe通信
- 创建一个进程A,并创建pipe
- 进程A fork一个子进程B, 子进程B关闭write end
- 进程A 关闭read end,然后fork子进程C,此时子进程C只有read
- 进程A关闭write end,和pipe脱离关系
- 子进程B, C实现了单向通信
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char *argv[]) {
int fds[2];
char buff[60];
int count;
int i;
pipe(fds);
int pid1 = fork();
if (pid1 == 0) {
printf("Child process A %d: Read from the pipe \n", getpid());
//child A close write, and has the read
close(fds[1]);
while ((count=read(fds[0], buff, 60)) > 0) {
for(i=0; i<count; i++) {
write(1, buff+i, 1);
write(1, " ", 1);
}
printf("\n");
}
close(fds[0]);
exit(0);
}
else {
//parent close read
close(fds[0]);
int pid2 = fork();
if (pid2 == 0) {
//child B has the write since read is already closed by its parent
printf("Child process B %d: write to the pipe \n", getpid());
for(i=1; i<argc; i++) {
write(fds[1], argv[i], strlen(argv[i]));
}
exit(0);
}
else {
//parent close write
close(fds[1]);
wait(NULL);
printf("This is the parent process %d\n", getpid());
}
}
exit(0);
}
pipe的缺点
- 没有名称,只能支持有共同祖先的进程之间的通信, 不能支持任意两个进程的通信
- 进程结束之后pipe也被释放
FIFO (Named Pipe)
什么是FIFO or Named Pipe
- 为了解决pipe的缺点,出现了named pipe
- named pipe一般是一个真正的文件,存在于文件系统中,只要操作系统不关闭就一直存在
- FIFO file就是named pipe的实现
- 所有进程可以对FIFO file进行读写操作,就像读写正常文件一样
- FIFO是多向通信
创建一个FIFO file — system call: mkfifo()
- int mkfifo(const char *filename,mode_t mode);
FIFO的同步机制
- FIFO总是处于阻塞状态。
- 当FIFO打开时设置了读权限,则读进程将一直“阻塞”,一直到其他进程打开该FIFO并且向管道中写入数据。这个阻塞动作反过来也是成立的,
- 如果一个进程打开一个管道写入数据,当没有进程从管道中读取数据的时候,写管道的操作也是阻塞的,直到已经写入的数据被读出后,才能进行写入操作。
- 如果不希望在进行命名管道操作的时候发生阻塞,可以在open()调用中使用 O_NONBLOCK标志,以关闭默认的阻塞动作。
————————————————
版权声明:本文为CSDN博主「Change_Improve」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Change_Improve/article/details/98731329
shared memory
什么是shared memory
- 多个进程可以访问同一块内存(正常情况下,进程不能互相访问对方的内存空间)
shared memory原理
- 在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。
————————————————
原文链接:https://blog.csdn.net/ypt523/article/details/79958188
shared memory的问题
- 下图中的读写操作不是原子操作(转换成汇编语言之后不是原子操作), 有可能在写入时读取或者读取时写入
- 需要使用信号量(semaphore) 来实现同步与互斥
使用shared memory
- The ftok function creates a identifier to be used with the IPC functions (semget, shmget, msgget)
- ftok token is valid across the system, so that two or more independent processes have the same access to IPC recourses (shared memory, message queue)
message queue
- message queue是存在内核中的一个链表
- 通过message queue indentifier辨别
- 通过system call操作
- 比shared memory要慢
![在这里插入图片描述](https://img-blog.csdnimg.cn/b679c5aa198f4e5ab069c3cae9e8c93d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBARDAxMDAxMTAxMQ==,size_20,color_FFFFFF,t_70,g_se,x_16)
socket
To be continued
Windows和Linux采用的进程通信的方法
Windows
- memory mapping
- pipelines
- messages
Linux
- pipe (pipe),
- FIFO (named pipe),
- socket (socket),
- SHM (Shared memory),
- MSG queue (Message queue),
- mmap (file map).