一个大型的应用系统,往往需要众多进程协作,进程间通信的重要性显而易见。
进程间通信的目的
*数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间
*共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。
*通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
*资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。
*进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
Linux进程间通信主要方式
Linux的进程间通信(IPC)方法有:管道(Pipe)和有名管道(FIFO)、消息队列、信号量(Semaphore)、共享内存(Shared Memory)、套接字(Socket)等。
一、管道(pipe)
管道,通常指无名管道,可用于具有亲缘关系进程间的通信。
1、特点:
1) 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。需要双方通信时,需要建立起两个管道。
2) 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
3) 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,而是自立门户,单独构成一种文件系统,并且只存在于内存中。
4) 数据的读出和写入:一个进程在管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
2、原型:
#include <unistd.h>
int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1
当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:
要关闭管道只需将这两个文件描述符关闭即可。
管道用于不同进程间的通信。通常先创建一个管道,再通过fork函数创建一个子进程,该子进程会继承父进程所创建的管道。注意:必须在系统调用fork()前调用pipe(),否则子进程将不会继承文件描述符。
3、读写无名管道
*从管道中读取数据:
1)如果管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0。
2)当管道的写端存在时,如果请求的字节数目大于PIPE_BUF,则返回管道中现有的数据字节数;如果请求的字节数目不大于PIPE_BUF,则返回管道中现有的数据字节数(此时,管道中数据量小于请求的数据量),或者返回请求的字节数(此时,管道中数据量不小于请求的数据量)。
*向管道中写数据:
向管道中写入数据时,Linux不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走缓冲区中的数据,那么写操作将一直阻塞。
注意:只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将会收到内核传来的SIFPIPE信号,应用程序可以处理该信号,也可以忽略(默认动作是应用程序终止)。
二、有名管道(FIFO)
有名管道(也叫FIFO,因为管道工作在先入先出的原则下,第一个写入管道的数据也是第一个被读出的数据)。与管道不同,FIFO不是临时的对象,它们是文件系统中真正的实体,可以用mkfifo命令创建。只要有合适的访问权限,进程就可以使用FIFO。FIFO的打开方式和管道稍微不同。一个管道(它的两个file数据结构、VFS I节点和共享数据页)是一次性创建的,而FIFO已经存在,可以由它的用户打开和关闭。Linux必须处理在写进程打开FIFO之前读进程对它的打开,也必须处理在写进程写数据之前读进程对管道的读。除此以外,FIFO几乎和管道的处理完全一样,而且它们使用一样的数据结构和操作
1、特点:
1)FIFO可以在无关的进程之间交换数据,与无名管道不同。
2)FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
2、原型:
1 #include <sys/stat.h>
2 // 返回值:成功返回0,出错返回-1
3 int mkfifo(const char *pathname, mode_t mode);
其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:
若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。
若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。
3、读写有名管道
**从FIFO中读取数据:
约定:如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。
1.)如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志的读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。
2.)对于设置了阻塞标志的读操作来说,造成阻塞的原因有两种:一种是当前FIFO内有数据,但有其它进程再读这些数据;另一种是FIFO内没有数据,阻塞原因是FIFO中有新的数据写入,而不论新写入数据量的大小,也不论读操作请求多少数据量。
3.)读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其他将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)
4.)如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞
5.)如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。
**向FIFO中写入数据
约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。
*对于设置了阻塞标志的写操作:
1.)当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,知道当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作
2.)当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。
*对于没有设置阻塞标志的写操作:
1.)当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。再写满所有FIFO空闲缓冲区后,写操作返回。
2.)当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写
4、应用
FIFO的通信方式类似于在进程中使用文件来传输数据,只不过FIFO类型文件同时具有管道的特性。在数据读出时,FIFO管道中同时清除数据,并且“先进先出”。