进程间通信方式有哪些?

#计算机网络#操作系统#面试八股

进程间通信方式有哪些?

进程有独立地址空间,彼此看不见对方的内存,所以通信必须借助内核中转或共享映射。 常见六类:管道(pipe/FIFO)、消息队列、共享内存、信号量、信号、套接字(socket,含 Unix domain)。 选型只看三点:数据量、是否需要双向/跨机、能不能接受拷贝。

1. 六种方式对比

方式是否内核中转数据拷贝方向跨主机适用场景
匿名管道 pipe是2 次单向否父子进程、shell 管道
命名管道 FIFO是2 次单向否无亲缘关系进程
消息队列 msgqueue是2 次双向否有格式的小消息
共享内存 shm否0 次双向否大数据量、高频
信号量 semaphore是无数据—否只做同步,不传数据
信号 signal是无数据单向否事件通知(kill)
Unix socket是2 次双向否本机 C/S,可靠有序
TCP socket是多次双向是跨机通信
进程 A                内核                 进程 B
  write() ──► [管道缓冲区 64KB] ──► read()
             (两次拷贝:A→内核,内核→B)

进程 A ──┐
         ├─► [同一块物理页] ◄── 需要信号量/锁保护
进程 B ──┘      (零拷贝,最快)

2. 共享内存为什么最快,又为什么最难用

最快是因为它完全绕过内核:两个进程的页表指向同一批物理页,写方一落笔,读方立刻可见,没有系统调用、没有拷贝。

最难用是因为它不提供任何同步:读方可能在写方写到一半时读到脏数据,所以必须配信号量、互斥锁或原子变量。 而且共享内存段要显式创建、映射、解除、删除,生命周期管理容易漏。

// Unix domain socket:本机最常用的"进程间 TCP"
ln, _ := net.Listen("unix", "/tmp/demo.sock")
defer os.Remove("/tmp/demo.sock")
go func() {
    for {
        conn, err := ln.Accept()
        if err != nil { return }
        go func(c net.Conn) {
            defer c.Close()
            io.Copy(c, c)            // echo
        }(conn)
    }
}()
// 共享内存:mmap 一块匿名共享区,父子进程都能看到
b, err := syscall.Mmap(-1, 0, 4096,
    syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED|syscall.MAP_ANON)
if err != nil { log.Fatal(err) }
copy(b, []byte("hello from parent"))
// fork 出的子进程继承该映射,读到同样的内容;写回时用原子操作或信号量同步

3. 怎么选

# 看系统里现有的 IPC 资源
ipcs -q      # 消息队列
ipcs -m      # 共享内存段
ipcs -s      # 信号量
ls -l /proc/1234/fd | grep pipe   # 进程持有的管道

# 造一个命名管道试试
mkfifo /tmp/f && echo hi > /tmp/f &
cat /tmp/f

经验规则:

  • shell 里串命令 → 匿名管道;
  • 本机两个服务互调,要可靠有序 → Unix domain socket(还能传文件描述符);
  • 高频、大数据量、双方都是自己写的程序 → 共享内存 + 信号量;
  • 只是通知"该干活了" → 信号或 eventfd;
  • 跨机器 → 只能 socket(TCP/UDP)。

4. 常见追问

Q:管道为什么是单向的,容量多大? 管道是内核里一个环形缓冲区,读写两端各持一个 fd,设计上就是单向流;双向要开两条。 Linux 默认容量 64KB(/proc/sys/fs/pipe-max-size 可调上限),写满后 write 会阻塞,这就是"背压"。

Q:匿名管道和命名管道差在哪? 匿名管道只能靠 fork 继承 fd 来共享,所以只能用于有亲缘关系的进程;FIFO 在文件系统里有名字, 任何进程 open 都能接入,但数据仍在内核缓冲区,不落盘。

Q:Unix socket 比 TCP 快吗? 本机上通常更快:不经过网络协议栈的校验和、路由、拥塞控制,直接走 socket 层; 还支持 SCM_RIGHTS 传递文件描述符和凭证。但它不能跨机。

Q:信号能传数据吗? 几乎不能,标准信号只带一个编号,sigqueue 可以附带一个 int 或指针值。信号适合"通知", 不适合"传输",而且处理函数里只能调异步信号安全的函数。

小结

通信方式的选择是"拷贝次数 × 同步复杂度 × 使用范围"的权衡:管道简单但单向且要拷两次, 共享内存零拷贝但要自己做同步,socket 通用且能跨机。答这题给出对比表 + 一句"共享内存必须配信号量", 再补 Unix socket 传 fd 这个细节,就能拉开差距。

笔记加载中…