Lazy loaded imageOS04 抽象-进程间通信

type
status
date
slug
summary
tags
category
icon
password

抽象:IPC、管道和套接字

在操作系统中,进程间通信(Inter-Process Communication, IPC)和网络通信是两个核心概念。它们的共同点在于:都被封装成文件 I/O 的形式,使得开发者可以使用熟悉的 read()write()close() 等系统调用进行操作。这种统一的抽象极大简化了编程模型,体现了 Unix “一切皆文件”的设计哲学。

进程间通信(IPC)

进程是操作系统对运行中程序的抽象。为了保障安全性和隔离性,每个进程拥有独立的地址空间,默认无法直接访问其他进程的内存。然而,现实中的任务往往需要多个进程协同完成(例如:一个进程生成数据,另一个进程处理数据),这就催生了对 IPC 机制的需求。
使用普通文件进行通信虽然可行,但存在明显缺陷:
  • 文件是持久化的,每次读写都涉及磁盘 I/O,开销大;
  • 若通信内容是瞬态(transient)的(如临时消息传递),则无需持久保存,使用文件反而低效。
因此,操作系统在内存中提供了一种轻量级的通信机制——管道(Pipe),它通过内核维护的缓冲区实现进程间的数据传递,避免了磁盘访问,显著提升了性能。

管道(Pipe)

管道是一种单向的 IPC 机制,基于先进先出(FIFO)的缓冲区实现。其核心特性如下:
  1. 缓冲区管理
      • 当写入端尝试向已满的缓冲区写入时,写入进程会被阻塞,直到有空间可用;
      • 当读取端尝试从缓冲区读取时,读取进程会被阻塞,直到有数据到达。
  1. 系统调用接口
      • 使用 int pipe(int fd[2]) 创建管道;
      • fd[0]读端fd[1]写端
      • 数据从 fd[1] 写入,从 fd[0] 读出;
      • 管道有固定大小(通常为 64KB,具体取决于系统);
      • 使用完毕后,必须显式关闭两个文件描述符,否则可能导致资源泄漏或死锁。
  1. 关闭语义
      • 所有写端关闭后,后续对读端的 read() 将返回 0(即 EOF);
      • 所有读端关闭后,向写端的 write() 会触发 SIGPIPE 信号(默认终止进程)。
以下是一个典型的父子进程通过管道通信的示例:
注意:实践中应始终关闭不需要的文件描述符,以避免资源浪费和意外行为。

管道的通信协议

管道本身只提供字节流传输,不包含结构化语义。因此,通信双方必须约定协议,包括:
  1. 语法(Syntax)
      • 数据的格式(如:定长消息、以 \\n 分隔的文本、JSON 等);
      • 消息的顺序(如:先发长度,再发内容)。
  1. 语义(Semantics)
      • 收到特定消息后应执行的操作;
      • 错误处理机制(如超时、校验失败等)。
这类协议常通过状态机建模。在更复杂的场景中(如跨语言、跨平台调用),还需解决数据表示差异(如字节序、对齐方式),这正是远程过程调用(RPC)要解决的问题——它在底层通信之上封装了函数调用的语义。

网络通信

网络通信扩展了 IPC 的概念,使不同机器上的进程也能协同工作。主流模型包括:
  • 客户端-服务器(Client-Server):服务器长期运行,客户端按需连接;
  • 对等网络(Peer-to-Peer, P2P):所有节点既是客户端也是服务器。
本节重点讨论基于 TCP 的可靠通信。

套接字(Socket)

套接字是网络通信的核心抽象,首次在 4.2BSD 中引入。它将网络端点(endpoint)视为一种特殊的“文件”,支持 read()write() 等操作,但不支持随机访问(如 lseek() 无效)。
关键特性:
  • 每个套接字关联两个内核队列:
    • 发送队列write() 将数据放入此队列,由协议栈异步发送;
    • 接收队列read() 从此队列取出已到达的数据。
  • 套接字屏蔽了底层网络细节,提供统一的 I/O 接口。
由于网络传输的是无结构的字节流,应用层需自行处理:
  • 分块(如按消息边界解析);
  • 数据翻译(如序列化/反序列化);
  • 可借助 RPC 框架(如 gRPC)自动完成这些工作。

TCP 套接字

TCP(Transmission Control Protocol)提供面向连接、可靠、保序的字节流服务,适用于对数据完整性要求高的场景。

TCP 的核心假设

  • 所有写入的数据最终都会完整送达接收方;
  • 数据按发送顺序被接收。

命名与寻址

TCP 使用 IP 地址 + 端口号 唯一标识一个通信端点:
组件
说明
主机名
example.com,通过 DNS 解析为 IP
IP 地址
IPv4(如 192.168.1.1)或 IPv6(如 2001:db8::1
端口号
16 位整数,范围 0–65535:<br>• 0–1023:知名端口(如 HTTP=80, SSH=22)<br>• 1024–49151:注册端口(用户程序可申请)<br>• 49152–65535:动态/私有端口(由 OS 自动分配)

连接标识

一个 TCP 连接由五元组唯一确定:

服务器与客户端的角色

服务器端

服务器需管理两类套接字:
  1. 监听套接字(Listening Socket)
      • 通过 socket() 创建,bind() 绑定到特定端口,listen() 开始监听;
      • 不可用于数据传输,仅用于接受新连接;
      • 调用 accept() 时,内核会为每个新连接创建一个新的连接套接字
  1. 连接套接字(Connection Socket)
      • accept() 返回;
      • 用于与特定客户端进行 read()/write() 通信。

客户端

  • 端口通常由操作系统自动分配(从动态端口范围中选取);
  • 通过 connect() 主动连接服务器。

典型调用流程

  • 客户端
    • socket()connect()write()/read()close()
  • 服务器
    • socket()bind()listen()accept()write()/read()close()

并发处理

为支持多客户端,服务器需并发处理连接。常见方案包括:
  1. 多进程fork() 为每个连接创建子进程;
  1. 多线程:为每个连接创建新线程;
  1. 线程池:预创建一组工作线程,从连接队列中领取任务(更高效,避免频繁创建/销毁线程)。
现代高性能服务器(如 Nginx)常结合 I/O 多路复用(如 epoll)与线程池,实现高并发、低延迟的服务。
Prev
OS03 抽象-文件与I/O
Next
OS05 并发与同步
Loading...
Article List
SunVapor的小站
计算机系统导论
操作系统
文档