Linux进程间通信总结

Linux进程间通信总结

1. 管道

管道是Linux支持的最初Unix IPC形式之一,具有以下特点:

(1)管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;

(2)只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);

(3)单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。

(4)数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

管道的创建

#include <unistd.h>

int pipe(int fd[2])

返回的fd[0]用于读,fd[1]用于写。因此,一个进程在由pipe()创建管道后,一般再fork一个子进程,然后通过管道实现父子进程间的通信(因此也不难推出,只要两个进程中存在亲缘关系,这里的亲缘关系指的是具有共同的祖先,都可以采用管道方式来进行通信)。

管道的应用:

* shell:

管道可用于输入输出重定向,它将一个命令的输出直接定向到另一个命令的输入。比如,当在某个shell程序键入who│wc -l后,相应shell程序将创建who以及wc两个进程和这两个进程间的管道

* 用于具有亲缘关系的进程间通信

管道的局限:

* 只支持单向数据流;

* 只能用于具有亲缘关系的进程之间;

* 没有名字;

* 管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小);

* 管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式。

2. 有名管道

FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。

有名管道创建:

int mkfifo(const char * pathname, mode_t mode)

3. 信号

信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源,最常用发送信号的系统函数是kill, raise,alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。

(1)信号的种类

可靠信号与不可靠信号, 实时信号与非实时信号

可靠信号就是实时信号, 那些从UNIX系统继承过来的信号都是非可靠信号, 表现在信号

不支持排队,信号可能会丢失, 比如发送多次相同的信号, 进程只能收到一次. 信号值小于SIGRTMIN的都是非可靠信号.

非可靠信号就是非实时信号, 后来, Linux改进了信号机制, 增加了32种新的信号, 这些信

号都是可靠信号, 表现在信号支持排队, 不会丢失, 发多少次, 就可以收到多少次. 信号值

位于 [SIGRTMIN, SIGRTMAX] 区间的都是可靠信号.

(2)信号的安装

早期的Linux使用系统调用 signal 来安装信号

#include <signal.h>

void (*signal(int signum, void (*handler))(int)))(int);

该函数有两个参数, signum指定要安装的信号, handler指定信号的处理函数.

该函数的返回值是一个函数指针, 指向上次安装的handler

经典安装方式:

if (signal(SIGINT, SIG_IGN) != SIG_IGN) {

signal(SIGINT, sig_handler);

}

先获得上次的handler, 如果不是忽略信号, 就安装此信号的handler

由于信号被交付后, 系统自动的重置handler为默认动作, 为了使信号在handler处理期间, 仍能对后继信号做出反应, 往往在handler的第一条语句再次调用 signal

sig_handler(ing signum)

{

/* 重新安装信号 */

signal(signum, sig_handler);

......

}

我们知道在程序的任意执行点上, 信号随时可能发生, 如果信号在sig_handler重新安装

信号之前产生, 这次信号就会执行默认动作, 而不是sig_handler. 这种问题是不可预料的.

使用库函数 sigaction  来安装信号

为了克服非可靠信号并同一SVR4和BSD之间的差异, 产生了 POSIX 信号安装方式, 使用sigaction安装信号的动作后, 该动作就一直保持, 直到另一次调用 sigaction建立另一个动作为止. 这就克服了古老的 signal 调用存在的问题

#include <signal.h> 
int sigaction(int signum,const struct sigaction *act,struct sigaction
*oldact));

/* 设置SIGINT */

action.sa_handler
= sig_handler;

sigemptyset(&action.sa_mask);

sigaddset(&action.sa_mask,
SIGTERM);

action.sa_flags
= 0;

/* 获取上次的handler, 如果不是忽略动作, 则安装信号 */

sigaction(SIGINT,
NULL, &old_action);

if
(old_action.sa_handler != SIG_IGN) {

sigaction(SIGINT, &action, NULL);

}

基于 sigaction 实现的库函数: signal

sigaction 自然强大, 但安装信号很繁琐, 目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。

3)如何屏蔽信号

所谓屏蔽, 并不是禁止递送信号, 而是暂时阻塞信号的递送,

解除屏蔽后, 信号将被递送, 不会丢失. 相关API为

int
sigemptyset(sigset_t *set);

int
sigfillset(sigset_t *set);

int
sigaddset(sigset_t *set, int signum);

int
sigdelset(sigset_t *set, int signum);

int
sigismember(const sigset_t *set, int signum);

int
sigsuspend(const sigset_t *mask);

int
sigpending(sigset_t *set);

-----------------------------------------------------------------

int
 sigprocmask(int  how,  const  sigset_t *set, sigset_t
*oldset));

sigprocmask()函数能够根据参数how来实现对信号集的操作,操作主要有三种:

* SIG_BLOCK  在进程当前阻塞信号集中添加set指向信号集中的信号

*
SIG_UNBLOCK   如果进程阻塞信号集中包含set指向信号集中的信号,则解除

对该信号的阻塞

*
SIG_SETMASK    更新进程阻塞信号集为set指向的信号集

屏蔽整个进程的信号:

4)信号的生命周期

从信号发送到信号处理函数的执行完毕

对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,

可以分为三个重要的阶段,这三个阶段由四个重要事件来刻画:

信号诞生;信号在进程中注册完毕;信号在进程中的注销完毕;信号处理函数执行完毕。

下面阐述四个事件的实际意义:

信号"诞生"。信号的诞生指的是触发信号的事件发生(如检测到硬件异常、定时器超时以及调用信号发送函kill()或sigqueue()等)。

信号在目标进程中"注册";

进程的task_struct结构中有关于本进程中未决信号的数据成员:

struct
sigpending pending:

struct
sigpending{

struct sigqueue *head, **tail;

sigset_t signal;

};

第三个成员是进程中所有未决信号集,第一、第二个成员分别指向一个

sigqueue类型的结构链(称之为"未决信号链表")的首尾,链表中

的每个sigqueue结构刻画一个特定信号所携带的信息,并指向下一个

sigqueue结构:

struct
sigqueue{

struct sigqueue *next;

siginfo_t info;

}

信号的注册

信号在进程中注册指的就是信号值加入到进程的未决信号集中

(sigpending结构的第二个成员sigset_t signal),

并且加入未决信号链表的末尾。 只要信号在进程的未决信号集中,

表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。

当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,

都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做"可靠信号"。

这意味着同一个实时信号可以在同一个进程的未决信号链表中添加多次.

当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册,

则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做"不可靠信号"。

这意味着同一个非实时信号在进程的未决信号链表中,至多占有一个sigqueue结构.

一个非实时信号诞生后,

(1)、如果发现相同的信号已经在目标结构中注册,则不再注册,对于进程来说,

相当于不知道本次信号发生,信号丢失.

(2)、如果进程的未决信号中没有相同信号,则在进程中注册自己。

信号的注销。

在进程执行过程中,会检测是否有信号等待处理

(每次从系统空间返回到用户空间时都做这样的检查)。如果存在未决

信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,

进程会把信号在未决信号链中占有的结构卸掉。是否将信号从进程未决信号集

中删除对于实时与非实时信号是不同的。对于非实时信号来说,由于在未决信

号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信

号在进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在

未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构

的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),

则应该把信号在进程的未决信号集中删除(信号注销完毕)。否则,不应该在进程

的未决信号集中删除该信号(信号注销完毕)。

进程在执行信号相应处理函数之前,首先要把信号在进程中注销。

信号生命终止。

进程注销信号后,立即执行相应的信号处理函数,执行完毕后,

信号的本次发送对进程的影响彻底结束。

4. system V 提供的进程间通信的三种方式

(1)消息队列

与命名管道相比,消息队列的优势在于,1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。

(2)信号量

信号量与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制。相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志。除了用于访问控制外,还可用于进程同步。信号量有以下两种类型:

二值信号量:最简单的信号量形式,信号灯的值只能取0或1,类似于互斥锁。

计算信号量:信号量的值可以取任意非负值(当然受内核本身的约束)。

信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:

P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行

V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.

(3)共享内存

速度最快,效率最高的进程间通信方式,进程之间直接访问内存,而不是通过传送数据。但是使用共享内存需要自己提供同步机制。

5. 套接字(unix域协议)

socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。UNIX域套接字与TCP套接字相比较,在同一台传输主机的速度前者是后者的两倍。这是因为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。

值得注意的是,Unix域协议表示协议地址的是路径名,而不是Internet域的IP地址和端口号。

#define UNIX_PATH_MAX    108

struct sockaddr_un

{

sa_family_t sun_family;               /* AF_UNIX */

char       
sun_path[UNIX_PATH_MAX];  /*
pathname */

};

socketpair 函数:创建一个全双工的流管道

int socketpair(int domain, int type, int
protocol, int sv[2]);

使用unix域协议的例子

* libevent网络库对信号的封装:libevent实现了对于socket网络套接口,定时器事件,信号事件的统一监听, 即统一事件源。简单地说,就是把信号也转换成IO事件,集成到Libevent中。网络套接口实际为文件描述符fd,可以在epoll中直接监听,定时器事件可以设置epoll的超时时间进行监听,信号的产生是随机的,libevent网络库是如何进行处理使得能用epoll来实现信号的监听呢?

假如用户要监听SIGINT这个信号,那么在实现的内部就对SIGINT这个信号设置捕抓函数。此外,在实现的内部还要建立一条管道(pipe),并把这个管道加入到多路IO复用函数中。当SIGINT这个信号发生后,捕抓函数将会被调用。而这个捕抓函数的工作就是往管道写入一个字符(这个字符往往等于所捕抓到信号的信号值)。此时,这个管道就变成是可读的了,多路IO复用函数能检测到这个管道变成可读的了。换句话说就是多路IO复用函数检测到这个SIGINT信号发生了,这也就完成了对信号的监听工作。

  1. 创建一个管道(Libevent实际上使用的是socketpair)
  2. 为这个socketpair的一个读端创建一个event,并将之加入到多路IO复用函数的监听之中
  3. 设置信号捕抓函数
  4. 有信号发生,就往socketpair写入一个字节
时间: 2024-10-18 05:13:11

Linux进程间通信总结的相关文章

Linux进程间通信 -- 消息队列 msgget()、msgsend()、msgrcv()、msgctl()

下面来说说如何用不用消息队列来进行进程间的通信,消息队列与命名管道有很多相似之处.有关命名管道的更多内容可以参阅我的另一篇文章:Linux进程间通信 -- 使用命名管道 一.什么是消息队列 消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法.  每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构.我们可以通过发送消息来避免命名管道的同步和阻塞问题.但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制. Linux用宏MSGMAX和MSGMNB来限制一条

Linux进程间通信 -- 信号量函数 signal()、sigaction()

一.什么是信号 用过Windows的我们都知道,当我们无法正常结束一个程序时,可以用任务管理器强制结束这个进程,但这其实是怎么实现的呢?同样的功能在Linux上是通过生成信号和捕获信号来实现的,运行中的进程捕获到这个信号然后作出一定的操作并最终被终止. 信号是UNIX和Linux系统响应某些条件而产生的一个事件,接收到该信号的进程会相应地采取一些行动.通常信号是由一个错误产生的.但它们还可以作为进程间通信或修改行为的一种方式,明确地由一个进程发送给另一个进程.一个信号的产生叫生成,接收到一个信号

Linux 进程间通信(posix消息队列 简单)实例

Linux 进程间通信(posix消息队列 简单)实例 详情见: http://www.linuxidc.com/Linux/2011-10/44828.htm 编译: gcc -o consumer consumer.c -lrt gcc -o producer producer.c -lrt /* * * Filename: producer.c * * Description: 生产者进程 * * Version: 1.0 * Created: 09/30/2011 04:52:23 PM

Linux进程间通信——信号

一.什么是信号 用过Windows的我们都知道,当我们无法正常结束一个程序时,可以用任务管理器强制结束这个进程,但这其实是怎么实现的呢?同样的功能在Linux上是通过生成信号和捕获信号来实现的,运行中的进程捕获到这个信号然后作出一定的操作并最终被终止. 信号是UNIX和Linux系统响应某些条件而产生的一个事件,接收到该信号的进程会相应地采取一些行动.通常信号是由一个错误产生的.但它们还可以作为进程间通信或修改行为的一种方式,明确地由一个进程发送给另一个进程.一个信号的产生叫生成,接收到一个信号

Linux进程间通信 -- 数据报套接字 socket()、bind()、sendto()、recvfrom()、close()

前一篇文章,Linux进程间通信——使用流套接字介绍了一些有关socket(套接字)的一些基本内容,并讲解了流套接字的使用,这篇文章将会给大家讲讲,数据报套接字的使用. 一.简单回顾——什么是数据报套接字 socket,即套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行.也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信.也因为这样,套接字明确地将客户端和服务器区分开来. 相对于流套接字,数据报套接字的

Linux进程间通信 -- 信号集函数 sigemptyset()、sigprocmask()、sigpending()、sigsuspend()

我们已经知道,我们可以通过信号来终止进程,也可以通过信号来在进程间进行通信,程序也可以通过指定信号的关联处理函数来改变信号的默认处理方式,也可以屏蔽某些信号,使其不能传递给进程.那么我们应该如何设定我们需要处理的信号,我们不需要处理哪些信号等问题呢?信号集函数就是帮助我们解决这些问题的. 有关Linux进程间使用信号通信的更多内容,可以参阅我的另一篇文章,Linux进程间通信 -- 信号量函数 signal().sigaction() 下面是信号函数集: 1.int sigemptyset(si

嵌入式 Linux进程间通信(十二)——多线程同步

嵌入式 Linux进程间通信(十二)--多线程同步 多线程编程中有三种线程同步机制:互斥锁.信号量.条件量.本文将使用生产者消费者问题编程实践三种线程同步方式. 生产者.消费者问题:生产者线程生产物品,然后将物品放置在一个空缓冲区中供消费者线程消费.消费者线程从缓冲区中获得物品,然后释放缓冲区.当生产者线程生产物品时,如果没有空缓冲区可用,那么生产者线程必须等待消费者线程释放出一个空缓冲区.当消费者线程消费物品时,如果没有满的缓冲区,那么消费者线程将被阻塞,直到新的物品被生产出来. 一.互斥锁

练习--LINUX进程间通信之无名管道PIPE

IBM上放的这个系统不错,刚好可以系统回温一下LINUX的系统知识. http://www.ibm.com/developerworks/cn/linux/l-ipc/part1/ 感觉年纪大了,前几年看的LINUX内核和系统的东东,忘了很多,要慢慢转化成永久记忆才可以. 今天,又拿起<LINUX内核设计与实现>,慢慢啃下去. ~~~~~~~~~~~~~~ 进程通信有如下一些目的:A.数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间B.共享数据:多个进程想

Linux进程间通信程序设计-1

一.进程间通信概述: 1.目的:为什么要进行进程间通信? 1)数据传输:一个进程需要把他的数据发送给另一个进程. 2)资源共享:协调共享资源. 3)通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件. 4)进程控制 2.发展 Linux进程间通信(IPC)由以下几部分发展而来: 1)UNIX进程间通信 2)基于System V进程间通信(System 5是UNIX操作系统众多版本中的一支) 3)POSIX进程间通信(POSIX可移植的操作系统接口) 3.分类 现在Linux