进程如果不是独立进程,那么它就需要和别的进程进行通信。在进程协作时可以采用共享一个缓冲区的方式来实现。当然,OS的IPC提供了一种机制,以允许不必通过共享地址空间来通信和同步其动作。这就不得不提Linux的的前身Unix。因为Linux一开始就是从这儿借鉴的。加上Linux从一开始就遵守POSIX标准。
Unix最早是由AT&T的贝尔实验室开发的,值得一提的是,在Unix操作系统发展的过程中,产生了许多副产物(POSIX标准也是副产物之一),其中最著名的应当是C语言。是的,它仅仅是个副产物。那个时候Ken Thompson 与Dennis Ritchie感到用汇编语言做移植太过于头痛,他们想用高级语言来完成第三版。后来他们改造了B语言,就形成了今天大名鼎鼎的C语言。这个自发明到现在这个物联网时代仍占据编程语言榜前10的稳固位置。不得不感叹其生命力的强大以及适应性的强大。当然,Ken Thompson 与Dennis Ritchie也是图灵奖得主。
到了1980年,有两个最主要的Unix的版本线,一个是UC Berkeley的BSD UNIX,另一个是AT&T的Unix。至今为止UC Berkeley仍在维护Unix(这学校真牛逼)。
最初的Unix的IPC包括,管道,FIFO,信号。贝尔实验室对Unix早期的进程通信进行了改进,形成了system V这个操作系统的IPC。它包括:system V消息队列,system V信号灯,system V共享内存。当然POSIX IPC也有相应的一套。BSD Unix设计了socket(套接字)通信。这样将进程之间的通信不仅仅限制在单机内。Linux继承了这些。
进程间通信的目的:
- 数据传输:一个进程将数据发送给另一个进程
- 共享数据:多个进程操作共享数据(比如:售票系统),一个进程对共享数据进行了修改,另外一个进程应该立即看到,(否则票买完了,但是另一边不知道,还在卖)
- 通知:一个进程告诉另外一个进程发生了某些事件。
- 资源共享
- 进程控制:一个进程控制另外一个进程的执行(例如debug程序)。它希望知道另一个进程的实时状态。
Linux进程通信方式:
管道:管道(pipe)分为无名管道和有名管道。无名管道用于具有亲缘关系进程间的通信,有名管道则可以在任意的进程中间进行通信。
管道通信具有以下的特点:
- 管道是半双工的。(双向通信的,但是不能同时向双方传输)
- 只能用于父子进程或者是兄弟进程之间(就是要具有亲缘关系)
- 管道是一种文件(能读写),它只存在于内存之中。他是具有亲缘关系的进程共享的。
- 写入的内容每次都添加到管道缓冲区的末尾,并且每次都是从缓冲区的头部读取数据。
Linux建立无名管道函数是pipe函数。它需要的头文件是#include<unistd.h>.
函数原型:int pipe(int filedes[2]);
函数功能:pipe建立一个无名管道文件,若成功返回0,否则返回-1.错误原因由errno给出。管道文件的描述符由filedes数组返回。其中filedes[0]为管道的读取端,filedes[1]为写入端。
代码测试如下:
#include<unistd.h> #include<errno.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<wait.h> #include<linux/limits.h> //这个头文件中有PIPE_BUF int main() { int filedes[2]; //保存管道文件的文件描述符 char str[30] = {"Hello World!"}; char temp[30] = {0}; if(0 != pipe(filedes)) //创建管道失败 { printf("errno=%d\n",errno); return 0; } if(0 < fork()) //父进程 { close(filedes[0]); //为避免不必要的错误,关闭读端 write(filedes[1],str,strlen(str)); close(filedes[1]); wait(NULL); //回收子进程 exit(0); } else { sleep(3); //让父进程先执行 close(filedes[1]); //为避免不必要的错误,关闭写端 read(filedes[0],temp,strlen(str)); close(filedes[0]); printf("%s\n",temp); exit(0); } return 0; }
在读写管道文件的时候,最好是严格遵守文件的读写规则,在使用完毕后一定要关闭文件。为了避免不必要的一些错误,在使用管道的文件的要先创建管道文件,然后创建新进程,这样所有的进程才能共享这个管道文件。代码中为了避免向读取端写入和从写入端读取而引发的错误,在读的时候关闭写端,在写的时候关闭读端。
代码中先让父进程向管道文件中写入了字符串“Hello World!”。然后子进程读取管道文件中的字符串,并向屏幕打印。程序执行结果如下:
如果子进程读取到的管道文件为空,那么read()函数将会使得进程阻塞,这时候父进程将会执行,然后完成对管道文件的写入。之后wait()将父进程挂起,子进程完成读取。同样,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。(典型的生产者——消费者模型)管道是存在于内存中的文件(实际上内核实现的一个队列),他是进程的资源,会随着进程的销毁而销毁。还有一点是管道中的东西在读取后就会被删除。管道文件有大小限制的,在我现在的内核版本下他是4KB。管道文件的大小由PIPE_BUF描述。它在#include<linux/limits.h>这个头文件中给出。
#define PIPE_BUF 4096 /* # bytes in atomic write to a pipe */
向管道写入数据的时候Linux不保证写入的原子性,管道缓冲区一有空闲,写进程就会去写入。所以要及时读取管道文件
同时管道还要求写端对读端的依赖性,示例代码如下:
#include<unistd.h> #include<errno.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<wait.h> #include<linux/limits.h> //这个头文件中有PIPE_BUF int main() { int filedes[2]; //保存管道文件的文件描述符 char str[30] = {"Hello World!"}; char temp[30] = {0}; int num; if (0 < fork()) { sleep(1); close(filedes[0]); //关闭读端 num = write(filedes[1],str,strlen(str)); if(-1 == num) { printf("error!\n"); } else { printf("write to pipe is %d\n",num); } close(filedes[1]); wait(NULL); //exit(0); } else { //子进程不读,不写,直接将管道文件两端都关闭 close(filedes[0]); close(filedes[1]); exit(0); } return 0; }
输出结果如下:
这个时候,在父进程中将无法写入。所以管道这个描述还是很形象的,当你向一段水管里面装水的时候,需要将另一端堵上,否则装入的水全都流走了。因此在父进程写的时候,需要先关闭读;在子进程读的时候需要先关闭写。同时,不能在没有读的情况下将管子两头堵上。
当子进程结束的时候,父进程关闭读,调用write写数据,这时候父进程将会收到子进程SIGPIPE信号,当前进程将会中断,而不是阻塞。
来源:https://www.cnblogs.com/zy666/p/10504274.html