news 2026/9/10 0:27:08

SerenityOS recvfd(2) 手册解析:通过本地域套接字带外传递文件描述符

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SerenityOS recvfd(2) 手册解析:通过本地域套接字带外传递文件描述符

SerenityOS recvfd(2) 手册解析:通过本地域套接字带外传递文件描述符

【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity

本篇围绕 Serenity OS 系统调用手册页 recvfd.md 展开,完整覆盖其函数原型、options标志、返回值与错误码语义,并结合内核源码 sys$recvfd 实现 与 LocalSocket::recvfd 的实现细节,说明 fd 如何以带外(out-of-band)方式在本地套接字两端传递。读完后你将掌握:如何在两个 SerenityOS 进程间通过AF_LOCAL套接字传递打开的文件描述符、recvfd()的非阻塞语义与全部错误码的内核出处、以及它与配对调用sendfd()的协作方式。

1. 功能概述:跨进程传递打开的 fd

recvfd()用于从经sockfd连接的本地套接字对端进程接收一个已打开的文件描述符(file descriptor)。它与sendfd()构成一对对称系统调用:

调用原型语义返回
sendfdint sendfd(int sockfd, int fd);发送一个打开的 fd 给本地套接字对端成功返回 0,失败返回 -1
recvfdint recvfd(int sockfd, int options);接收对端发来的 fd成功返回新分配的非负 fd,失败返回 -1

两者都要求sockfd指向本地域(local domain)套接字,且 fd 的传递是带外的:走独立的 fd 队列,不影响套接字常规数据流(read/write缓冲区)的内容与顺序。这一机制源自 Plan 9 from User Space,SerenityOS 将其纳入自身系统调用集。

1.1 与常规数据流的分离

这一点在内核结构上可以直接印证。LocalSocket 为每对连接维护了两个DoubleBuffer数据缓冲(m_for_client/m_for_server,见 LocalSocket 构造函数)用于常规读写;而sendfd/recvfd使用的m_fds_for_client/m_fds_for_server两个Vector<NonnullRefPtr<OpenFileDescription>>队列是完全独立的。从源码结构看,两个通道的阻塞判定互不影响,因此传 fd 不会干扰同一连接上的普通字节流通信。

1.2 系统调用层与 LibC 封装

recvfd在系统调用表中的注册条目见 Syscall.h:

S(recvfd, NeedsBigProcessLock::No)

NeedsBigProcessLock::No表明该调用不要求持有进程大锁,执行路径较短。用户态入口位于 LibC 的 sys/socket.cpp:

int recvfd(int sockfd, int options) { int rc = syscall(SC_recvfd, sockfd, options); ... }

原型声明见 sys/socket.h。与手册页一致,需要#include <sys/socket.h>后即可调用。

2. Synopsis 与 options 参数

手册页给出的函数原型为:

#include <sys/socket.h> int recvfd(int sockfd, int options);

options是一个位掩码,目前手册页仅定义了一个标志:

  • O_CLOEXEC:接收到的新 fd 应设置 close-on-exec 标志,即调用进程后续exec时该 fd 自动关闭(exec(2) 手册页交叉引用的执行语义)。

在内核实现中,该标志的解析只有一步:sys$recvfd 中若options & O_CLOEXEC,则向新 fd 写入FD_CLOEXEC标志位,随后连同收到的OpenFileDescription一起挂入调用进程的 fd 表。换言之,recvfd()成功返回的 fd 是调用进程 fd 表中新分配的最小空闲号,它引用的是对端发来的同一份打开文件描述对象(共享打开状态,如文件偏移、异步信号状态)。

3. 非阻塞语义与接收流程

手册页明确指出:recvfd()非阻塞调用,若套接字队列中没有等待接收的 fd 则会失败(返回EAGAIN)。内核侧的完整调用链为:

  1. 前置检查(sys$recvfd):
    • require_promise(Pledge::recvfd)——进程必须已pledgerecvfd能力,否则拒绝(EDEFAULT);
    • open_file_description(sockfd)失败则返回对应错误(fd 未打开即EBADF);
    • 不是套接字 →ENOTSOCK;不是本地套接字 →EAFNOSUPPORT
  2. 预分配 fd 槽位:先在调用进程 fd 表中allocate()一个 fd 号,为即将收到的描述符预留位置。
  3. 出队:LocalSocket::recvfd 在套接字互斥锁下按当前 fd 的角色(Role::ConnectedRole::Accepted)选出对应的recvfd队列;队列为空则返回EAGAIN,否则queue.take_first()取出队首的OpenFileDescription并转移所有权。
  4. 落入 fd 表:回到 sys$recvfd,将描述符连同O_CLOEXEC转换后的标志写入已分配的 fd 槽位,返回 fd 号。

角色(role)与队列的对应关系由 recvfd_queue_for / sendfd_queue_for 决定:客户端角色(Connected)从m_fds_for_client收、往m_fds_for_server发;被接受端角色(Accepted)反之。两个方向各自独立排队,互不干扰。

4. 返回值与错误码(附内核出处)

成功时recvfd()返回收到的 fd(非负整数);失败返回 -1 并设置errno。手册页列出的五个错误在内核中的产生位置如下:

errno手册页含义内核产生位置
EBADFsockfd不是打开的 fdopen_file_description(sockfd)解析失败,见 sys$recvfd
ENOTSOCKsockfd不指向套接字!socket_description->is_socket(),见 sys$recvfd
EAFNOSUPPORTsockfd不是本地域套接字!socket.is_local(),见 sys$recvfd
EINVALsockfd不是已连接/已接受的套接字LocalSocket::recvfd 中 role 既非Connected也非Accepted
EAGAIN该套接字上没有排队等待的 fdLocalSocket::recvfd 队列为空时

与之配对的sendfd()(手册页见 sendfd.md)还额外定义了两个错误:ENOTCONN(sys$sendfd 中!socket.is_connected())与EBUSY(LocalSocket::sendfd 中对端待收队列超过 128 个 fd 时拒绝,源码中该上限标注为 FIXME、尚待确定正式的限制策略)。这解释了为何recvfd()是非阻塞的:队列无界增长不受鼓励,发送端满 128 即快速失败,接收端则以EAGAIN表示"当前没有可取的 fd"。

5. 典型使用模式

基于前文语义,一次 fd 传递的完整流程示意如下(以AF_LOCAL流式套接字为例):

#include <sys/socket.h> #include <fcntl.h> /* 接收方:先建立本地连接(bind/listen/accept 或 connect 完成后再取 fd) */ int client = /* accept() 返回的已接受套接字,或 connect() 后的对端套接字 */; int fd = recvfd(client, O_CLOEXEC); /* 要求 pledge 过 "recvfd" */ if (fd < 0) { /* errno: EBADF/ENOTSOCK/EAFNOSUPPORT/EINVAL/EAGAIN */ } /* 发送方:在与 client 同一连接的另一个 fd 上执行 sendfd(peer_fd, fd_to_pass); 要求 pledge 过 "sendfd";返回 0 表示入队成功。 */

要点归纳:

  • 两端必须先完成本地套接字的连接/接受,使角色达到ConnectedAccepted,否则会收到EINVAL
  • recvfd()返回EAGAIN时通常意味着对端尚未sendfd或已取空队列,可稍后重试(该调用本身不会挂起);
  • 传过来的是"打开的文件描述符",接收方得到的是一个全新 fd 号但共享同一打开文件状态,可直接读写、dup或再传给第三方。

6. 历史与延伸阅读

recvfd()最早由 Plan 9 from User Space 引入,SerenityOS 保留了该命名与语义并扩展了options位掩码(当前仅O_CLOEXEC)。相关手册页与源码入口:

  • 配对调用手册页:sendfd(2)
  • 系统调用实现:Kernel/Syscalls/sendfd.cpp(sys$sendfdsys$recvfd同文件实现)
  • 内核套接字层队列:Kernel/Net/LocalSocket.cpp
  • 系统调用注册表:Kernel/API/Syscall.h
  • 用户态封装:Userland/Libraries/LibC/sys/socket.cpp

适用前提:以上行为描述基于当前仓库内核与 LibC 的实际实现;sendfd端 128 个 fd 的队列上限是源码中明确标注待完善的临时值(FIXME),后续版本可能调整,以仓库当时的 LocalSocket::sendfd 为准。

【免费下载链接】serenityThe Serenity Operating System 🐞项目地址: https://gitcode.com/GitHub_Trending/se/serenity

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 0:21:39

KMS权限故障排查实录:区块链验证节点签名中断的隐形陷阱

接手这条链的第五天&#xff0c;我盯着一台明明在线、却连续好几轮没能出块的验证节点&#xff0c;日志里反复出现同一段来自 KMS 的报错。报错本身不可怕&#xff0c;可怕的是它不致命——节点进程不崩、网络不断、区块照常同步&#xff0c;只有仔细对比出块记录时&#xff0c…

作者头像 李华
网站建设 2026/9/10 0:21:11

论文降AI率避坑指南:七大常见误区与正确重写方法

先讲个真实场景。工作室里带过的学弟&#xff0c;交完论文初稿来找我&#xff0c;一脸崩溃&#xff1a;“学姐&#xff0c;我这段几乎每个字都改过了&#xff0c;为什么AI检测出来反而比之前更高&#xff1f;”我点开他的稿子一看&#xff0c;第一段写的是“近年来&#xff0c;…

作者头像 李华