Unix 域套接字、管道与 FIFO

共 27 题
📑 题目列表 27 题
#
★★★

1. Unix 域套接字(AF_UNIX)相比 TCP loopback 省去了哪些协议栈处理,性能优势具体来自哪几层?

请解释 Unix 域套接字(AF_UNIX)相比 TCP loopback 省去了哪些协议栈处理,性能优势具体来自哪几层?

  • 理解 AF_UNIX 与 TCP loopback 的差异
  • 理解性能优势来源
  • 理解本地 IPC 特性

AF_UNIX 是内核内的本地进程间通信,数据直接在内核内存中传递,经 socket 层与 VFS 层处理,不经过网络协议栈。相比 TCP loopback,AF_UNIX 省去了:1) IP 层(IP 头封装、路由)、TCP 层(三次握手、序列号、校验、拥塞控制、重传)的处理;2) 网络设备层(loopback 接口的收发、队列);3) 相关状态机与定时器。性能优势来自:省去 TCP/IP 协议栈的计算开销(校验、分段、状态机)、减少数据拷贝与缓冲处理、更低的延迟与 CPU 占用。AF_UNIX 利用内核内存直接投递,无需跨网络栈。因此 AF_UNIX 延迟更低、吞吐更高,是本地 IPC 的高效选择。

AF_UNIX 优势源于"无需 TCP/IP 状态机与网络设备层"。数据在内核内直接传递,省去封装、校验、路由等。理解省去哪些层是回答性能优势的关键。

#
★★

2. SCM_RIGHTS 辅助数据如何在进程间传递文件描述符,内核在接收端对 fd 表做了怎样的操作?

请解释 SCM_RIGHTS 辅助数据如何在进程间传递文件描述符,以及内核在接收端对 fd 表做了怎样的操作?

  • 理解 SCM_RIGHTS 辅助数据
  • 理解接收端 fd 表操作
  • 理解引用计数

SCM_RIGHTS 是 AF_UNIX 的辅助数据(control message),通过 sendmsg 的 cmsghdr 携带,可把发送端的一个或多个 fd 传递给接收端。内核在传递时:发送端把 fd 对应的 file 对象(打开文件描述)的引用计数增加,并把它交给接收端;接收端在 recvmsg 时,内核在接收进程的 fd 表中分配一个新的 fd 号,指向同一个 file 对象(共享同一文件描述,共享偏移与状态)。因此接收端得到一个新的 fd,它指向与发送端相同的底层文件。接收端必须显式关闭该 fd 以释放引用。SCM_RIGHTS 常用于传递 socket、pipe、文件等 fd,实现权限/句柄传递。

SCM_RIGHTS 传递的是"file 对象引用",接收端 fd 表新增 fd 指向同一 file 对象,共享打开状态。引用计数由内核管理,发送与接收都需正确关闭。可传递任意 fd 类型。

#
★★

3. 管道写入长度不超过 PIPE_BUF 时保证原子性,超过后原子性如何被破坏,多写者场景应如何规避交错?

请解释管道写入长度不超过 PIPE_BUF 时保证原子性,超过后原子性如何被破坏,以及多写者场景应如何规避交错?

  • 理解超过 PIPE_BUF 的破坏
  • 理解多写者交错
  • 理解规避方法

POSIX 规定写入长度不超过 PIPE_BUF(Linux 上为 4096 字节)时,写操作是原子的:数据一次性写入,不会与其他写者交错。当写入长度超过 PIPE_BUF 时,管道将数据分段写入,可能与其他写者的数据交错(A 写一半、B 写一半、A 再写),破坏原子性。多写者场景规避交错:1) 每次写入不超过 PIPE_BUF,保证原子;2) 用互斥锁/信号量串行化写;3) 用一次写完整消息(消息长度 <= PIPE_BUF)的方式;4) 用带边界的记录(如 socketpair 的 SOCK_SEQPACKET 保留报文边界)。超长时需应用层加锁或改用带报文边界的机制。

PIPE_BUF 原子性是无锁的保证。超过则可能交错。规避靠"限制写长 <= PIPE_BUF"或加锁,或改用保边界的 IPC。这是多写者管道通信的关键。

#
★★

4. 命名管道(FIFO)与匿名管道在生命周期、命名方式以及无亲缘关系进程通信上有何差异?

请解释命名管道(FIFO)与匿名管道在生命周期、命名方式以及无亲缘关系进程通信上的差异?

  • 理解 FIFO
  • 理解生命周期与命名
  • 理解无亲缘通信

匿名管道(pipe)由 pipe() 创建,无名字,只有创建它的进程及其 fork 出的子进程能用(通过继承的 fd),生命周期随进程,进程退出后销毁,不能用于无亲缘关系的进程。FIFO(命名管道)在文件系统中有名字(通过 mkfifo 创建),生命周期与文件系统对象相关(可持久存在),任何知道路径的进程都能打开(open O_RDONLY/O_WRONLY),实现无亲缘关系进程间的通信。差异:匿名管道无名、靠 fd 继承、仅亲缘进程;FIFO 有名、靠路径访问、支持无亲缘进程。FIFO 类似文件,需双方 open 打开,open 默认阻塞直到对端打开。

核心差异是"是否可被无亲缘进程访问"。匿名管道靠 fd 继承(亲缘),FIFO 靠路径(任意进程)。生命周期随进程 vs 随文件系统。理解差异便于选择 IPC 方式。

#
★★

5. AF_UNIX 的抽象命名空间(abstract namespace)地址与文件系统路径地址有何区别,为何不占 inode 却随网络命名空间消亡?

请解释 AF_UNIX 的抽象命名空间地址与文件系统路径地址的区别,以及为何不占 inode 却随网络命名空间消亡?

  • 理解路径命名空间
  • 理解 inode 占用
  • 理解网络命名空间绑定

AF_UNIX 地址既可以是文件系统路径(sun_path 以可打印字符开头,作为路径名),也可以是抽象命名空间(sun_path 以 NUL 开头(\0),后续字节作为抽象名)。区别:路径地址在文件系统占据一个 inode/目录项,可被 ls/stat 看到,受权限保护,随挂载命名空间存在;抽象命名空间地址不占文件系统 inode(不创建目录项,仅在内核抽象的 socket 命名空间中注册),因此无文件系统权限/可见性,但随创建它的网络命名空间存在而消亡。抽象命名空间既无磁盘 inode,生命周期绑定网络命名空间,网络命名空间销毁时其内 socket 枯竭、地址消亡。路径地址受挂载命名空间影响。

抽象命名空间"不占 inode、不可见、无权限"但绑定网络命名空间;路径地址"占 inode、可见、有权限"但受挂载命名空间影响。理解差异便于选择地址形式。

#
★★

6. Unix 域流式套接字接收缓冲满时会阻塞发送方,这一背压特性与管道触发 SIGPIPE 的行为有何不同?

请解释 Unix 域流式套接字接收缓冲满时阻塞发送方这一背压特性,与管道触发 SIGPIPE 的行为有何不同?

  • 理解 AF_UNIX 流套接字的背压
  • 理解 SIGPIPE
  • 理解两者差异

AF_UNIX 流式套接字在接收缓冲满时,发送方(阻塞模式)会阻塞,等待接收方消费数据腾出缓冲,这是"背压":发送速率被接收方消费速率限制,数据不会丢失。而管道在写端 no reader(读端已关闭)时,写会触发 SIGPIPE(默认终止进程)而非阻塞。差异:AF_UNIX 流套接字缓冲满时是"阻塞等待",只要对端仍开放并读取,发送会持续;只有对端关闭(读端关闭)时写才触发 SIGPIPE/EPIPE。管道同样:读端关闭时写触发 SIGPIPE,写端自己关闭则读 EOF。两者背压都靠"缓冲满阻塞",但 SIGPIPE 是"对端已关闭"时的错误信号,与"缓冲满"不同。缓冲满阻塞是健康背压,SIGPIPE 是连接失效。

缓冲满阻塞 = 正常背压(对端活跃);SIGPIPE = 对端已关闭(连接失效)。要区分"慢但对端在"与"对端已关"。背压保证不丢数据,SIGPIPE 是错误处理。

#
★★

7. socketpair 创建的成对套接字在 fork 后如何充当父子进程双向通道,相比两个管道有何便利?

请解释 socketpair 创建的成对套接字在 fork 后如何充当父子进程双向通道,相比两个管道的便利?

  • 理解 socketpair
  • 理解双向通信
  • 理解与两个管道对比

socketpair(AF_UNIX, SOCK_STREAM, 0, sv) 创建一对已连接的套接字(sv[0] 与 sv[1]),它们是双向的:任一端可读可写。fork 后父子进程各持两个 fd(sv[0]、sv[1]),各自关闭一个(父关 sv[0] 用 sv[1],子关 sv[1] 用 sv[0]),即可形成双向通道:父写 sv[1] 子读 sv[0],父读 sv[1] 子写 sv[0]。便利:一个 socketpair 即可提供双向通信,而管道是单向的(一个 pipe 只能一个方向),需要两个管道才能双向,且要小心管理四端。socketpair 是一个 fd 对、双向、无需额外创建,更简洁。也支持 SOCK_DGRAM(数据报)与 SOCK_SEQPACKET(保边界)。

socketpair 成对且双向,一个调用提供"双向通道",而管道单向需两个。fork 后各留一端形成全双工。这是 socketpair 相对两个管道的便利。

#
★★

8. 为何传输大量数据时共享内存通常优于 Unix 域套接字,而控制消息与凭据传递仍首选 AF_UNIX?

请解释为何传输大量数据时共享内存通常优于 Unix 域套接字,而控制消息与凭据传递仍首选 AF_UNIX?

  • 理解 AF_UNIX 数据拷贝
  • 理解控制消息/凭据需求
  • 理解选型

传输大量数据时,共享内存(mmap MAP_SHARED 或 System V shm)直接映射到各进程地址空间,进程读写共享内存不经过内核、无数据拷贝,性能极高;AF_UNIX 即使走内核,也可能有数据拷贝(recvmsg 从内核缓冲拷贝到用户态),且受缓冲区与背压限制。因此大批量数据共享内存更优。但控制消息与凭据传递仍首选 AF_UNIX:共享内存无"消息+N个进程"的同步/通知机制,且无法传递 fd 或对端凭据;AF_UNIX 支持 SCM_RIGHTS(传 fd)、SCM_CREDENTIALS(传对端 PID/UID/GID)、事件通知(读可唤醒),适合控制消息与凭据。选型:大数据用共享内存 + 事件通知,控制/凭据用 AF_UNIX。

共享内存零拷贝、无背压,适合大数据;AF_UNIX 支持 fd/凭据传递与事件通知,适合控制消息。两者常配合:共享内存传数据,AF_UNIX 传控制与凭据。

#
★★

9. AF_UNIX 的 SOCK_SEQPACKET 相比 SOCK_STREAM 保留每次 send 的报文边界且保证有序交付,适合哪些本地协议场景,报文超过接收缓冲时如何处理?

请解释 AF_UNIX 的 SOCK_SEQPACKET 相比 SOCK_STREAM 保留报文边界且保证有序交付的特性,适合哪些本地协议场景,以及报文超过接收缓冲时如何处理?

  • 理解 SOCK_SEQPACKET
  • 理解有序交付
  • 理解超缓冲处理

SOCK_SEQPACKET 是顺序包(顺序报文)套接字:每次 send 的数据作为一个完整报文(record)交付,保留报文边界,接收端 recv 一次读到一条报文(与 send 长度一致),且保证有序交付(同流顺序)。相比 SOCK_STREAM(字节流,无边界,需应用自行分包),SOCK_SEQPACKET 适合需要"消息边界 + 有序"的本地协议场景:如 RPC 请求/响应、日志转发、命令分发、需要按消息解析的协议。报文超过接收缓冲时:若报文长度超过缓冲,则整条报文被截断(多余数据丢弃),recv 返回截断长度(可能设置 MSG_TRUNC),应用需处理超长报文(增大缓冲或分片发送)。SOCK_SEQPACKET 保留边界,超长报文会截断。

SOCK_SEQPACKET 保留报文边界 + 有序,适合面向消息的本地协议。超长报文会被截断(非流式分段),故需控制报文大小或增大缓冲。流式需自行分包。

#
★★

10. AF_UNIX 数据报(SOCK_DGRAM)的单报文长度上限由什么决定,发送超限返回 EMSGSIZE 时应用应如何分片或改用流式套接字?

请解释 AF_UNIX 数据报(SOCK_DGRAM)的单报文长度上限由什么决定,以及发送超限返回 EMSGSIZE 时应用应如何分片或改用流式套接字?

  • 理解 SOCK_DGRAM 报文上限
  • 理解 EMSGSIZE
  • 理解改用流式

AF_UNIX 数据报的单报文长度上限由内核的发送缓冲(SO_SNDBUF)与系统限制决定,通常上限与缓冲大小相关(送出的报文长度不能超过发送缓冲,Linux 上约为 2 倍缓冲或上限)。当报文长度超过上限时,send 返回 -1 且 errno=EMSGSIZE。处理:1) 分片:应用把大消息拆成多条小报文(每条在上限内),接收端按顺序重组;2) 增大缓冲(setsockopt SO_SNDBUF)提高上限(但受系统上限限制);3) 改用流式套接字(SOCK_STREAM)或 SOCK_SEQPACKET:流式无单报文上限(可连续发送),适合大数据传输,但需自行维护边界。选型:数据量大且需边界用 SOCK_SEQPACKET,纯字节流用 SOCK_STREAM,小消息用 SOCK_DGRAM。

SOCK_DGRAM 报文上限受缓冲限制,超限返回 EMSGSIZE。分片、调缓冲或改用流式/顺序包。保留边界用 SEQPACKET,无边界用 STREAM。

#
★★

11. AF_UNIX 流套接字的 SO_SNDBUF/SO_RCVBUF 如何影响背压阈值,与 TCP 的缓冲区自动调优有何不同,为什么本地场景更易出现缓存堆积?

请解释 AF_UNIX 流套接字的 SO_SNDBUF/SO_RCVBUF 如何影响背压阈值,与 TCP 的缓冲区自动调优有何不同,以及为何本地场景更易出现缓存堆积?

  • 理解 SO_SNDBUF/SO_RCVBUF
  • 理解与 TCP 自动调优差异
  • 理解本地缓存堆积

AF_UNIX 流套接字的 SO_SNDBUF/SO_RCVBUF 决定发送/接收缓冲大小,缓冲满即触发背压(发送阻塞)。背压阈值 = 缓冲大小:缓冲越大,越晚触发背压,容忍更多未消费数据;缓冲越小,越早阻塞。与 TCP 不同:TCP 有窗口与缓冲区自动调优(如 tcp_rmem 动态调整、窗口缩放),根据网络状况与流大小动态调整缓冲;AF_UNIX 是本地、无网络,内核默认缓冲通常较小,且没有 TCP 的自动调优机制,缓冲相对固定。本地场景更易缓存堆积:因为本地进程间数据传输快,若生产者快于消费者,小缓冲迅速填满,且无网络拥塞控制调节,堆积更明显。可通过调大 SO_SNDBUF/RCVBUF 缓解。

背压阈值由缓冲决定,AF_UNIX 无 TCP 的自动调优,缓冲固定,本地快生产者易堆积。调大缓冲可缓解背压与堆积,但过度会增大内存。

#
★★

12. listen() 的 backlog 在 AF_UNIX 流套接字上如何生效,accept 队列满时对端 connect 为何立即失败,Linux 上返回 EAGAIN 与 TCP 的 SYN 重传等待有何不同?

请解释 listen() 的 backlog 在 AF_UNIX 流套接字上如何生效,accept 队列满时对端 connect 为何立即失败,以及 Linux 上返回 EAGAIN 与 TCP 的 SYN 重传等待有何不同?

  • 理解 accept 队列满
  • 理解 connect 立即失败
  • 理解与 TCP 差异

AF_UNIX 流式套接字 listen(backlog) 的 backlog 决定 accept 队列(未完成+完成连接)的最大长度。当队列满时,新的 connect 请求无法入队,返回错误。在 Linux 上,AF_UNIX 的 connect 在被拒绝时立即失败(返回 EAGAIN,表示暂时无法连接,队列满),因为 AF_UNIX 是本地、无网络重传机制。与 TCP 不同:TCP 的 connect 会进入 SYN 重传等待(SYN 丢包后重发,客户端可能长时间阻塞等待),即 TCP 在队列满时可能丢弃 SYN 或延迟,客户端重试等待;AF_UNIX 则立即返回 EAGAIN,无重传等待。语义:AF_UNIX 队列满立即失败,需应用重试或调大 backlog;TCP 则有 SYN 重传的等待机制。

AF_UNIX 本地无网络,队列满立即 EAGAIN,无 SYN 重传。TCP 有 SYN 重传等待。差异源于"本地 vs 网络"。backlog 调大可缓解队列满。

#
★★

13. 向已关闭的 AF_UNIX 流或管道写入会触发 SIGPIPE 杀死进程,如何用 MSG_NOSIGNAL、signal 忽略或 SO_NOSIGPIPE 规避,各自的适用边界是什么?

请解释向已关闭的 AF_UNIX 流或管道写入会触发 SIGPIPE 杀死进程的机制,以及如何用 MSG_NOSIGNAL、signal 忽略、SO_NOSIGPIPE 规避,各自的适用边界?

  • 理解 MSG_NOSIGNAL
  • 理解 signal 忽略
  • 理解 SO_NOSIGPIPE

向已关闭(对端 read 端关闭)的 AF_UNIX 流或管道写入时,内核触发 SIGPIPE(默认终止进程),同时 write/send 返回 EPIPE。规避方法:1) MSG_NOSIGNAL:在 send 时指定该标志,内核不发送 SIGPIPE,send 返回 -1/EPIPE,由应用处理错误;适用于 send/recvmsg 等带 msg_flags 的调用。2) signal(SIGPIPE, SIG_IGN):全局忽略 SIGPIPE,所有写操作不再触发信号,但需注意这是进程级全局,且 write 仍返回 EPIPE;适用于所有写操作(含 write 到管道)。3) SO_NOSIGPIPE:socket 选项(BSD 系),对特定 socket 禁用 SIGPIPE,send 返回 EPIPE;适用于特定 socket,粒度更细。边界:MSG_NOSIGNAL 是 per-call、仅 send 系列;SO_NOSIGPIPE 是 per-socket;signal 忽略是进程级、影响所有。Linux 常用 MSG_NOSIGNAL,BSD 常用 SO_NOSIGPIPE。

三种规避粒度不同:MSG_NOSIGNAL(per-call,send 系列)、SO_NOSIGPIPE(per-socket,BSD)、signal 忽略(进程级,所有)。选型取决于平台与粒度。都用 EPIPE 处理错误。

#
★★

14. sendfile 在 AF_UNIX 上是否能实现零拷贝,内核为何对本地套接字仍可能走缓存复制路径,与 TCP 上的 sendfile 有何差异?

请解释 sendfile 在 AF_UNIX 上是否能实现零拷贝,内核为何对本地套接字仍可能走缓存复制路径,以及与 TCP 上的 sendfile 的差异?

  • 理解 AF_UNIX 上的 sendfile
  • 理解缓存复制路径
  • 理解与 TCP 差异

sendfile 在支持零拷贝的文件系统(如页缓存)与 socket 之间可避免用户态拷贝,直接从文件页缓存发送到 socket。在 AF_UNIX 上,零拷贝的实现取决于内核:某些内核版本对 AF_UNIX 的 sendfile 支持有限,可能走"先读入内核缓冲再写入 socket"的缓存复制路径,无法完全零拷贝;且 AF_UNIX 的接收端是进程(需把数据拷入用户态),即使内核端零拷贝,接收端 recv 仍需拷贝。与 TCP 相比:TCP 的 sendfile 成熟支持零拷贝(页缓存直接送网络),而 AF_UNIX 因本地语义(接收端要读入用户态)与内核实现,零拷贝收益有限。因此 AF_UNIX 上 sendfile 未必零拷贝,大数据本地传输更常用共享内存或标准 send/recv。

sendfile 零拷贝依赖"文件页缓存直送 socket",AF_UNIX 接收端需读入用户态,且内核实现可能走缓存复制,故零拷贝收益有限。这是与 TCP 的差异。

#
★★

15. AF_UNIX 地址的 sun_path 为何限制为约 108 字节,抽象命名空间(首字节为 \0)与文件系统路径套接字在生命周期和权限上有何不同?

请解释 AF_UNIX 地址的 sun_path 为何限制为约 108 字节,以及抽象命名空间与文件系统路径套接字在生命周期和权限上的不同?

  • 理解 sun_path 长度限制
  • 理解抽象命名空间
  • 理解权限差异

AF_UNIX 的 sockaddr_un.sun_path 因历史原因限制为约 108 字节(在 Linux 上实际为 108,因 sockaddr_un 结构体 110 字节,sun_path 占 sun_len 等扣除后约 108)。这是历史 ABI 限制,路径名不能超过该长度。抽象命名空间(首字节为 \0)与文件系统路径(路径名)不同:生命周期上,抽象命名空间不占文件系统 inode,随网络命名空间存在而消亡,无磁盘残留;路径套接字占用文件系统目录项,需 bind 后 unlink 清理,进程退出后套接字文件可能残留。权限上,抽象命名空间无文件系统权限控制(仅内核命名空间隔离),路径套接字受文件系统权限(目录/文件权限)保护,可限制访问。因此路径套接字有权限控制与持久性,抽象命名空间无权限但更"干净"。

sun_path 108 字节是历史 ABI 限制。抽象命名空间无 inode、无权限、随网络命名空间消亡;路径套接字有 inode、受权限保护、需清理。二者生命周期与权限不同。

#
★★

16. SCM_RIGHTS 传 fd 时内核如何管理引用计数,发送方与接收方各自的关闭顺序为何会影响对端 fd 的有效性,fd 泄漏如何排查?

请解释 SCM_RIGHTS 传 fd 时内核如何管理引用计数,发送方与接收方关闭顺序为何影响对端 fd 有效性,以及 fd 泄漏如何排查?

  • 理解关闭顺序
  • 理解 fd 有效性
  • 理解泄漏排查

SCM_RIGHTS 传递 fd 时,内核把 file 对象引用计数 +1 并交给接收端;接收端在 recvmsg 时为新 fd 分配 fd 表项并指向同一 file 对象。file 对象引用计数 = 各进程中打开它的 fd 数。发送方与接收方关闭顺序影响:若发送方发送后关闭自己的 fd,只要接收端已成功 recvmsg 获得 fd 并持有引用,file 对象仍存活(引用计数仍 >0),对端 fd 有效;若接收端还没 recvmsg(fd 仍在排队)发送方就关闭,内核仍持有引用,接收端 recv 后仍有效。关键:只要一端持有 fd 引用,file 对象就存活。fd 泄漏排查:查看 /proc//fd 目录统计 fd 数,检查是否持续增长;用 lsof 查看进程打开的 fd;检查 SCM_RIGHTS 传递后是否所有端都正确 close(接收端用完要 close,发送端发送后也要 close),否则 fd 泄漏导致引用计数持续,文件/资源无法释放。

file 对象引用计数决定存活。SCM_RIGHTS 传递 +1,接收端持有引用则有效。泄漏源于未正确 close。排查用 /proc/pid/fd 与 lsof 观察 fd 增长。

#
★★

17. 匿名管道的默认容量(64KB)与 PIPE_BUF(4KB)原子写的关系是什么,fcntl(F_SETPIPE_SZ) 如何调整容量,上限受什么限制?

请解释匿名管道默认容量(64KB)与 PIPE_BUF(4KB)原子写的关系,fcntl(F_SETPIPE_SZ) 如何调整容量,以及上限受什么限制?

  • 理解原子写条件
  • 理解 F_SETPIPE_SZ
  • 理解容量上限

匿名管道的默认容量在 Linux 上为 64KB(65536 字节),指管道可缓冲的数据总量。PIPE_BUF 为 4096 字节,是"原子写"的上限:写入不超过 PIPE_BUF 时原子,与管道总容量无关。关系:管道容量(64KB)决定可缓冲总量,PIPE_BUF(4KB)决定单次原子写长度。容量固定后,写不超过 PIPE_BUF 原子,超过则可能交错。fcntl(fd, F_SETPIPE_SZ, size) 可调整管道容量(增大或减小),但增大受系统限制(/proc/sys/fs/pipe-max-size,默认 1MB,非特权用户受此限制;特权用户可更大)。减小容量时若管道已有数据超过新容量则失败(EBUSY)。调整容量影响缓冲总量,不影响 PIPE_BUF(原子写长度不变)。

容量(64KB)是可缓冲总量,PIPE_BUF(4KB)是原子写长度,两者独立。F_SETPIPE_SZ 调容量,上限受 pipe-max-size 限制。理解二者关系避免误判原子性。

#
★★

18. fork 后父子进程若不关闭管道未使用端,为何会导致 read 永远阻塞或 SIGPIPE 延迟,select/poll 的 EOF 判断如何受影响?

请解释 fork 后父子进程若不关闭管道未使用端,为何会导致 read 永远阻塞或 SIGPIPE 延迟,以及 select/poll 的 EOF 判断如何受影响?

  • 理解 fork 后管道 fd 继承
  • 理解 read 阻塞与 SIGPIPE
  • 理解 EOF 判断

fork 后父子进程都持有管道的读写两端 fd。若父进程只读不写,却不关闭写端,子进程也保留写端,则管道写端仍存在(引用计数 >0),read 不会遇到 EOF(因为写端未全部关闭),可能永远阻塞。同理,写端若未关闭读端,写入不会触发 SIGPIPE(读端仍存在)。EOF 判断:read 返回 0(EOF)仅在"所有写端都已关闭"时发生;select/poll 报告可读(POLLIN)在缓冲有数据或 EOF 时,但 EOF 事件(POLLHUP/读返回 0)需写端全关。若未关闭所有写端,select/poll 不会报告 EOF(POLLHUP),read 可能阻塞。因此 fork 后必须关闭未使用端,否则 EOF 与 SIGPIPE 语义被破坏。子进程应关闭父端,父进程关闭子端。

未关闭端保留引用,导致写端/读端"仍存在",EOF 与 SIGPIPE 延迟。正确做法:fork 后各关未用端,使 EOF/SIGPIPE 语义正确。这是管道编程的经典约束。

#
★★

19. netlink 与 AF_UNIX 在用户态与内核通信上的定位差异,为什么路由、邻居表等内核子系统使用 netlink 而非本地套接字?

请解释 netlink 与 AF_UNIX 在用户态与内核通信上的定位差异,以及为何路由、邻居表等内核子系统使用 netlink 而非本地套接字?

  • 理解 AF_UNIX 定位
  • 理解内核子系统通信
  • 理解 netlink 优势

netlink 是专门用于用户态与内核通信的套接字家族(AF_NETLINK),支持双向、异步、多播(内核向多个用户态接收者广播事件),并有协议族(ROUTE、NEIGH、UPD 等)分类。AF_UNIX 是用户态进程间通信,不直接用于"用户态<->内核"的系统通信(内核不是 AF_UNIX 的普通对端)。定位差异:netlink 面向"内核<->用户态",AF_UNIX 面向"用户态<->用户态"。路由、邻居表等内核子系统用 netlink 而非 AF_UNIX 的原因:1) netlink 支持内核主动向用户态推送事件(多播,如路由变化、邻居状态变化),AF_UNIX 需用户态主动关联;2) netlink 有专用的协议族与消息格式,适合内核子系统(ROUTE、NEIGH、FIB 等);3) netlink 与内核网络栈集成,能直接访问内核数据结构;4) 支持内核与用户态的异步通信。因此内核子系统用 netlink。

netlink 定位"内核<->用户态",支持多播与内核事件推送;AF_UNIX 定位"用户态<->用户态"。路由/邻居需内核推事件,故用 netlink。这是定位差异的体现。

#
★★

20. SO_PASSCRED 与 SCM_CREDENTIALS 如何让接收方获取对端 PID/UID/GID,常用于哪些鉴权场景?

请解释 SO_PASSCRED 与 SCM_CREDENTIALS 如何让接收方获取对端 PID/UID/GID,以及常用于哪些鉴权场景?

  • 理解 SCM_CREDENTIALS
  • 理解凭据获取
  • 理解鉴权场景

SO_PASSCRED 是 socket 选项,设置后使接收方在 recvmsg 时自动接收到对端的凭据(SCM_CREDENTIALS 辅助数据),包含对端进程的 PID、UID、GID。SCM_CREDENTIALS 是辅助数据(cmsg),接收方通过解析它获取对端身份。发送方也可用 SCM_CREDENTIALS 显式携带凭据(但内核会校验/覆盖)。用途:鉴权场景——服务端可通过 SO_PASSCRED 获取客户端的真实 PID/UID/GID,用于基于身份的访问控制(如只有特定 UID 的进程可访问服务)、审计、进程间安全校验。与 SO_PEERCRED 不同,SO_PASSCRED 是一次性获取的辅助数据(每条消息),用于流式连接中获取对端身份;SO_PEERCRED 是流式连接的对端凭据,一次性获取。SO_PASSCRED 常用于数据报/流式 AF_UNIX 的细粒度鉴权。

SO_PASSCRED 让接收方通过辅助数据获取对端 PID/UID/GID,用于鉴权。它是每消息/每次获取,与 SO_PEERCRED(连接级一次性)不同。凭据由内核提供,可靠。

#
★★

21. AF_UNIX 抽象命名空间地址为何随网络命名空间隔离,路径名套接字受挂载命名空间影响,容器场景下应如何选择地址形式?

请解释 AF_UNIX 抽象命名空间地址为何随网络命名空间隔离,路径名套接字受挂载命名空间影响,以及容器场景下应如何选择地址形式?

  • 理解路径套接字与挂载命名空间
  • 理解容器场景
  • 理解地址形式选择

AF_UNIX 抽象命名空间地址(首字节 \0)注册在内核的抽象套接字命名空间中,该命名空间与网络命名空间(net namespace)绑定,因此抽象命名空间地址随网络命名空间隔离:不同网络命名空间的进程看不到对方的抽象命名空间地址。路径名套接字(文件系统路径)受挂载命名空间影响:路径解析依赖挂载命名空间,不同挂载命名空间看到的路径可能不同。容器场景:容器通常有独立网络命名空间与挂载命名空间。选择:1) 跨容器通信(不同网络命名空间)用路径名套接字(共享挂载点,如共享 /var/run)或改用本地 TCP/其他 IPC;2) 容器内通信(同网络命名空间)可用抽象命名空间(无文件残留、无权限);3) 需权限控制用路径名套接字(受文件权限保护)。需考虑容器命名空间隔离,选择可到达的地址形式。

抽象命名空间绑定网络命名空间,路径套接字绑定挂载命名空间。容器隔离影响可达性。选择需考虑"是否跨命名空间"与"权限需求"。跨容器共享挂载点用路径名。

#

22. AF_UNIX 流套接字在 O_NONBLOCK 下写满返回 EAGAIN 而非阻塞,应用应如何结合 epoll 与部分写(partial write)处理背压?

请解释 AF_UNIX 流套接字在 O_NONBLOCK 下写满返回 EAGAIN 而非阻塞,以及应用如何结合 epoll 与部分写处理背压?

  • 理解 O_NONBLOCK
  • 理解部分写
  • 理解 epoll 处理背压

AF_UNIX 流套接字在 O_NONBLOCK(非阻塞)模式下,若发送缓冲满,send 不会阻塞,而是返回 -1 且 errno=EAGAIN(表示暂时无法发送)。应用需处理背压:1) 接受部分写(partial write):send 可能只发送部分数据(返回值 < 请求长度),应用需记录已发送字节,把剩余数据加入待发送队列;2) 用 epoll 监控 socket 的可写事件(EPOLLOUT):当 EAGAIN 时把 socket 加入 epoll 等待 EPOLLOUT,缓冲腾出后 epoll 通知可写,再继续发送队列中的剩余数据。这是"非阻塞 + 事件驱动 + 待发送队列"的典型背压处理模式,避免阻塞占用线程,适合高并发。

非阻塞 + EAGAIN + 部分写 + epoll EPOLLOUT 是处理背压的标准模式。发送要维护偏移与队列,EAGAIN 时挂起等可写事件。避免阻塞是异步非阻塞网络编程的核心。

#

23. SO_PEERCRED 与 SCM_CREDENTIALS 在获取对端身份上的差异,为何服务端应优先用 SO_PEERCRED 校验而非信任客户端自报身份?

请解释 SO_PEERCRED 与 SCM_CREDENTIALS 在获取对端身份上的差异,以及为何服务端应优先用 SO_PEERCRED 校验而非信任客户端自报身份?

  • 理解 SCM_CREDENTIALS
  • 理解身份获取差异
  • 理解安全性

SO_PEERCRED 是 socket 选项(getsockopt),用于流式连接(SOCK_STREAM)一次性获取对端进程的凭据(PID/UID/GID),由内核在连接建立时确定,可靠且固定。SCM_CREDENTIALS 是辅助数据,由发送方在 sendmsg 时携带,接收方通过 recvmsg 获取;但发送方可以"自报"凭据(虽然内核会检查特权,但普通场景下可由发送方构造),因此对端身份可能被伪造/不可信。差异:SO_PEERCRED 由内核根据连接对端真实进程提供,可靠;SCM_CREDENTIALS 是发送方可携带的辅助数据,可能被伪造。因此服务端鉴权应优先用 SO_PEERCRED(内核背书),而非信任客户端自报的 SCM_CREDENTIALS,避免伪装身份绕过鉴权。

SO_PEERCRED 内核背书、可靠;SCM_CREDENTIALS 可自报、可能伪造。鉴权用 SO_PEERCRED 更安全。这是"内核可信来源 vs 客户端自报"的区别。

#

24. 高并发本地 IPC 选型,AF_UNIX、共享内存、io_uring 各自在吞吐、延迟与编程复杂度上的取舍,什么场景下共享内存仍不可替代?

请解释高并发本地 IPC 选型中 AF_UNIX、共享内存、io_uring 在吞吐、延迟与编程复杂度上的取舍,以及什么场景下共享内存仍不可替代?

  • 理解 AF_UNIX 特性
  • 理解 io_uring 特性
  • 理解选型取舍与共享内存不可替代场景

AF_UNIX:延迟低、吞吐高(内核内直接传递),支持 fd/凭据传递与事件通知,编程复杂度中等(socket 编程),适合控制消息与中等数据量。共享内存(mmap MAP_SHARED):吞吐最高(零拷贝、无内核缓冲),延迟最低(直接映射),但编程复杂度高(需自己同步、通知、处理生命周期),适合大数据量传输。io_uring:异步 I/O 框架,吞吐高、延迟低(批量提交、零拷贝),支持文件与 socket 操作,编程复杂度较高,适合高性能异步 I/O。共享内存不可替代的场景:超大体积数据(如视频帧、大 buffer)的持续传输,零拷贝直接映射是唯一能避免频繁拷贝与内核缓冲限制的方案;且需低延迟、低 CPU 拷贝的场景共享内存优势不可替代。选型:控制/凭据用 AF_UNIX,大数据用共享内存,异步 I/O 用 io_uring。

三者的取舍:AF_UNIX 灵活(fd/凭据)、共享内存高吞吐零拷贝、io_uring 异步高性能。共享内存在大数据零拷贝与低延迟场景不可替代。选型按数据量与语义需求。

#

25. FIFO 的 open 为何默认阻塞直到对端打开,O_RDONLY/O_WRONLY 与 O_NONBLOCK 组合下 open 的返回行为分别是什么?

请解释 FIFO 的 open 为何默认阻塞直到对端打开,以及 O_RDONLY/O_WRONLY 与 O_NONBLOCK 组合下 open 的返回行为?

  • 理解 FIFO open 阻塞
  • 理解 O_NONBLOCK 效果
  • 理解返回行为

FIFO 是命名管道,open 时需读写两端配对才能通信。默认(无 O_NONBLOCK)下,open(O_RDONLY) 会阻塞直到有写端打开,open(O_WRONLY) 会阻塞直到有读端打开,因为 FIFO 需两端都存在才能传输数据,内核等待对端。加上 O_NONBLOCK 后:open(O_RDONLY|O_NONBLOCK) 立即成功(即使无写端,读端可先打开);open(O_WRONLY|O_NONBLOCK) 若无读端则立即失败返回 ENXIO(无对端),若有读端则成功。若读写两端都带 O_NONBLOCK,则 open 不阻塞。语义:O_NONBLOCK 使读端 open 不等待写端,写端 open 无读端时失败。这是 FIFO 打开时的配对约束。

FIFO 需两端配对,open 默认阻塞等待对端。O_NONBLOCK 改变:读端立即成功、写端无读端失败(ENXIO)。理解配对与 O_NONBLOCK 是 FIFO 编程的关键。

#

26. AF_UNIX 流套接字接收缓冲满时的背压与 TCP 有何不同,为什么本地写者会阻塞而不会像 UDP 一样静默丢包?

请解释 AF_UNIX 流套接字接收缓冲满时的背压与 TCP 的不同,以及为何本地写者会阻塞而不会像 UDP 一样静默丢包?

  • 理解 AF_UNIX 背压
  • 理解 TCP 背压
  • 理解为什么不丢包

AF_UNIX 流式套接字在接收缓冲满时,阻塞写者会阻塞(等待缓冲腾出),这是基于流式可靠传输的背压机制。TCP 类似:接收窗口满时发送方阻塞/节流(通过窗口控制)。差异:AF_UNIX 是本地、无网络,背压直接由缓冲满触发阻塞,无窗口协商/拥塞控制;TCP 有窗口与拥塞控制,背压通过窗口体现。UDP 数据报套接字在缓冲满时不会阻塞写者,而是静默丢弃数据报(发送方可能不知道,或返回 EAGAIN 于非阻塞)。AF_UNIX 流式写者阻塞而非丢包的原因:流式套接字保证可靠有序交付,背压机制确保不丢数据——写者阻塞等待对端消费,数据缓存在缓冲中直至对端读取,不会像 UDP 那样丢弃。这是流式(可靠)与数据报(best-effort)的本质区别。

AF_UNIX 流式是可靠、有背压,缓冲满阻塞写者,不丢包;UDP 是 best-effort,缓冲满丢包。背压保证不丢,TCP 有窗口、AF_UNIX 直接阻塞。可靠与不可靠是核心。

#

27. 如何用 ss -x 与 /proc/net/unix 排查 AF_UNIX 连接泄漏,连接数过多时的 inode 与内存占用如何评估?

请解释如何用 ss -x 与 /proc/net/unix 排查 AF_UNIX 连接泄漏,以及连接数过多时 inode 与内存占用如何评估?

  • 理解 /proc/net/unix
  • 理解连接泄漏排查
  • 理解 inode 与内存评估

ss -x 显示 AF_UNIX 套接字连接(地址、状态、inode、对端),可观察连接数量与状态。/proc/net/unix 列出内核中所有 AF_UNIX 套接字(Num、RefCount、Flags、Type、State、Inode、Path),用于排查连接泄漏:对比连接数是否持续增长、状态是否异常(如 CONNECTED 但不活跃)、活跃连接去向。连接数过多时评估 inode 与内存:每个 AF_UNIX 套接字对应一个 inode(可查 /proc/sys/fs/inode-state 或 ls -i 对比),占用内核内存(socket struct、缓冲);用 /proc/net/unix 的 RefCount 与连接数估计,用 ss -m 查看 socket 内存(rmem/wmem 缓冲),用系统内存统计(free、/proc/meminfo)评估。排查关键:找出未关闭的连接(客户端未 close、服务端未 accept 后关闭),修复泄漏。

ss -x 与 /proc/net/unix 提供连接清单与 inode。泄漏排查看连接数增长与状态。inode 与内存评估结合 /proc/net/unix 的 inode、ss -m 的缓冲与系统内存。修复未关闭连接。