Linux系统进程间通信

一、Linux进程间通信概述

  • Linux系统进程通信手段是基于UNIX平台的进程通信的。UNIX的进程通信机制主要包括两种:(1)针对单个计算机的system V IPC与(2)基于套接口(socket)的进程通信机制,因此,Linux系统包含这两种进程通信机制,除此之外,Liunx系统还有POSIX进程间通信方式。
    在这里插入图片描述
  • Linux系统的进程间通信主要包括以下几种手段:
  • (1)管道(pipe)与有名管道(named pipe):管道可用于有亲缘关系进程间的通信,而有名通道则克服了该局限。因此,无亲缘关系的进程可由有名通道实现。
  • (2)信号(signal):信号是比较复杂的通信方式,用于通知接收进程某个事件已经发生。
  • (3)消息队列(报文队列):消息队列是消息的链接表,它包括POSIX消息队列、system V消息队列。具有权限的进程可以向队列添加消息,被赋予读权限的进程则可以读取队列中的消息。
  • (4)共享内存:多个进程可以访问同一块内存空间,它是针对其他通信机制运行效率低而设计的。
  • (5)信号量(semaphore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  • (6)套接口(socket):套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。由于套接口非常复杂且非常重要,本文先不展开介绍,后续的博文专门介绍。

二、信号

  • 信号是UNIX系统中最古老的进程间通信方式,它被用来向一个或多个进程发送异步事件信号。信号可以从键盘中断产生,也可以被shell程序用来向其子进程发送任务控制命令。
  • 系统中有一组被详细定义的信号类型,它们由系统中具有某些权限的进程产生。Linux通过存储在task_struct中的信息来实现信号,信号个数收到存储器字长的限制(32位字长的处理器最多可以有32个信号,而64位最多可以有64个信号)。
  • 并不是系统中的每个进程都可以向所有其它进程发送信号,只有核心或超级用户才具有此权限。普通进程只能向具有相同uid和gid的进程或在同一个进程组中的进程发送信号。信号是通过设置task_struct中的signal域中的某一位产生的。

三、管道

  • 通常的Linux shell程序都允许重定向,如:
$ ls|pr|lpr
  • shell程序负责在进程间建立临时的管道。ls的作用为列出当前目录的输出,并将输出作为标准输入送到pr程序中,而pr的输出又被作为标准输入送到lpr程序中。管道是单向的字节流,它将某个进程的标准输出连接到另外进程的标准输入。但是使用管道的进程并不会意识到重定向的存在,执行结果也不会有什么不同。
  • 在Linux系统中,管道是通过指向同一个临时VFS inode(虚拟文件节点)的两个file数据结构来实现的,该VFS inode指向内存中的一个物理页面。下图中的每个file数据结构(通过f_op)指向不同的文件操作例程向量,一个实现对管道的写操作,另一个从管道中执行读操作。
    在这里插入图片描述
  • 这样就隐藏了读/写管道和读/写普通文件时系统调用的差别。当写入进程对管道进行写操作时,字节被拷贝到共享数据页面中;当读取进程对管道进行读操作时,字节从共享数据页面拷贝出来。
  • 管道写操作:
  • 在对管道进行写操作时,通常使用标准的写库函数,其中打开文件与管道的描述符用来对进程的file数据结构集合进行索引。Linux系统调用使用由管道file数据结构指向的write过程,这个write过程用保存在表示管道的VFS inode中的信息来管理写请求,通常具有以下几种情况:
  • (1)如果没有足够的空间容纳所有写入管道的数据,并且管道没有被加锁,则Linux为管道自动上锁,并将写入地址空间的字节拷贝到共享数据页面中。
  • (2)如果管道已经加锁或没有足够空间存储数据,当前进程在管道inode的等待队列中睡眠,同时调度管理器开始执行以选择其他进程。
  • (3)如果写入进程是可以中断的,则当有足够的空间或管道被解锁时,它则会被唤醒,当数据被写入时,管道的VFS inode被解锁,同时任何在inode的等待队列上睡眠的进程都会被唤醒。
  • 管道读操作:
  • (1)进程允许进行非阻塞读,如果没有数据可读或者管道被加锁,则返回错误信息,表明进程可以继续执行。
  • (2)阻塞方式则使读者进程在管道inode的等待队列上睡眠直到进程结束,当两个进程对管道的使用结束时,管道inode和共享数据页面将同时被遗弃。

四、命名管道

  • 命名管道(named pipe)也称为FIFO管道,因为它总是按照先进先出的原则工作,第一个被写入的数据被首先从管道中读出来。与其他管道不同,FIFO管道不是临时对象,而是文件系统中的实体,并且可以通过mkfifo命令来创建。进程只要具有适当的权限,就可以自由使用FIFO管道。
  • 命名管道的打开方式与管道的打开方式稍有不同,管道首先需要创建,即需要进阿里两个file数据结构、VFS inode与共享数据页面。而FIFO管道则已经存在,在调用命名管道时候,并不需要建立,只需要打开与关闭即可。FIFO管道的使用方法与普通管道基本相同,同时它们使用相同的数据结构与操作。

五、 消息队列

  • 消息队列允许一个或者多个进程向它写入或读取消息。Linux系统维护着一个msgque消息队列链表,其中每个元素指向一个描述消息队列的msqid_ds结构。当创建新的消息队列时,系统将从系统内存中分配一个msqid_ds结构,同时将其插入数组中,如下图所示:
    在这里插入图片描述
  • 说明
  • (1)每个msqid_ds结构包含一个ipc_perm结构和指向已经进入此队列消息的指针。另外,Linux保留有关队列修改的时间信息,比如上次系统向队列中写入的时间等。msqid_ds包含两个等待队列:一个为队列写入进程使用,另一个由队列读取进程使用。
  • (2)每次进程试图向写入队列写入消息时,系统把其有效用户和组标识符与此队列的ipc_perm结构中的模式进行比较。如果允许写入操作,则把此消息从进程的地址空间拷贝到msg数据结构中的消息队列尾部。
  • (3)从队列中读取信息的过程与写入队列类似,进程对要写入队列的访问权限进行检验。读取的进程将选择队列中第一个消息或第一个某特定类型的消息。如果没有消息满足读取条件,则读取进程将添加到消息队列的读取等待队列中,然后系统运行调度管理器。

六、信号灯

  • 信号灯最简单的形式是某个可以被多个进程检验和设置的内存单元,该检测与设置操作是不可以被中断的,也就是所谓的原子性操作:一旦启动就无法终止。进程可能一直睡眠到此信号灯的值被另一个进程更改为止;信号灯也可以用来实现临界值(critical region):某一时刻在此区域内的代码只能被一个进程执行。
  • 每个system V IPC信号灯对象对应一个信号灯数组,在Linux系统中使用semid_ds结构来表示。系统中所有semid_ds结构由一组semary指针来指示。在每个信号灯数组中由一个sem_nsems,它表示一个由sem_base指向的sem结构。
    在这里插入图片描述
  • 说明:
  • (1)授权的进程可以使用系统调用来操纵信号灯对象的信号灯数组。该系统调用可以定义很多类型操作,并用3个输入来描述:信号灯索引、操作值、一组标志。其中,信号灯索引是一个信号灯数组的索引,而操作值则是加到信号灯上的数值。
  • (2)系统还在堆栈上建立sem_queue结构并填充各个域,sem_queue结构通过sem_pending与sem_pending_last指针放到此信号灯对象等待队列的尾部。系统吧当前进程放入sem_queue结构中的等待队列中,然后启动调度管理器选择其他进程运行。
  • (3)如果所有信号灯操作都操作成功则无需挂起当前进程,Linux将对信号灯数组中的其他成员进行相同操作,然后检查这些处于等待或挂起状态的进程。具体操作过程为:首先,一次检查挂起队列(sem_pending)中的每个成员,查看信号灯操作能够继续,如果可以则将其sem_queue结构从挂起链表中删除并对信号灯数组发出信号灯操作;另外,Linux还将唤醒处于睡眠状态的进程并使之作为下一个运行的进程。如果在对挂起队列的遍历过程中某些操作灯操作不能完成,则会一直重复该过程,直至所有信号灯操作完成且没有进程需要继续睡眠。
  • (4)信号灯使用过程存在一种严重问题:死锁,即当一个进程处于临界区时它改变了信号灯的值,而离开临界区时如果由于运行是被被kill,而导致没有改回信号灯,则会产生死锁现象。针对该问题,Linux通过维护一组描述信号灯数组变化的链表来防止该现象的发生。其具体操作为思路为八次信号灯设置为进程对其进行操作前的状态。这些状态值被保存在使用该信号灯数组进程的semid_ds与task_struct结构的sem_undo结构中。
  • (5)信号灯操作使系统对它因此的状态变化进行维护,Linux为每个进程维护至少一个对应于信号灯数组的sem_undo结构。如果请求进行信号灯操作的进程没有该结构,则必要时Linux会为其创建一个。这个sem_undo结构将同时放入此进程的task_struct结构和此信号灯数组的semid_ds结构中。
  • (6)当进程被删除时,Linux将遍历该进程的sem_undo集合对信号灯数组使用调整值。如果信号灯集合被删除而sem_undo数据结构还在进程task_struct结构中,则此信号灯数组标识符将被置为无效。

共享内存

  • 共享内存允许一个或多个进程通过同时出现在它们的虚拟地址空间中的内存来通信。此虚拟内存的页面出现在每个共享进程页表中,但此页面并不一定位于所有共享进程虚拟内存的相同位置。与其他system V IPC对象的使用方法一样,对共享内存区域的访问是通过键与访问权限检验来控制的。一旦内存被共享,则不会在检验进程对对象的使用方式。共享内存依赖于其他机制,如system V信号灯,来同步对共享内存的访问。
    在这里插入图片描述
  • (1)每个新建的共享内存区域由一个shmid_ds数据结构来表示,它们被保存在shm_segs数组中。shmid_ds数据结构描述共享内存的大小、进程如何使用以及共享内存映射到各自地址空间的方式。
  • (2)每个使用此共享内存的进程必须通过系统调用将其连接到虚拟内存上。此时,进程创建新的vm_area_struct来描述此共享内存。进程可以决定此共享内存在其虚拟地址空间的位置。新的vm_area_struct结构将被放到由shmid_ds指向的vm_area_struct链表中。通过vm_next_shared与vm_prev_shared指针将它们连接起来。
  • (3)当进程不在共享此虚拟内存时,进程与共享捏村的连接将被断开。如果其他进程还在使用该内存,则此操作只影响当前进程,其对应的vm_area_struct结构将从shmid_ds结构中删除并收回。当前进程对此共享内存地址的页表入口将被更新并设置为无效。当最后一个进程断开与共享内存的连接时,当前位于物理内存中的共享内存页面将被释放,同时被释放的还有此共享内存的shmid_ds结构。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值