Linux进程间通讯机制

Linux支持管道、信号、unix system V三种IPC(Inter-Process-Communication)机制。如下分别对三种机制加以简单介绍。linux

 

1、信号机制:shell

信号又称做软中断,用来通知进程发生了异步事件;主要用于向一个或多个进程发异步事件信号,信号能够经过键盘中断触发、当进程访问虚拟内存中不存在的线性地址或者禁止访问的地址时也会触发。也能够用于shell做业任务时向子进程发送控制命令。编程

Linux系统共有30多种信号,每一个信号名称都以 SIG 三个字符开头,例如异常终止信号名为 SIGABRT。在头文件<signal.h>中,这些信号都被定义为正整数。如下为经常使用信号:数组

 

Linux 可使用 kill 命令向指定进程发送指定信号,kill命令的用法为:数据结构

kill [选项]PID异步

进程能够选择忽略上面的大多数信号,但和SIGKILL是不可忽略的。其中SIGSTOP信号,使进程中止执行;而SIGKILL信号使进程停止。对于其余状况,进程能够自主决定如何处理各类信号:它能够阻塞信号;若是不阻塞,也能够选择由进程本身处理信号或者由内核来处理。由内核来处理信号时,内核对每一个信号使用相应的缺省处理动做,例如:当进程收到SIGFPE信号(浮点异常)时,内核的缺省动做是进行内核转贮 (core dump),而后停止该进程。函数

信号之间不存在内在的相对优先级。若是对同一个进程同时产生两个信号的话,它们会按照任意顺序提交给该进程,而且对同种信号没法区分信号的数量。测试

Linux使用存贮在每一个进程task_struct结构中的信息实现信号机制它支持的信号数受限于处理器的字长,具备32位字长的处理器有32种信号,64位字长的处理器最多有64种信号,task_struct以下所示:优化

struct task_struct {ui

         volatile long state;  /* -1 unrunnable, 0 runnable, >0 stopped */

         void *stack;

         atomic_t usage;

         unsigned int flags;   /* per process flags, defined below */

         unsigned int ptrace;

 

         int lock_depth;                   /* BKL lock depth */        

  …

  /* signal handlers */

  struct signal_struct *signal;

  struct sighand_struct *sighand;

         …

}

当前未处理的信号记录在signal域中,并把阻塞信号掩码对应位设置为阻塞状态。但对 SIGSTOP和SIGKILL信号来讲,全部的信号都被设置为阻塞状态。若是一个被阻塞的信号产生了,就将一直保持未处理状态,直到阻塞被取消。

并非系统中的每一个进程均可以向其余的进程发消息,只有内核和超级用户能够作到这一点。普通的进程只能向同一进程组或具备相同的uid和gid的进程发送信号。信号能够经过设置task_struct结构signal域中相应中的位来产生。若是一个进程没有阻塞信号,正处于可中断的等待信号状态中,当等待的信号出现时 ,系统能够经过把该进程的状态变成运行状态 ,而后放入候选运行队列中的方法来唤醒它。经过上面的方法在下次调度时 ,进程调度器会把该进程做为候选运行进程进行调度。

Linux兼容POSIX标准,进程在某个信号处理例程被调用时,能指出哪些信号能够被阻塞。这意味着在调用进程信号处理例程时须要改变阻塞掩码,当信号处理例程结束时,阻塞掩码必需要恢复到初始值。所以linux增长了一次对清理例程的调用。清理例程按照信号处理例程的调用栈来恢复初始的阻塞掩码。在几个信号处理例程都须要被调用时,linux也提供了优化方案。linux把这些例程压入栈中,这样每当一个处理例程退出时,下一个处理例程当即被调用,当全部处理例程都完成后清理例程被调用。

2、管道机制:

管道是一个进程链接数据流到另外一个进程的通道, 它一般是用做把一个进程的输出经过管道链接到另外一个进程的输入。在 Linux 命令中一般经过符号“ |”来使用管道,例如:

$ ps -ef | grep init

此命令中 ps 是一个独立的进程, grep 也是一个独立的进程,中间的管道把原本要输出到屏幕的数据输出到 grep 这个进程中,做为 grep 这个进程的输入。

在Linux系统中,管道用两个指向同一个临时性VFS索引节点的文件数据结构来实现。这个临时性的VFS索引节点指向内存中的一个物理页面。下图代表每一个文件数据结构包含指向不一样文件操做例程向量的指针。一个例程用于写管道,另外一个用于从管道中读数据。从通常读写普通文件的系统调用的角度来看,这种实现方法隐藏了下层的差别。当写进程执行写管道操做时,数据被复制到共享的数据页面中 ;而读进程读管道时,数据又从共享数据页中复制出来。Linux必须同步对管道的访问,使读进程和写进程步调一致。为了实现同步, Linux使用锁、等待队列和信号量这三种方式。

 

管道分为匿名管道和命名管道两种,匿名管道主要用于两个进程间有父子关系的进程间通讯,命名管道主要用于没有父子关系的进程间通讯。

2.1 匿名管道

匿名管道是不能在文件系统中以任何方式看到的半双工管道。半双工管道意味着管道的一端只读或只写。父子进程间匿名管道通讯示意图如图:

 

2.2命名管道

命名管道也被称为 FIFO 文件, 它突破了匿名管道没法在无关进程之间通讯的限制,使得同一主机内的全部的进程均可以通讯。同时命名管道是一个特殊的文件类型, 它在文件系统中以文件名的形式存在,在stat结构中 st_mode 指明一个文件结点是否是命名管道。

struct stat {

         unsigned long  st_dev;              /* Device.  */

         unsigned long  st_ino;               /* File serial number.  */

         unsigned int   st_mode;                  /* File mode.  */

};

mkfifo()函数用来建立一个命名管道,它的原型以下:

int mkfifo(const char *pathname, mode_t mode);

mkfifo()建立一个真实存在于文件系统中的命名管道文件,参数 pathname指定了文件名,参数 mode 则指定了文件的读写权限。 函数成功返回 0,不然返回-1 并设置 errno。

mkfifo()建立命名管道文件后,须要经过命名管道通讯的进程须要打开该管道文件,而后经过 read、 write 函数像操做普通文件同样进行通讯。

3、System V机制:

System V是Unix中出现最先的三种进程间通讯机制,linux也支持;它们是消息队列、信息量和共享存储器。这些 System V的进程间通讯机制使用相同的认证方法,即经过系统调用向内核传递这些资源的全局惟一标识来访问它们,linux使用访问许可的方式核对对 System V-IPC对象的访问,这种方式与文件访问权限的检查十分类似。

System V IPC对象的访问权限是由该对象的建立者经过系统调用来实现的。 linux的每种IPC机制都把 IPC对象的访问标识做为对系统资源表的索引 ,但访问标识不是一种直接的索引,而是由索引标识经过某些运算来产生的对象索引。

Linux系统中全部表明 System V IPC 对象的数据结构中都包括ipc_perm数据结构,在ipc_perm结构中有拥有者和建立者进程的用户标识和组标识、该对象的访问模式以及 IPC对象的密钥。密钥的用处是肯定 System V IPC 对象的索引标识。 Linux系统中支持两种密钥:公钥和私钥。若是 IPC对象的密钥是公共的,那么系统中的进程在经过权限检查后就能够获得System V IPC对象的索引标识。但要注意 System V IPC对象不是经过密钥而是经过它们的索引标识来访问的。

如下为ipc_perm数据结构:

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_t    mode;

         unsigned short         seq;

};

3.1 消息队列

消息队列容许一个或多个进程向队列中写入消息, 而后由一个或多个读进程读出(见下图)。Linux系统维护一个消息队列的表。该表是msqid_ds结构的数组,数组中每一个元素指向一个能彻底描述消息队列的msqid_ds数据结构。一旦一个新的消息队列被建立,则在系统内存中会为一个新的msqid_ds数据结构分配空间,并把它插入到数组中。每一个msqid_ds结构都包含ipc_perm数据结构以及指向进入该队列的消息的指针。除此以外,Linux还记录像队列最后被更改的时间等队列时间更改信息。 msqid_d结构还包括两个等待队列;一个用于存放写进程的消息,另外一个用于消息队列。

每次进程要向写队列写入消息时,系统都要把它的有效用户标识和组标识与该队列的ipc_perm数据结构中的访问模式进行比较。若是进程能够写队列,那么消息会从进程的地址空间复制到一个 msg数据结构中,而后系统把该msg数据结构放在消息队列的尾部。因为Linux限制写消息的数量和消息的长度,因此可能会出现没有足够的空间来存放消息的状况。这时当前进程会被放入对应消息的写等待队列中,系统调用进程调度器选择合适的进程运行。在该消息队列中有一个或多个消息被读出时,睡眠的进程会被唤醒。

 struct msqid_ds {

         struct ipc_perm msg_perm;

         struct msg *msg_first;             /* first message on queue,unused  */

         struct msg *msg_last;              /* last message in queue,unused */

         unsigned long  msg_lcbytes; /* Reuse junk fields for 32 bit */

         unsigned long  msg_lqbytes;         /* ditto */

         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 */

};

从队列中读消息的过程与前面类似,进程对写队列的访问权限会再次被核对。一个读进程能够选择得到队列中的第一个消息而不考虑消息的类型,仍是读取某种特别类型的消息。若是没有符合要求的消息的话,读进程会被加入到该消息的读等待队列中,系统唤醒进程调度器调度新进程运行。一旦有新消息被写入消息队列。睡眠的进程被唤醒,并再次运行。

3.2 信号量

多进程编程中须要关注进程间同步及互斥。同步是指多个进程为了完成同一个任务相互协做运行,而互斥是指不一样的进程为了争夺有限的系统资源(硬件或软件资源)而相互竞争运行。

信号量是用来解决进程间的同步与互斥问题的一种进程间通讯机制,它是一个特殊的变量,变量的值表明着关联资源的可用数量。信号量只能进行的两个原子操做: P 操做: V 操做。

P 操做:若是有可用的资源(信号量值>0),则占用一个资源(给信号量值减 1);若是没有可用的资源(信号量值=0),则进程被阻塞直到系统将资源分配给该进程(进入信号量的等待队列,等到资源后唤醒该进程)。

V 操做:若是在该信号量的等待队列中有进程在等待资源,则唤醒一个阻塞进程;若是没有进程等待它,则释放一个资源(给信号量值加 1)。

System V的每一个IPC信号量对象都对应一个信号量数组,在 Linux中用semid_ds数据结构来表示它,以下:

struct semid_ds {

         struct ipc_perm       sem_perm;                /* permissions .. see ipc.h */

         __kernel_time_t      sem_otime;               /* last semop time */

         __kernel_time_t      sem_ctime;               /* last change time */

         struct sem        *sem_base;              /* ptr to first semaphore in array */

         struct sem_queue *sem_pending;          /* pending operations to be processed */

         struct sem_queue **sem_pending_last;        /* last pending operation */

         struct sem_undo     *undo;                        /* undo requests on this array */

         unsigned short         sem_nsems;             /* no. of semaphores in array */

};

系统中全部的semid_ds数据结构都被一个叫semary的指针向量指向。在每一个信号量数组中都有 sem_nsems域,这个域由sem_base指向的sem数据结构来描述。全部容许对System V IPC信号量对象的信号量数组进行操做的进程,都必须经过系统调用来执行这些操做。在系统调用中能够指出有多少个操做。而每一个操做包含三个输入项:信号量的索引、操做值和一组标志位。信号量索引是对信号量数组的索引值,而操做值是加到当前信号量值上的数值。首先Linux会测试是否全部的操做都会成功 (操做成功指操做值加上信号量当前值的结果大于 0,或者操做值和信号量的当前值都是 0 )。若是信号量操做中有任何一个操做失败, Linux在操做标志没有指明系统调用为非阻塞状态时,会挂起当前进程。若是进程被挂起了,系统会保存要执行的信号量操做的状态,并把当前进程放入等待队列中。经过在栈中创建一个sem_queue数据结构,并填入相应的信息的方法来实现前面的保存信号量操做状态的。新的 sem_queu 数据结构被放在对应信号量对象的等待队列的末尾 ( 经过使用sem_pending和sem_pending_lastt指针),当前进程被放在 sem_queue数据结构的等待队列中,而后系统唤醒进程调度器选择其余进程执行。

 

信号量存在着死锁的问题,当一个进程进入了关键段,改变了信号量的值后,因为进程崩溃或被停止等缘由而没法离开关键段时,就会形成死锁。 Linux经过为信号量数组维护一个调整项列表来防止死锁。主要的想法是在使用调整项后,信号量会被恢复到一个进程的信号量操做集合执行前的状态。调整项被保存在sem_undo数据结构中,而这些sem_undo数据结构则按照队列的形式放在 semid_ds数据结构和进程使用信号量数组的task_struct数据结构中。

当进程被删除时,退出时Linux会用这些sem_undo数据结构集合对信号量数组进行调整。若是信号量集合被删除了,那么这些sem_undo数据结构还存在于进程的sem_undo结构的队列中,而仅把信号量数组标识标记为无效。在这种状况下,信号量清理程序仅仅丢掉这些数据结构而不释放它们所占用的空间。

3.3 共享内存

共享内存是容许两个不相关的进程访问同一个逻辑内存的进程间通讯方法,是在两个正在运行的进程之间共享和传递数据的一种很是有效的方式。

不一样进程之间共享的内存一般安排为同一段物理内存。进程能够将同一段共享内存链接到它们本身的地址空间中,全部进程均可以访问共享内存中的地址,就好像它们是由用 C语言 malloc()分配的内存同样。两个进程使用共享内存通讯机制以下图所示。

 

共享存储区不须要在全部进程的虚存中占有相同的虚地址。像全部的 System V IPC对象同样,共享存储区的访问控制是经过密钥和访问权限检查来实现的。一旦某一内存区域被共享了,系统就没法检查进程如何使用这部份内存区域。所以系统必须使用信号量等其余的机制来同步对存储器的访问。

相关文章
相关标签/搜索