在现代计算机上,一个任务的完成,往往需要多个进程协调,这时进程间如何交流就成了必须解决的问题。实现进程间通信(IPC)有很多方法,下面简单介绍一下各个通讯方式的原理,不讲具体代码实现。
管道
管道一般指无名管道(还有另一种叫有名管道),是Unix系统最古老的进程通信方式。管道通信有以下特点:
• 管道是半双工的,单向的,同一时刻只能允许一方向另一方发送;
• 管道只能在有父子进程,或者兄弟进程之间使用;
创建管道的函数原型:int pipe(int fd[2]);
管道利用两个标识符进行读写控制,fd[0]用于控制读,fd[1]用于控制写。如下图,管道创建后所有标识符都打开状态,如要实现A向B传递数据,则需要关闭A的fd[0],关闭B的fd[1]。
值得一提的是,管道概念在linux的shell中也用到,管道符'|',也是利用了管道的抽象,进行输出重定向,将一条命令的输出传递给另一条命令作为其输入。
有名管道FIFO
和上面的无名管道类似,不过有名管道可以在无关的进程之间交换信息。
有名管道的创建函数原型如下:int mkfifo(const char *_path,mode_t mode);管道文件的path就是它的名字,Linux上一切都是文件,有名管道也不例外,它是设备文件。
利用有名管道进行通信的步骤大概是这样:
创建一个有名管道文件 -> 进程A用文件write()操作发送消息到管道 -> 进程B用文件read()操作从管道读取消息。
信号
像我们常用ctrl+c来结束当前进程,就是通过shell发送了一个SIGINT信号。linux一共提供了64个信号,它们的编号从1到64。使用$man 7 signal命令可以查看每种信号的名称、编号、作用。
发送端如何发送信号
• 硬件产生信号,如我们键盘组合命令可以产生信号,硬件的故障也可能产生信号;
• 软件发送信号,最常用的信号发送函数为kill,指定信号类别和目标进程id就可以发送信号。除了kill,还有sigqueue、raise等也能产生信号。
接收端如何处理信号
• 忽略信号,不对信号做任何处理;
• 执行默认处理程序,每个信号都有自己默认的处理函数;
• 执行指定的处理函数,我们可以将信号和自定义的处理函数进行绑定(也可以说是注册一种信号处理函数),这样接收端会优先执行自定义的信号处理函数。
如何自定义处理函数
• signal(int signum,sighandler_t handler);自定义一个handler,绑定到signum信号上。
• sigaction(int signum,struct sigaction * act,struct sigaction * oldact);也可以用来注册。
消息队列
和分布式应用的消息队列类似,消息队列是异步通信的一种机制。和数据结构学到的队列一样,我们可以创建一个消息队列,发送端发送消息入队,读取端按需从队列读取信息。这样的通讯是异步的,不会造成进程阻塞。
简单归纳下消息队列和管道通信的区别:
• 消息队列是异步通信,管道是同步通信,会使进程阻塞;
• 消息队列可以在不相干进程间通信,匿名管道只能在近亲进程间通信;
• 消息队列存放在内存中,而有名管道存放在磁盘上,因此消息队列存取速度快;匿名管道在内核缓冲区理论最快,不过使用限制很多;
• 因为消息队列存在内存里,因此是以块式存取,而管道都是流式存取。
信号量
在PV操作和锁中已经学到过,信号量是用于并发时资源互斥和同步的一种机制。信号量本身不能传递数据,通常搭配共享内存一同使用。
顾名思义,信号量指的就是信号(资源)的数量。
假如资源数是1,那么信号量初始值s=1;当没有进程访问资源时,即资源空闲时,s=1;这时有一个进程申请访问,执行一次P操作,信号量减一,s=0;紧接着又来了一个进程想用资源,因为s<=0,资源非空闲,它就要等;等到上一个进程用完了,退出时会执行V操作,将信号量加一,这时等待使用的进程发现,s=1了,他就可以进入使用了。
共享内存
顾名思义,即进程之间共享一段内存区域。共享内存理论上是最快的IPC方法,因为其直接对内存的数据进行操作。多个进程不能同时读写一个数据,需要进行同步,而共享内存的系统调用中并未实现同步,需要我们借助信号量机制实现同步。因此共享内存常常和信号量结合使用。
Socket套接字
Socket是否还有印象?在学习计算机网络时我们经常遇见它,它是一种在不同主机的进程间进行网络通信的机制。除了这种网络套接字外,还有一种叫IPC套接字,特指用于同一主机进程间通信的方式。在这几种IPC之中,套接字通信是功能最强大的,也是最为复杂的一种。
本文仅仅是对进程间通信方式的总结概述,并未详尽。文中如有错误之处,望网友指正。