进程间通信(IPC)介绍

概述

进程间通信就是在不同进程之间传播或交换信息,那么不同进程之间存在着什么双方都可以访问的介质呢?进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区。另外,系统空间是“公共场所”,各进程均可以访问,所以内核也可以提供这样的条件。此外,还有双方都可以访问的外设。在这个意义上,两个进程当然也可以通过磁盘上的普通文件交换信息,或者通过“注册表”或其它数据库中的某些表项和记录交换信息。广义上这也是进程间通信的手段,但是一般都不把这算作“进程间通信”。


进程间通信(IPC,InterProcess Communication)

是指在不同进程之间传播或交换信息。是一组编程接口,让程序员能够协调不同的进程,使之能在一个操作系统里同时运行,并相互传递、交换信息。这使得一个程序能够在同一时间里处理许多用户的要求。因为即使只有一个用户发出要求,也可能导致一个操作系统中多个进程的运行,进程之间必须互相通话。IPC接口就提供了这种可能性。每个IPC方法均有它自己的优点和局限性,一般,对于单个程序而言使用所有的IPC方法是不常见的。


IPC的方式通常有 管道(PIPE)(包括无名管道和命名管道)、 消息队列信号量共享内存套接字(Socket)Streams旗语等。其中 Socket和Streams支持不同主机上的两个进程IPC。


进程间通信(IPC)常见分类的特点

管道


管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。
特点:
它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

FIFO (命名管道)

FIFO,也称为命名管道,它是一种文件类型。
特点:
FIFO可以在无关的进程之间交换数据,与无名管道不同。
FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。


消息队列

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
特点:
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

信号量

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
特点:
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
支持信号量组。

共享内存

共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
特点:
共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
因为多个进程可以同时操作,所以需要进行同步。
信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。



主要分类的详解

进程间通信主要包括 管道, 系统IPC(包括 消息队列,信号, 共享内存), 套接字(SOCKET).

管道
管道包括三种:
1)普通管道PIPE, 通常有两种限制,一是单工,只能单向传输;二是只能在父子或者兄弟进程间使用.
2)流管道s_pipe: 去除了第一种限制,为半双工,可以双向传输.
3)命名管道:name_pipe, 去除了第二种限制,可以在许多并不相关的进程之间进行通讯.

普通的Linux shell都允许重定向,而重定向使用的就是管道。
例如:
$ ls|pr|lpr
把命令ls(列出目录中的文件)的输出通过管道连接到命令pr的标准输入上进行分页。最后,命令pr的标准输出通过管道连接到命令lpr的标准输入上,从而在缺省打印机上打印出结果。进程感觉不到这种重定向,它们和平常一样地工作。正是shell建立了进程之间的临时管道。
管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的首端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。管道提供了简单的流控制机制。进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。
传统上有很多种实现管道的方法,如利用文件系统、利用套接字(sockets)、利用流等。在Linux中,使用两个file数据结构来实现管道。这两个file数据结构中的f_inode(f_dentry)指针指向同一个临时创建的VFS I节点,而该VFS I节点本身又指向内存中的一个物理页,如图所示。


两个file数据结构中的f_op指针指向不同的文件操作例程向量表:一个用于向管道中写,另一个用于从管道中读。这种实现方法掩盖了底层实现的差异,从进程的角度来看,读写管道的系统调用和读写普通文件的普通系统调用没什么不同。当写进程向管道中写时,字节被拷贝到了共享数据页,当读进程从管道中读时,字节被从共享页中拷贝出来。Linux必须同步对于管道的存取,必须保证管道的写和读步调一致。Linux使用锁、等待队列和信号(locks,wait queues and signals)来实现同步。

管道分为有名管道和无名管道,无名管道只能用于亲属进程之间的通信,而有名管道则可用于无亲属关系的进程之间。
在Linux系统下,命名管道可由两种方式创建(假设创建一个名为“fifoexample”的有名管道):
(1)mkfifo("fifoexample","rw");
(2)mknod fifoexample p
mkfifo是一个函数,mknod是一个系统调用,即我们可以在shell下输出上述命令。
有名管道创建后,我们可以像读写文件一样读写它。
消息队列用于运行于同一台机器上的进程间通信,与管道相似。

当写进程向管道写的时候,它使用标准的write库函数。这些库函数(read、write等)要求传递一个文件描述符作为参数。文件描述符是该文件对应的file数据结构在进程的file数据结构数组中的索引,每一个都表示一个打开的文件,在这种情况下,是打开的管道。Linux系统调用使用描述这个管道的file数据结构中f_op所指的write例程,该write例程使用表示管道的VFS I 节点中存放的信息,来管理写请求。如果共享数据页中有足够的空间能把所有的字节都写到管道中,而且管道没有被读进程锁定,则Linux就在管道上为写进程加锁,并把字节从进程的地址空间拷贝到共享数据页。如果管道被读进程锁定或者共享数据页中没有足够的空间,则当前进程被迫睡眠,它被挂在管道I节点的等待队列中等待,而后调用调度程序,让另外一个进程运行。睡眠的写进程是可以中断的(interruptible),所以它可以接收信号。当管道中有了足够的空间可以写数据,或者当锁定解除时,写进程就会被读进程唤醒。当数据写完之后,管道的VFS I 节点上的锁定解除,在管道I节点的等待队列中等待的所有读进程都会被唤醒。
参见fs/pipe.c pipe_write()
从管道中读取数据和写数据非常相似。Linux允许进程无阻塞地读文件或管道(依赖于它们打开文件或者管道的模式),这时,如果没有数据可读或者管道被锁定,系统调用会返回一个错误。这意味着进程会继续运行。另一种方式是阻塞读,即进程在管道I节点的等待队列中等待,直到写进程完成。
如果所有的进程都完成了它们的管道操作,则管道的I节点和相应的共享数据页会被废弃。
参见fs/pipe.c pipe_read()
Linux也支持命名管道(也叫FIFO,因为管道工作在先入先出的原则下,第一个写入管道的数据也是第一个被读出的数据)。与管道不同,FIFO不是临时的对象,它们是文件系统中真正的实体,可以用mkfifo命令创建。只要有合适的访问权限,进程就可以使用FIFO。FIFO的打开方式和管道稍微不同。一个管道(它的两个file数据结构、VFS I节点和共享数据页)是一次性创建的,而FIFO已经存在,可以由它的用户打开和关闭。Linux必须处理在写进程打开FIFO之前读进程对它的打开,也必须处理在写进程写数据之前读进程对管道的读。除此以外,FIFO几乎和管道的处理完全一样,而且它们使用一样的数据结构和操作。
从IPC的角度看,管道提供了从一个进程向另一个进程传输数据的有效方法。但是,管道有一些固有的局限性:
因为读数据的同时也将数据从管道移去,因此,管道不能用来对多个接收者广播数据。
管道中的数据被当作字节流,因此无法识别信息的边界。
如果一个管道有多个读进程,那么写进程不能发送数据到指定的读进程。同样,如果有多个写进程,那么没有办法判断是它们中那一个发送的数据。

系统IPC
系统IPC的三种方式类同,都是使用了内核里的标识符来识别.
FAQ1: 管道与文件描述符,文件指针的关系?
答: 其实管道的使用方法与文件类似,都能使用read,write,open等普通IO函数. 管道描述符来类似于文件描述符. 事实上, 管道使用的描述符,文件指针和文件描述符最终都会转化成系统中SOCKET描述符. 都受到系统内核中SOCKET描述符的限制. 本质上LINUX内核源码中管道是通过空文件来实现.
FAQ2: 管道的使用方法?
答: 主要有下面几种方法: 1)pipe, 创建一个管道,返回2个管道描述符.通常用于父子进程之间通讯. 2)popen, pclose: 这种方式只返回一个管道描述符,常用于通信另一方是stdin or stdout; 3)mkpipe:命名管道, 在许多进程之间进行交互.
FAQ3: 管道与系统IPC之间的优劣比较?
答: 管道: 优点是所有的UNIX实现都支持, 并且在最后一个访问管道的进程终止后,管道就被完全删除;缺陷是管道只允许单向传输或者用于父子进程之间.
系统IPC: 优点是功能强大,能在毫不相关进程之间进行通讯; 缺陷是关键字KEY_T使用了内核标识,占用了内核资源,而且只能被显式删除,而且不能使用SOCKET的一些机制,例如select,epoll等.
FAQ4: WINDOS进程间通信与LINUX进程间通信的关系?
答: 事实上,WINDOS的进程通信大部分移植于UNIX, WINDOS的剪贴板,文件映射等都可从UNIX进程通信的共享存储中找到影子.
FAQ5: 进程间通信与线程间通信之间的关系?
答: 因为WINDOWS运行的实体是线程, 狭义上的进程间通信其实是指分属于不同进程的线程之间的通讯.而单个进程之间的线程同步问题可归并为一种特殊的进程通信.它要用到内核支持的系统调用来保持线程之间同步. 通常用到的一些线程同步方法包括:Event, Mutex,信号量Semaphore,临界区资源等.

IPC目的
1)数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间。
2)共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。
3)通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
4)资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。
5)进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
进程通过与内核及其它进程之间的互相通信来协调它们的行为。Linux支持多种进程间通信(IPC)机制,信号和管道是其中的两种。除此之外,Linux还支持System V 的IPC机制(用首次出现的Unix版本命名)。


系统V IPC机制(System V IPC Mechanisms)
前面讨论的信号和管道虽然可以在进程之间通信,但还有许多应用程序的IPC需求它们不能满足。因此在System V UNIX(1983)中首次引入了另外三种进程间通信机制(IPC)机制:消息队列、信号灯和共享内存(message queues,semaphores and shared memory)。它们最初的设计目的是满足事务式处理的应用需求,但目前大多数的UNIX供应商(包括基于BSD的供应商)都实现了这些机制。 Linux完全支持Unix System V中的这三种IPC机制。
System V IPC机制共享通用的认证方式。进程在使用某种类型的IPC资源以前,必须首先通过系统调用创建或获得一个对该资源的引用标识符。进程只能通过系统调用,传递一个唯一的引用标识符到内核来访问这些资源。在每一种机制中,对象的引用标识符都作为它在资源表中的索引。但它不是直接的索引,需要一个简单的操作来从引用标识符产生索引。对于System V IPC对象的访问,使用访问许可权检查,这很象对文件访问时所做的检查。System V IPC对象的访问权限由对象的创建者通过系统调用设置。
系统中表示System V IPC对象的所有Linux数据结构中都包括一个ipc_perm数据结构,用它记录IPC资源的认证信息。其定义如下:
struct ipc_perm
{
__kernel_key_t key;
__kernel_uid_t uid;
__kernel_gid_t gid;
__kernel_uid_t cuid;
__kernel_gid_t cgid;
__kernel_mode_tmode;
unsigned short seq;
};
在ipc_perm数据结构中包括了创建者进程的用户和组标识、所有者进程的用户和组标识、对于这个对象的访问模式(属主、组和其它)以及IPC对象的键值(key)。Linux通过key 来定位System V IPC对象的引用标识符,每个IPC对象都有一个唯一的key。Linux支持两种key:公开的和私有的。如果key是公开的,那么系统中的任何进程,只要通过了权限检查,就可以找到它所对应的System V IPC对象的引用标识符。System V IPC对象不能直接使用key来引用,必须使用它们的引用标识符来引用。(参见include/linux/ipc.h)每种IPC机制都提供一种系统调用,用来将键值(key)转化为对象的引用标识符。
对所有的System V IPC,Linux提供了一个统一的系统调用:sys_ipc,通过该函数可以实现对System V IPC的所有操作。函数sys_ipc的定义如下:
int sys_ipc (uint call, int first, int second,
int third, void *ptr, long fifth)
这里call是一个32位的整数,其低16位指明了此次调用所要求的工作。对不同的call值,其余各参数的意义也不相同。以下将分别介绍各IPC机制及其所提供的操作。
消息队列(Message Queues)
消息队列就是消息的一个链表,它允许一个或多个进程向它写消息,一个或多个进程从中读消息。Linux维护了一个消息队列向量表:msgque,来表示系统中所有的消息队列。其定义如下:
struct msqid_ds *msgque[MSGMNI];
该向量表中的每一个元素都是一个指向msqid_ds数据结构的指针,而一个msqid_ds数据结构完整地描述了一个消息队列。
MSGMNI的值是128,就是说,系统中同时最多可以有128个消息队列。
msqid_ds数据结构的定义如下:
struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first; /* first message on queue */
struct msg *msg_last; /* last message in queue */
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
struct wait_queue *wwait;
struct wait_queue *rwait;
unsigned short msg_cbytes; /* current number of bytes on queue */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
};
其中包括:
l 一个ipc_perm的数据结构(msg_perm域),描述该消息队列的通用认证方式。
l 一对消息指针(msg_first、msg_last),分别指向该消息队列的队头(第一个消息)和队尾(最后一个消息)(msg)。发送者将新消息加到队尾,接收者从队头读取消息。
l 三个时间域(msg_stime、msg_rtime、msg_ctime)用于记录队列最后一次发送时间、接收时间和改动时间。
l 两个进程等待队列(wwait、rwait)分别表示等待向消息队列中写的进程(wwait)和等待从消息队列中读的进程(rwait)。如果某进程向一个消息队列发送消息而发现该队列已满,则进程挂在wwait队列中等待。从该消息队列中读取消息的进程将从队列中删除消息,从而腾出空间,再唤醒wwait队列中等待的进程。如果某进程从一个消息队列中读消息而发现该队列已空,则进程挂在rwait队列中等待。向该消息队列中发送消息的进程将消息加入队列,再唤醒rwait队列中等待的进程。
l 三个记数域(msg_cbytes、msg_qnum、msg_qbytes)分别表示队列中的当前字节数、队列中的消息数和队列中最大字节数;
l 两个PID域(msg_lspid、msg_lrpid)分别表示最后一次向该消息队列中发送消息的进程和最后一次从该消息队列中接收消息的进程。


System V IPC 机制——消息队列

当创建消息队列时,一个新的msqid_ds数据结构被从系统内存中分配出来,并被插入到msgque 向量表中。
每当进程试图向消息队列写消息时,它的有效用户和组标识符就要和消息队列的ipc_perm数据结构中的模式域比较。如果进程可以向这个消息队列写(比较成功),则消息会从进程的地址空间拷贝到一个msg数据结构中,该msg数据结构被放到消息队列的队尾。每一个消息都带有进程间约定的、与应用程序相关的类型标记。但是,因为Linux限制了可以写的消息的数量和长度,所以可能会没有空间来容纳该消息。这时,进程会被放到消息队列的写等待队列中,然后调度程序会选择一个新的进程运行。当一个或多个消息从这个消息队列中读出去时,等待的进程会被唤醒。
从队列中读消息与向队列中写消息是一个相似的过程。进程对消息队列的访问权限一样要被检查。读进程可以选择从队列中读取第一条消息而不管消息的类型,也可以选择从队列中读取特殊类型的消息。如果没有符合条件的消息,读进程会被加到消息队列的读等待队列,然后运行调度程序。当一个新的消息写到消息队列时,这个进程会被唤醒,继续它的运行。
Linux提供了四个消息队列操作。
1. 创建或获得消息队列(MSGGET)
在系统调用sys_ipc中call值为MSGGET,调用的函数为sys_msgget。该函数的定义如下:
int sys_msgget (key_t key, int msgflg)
其中key是一个键值,而msgflg是一个标志。
该函数的作用是创建一个键值为key的消息队列,或获得一个键值为key的消息队列的引用标识符。这是使用消息队列的第一步,即获得消息队列的引用标识符,以后就通过该标识符使用这个消息队列。
工作过程如下:
1) 如果key == IPC_PRIVATE,则申请一块内存,创建一个新的消息队列(数据结构msqid_ds),将其初始化后加入到msgque向量表中的某个空位置处,返回标识符。
2) 在msgque向量表中找键值为key的消息队列,如果没有找到,结果有二:
l msgflg表示不创建新的队列,则错误返回。
l msgflg表示要创建新的队列,则创建新消息队列,创建过程如1)。
3) 如果在msgque向量表中找到了键值为key的消息队列,则有以下情况:
l 如果msgflg表示一定要创建新的消息队列而且不允许有相同键值的队列存在,则错误返回。
l 如果找到的队列是不能用的或已损坏的队列,则错误返回。
l 认证和存取权限检查,如果该队列不允许msgflg要求的存取,则错误返回。
l 正常,返回队列的标识符。
2. 发送消息
在系统调用sys_ipc中call值为MSGSND,调用的函数为sys_msgsnd。该函数的定义如下:
int sys_msgsnd (int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg)
其中:msqid是消息队列的引用标识符;
msgp是消息内容所在的缓冲区;
msgsz是消息的大小;
msgflg是标志。
该函数做如下工作:
1) 该消息队列在向量msgque中的索引是id = (unsigned int) msqid % MSGMNI,认证检查(权限、模式),合法性检查(类型、大小等)。
2) 如果队列已满,以可中断等待状态(TASK_INTERRUPTIBLE)将当前进程挂起在wwait等待队列上。
3) 申请一块空间,大小为一个消息数据结构加上消息大小,在其上创建一个消息数据结构struct msg,将消息缓冲区中的消息内容拷贝到该内存块中消息头的后面(从用户空间拷贝到内核空间)。
4) 将消息数据结构加入到消息队列到的队尾,修改队列的相应参数(大小等)。
5) 唤醒在该消息队列的rwait进程队列上等待读的进程。
6) 返回
3. 接收消息
在系统调用sys_ipc中call值为MSGRCV,调用的函数为sys_msgrcv。该函数的定义如下:
int sys_msgrcv (int msqid, struct msgbuf *msgp, size_t msgsz,
long msgtyp, int msgflg)
其中:msqid是消息队列的引用标识符;
msgp是接收到的消息将要存放的缓冲区;
msgsz是消息的大小;
msgtyp是期望接收的消息类型;
msgflg是标志。
该函数做如下工作:
1) 该消息队列在向量msgque中的索引是id = (unsigned int) msqid % MSGMNI,认证检查(权限、模式),合法性检查。
2) 根据msgtyp和msgflg搜索消息队列,情况有二:
l 如果找不到所要的消息,则以可中断等待状态(TASK_INTERRUPTIBLE)将当前进程挂起在rwait等待队列上。
l 如果找到所要的消息,则将消息从队列中摘下,调整队列参数,唤醒该消息队列的wwait进程队列上等待写的进程,将消息内容拷贝到用户空间的消息缓冲区msgp中,释放内核中该消息所占用的空间,返回。
4. 消息控制
在系统调用sys_ipc中call值为MSGCTL,调用的函数为sys_msgctl。该函数的定义如下:
int sys_msgctl (int msqid, int cmd, struct msqid_ds *buf)
其中:msqid是消息队列的引用标识符;
cmd是执行命令;
buf是一个缓冲区。
该函数做如下工作:
该函数对消息队列做一些控制动作,如:释放队列,获得队列的认证信息,设置队列的认证信息等。
消息队列和管道提供相似的服务,但消息队列要更加强大并解决了管道中所存在的一些问题。消息队列传递的消息是不连续的、有格式的信息,给对它们的处理带来了很大的灵活性。可以用不同的方式解释消息的类型域,如可以将消息的类型同消息的优先级联系起来,类型域也可以用来指定接收者。
小消息的传送效率很高,但大消息的传送性能则较差。因为消息传送的过程中要经过从用户空间到内核空间,再从内核空间到用户空间的拷贝,所以,大消息的传送其性能较差。另外,消息队列不支持广播,而且内核不知道消息的接收者。

信号
信号(Signals )是Unix系统中使用的最古老的进程间通信的方法之一。操作系统通过信号来通知进程系统中发生了某种预先规定好的事件(一组事件中的一个),它也是用户进程之间通信和同步的一种原始机制。一个键盘中断或者一个错误条件(比如进程试图访问它的虚拟内存中不存在的位置等)都有可能产生一个信号。Shell也使用信号向它的子进程发送作业控制信号。
信号是在Unix System V中首先引入的,它实现了15种信号,但很不可靠。BSD4.2解决了其中的许多问题,而在BSD4.3中进一步加强和改善了信号机制。但两者的接口不完全兼容。在Posix 1003.1标准中做了一些强行规定,它定义了一个标准的信号接口,但没有规定接口的实现。目前几乎所有的Unix变种都提供了和Posix标准兼容的信号实现机制。
一、 在一个信号的生命周期中有两个阶段:生成和传送。当一个事件发生时,需要通知一个进程,这时生成一个信号。当进程识别出信号的到来,就采取适当的动作来传送或处理信号。在信号到来和进程对信号进行处理之间,信号在进程上挂起(pending)。
内核为进程生产信号,来响应不同的事件,这些事件就是信号源。主要的信号源如下:
异常:进程运行过程中出现异常;
其它进程:一个进程可以向另一个或一组进程发送信号;
终端中断:Ctrl-C,Ctrl-\等;
作业控制:前台、后台进程的管理;
分配额:CPU超时或文件大小突破限制;
通知:通知进程某事件发生,如I/O就绪等;
报警:计时器到期。
在 Linux 中,信号的种类和数目与硬件平台有关。内核用一个字代表所有的信号,每个信号占一位,因此一个字的位数就是系统可以支持的最多信号种类数。i386 平台上有32 种信号,而Alpha AXP 平台上最多可有 64 种信号。系统中有一组定义好的信号,它们可以由内核产生,也可以由系统中其它有权限的进程产生。可以使用kill命令列出系统中的信号集。
下面是几个常见的信号。
SIGHUP: 从终端上发出的结束信号;
SIGINT: 来自键盘的中断信号(Ctrl-C);
SIGQUIT:来自键盘的退出信号(Ctrl-\);
SIGFPE: 浮点异常信号(例如浮点运算溢出);
SIGKILL:该信号结束接收信号的进程;
SIGALRM:进程的定时器到期时,发送该信号;
SIGTERM:kill 命令发出的信号;
SIGCHLD:标识子进程停止或结束的信号;
SIGSTOP:来自键盘(Ctrl-Z)或调试程序的停止执行信号;
…………
每一个信号都有一个缺省动作,它是当进程没有给这个信号指定处理程序时,内核对信号的处理。有5种缺省的动作:
异常终止(abort):在进程的当前目录下,把进程的地址空间内容、寄存器内容保存到一个叫做core的文件中,而后终止进程。
退出(exit):不产生core文件,直接终止进程。
忽略(ignore):忽略该信号。
停止(stop):挂起该进程。
继续(continue):如果进程被挂起,则恢复进程的运行。否则,忽略信号。
进程可以对任何信号指定另一个动作或重载缺省动作,指定的新动作可以是忽略信号。进程也可以暂时地阻塞一个信号。因此进程可以选择对某种信号所采取的特定操作,这些操作包括:
忽略信号:进程可忽略产生的信号,但 SIGKILL 和 SIGSTOP 信号不能被忽略,必须处理(由进程自己或由内核处理)。进程可以忽略掉系统产生的大多数信号。
阻塞信号:进程可选择阻塞某些信号,即先将到来的某些信号记录下来,等到以后(解除阻塞后)再处理它。
由进程处理该信号:进程本身可在系统中注册处理信号的处理程序地址,当发出该信号时,由注册的处理程序处理信号。
由内核进行缺省处理:信号由内核的缺省处理程序处理,执行该信号的缺省动作。例如,进程接收到SIGFPE(浮点异常)的缺省动作是产生core并退出。大多数情况下,信号由内核处理。
需要指出的是,对信号的任何处理,包括终止进程,都必须由接收到信号的进程来执行。而进程要执行信号处理程序,就必须等到它真正运行时。因此,对信号的处理可能需要延迟一段时间。
信号没有固有的优先级。如果为一个进程同时产生了两个信号,这两个信号会以任意顺序出现在进程中并会按任意顺序被处理。另外,也没有机制用于区分同一种类的多个信号。如果进程在处理某个信号之前,又有相同的信号发出,则进程只能接收到一个信号。进程无法知道它接收了1个还是42个SIGCONT信号。


共享内存
通常由一个进程创建,其余进程对这块内存区进行读写。得到共享内存有两种方式:映射/dev/mem设备和内存映像文件。前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的是实际的物理内存;常用的方式是通过shmXXX函数族来实现共享内存:
int shmget(key_t key, int size, int flag); /* 获得一个共享存储标识符*/
该函数使得系统分配size大小的内存用作共享内存;
void *shmat(int shmid, void *addr, int flag); /* 将共享内存连接到自身地址空间中*/
如果一个进程通过fork创建了子进程,则子进程继承父进程的共享内存,既而可以直接对共享内存使用,不过子进程可以自身脱离共享内存。
shmid为shmget函数返回的共享存储标识符,addr和flag参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址。此后,进程可以对此地址进行读写操作访问共享内存。
对于共享内存,linux本身无法对其做同步,需要程序自己来对共享的内存做出同步计算,而这种同步很多时候就是用信号量实现。

获得共享资源
本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。信号量,分为互斥信号量,和条件信号量。一般说来,为了获得共享资源,进程需要执行下列操作:
(1)测试控制该资源的信号量;
(2)若此信号量的值为正,则允许进行使用该资源,进程将信号量减去所需的资源数;
(3)若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1);
(4)当进程不再使用一个信号量控制的资源时,信号量值加其所占的资源数,如果此时有进程正在睡眠等待此信号量,则唤醒此进程。


其他信息
套接字通信并不为Linux所专有,在所有提供了TCP/IP协议栈的操作系统中几乎都提供了socket,而所有这样操作系统,对套接字的编程方法几乎是完全一样的。


效率比较

进程间通信各种方式效率比较
类型
无连接
可靠
流控制
记录
消息类型优先级
普通PIPE
N
Y
Y
 
N
流PIPE
N
Y
Y
 
N
命名PIPE(FIFO)
N
Y
Y
 
N
消息队列
N
Y
Y
 
Y
信号量
N
Y
Y
 
Y
共享存储
N
Y
Y
 
Y
UNIX流SOCKET
N
Y
Y
 
N
UNIX数据包SOCKET
Y
Y
N
 
N



猜你喜欢

转载自blog.csdn.net/catstarxcode/article/details/79487949