Linux下进程间通信的几种方式:
- 管道(Pipe)和有名管道(FIFO)
- 信号(Signal)
- 消息队列
- 共享内存(Shared Memory)
- 信号量(Semaphore)
- 套接字(Socket)
1.管道通信
管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,
除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
2.信号
信号是比较复杂的通信方式,用于通知接收进程有某种事件发生,除了用于进程间通信
外,进程还可以发送信号给进程本身;Linux除了支持UNIX早期信号语义函数signal外,还支
持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现
可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。
信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式。
信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用
户空间进程发生了哪些系统事件,它可以在任何时候发给某一进程,而无须知道该进程的状态。
如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传
递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才传
递给进程。
3.消息队列
消息队列是消息的链接表,包括Posix消息队列System V消息队列。有足够权限的进程可以
向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信
息量少、管道只能承载无格式字节流,以及缓冲区大小受限等缺点。
4.信号量/信号灯
主要作为进程期间,以及同一进程的不同线程之间的同步手段。信号量是用来解决进程之间
的同步与互斥问题的一种进程之间通信机制,包括一个称为信号量的变量和在该信号量下等待资
源的进程等待队列,以及对信号量进行的两个原子操作(PV操作)。其中信号量对应于某一种资
源,取一个非负的整型值。信号量的值是指当前可用的资源数量,若它等于0则意味着目前没有可
用的资源。
5.共享内存
共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内
存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B
对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,
互斥锁和信号量都可以。采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读
写内存,而不需要任何数据的复制。
6.套接字
更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由UNIX系统的BSD分
支开发出来的,但现在一般可以移植到其他类UNIX系统上,Linux和System V的变种都支持套接字。
管道通信
特点:
- 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需建立起两个管道。
- 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)。
- 单独构成一种独立的文件系统。管道对于管道两端的进程而言,就是一个文件,但它不是普通的文
件,不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在于内存中。
- 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加
在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
无名管道
建立
相关函数 | mkfifo、popen、read、write、fork |
表头文件 | #include <unistd.h> |
定义函数 | int pipe(int filedes[2]) |
函数说明 | pipe()会建立管道,并将文件描述符由参数filedes数组返回。filedes[0]为管道里的读取段filedes[1]则为管道的写入端 |
返回值 | 若成功则返回零,否则返回-1,错误原因存于errno中 |
错误代码 |
|
1 /*父进程借管道将字符串“hello! ”传给子进程并显示*/ 2 #include <unistd.h> 3 int main() 4 { 5 int filedes[2]; 6 char buffer[80]; 7 8 pipe(filedes); 9 if (fork() > 0) 10 { 11 /*父进程*/ 12 char s[] = "hello! "; 13 write(filedes[1], s, sizeof(s)); 14 } 15 else 16 { 17 /*子进程*/ 18 read(filedes[0], buffer, 80); 19 printf("%s", buffer); 20 } 21 }
读写
管道两端可分别用描述符fd[0]和fd[1]来描述,需要注意的是,管道的两端固定了任务,即一端只能用于
读,由描述符fd[0]表示,称其为管道读端;另一端则只能用于手写,由描述符fd[1]来表示,称其为管道写端。
如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生。一般文件的I/O函数都可以用于
管道,如close、read、write等等。
从管道中读取数据:
- 如果管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0。
- 当管道的写端存在时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数;如果
请求的字节数目不大于PIPE_BUF,则返回管道中现有数据字节数(此时,管道中数据量小于请求的数据量),
或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)。
向管道中写入数据:
向管道中写入数据时,Linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道
写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。
有名管道
有名管道和无名管道基本相同,但也有不同点:无名管道只能由父子进程使用;但是通过有名管道,不相关的
进程也能交换数据。
建立
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname: FIFO文件名
mode:属性(同文件操作)
一旦创建了一个FIFO,就可用open打开它,一般的文件访问函数(close、read、write等)都可用于FIFO。
当打开FIFO时,非阻塞标识(O_NONBLOCK)将对以后的读写产生影响:
1、没有使用O_NONBLOCK:访问要求无法满足时进程将阻塞。如果试图读取空的FIFO,将导致进程阻塞。
2、使用O_NONBLOCK:访问要求无法满足时不阻塞,立刻出错返回。errno是ENXIO。