Linux 操作系统中的管道与共享内存

时间:2024-12-18 07:10:36

目录

一、匿名管道

(一)基本概念

(二)关键现象

(三)管道特性

二、命名管道

(一)基本概念

(二)关键特性

三、共享内存

(一)基本概念

(二)关键函数

(三)共享内存的特点

(四)共享内存的生命周期

四、总结


在 Linux 操作系统中,管道和共享内存是两种重要的进程间通信(IPC)机制。本文将介绍 Linux 中的管道,包括匿名管道和命名管道,以及与共享内存相关的内容。

一、匿名管道

(一)基本概念

匿名管道是一种单向的数据通信通道,主要用于具有血缘关系的进程之间进行 IPC,通常在父子进程中使用。它由两个文件描述符fd[0](读端)和fd[1](写端)组成。

(二)关键现象

  1. 管道为空且管道正常,read 会阻塞:当管道中没有数据可读时,调用read函数的进程会被阻塞,直到有数据写入管道。
    • 例如,在一个父子进程通信的场景中,子进程负责从管道读取数据,如果管道为空,子进程会暂停执行,等待父进程写入数据。
  1. 管道为满且管道正常,write 会阻塞:管道有一定的容量上限,在 Ubuntu 中通常为 64KB。当管道已满时,调用write函数的进程会被阻塞,直到管道中有空间可供写入。
    • 假设一个进程不断向管道写入数据,当管道达到容量上限时,该进程会暂停,直到管道中的数据被读取,腾出空间。
  1. 管道写端关闭且读端继续,读端读到 0,表示读到文件结尾:当管道的写端被关闭时,继续从读端读取数据的进程会读到文件结尾的标志(0)。
    • 比如,父进程关闭了管道的写端,子进程在读取完管道中的所有数据后,会收到文件结尾的信号。
  1. 管道写端正常且读端关闭,OS 会直接杀掉写入的进程:如果管道的读端被关闭,而写端的进程继续写入数据,操作系统会发送信号终止该写入进程。
    • 这是为了防止进程在无法写入数据的情况下无限阻塞或出现错误行为。

(三)管道特性

  1. 面向字节流:数据以字节流的形式在管道中传输,没有固定的消息边界。
    • 这意味着写入和读取的数据可以是任意长度的字节序列,不像消息队列那样有明确的消息边界。
  1. 用于具有血缘关系的进程进行 IPC:通常在父子进程或有共同祖先的进程之间使用。
    • 例如,在一个 shell 命令管道中,多个命令通过管道连接,这些命令通常是由同一个 shell 进程启动的子进程。
  1. 文件的生命周期随进程,管道也是:当创建管道的进程结束时,管道也会被销毁。
    • 如果一个父进程创建了管道并启动了子进程进行通信,当父进程和子进程都结束时,管道将不再存在。
  1. 单向数据通信:管道只能在一个方向上传输数据,要么从写端到读端,要么从读端到写端。
    • 要实现双向通信,需要创建两个管道。
  1. 管道自带同步互斥等保护机制:管道内部实现了同步和互斥机制,确保数据的正确读写。
    • 当一个进程正在读取管道数据时,其他进程不能同时写入数据,反之亦然。

二、命名管道

(一)基本概念

命名管道是一种真正存在的文件,具有路径和文件名,具有唯一性。可以通过mkfifo函数创建命名管道,通过unlink函数删除命名管道。命名管道可以被多个没有血缘关系的进程访问,是一种公共资源。

(二)关键特性

  1. 读端打开文件时,写端还没有打开的时候,读端的 open 会阻塞:当一个进程以读方式打开命名管道时,如果没有其他进程以写方式打开该管道,读进程会被阻塞,直到有写进程打开管道。
    • 例如,在一个服务器 - 客户端模型中,服务器进程以读方式打开命名管道,等待客户端进程写入数据。如果没有客户端连接,服务器进程会一直阻塞。
  1. 文件会记录用户 UID,进程的 PCB 有 UID 就可以查看是拥有者所属组 other:命名管道文件与普通文件一样,记录了创建者的用户 ID(UID)和所属组等信息。进程可以根据自己的 UID 和权限来访问命名管道。
    • 这确保了命名管道的安全性和访问控制。

三、共享内存

(一)基本概念

共享内存是一种让两个进程在各自的用户空间共享内存块的 IPC 机制。它的通信速度非常快,但没有自带保护机制,需要用户自己通过信号量、命名管道等方式实现保护。

(二)关键函数

  1. shmget(key, size, shmflg):创建共享内存的函数。
    • key是用户输入的一个值,用于保障共享内存的唯一性。可以通过ftok函数生成唯一值,确保不同的进程能够看到同一个共享内存。
    • size是要创建的共享内存的大小。
    • shmflg是权限标志等参数。
  1. shmat:将共享内存挂接到进程的地址空间上。
    • 一旦共享内存被创建,进程可以使用shmat函数将其映射到自己的地址空间,以便直接访问共享内存中的数据。
  1. shmdt:去关联共享内存。
    • 当进程不再需要访问共享内存时,可以使用shmdt函数解除与共享内存的关联。

(三)共享内存的特点

  1. 通信速度最快:由于共享内存直接在进程的用户空间进行数据共享,不需要进行数据的复制和传递,因此通信速度非常快。
    • 相比其他 IPC 机制,如管道和消息队列,共享内存能够实现更高效的数据传输。
  1. 没有加任何保护机制:共享内存本身没有提供同步和互斥机制,需要用户自己实现保护。
    • 例如,可以使用信号量来控制对共享内存的访问,确保数据的一致性和完整性。
  1. 权限和文件一样:共享内存具有与文件类似的权限设置,可以通过shmget函数的参数来指定共享内存的权限。
    • 如果进程没有足够的权限,可能无法创建或访问共享内存。

(四)共享内存的生命周期

  1. 用户必须让 OS 释放
    • 指令释放:可以使用ipcrm -m +shmid命令删除共享内存,使用ipcs -m命令查看共享内存的状态。
    • 代码释放:可以使用shmctl函数来控制共享内存的状态,包括释放共享内存。
  1. 随内核:进程结束后,共享内存的生命周期随内核。如果没有被显式释放,共享内存可能会一直存在,直到操作系统重启。

四、总结

Linux 操作系统中的管道和共享内存是强大的进程间通信机制。匿名管道适用于具有血缘关系的进程之间的单向通信,具有自动的同步互斥保护机制。命名管道是一种真正的文件,可以被多个进程访问,实现了更灵活的 IPC。共享内存提供了最快的通信速度,但需要用户自己实现保护机制。在实际应用中,根据不同的需求选择合适的 IPC 机制,可以提高系统的性能和可靠性。