LWIP教程 40:从 `socket()` 到 `lwip_socket()`——DFS fd、SAL Socket 与 lwIP Backend


title: 40-rtthread-socket-sal-lwip

categories:

  • LWIP
    tags:
  • LWIP
    abbrlink: b63d4452
    date: 2026-10-03 16:27:36

教程 40:从 socket() 到 lwip_socket()------DFS fd、SAL Socket 与 lwIP Backend

摘要:沿 RT-Thread 标准 Socket 入口追踪 DFS fd、SAL socket、lwIP socket 与 Netconn,解释创建、连接、收发、poll 和关闭如何保持同一 backend 上下文。

文章目录

  • [教程 40:从 `socket()` 到 `lwip_socket()`------DFS fd、SAL Socket 与 lwIP Backend](#教程 40:从 socket() 到 lwip_socket()——DFS fd、SAL Socket 与 lwIP Backend)
    • [阅读源码前:SAL 与 VFS/DFS 的框架关系直接看官方文档](#阅读源码前:SAL 与 VFS/DFS 的框架关系直接看官方文档)
    • [1. 真实入口:`socket()` 先创建 DFS fd,而不是直接调用 lwIP](#1. 真实入口:socket() 先创建 DFS fd,而不是直接调用 lwIP)
    • [2. `dfs_net_getsocket()`:后续每个 BSD API 都靠 vnode 找回 SAL socket](#2. dfs_net_getsocket():后续每个 BSD API 都靠 vnode 找回 SAL socket)
    • [3. 进入 `sal_socket()` 前先看 `struct sal_socket`:它保存 backend 选择结果](#3. 进入 sal_socket() 前先看 struct sal_socket:它保存 backend 选择结果)
    • [4. 进入 `sal_socket()`:先分配 SAL descriptor,再调用 `socket_init()` 选择 provider](#4. 进入 sal_socket():先分配 SAL descriptor,再调用 socket_init() 选择 provider)
    • [5. 进入 `socket_init()`:backend 只在创建阶段选择一次](#5. 进入 socket_init():backend 只在创建阶段选择一次)
    • [6. `lwip_inet_family`:SAL 如何把 `AF_INET` 映射到 lwIP operation table](#6. lwip_inet_family:SAL 如何把 AF_INET 映射到 lwIP operation table)
    • [7. 进入 `inet_socket()`:真正创建 lwIP socket,并安装 poll 事件桥](#7. 进入 inet_socket():真正创建 lwIP socket,并安装 poll 事件桥)
    • [8. 进入 `lwip_socket()`:lwIP Socket 层再创建 Netconn](#8. 进入 lwip_socket():lwIP Socket 层再创建 Netconn)
    • [9. 创建结束后,三个 descriptor 怎样一一关联](#9. 创建结束后,三个 descriptor 怎样一一关联)
    • [10. `connect(fd, ...)`:先从 DFS fd 找回 SAL socket,再从 `user_data` 找回 lwIP socket](#10. connect(fd, ...):先从 DFS fd 找回 SAL socket,再从 user_data 找回 lwIP socket)
    • [11. 进入 `lwip_connect()`:再次回到 Stage 06/07 的 Netconn/TCP 路径](#11. 进入 lwip_connect():再次回到 Stage 06/07 的 Netconn/TCP 路径)
    • [12. `recv()` / `send()`:BSD Socket wrapper 继续复用同一个 dispatch context](#12. recv() / send():BSD Socket wrapper 继续复用同一个 dispatch context)
    • [13. `read()` / `write()` 为什么也能操作 socket:DFS file operations 直接桥到同一组 SAL API](#13. read() / write() 为什么也能操作 socket:DFS file operations 直接桥到同一组 SAL API)
    • [14. `poll()` 的异步桥:为什么 `inet_socket()` 必须替换 Netconn callback](#14. poll() 的异步桥:为什么 inet_socket() 必须替换 Netconn callback)
    • [15. 为什么标准 `socket()` 与 lwIP 自己的兼容别名不会冲突](#15. 为什么标准 socket() 与 lwIP 自己的兼容别名不会冲突)
    • [16. `closesocket()`:按 DFS → SAL → lwIP 的反方向释放](#16. closesocket():按 DFS → SAL → lwIP 的反方向释放)
    • [17. Stage 40 的完整心智模型](#17. Stage 40 的完整心智模型)
    • 资料来源
      • [S1 RT-Thread SAL / DFS 配置与官方组件说明](#[S1] RT-Thread SAL / DFS 配置与官方组件说明)
      • [S2 RT-Thread BSD Socket 与 DFS bridge](#[S2] RT-Thread BSD Socket 与 DFS bridge)
      • [S3 RT-Thread SAL Core](#[S3] RT-Thread SAL Core)
      • [S4 RT-Thread lwIP SAL Adapter](#[S4] RT-Thread lwIP SAL Adapter)
      • [S5 RT-Thread lwIP `lwipopts.h`](#[S5] RT-Thread lwIP lwipopts.h)
      • [S6 RT-Thread vendored lwIP 2.1.2 Socket / Netconn](#[S6] RT-Thread vendored lwIP 2.1.2 Socket / Netconn)
      • [S7 RT-Thread 官方 SAL 文档](#[S7] RT-Thread 官方 SAL 文档)
      • [S8 RT-Thread 官方 Virtual File System 文档](#[S8] RT-Thread 官方 Virtual File System 文档)

Stage 39 已经回答"Ethernet Driver 怎样把 packet 送进 lwIP"。Stage 40 改从应用侧进入:**SAL(Socket Abstraction Layer,Socket 抽象层)**为不同网络协议栈提供统一 BSD Socket API;**DFS(Device File System,设备文件系统)**在启用 POSIX 兼容时提供统一文件描述符和 file operations;lwIP 则仍是实际执行 TCP/UDP Socket 的 backend。对嵌入式产品,这种分层允许应用继续使用 socket()/connect()/read()/write()/poll(),底层却可以由 lwIP、AT 或其他网络实现承担。 S1 S2

本文只追一条真实主线:socket(AF_INET, SOCK_STREAM, 0) 为什么最终进入 lwip_socket(),随后同一个应用 fd 又怎样继续找到 lwip_connect()、lwip_sendto()、lwip_recvfrom() 和 lwip_close()。RT-Thread 源码固定到 commit dc8aaa73f2dbea255325ec058a083aeeb5381d0a(2026-09-28),lwIP backend 使用该提交内置 lwIP 2.1.2。S1S6

阅读源码前:SAL 与 VFS/DFS 的框架关系直接看官方文档

RT-Thread 官方 SAL 文档已经完整画出 Application → VFS/DFS → SAL → protocol stack 的分层,并用 connect() 示例说明标准 BSD API 怎样经 SAL operation table 调到 lwip_connect() 或其他 backend;官方 VFS 文档则负责解释统一 fd/file-operation 基础设施。S7S8 因此本文不再承担"什么是 SAL、为什么 socket 也能 read/write"这类框架教学。

这里真正需要源码回答的是另外三个实现问题:一个应用 fd 怎样关联 SAL descriptor;backend 在创建时怎样被选择并保存;poll/close 等后续操作怎样继续复用同一个 provider。 live 文档用于建立框架,下面的 descriptor ownership 与 dispatch 顺序仍以固定 commit 的 [S2]~[S6] 为准。

1. 真实入口:socket() 先创建 DFS fd,而不是直接调用 lwIP

启用 SAL POSIX 层后,应用调用的标准 socket() 实现在 components/net/sal/socket/net_sockets.c。下面直接进入该函数。S2

c 复制代码
int socket(int domain, int type, int protocol)
{
    /* create a BSD socket */
    int fd;
    int socket;
    struct dfs_file *d;

    /* allocate a fd */
    fd = fd_new();
    if (fd < 0)
    {
        rt_set_errno(-ENOMEM);

        return -1;
    }
    d = fd_get(fd);

#ifdef RT_USING_DFS_V2
    d->fops = dfs_net_get_fops();
#endif

    d->vnode = (struct dfs_vnode *)rt_malloc(sizeof(struct dfs_vnode));
    if (!d->vnode)
    {
        /* release fd */
        fd_release(fd);
        rt_set_errno(-ENOMEM);
        return -1;
    }
    dfs_vnode_init(d->vnode, FT_SOCKET, dfs_net_get_fops());

    /* create socket  and then put it to the dfs_file */
    socket = sal_socket(domain, type, protocol);
    if (socket >= 0)
    {
        d->flags = O_RDWR; /* set flags as read and write */

        /* set socket to the data of dfs_file */
        d->vnode->data = (void *)(size_t)socket;
    }
    else
    {
#ifdef RT_USING_DFS_V2
        dfs_vnode_destroy(d->vnode);
        d->vnode = RT_NULL;
#endif
        /* release fd */
        fd_release(fd);
        rt_set_errno(-ENOMEM);
        return -1;
    }

    return fd;
}

这段代码已经建立第一层 ownership:
#mermaid-svg-4kZx2phvbJYAw3nw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4kZx2phvbJYAw3nw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4kZx2phvbJYAw3nw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4kZx2phvbJYAw3nw .error-icon{fill:#552222;}#mermaid-svg-4kZx2phvbJYAw3nw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4kZx2phvbJYAw3nw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4kZx2phvbJYAw3nw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4kZx2phvbJYAw3nw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4kZx2phvbJYAw3nw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4kZx2phvbJYAw3nw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4kZx2phvbJYAw3nw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4kZx2phvbJYAw3nw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4kZx2phvbJYAw3nw .marker.cross{stroke:#333333;}#mermaid-svg-4kZx2phvbJYAw3nw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4kZx2phvbJYAw3nw p{margin:0;}#mermaid-svg-4kZx2phvbJYAw3nw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4kZx2phvbJYAw3nw .cluster-label text{fill:#333;}#mermaid-svg-4kZx2phvbJYAw3nw .cluster-label span{color:#333;}#mermaid-svg-4kZx2phvbJYAw3nw .cluster-label span p{background-color:transparent;}#mermaid-svg-4kZx2phvbJYAw3nw .label text,#mermaid-svg-4kZx2phvbJYAw3nw span{fill:#333;color:#333;}#mermaid-svg-4kZx2phvbJYAw3nw .node rect,#mermaid-svg-4kZx2phvbJYAw3nw .node circle,#mermaid-svg-4kZx2phvbJYAw3nw .node ellipse,#mermaid-svg-4kZx2phvbJYAw3nw .node polygon,#mermaid-svg-4kZx2phvbJYAw3nw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4kZx2phvbJYAw3nw .rough-node .label text,#mermaid-svg-4kZx2phvbJYAw3nw .node .label text,#mermaid-svg-4kZx2phvbJYAw3nw .image-shape .label,#mermaid-svg-4kZx2phvbJYAw3nw .icon-shape .label{text-anchor:middle;}#mermaid-svg-4kZx2phvbJYAw3nw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4kZx2phvbJYAw3nw .rough-node .label,#mermaid-svg-4kZx2phvbJYAw3nw .node .label,#mermaid-svg-4kZx2phvbJYAw3nw .image-shape .label,#mermaid-svg-4kZx2phvbJYAw3nw .icon-shape .label{text-align:center;}#mermaid-svg-4kZx2phvbJYAw3nw .node.clickable{cursor:pointer;}#mermaid-svg-4kZx2phvbJYAw3nw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4kZx2phvbJYAw3nw .arrowheadPath{fill:#333333;}#mermaid-svg-4kZx2phvbJYAw3nw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4kZx2phvbJYAw3nw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4kZx2phvbJYAw3nw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4kZx2phvbJYAw3nw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4kZx2phvbJYAw3nw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4kZx2phvbJYAw3nw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4kZx2phvbJYAw3nw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4kZx2phvbJYAw3nw .cluster text{fill:#333;}#mermaid-svg-4kZx2phvbJYAw3nw .cluster span{color:#333;}#mermaid-svg-4kZx2phvbJYAw3nw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4kZx2phvbJYAw3nw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4kZx2phvbJYAw3nw rect.text{fill:none;stroke-width:0;}#mermaid-svg-4kZx2phvbJYAw3nw .icon-shape,#mermaid-svg-4kZx2phvbJYAw3nw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4kZx2phvbJYAw3nw .icon-shape p,#mermaid-svg-4kZx2phvbJYAw3nw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4kZx2phvbJYAw3nw .icon-shape .label rect,#mermaid-svg-4kZx2phvbJYAw3nw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4kZx2phvbJYAw3nw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4kZx2phvbJYAw3nw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4kZx2phvbJYAw3nw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} socket(AF_INET, SOCK_STREAM, 0)
fd_new(): allocate DFS fd
fd_get(): dfs_file
allocate dfs_vnode
dfs_vnode_init(... FT_SOCKET ...)
sal_socket(domain,type,protocol)
vnode->data = SAL socket id
return DFS fd

应用最终拿到的是 fd。sal_socket() 返回的整数没有直接返回应用,而是保存到 d->vnode->data。因此从第一步开始就必须区分:

text 复制代码
应用整数 fd
    !=
SAL socket descriptor

如果 sal_socket() 失败,外层会释放刚分配的 DFS fd/vnode;这说明 DFS 对象是本次 API 的最外层 owner。

2. dfs_net_getsocket():后续每个 BSD API 都靠 vnode 找回 SAL socket

socket() 把 SAL descriptor 放进 vnode->data 后,connect()、recv()、send() 等入口都需要把应用 fd 重新还原成 SAL descriptor。这个桥接函数位于 dfs_net.c。S2

c 复制代码
int dfs_net_getsocket(int fd)
{
    int socket;
    struct dfs_file *file;

    file = fd_get(fd);
    if (file == NULL) return -1;

    if (file->vnode->type != FT_SOCKET) socket = -1;
    else socket = (int)(size_t)file->vnode->data;

    return socket;
}

它只做两件事:

  1. fd_get(fd) 找回 DFS 的 struct dfs_file;
  2. 确认 vnode 是 FT_SOCKET 后读取 vnode->data。

因此 DFS 在这里回答的是:

这个整数 fd 对应哪个 socket 对象?

它没有决定 TCP 还是 UDP,也没有决定 lwIP 还是 AT。backend 选择发生在下一层 SAL。

3. 进入 sal_socket() 前先看 struct sal_socket:它保存 backend 选择结果

socket() 的直接下一步是 sal_socket()。在进入函数前,先看它实际维护的 per-socket control object。S3

c 复制代码
struct sal_socket
{
    uint32_t magic;                    /* SAL socket magic word */

    int socket;                        /* SAL socket descriptor */
    int domain;
    int type;
    int protocol;

    struct netdev *netdev;             /* SAL network interface device */
    const struct sal_proto_family *protocol_family; /* selected protocol provider */

    void *user_data;                   /* user-specific data */
#ifdef SAL_USING_TLS
    void *user_data_tls;               /* user-specific TLS data */
#endif
};

这些字段构成 per-socket dispatch context:netdev 与 protocol_family 保存创建时选中的接口/provider,user_data 保存 backend 返回的实际 socket descriptor;SAL 本身不重新实现 TCP state machine。

4. 进入 sal_socket():先分配 SAL descriptor,再调用 socket_init() 选择 provider

回到 net_sockets.c::socket() 的直接调用点,下面进入 sal_socket()。S3

c 复制代码
int sal_socket(int domain, int type, int protocol)
{
    int retval;
    int socket, proto_socket;
    struct sal_socket *sock;
    const struct sal_proto_family *pf;

    /* allocate a new socket and registered socket options */
    socket = socket_new();
    if (socket < 0)
    {
        return -1;
    }

    /* get sal socket object by socket descriptor */
    sock = sal_get_socket(socket);
    if (sock == RT_NULL)
    {
        socket_delete(socket);
        return -1;
    }

    /* Initialize sal socket object */
    retval = socket_init(domain, type, protocol, &sock);
    if (retval < 0)
    {
        LOG_E("SAL socket protocol family input failed, return error %d.", retval);
        socket_delete(socket);
        return retval;
    }

    /* valid the network interface socket opreation */
    SAL_SOCKETOPS_VALID(sock, pf, socket);

    proto_socket = pf->skt_ops->socket(domain, type, protocol);
    if (proto_socket >= 0)
    {
#ifdef SAL_USING_TLS
        if (SAL_SOCKOPS_PROTO_TLS_VALID(sock, socket))
        {
            sock->user_data_tls = proto_tls->ops->socket(socket);
            if (sock->user_data_tls == RT_NULL)
            {
                socket_delete(socket);
                return -1;
            }
        }
#endif
        sock->user_data = (void *)(size_t)proto_socket;
        return sock->socket;
    }
    socket_delete(socket);
    return -1;
}

当前主线的关键状态变化是:

text 复制代码
socket_new()
  -> 分配 SAL descriptor

socket_init()
  -> 写 sock->netdev
  -> 写 sock->protocol_family

pf->skt_ops->socket()
  -> 调真正 backend 创建 socket

sock->user_data = proto_socket
  -> 保存 backend descriptor

注意 proto_socket 与 SAL socket 是两个不同整数。sock->user_data 正是后续 connect/send/recv/close 能继续找到同一 backend socket 的关键。

5. 进入 socket_init():backend 只在创建阶段选择一次

sal_socket() 调用 socket_init() 后,SAL 才真正确定 protocol provider。S3

c 复制代码
static int socket_init(int family, int type, int protocol, struct sal_socket **res)
{
    struct sal_socket *sock;
    const struct sal_proto_family *pf;
    struct netdev *netdv_def = netdev_default;
    struct netdev *netdev = RT_NULL;
    rt_bool_t flag = RT_FALSE;

    /* Existing range checks for family and type */
    if (family < 0 || family > AF_MAX)
    {
        LOG_E("Invalid family: %d (must be 0 ~ %d)", family, AF_MAX);
        return -1;
    }

    if (type < 0 || type > SOCK_MAX)
    {
        LOG_E("Invalid type: %d (must be 0 ~ %d)", type, SOCK_MAX);
        return -2;
    }

    /* Range check for protocol */
    if (!VALID_PROTOCOL(protocol))
    {
        LOG_E("Invalid protocol: %d (must be 0 ~ %d)", protocol, IPPROTO_RAW);
        rt_set_errno(EINVAL);
        return -4;
    }

    sock = *res;
    sock->domain = family;
    sock->type = type;
    sock->protocol = protocol;

    /* Combo compatibility check */
    if (!VALID_COMBO(family, type, protocol))
    {
        LOG_E("Invalid combo: domain=%d, type=%d, protocol=%d", family, type, protocol);
        rt_set_errno(EINVAL);
        return -4;
    }

    pf = sal_proto_family_find(family);
    if (pf != RT_NULL)
    {
        sock->protocol_family = pf;
        sock->netdev = RT_NULL;
        return 0;
    }

    /* Existing netdev selection logic */
    if (netdv_def && netdev_is_up(netdv_def))
    {
        /* check default network interface device protocol family */
        pf = (struct sal_proto_family *)netdv_def->sal_user_data;
        if (pf != RT_NULL && pf->skt_ops && (pf->family == family || pf->sec_family == family))
        {
            sock->netdev = netdv_def;
            sock->protocol_family = pf;
            flag = RT_TRUE;
        }
    }

    if (flag == RT_FALSE)
    {
        /* get network interface device by protocol family */
        netdev = netdev_get_by_family(family);
        if (netdev == RT_NULL)
        {
            LOG_E("not find network interface device by protocol family(%d).", family);
            return -3;
        }

        sock->netdev = netdev;
        sock->protocol_family = (const struct sal_proto_family *)netdev->sal_user_data;
        if (sock->protocol_family == RT_NULL || sock->protocol_family->skt_ops == RT_NULL)
        {
            return -3;
        }
    }

    LOG_D("Socket init success: domain=%d, type=%d, protocol=%d, netdev=%s",
          family, type, protocol, sock->netdev ? sock->netdev->name : "default");
    return 0;
}

Stage 41 会逐行分析这里的 NetDev 选择算法。Stage 40 只需要抓住生命周期结论:

text 复制代码
socket 创建时
    ↓
选出 netdev + protocol_family
    ↓
保存进 struct sal_socket
    ↓
之后 connect/send/recv 不重新做 backend 选择

这就是为什么一个已经创建好的 socket 不会因为稍后 netdev_default 改变就自动迁移到另一套协议栈。

6. lwip_inet_family:SAL 如何把 AF_INET 映射到 lwIP operation table

在当前 lwIP backend 中,af_inet_lwip.c 定义了两层表:一层是 Socket 操作,一层是 protocol family 描述。S4

c 复制代码
static const struct sal_socket_ops lwip_socket_ops =
{
    .socket      = inet_socket,
    .closesocket = lwip_close,
    .bind        = lwip_bind,
    .listen      = lwip_listen,
    .connect     = lwip_connect,
    .accept      = inet_accept,
    .sendto      = (int (*)(int, const void *, size_t, int, const struct sockaddr *, socklen_t))lwip_sendto,
#if LWIP_VERSION >= 0x20102ff
    .sendmsg     = (int (*)(int, const struct msghdr *, int))lwip_sendmsg,
    .recvmsg     = (int (*)(int, struct msghdr *, int))lwip_recvmsg,
#endif
    .recvfrom    = (int (*)(int, void *, size_t, int, struct sockaddr *, socklen_t *))lwip_recvfrom,
    .getsockopt  = lwip_getsockopt,
    //TODO fix on 1.4.1
    .setsockopt  = lwip_setsockopt,
    .shutdown    = lwip_shutdown,
    .getpeername = lwip_getpeername,
    .getsockname = inet_getsockname,
    .ioctlsocket = inet_ioctlsocket,
    .socketpair  = RT_NULL,
#ifdef SAL_USING_POSIX
    .poll        = inet_poll,
#endif
};

紧接着是 family descriptor:S4

c 复制代码
static const struct sal_proto_family lwip_inet_family =
{
    .family     = AF_INET,
#if LWIP_VERSION > 0x2000000
    .sec_family = AF_INET6,
#else
    .sec_family = AF_INET,
#endif
    .skt_ops    = &lwip_socket_ops,
    .netdb_ops  = &lwip_netdb_ops,
};

因此 socket_init() 选中 lwip_inet_family 后,后续分发已经确定:

text 复制代码
SAL connect      -> lwip_connect
SAL sendto       -> lwip_sendto
SAL recvfrom     -> lwip_recvfrom
SAL closesocket  -> lwip_close
SAL poll         -> inet_poll

只有 .socket 不是直接写 lwip_socket,而是先进入 inet_socket(),因为 POSIX poll integration 还需要额外安装 event callback。

7. 进入 inet_socket():真正创建 lwIP socket,并安装 poll 事件桥

sal_socket() 通过 pf->skt_ops->socket() 到达 inet_socket()。S4

c 复制代码
static int inet_socket(int domain, int type, int protocol)
{
#ifdef SAL_USING_POSIX
    int socket;

    socket = lwip_socket(domain, type, protocol);
    if (socket >= 0)
    {
        struct lwip_sock *lwsock;

        lwsock = lwip_tryget_socket(socket);
        lwsock->conn->callback = event_callback;

        rt_wqueue_init(&lwsock->wait_head);
    }

    return socket;
#else
    return lwip_socket(domain, type, protocol);
#endif /* SAL_USING_POSIX */
}

这里完成第二次对象映射:

text 复制代码
SAL socket
  sock->user_data
       ↓
lwIP socket descriptor
       ↓
struct lwip_sock
       ↓
struct netconn

同时 lwsock->conn->callback = event_callback 把 lwIP Netconn 的事件通知改接到 RT-Thread wait queue。这条异步桥后面解释 poll() 时再完整展开。

8. 进入 lwip_socket():lwIP Socket 层再创建 Netconn

inet_socket() 直接调用 vendored lwIP 2.1.2 lwip_socket()。下面进入该函数。S6

c 复制代码
int
lwip_socket(int domain, int type, int protocol)
{
  struct netconn *conn;
  int i;

  LWIP_UNUSED_ARG(domain); /* @todo: check this */

  /* create a netconn */
  switch (type) {
    case SOCK_RAW:
      conn = netconn_new_with_proto_and_callback(DOMAIN_TO_NETCONN_TYPE(domain, NETCONN_RAW),
             (u8_t)protocol, DEFAULT_SOCKET_EVENTCB);
      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_RAW, %d) = ",
                                  domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol));
      break;
    case SOCK_DGRAM:
      conn = netconn_new_with_callback(DOMAIN_TO_NETCONN_TYPE(domain,
                                       ((protocol == IPPROTO_UDPLITE) ? NETCONN_UDPLITE : NETCONN_UDP)),
                                       DEFAULT_SOCKET_EVENTCB);
      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_DGRAM, %d) = ",
                                  domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol));
#if LWIP_NETBUF_RECVINFO
      if (conn) {
        /* netconn layer enables pktinfo by default, sockets default to off */
        conn->flags &= ~NETCONN_FLAG_PKTINFO;
      }
#endif /* LWIP_NETBUF_RECVINFO */
      break;
    case SOCK_STREAM:
      conn = netconn_new_with_callback(DOMAIN_TO_NETCONN_TYPE(domain, NETCONN_TCP), DEFAULT_SOCKET_EVENTCB);
      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%s, SOCK_STREAM, %d) = ",
                                  domain == PF_INET ? "PF_INET" : "UNKNOWN", protocol));
      break;
    default:
      LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_socket(%d, %d/UNKNOWN, %d) = -1\n",
                                  domain, type, protocol));
      set_errno(EINVAL);
      return -1;
  }

  if (!conn) {
    LWIP_DEBUGF(SOCKETS_DEBUG, ("-1 / ENOBUFS (could not create netconn)\n"));
    set_errno(ENOBUFS);
    return -1;
  }

  i = alloc_socket(conn, 0);

  if (i == -1) {
    netconn_delete(conn);
    set_errno(ENFILE);
    return -1;
  }
  conn->socket = i;
  done_socket(&sockets[i - LWIP_SOCKET_OFFSET]);
  LWIP_DEBUGF(SOCKETS_DEBUG, ("%d\n", i));
  set_errno(0);
  return i;
}

对于本文 SOCK_STREAM 路径:

text 复制代码
lwip_socket()
  -> netconn_new_with_callback(... NETCONN_TCP ...)
  -> alloc_socket(conn, 0)
  -> conn->socket = lwIP socket id

Stage 06 已经完整讲过 Socket → Netconn → tcpip_thread,因此这里停止继续下钻。Stage 40 新增的知识是:RT-Thread 的 DFS/SAL 最终重新接回了原来那条 lwIP Socket 主线。

9. 创建结束后,三个 descriptor 怎样一一关联

lwip_socket() 返回后,控制依次返回:

text 复制代码
lwip_socket id
    ↓ return to inet_socket()
inet_socket returns backend id
    ↓ return to sal_socket()
sock->user_data = backend id
    ↓ return SAL descriptor
socket() stores SAL descriptor in vnode->data
    ↓ return DFS fd to application

因此一个成功创建的 TCP socket 形成:
#mermaid-svg-jV436K8uKWFlDa1T{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jV436K8uKWFlDa1T .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jV436K8uKWFlDa1T .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jV436K8uKWFlDa1T .error-icon{fill:#552222;}#mermaid-svg-jV436K8uKWFlDa1T .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jV436K8uKWFlDa1T .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jV436K8uKWFlDa1T .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jV436K8uKWFlDa1T .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jV436K8uKWFlDa1T .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jV436K8uKWFlDa1T .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jV436K8uKWFlDa1T .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jV436K8uKWFlDa1T .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jV436K8uKWFlDa1T .marker.cross{stroke:#333333;}#mermaid-svg-jV436K8uKWFlDa1T svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jV436K8uKWFlDa1T p{margin:0;}#mermaid-svg-jV436K8uKWFlDa1T .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jV436K8uKWFlDa1T .cluster-label text{fill:#333;}#mermaid-svg-jV436K8uKWFlDa1T .cluster-label span{color:#333;}#mermaid-svg-jV436K8uKWFlDa1T .cluster-label span p{background-color:transparent;}#mermaid-svg-jV436K8uKWFlDa1T .label text,#mermaid-svg-jV436K8uKWFlDa1T span{fill:#333;color:#333;}#mermaid-svg-jV436K8uKWFlDa1T .node rect,#mermaid-svg-jV436K8uKWFlDa1T .node circle,#mermaid-svg-jV436K8uKWFlDa1T .node ellipse,#mermaid-svg-jV436K8uKWFlDa1T .node polygon,#mermaid-svg-jV436K8uKWFlDa1T .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jV436K8uKWFlDa1T .rough-node .label text,#mermaid-svg-jV436K8uKWFlDa1T .node .label text,#mermaid-svg-jV436K8uKWFlDa1T .image-shape .label,#mermaid-svg-jV436K8uKWFlDa1T .icon-shape .label{text-anchor:middle;}#mermaid-svg-jV436K8uKWFlDa1T .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jV436K8uKWFlDa1T .rough-node .label,#mermaid-svg-jV436K8uKWFlDa1T .node .label,#mermaid-svg-jV436K8uKWFlDa1T .image-shape .label,#mermaid-svg-jV436K8uKWFlDa1T .icon-shape .label{text-align:center;}#mermaid-svg-jV436K8uKWFlDa1T .node.clickable{cursor:pointer;}#mermaid-svg-jV436K8uKWFlDa1T .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jV436K8uKWFlDa1T .arrowheadPath{fill:#333333;}#mermaid-svg-jV436K8uKWFlDa1T .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jV436K8uKWFlDa1T .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jV436K8uKWFlDa1T .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jV436K8uKWFlDa1T .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jV436K8uKWFlDa1T .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jV436K8uKWFlDa1T .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jV436K8uKWFlDa1T .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jV436K8uKWFlDa1T .cluster text{fill:#333;}#mermaid-svg-jV436K8uKWFlDa1T .cluster span{color:#333;}#mermaid-svg-jV436K8uKWFlDa1T div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jV436K8uKWFlDa1T .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jV436K8uKWFlDa1T rect.text{fill:none;stroke-width:0;}#mermaid-svg-jV436K8uKWFlDa1T .icon-shape,#mermaid-svg-jV436K8uKWFlDa1T .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jV436K8uKWFlDa1T .icon-shape p,#mermaid-svg-jV436K8uKWFlDa1T .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jV436K8uKWFlDa1T .icon-shape .label rect,#mermaid-svg-jV436K8uKWFlDa1T .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jV436K8uKWFlDa1T .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jV436K8uKWFlDa1T .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jV436K8uKWFlDa1T :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} dfs_file.vnode->data
sal_socket.user_data
DFS fd
SAL socket id
lwIP socket id
struct lwip_sock
struct netconn

三个整数可能碰巧相同,但代码不能依赖数值相等;映射必须经各层对象表完成。

10. connect(fd, ...):先从 DFS fd 找回 SAL socket,再从 user_data 找回 lwIP socket

应用继续调用标准 connect()。下面进入 net_sockets.c::connect()。S2

c 复制代码
int connect(int s, const struct sockaddr *name, socklen_t namelen)
{
    int socket = dfs_net_getsocket(s);
    return sal_connect(socket, name, namelen);
}

第一步通过前面已经解释过的 dfs_net_getsocket() 得到 SAL descriptor。随后进入 sal_connect()。S3

c 复制代码
int sal_connect(int socket, const struct sockaddr *name, socklen_t namelen)
{
    struct sal_socket *sock;
    const struct sal_proto_family *pf;
    int ret;

    /* get the socket object by socket descriptor */
    SAL_SOCKET_OBJ_GET(sock, socket);

    /* check the network interface is up status */
    SAL_NETDEV_IS_UP(sock->netdev);
    /* check the network interface socket opreation */
    SAL_SOCKETOPS_VALID(sock, pf, connect);

    ret = pf->skt_ops->connect((int)(size_t)sock->user_data, name, namelen);
#ifdef SAL_USING_TLS
    if (ret >= 0 && SAL_SOCKOPS_PROTO_TLS_VALID(sock, connect))
    {
        if (proto_tls->ops->connect(sock->user_data_tls) < 0)
        {
            return -1;
        }

        return ret;
    }
#endif

    return ret;
}

这里能直接看到"创建时选择、运行时复用"的 contract:

text 复制代码
SAL_SOCKET_OBJ_GET()
    -> 取回创建时的 struct sal_socket

sock->protocol_family
    -> 决定 pf->skt_ops

sock->user_data
    -> 取回创建时的 lwIP socket id

对 lwip_inet_family,pf->skt_ops->connect 就是 lwip_connect()。

11. 进入 lwip_connect():再次回到 Stage 06/07 的 Netconn/TCP 路径

下面进入 vendored lwIP lwip_connect()。S6

c 复制代码
int
lwip_connect(int s, const struct sockaddr *name, socklen_t namelen)
{
  struct lwip_sock *sock;
  err_t err;

  sock = get_socket(s);
  if (!sock) {
    return -1;
  }

  if (!SOCK_ADDR_TYPE_MATCH_OR_UNSPEC(name, sock)) {
    /* sockaddr does not match socket type (IPv4/IPv6) */
    sock_set_errno(sock, err_to_errno(ERR_VAL));
    done_socket(sock);
    return -1;
  }

  LWIP_UNUSED_ARG(namelen);
  if (name->sa_family == AF_UNSPEC) {
    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_connect(%d, AF_UNSPEC)\n", s));
    err = netconn_disconnect(sock->conn);
  } else {
    ip_addr_t remote_addr;
    u16_t remote_port;

    /* check size, family and alignment of 'name' */
    LWIP_ERROR("lwip_connect: invalid address", IS_SOCK_ADDR_LEN_VALID(namelen) &&
               IS_SOCK_ADDR_TYPE_VALID_OR_UNSPEC(name) && IS_SOCK_ADDR_ALIGNED(name),
               sock_set_errno(sock, err_to_errno(ERR_ARG)); done_socket(sock); return -1;);

    SOCKADDR_TO_IPADDR_PORT(name, &remote_addr, remote_port);
    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_connect(%d, addr=", s));
    ip_addr_debug_print_val(SOCKETS_DEBUG, remote_addr);
    LWIP_DEBUGF(SOCKETS_DEBUG, (" port=%"U16_F")\n", remote_port));

#if LWIP_IPV4 && LWIP_IPV6
    /* Dual-stack: Unmap IPv4 mapped IPv6 addresses */
    if (IP_IS_V6_VAL(remote_addr) && ip6_addr_isipv4mappedipv6(ip_2_ip6(&remote_addr))) {
      unmap_ipv4_mapped_ipv6(ip_2_ip4(&remote_addr), ip_2_ip6(&remote_addr));
      IP_SET_TYPE_VAL(remote_addr, IPADDR_TYPE_V4);
    }
#endif /* LWIP_IPV4 && LWIP_IPV6 */

    err = netconn_connect(sock->conn, &remote_addr, remote_port);
  }

  if (err != ERR_OK) {
    LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_connect(%d) failed, err=%d\n", s, err));
    sock_set_errno(sock, err_to_errno(err));
    done_socket(sock);
    return -1;
  }

  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_connect(%d) succeeded\n", s));
  sock_set_errno(sock, 0);
  done_socket(sock);
  return 0;
}

lwip_connect() 将 sockaddr 转为 lwIP ip_addr_t + port,最终调用 netconn_connect()。到这里,应用侧链路已经和 Stage 06/07 完全汇合:

text 复制代码
RT-Thread connect(fd)
  -> DFS
  -> SAL
  -> lwip_connect(lwIP socket)
  -> netconn_connect()
  -> tcpip_thread
  -> TCP active open

12. recv() / send():BSD Socket wrapper 继续复用同一个 dispatch context

标准 recv() 与 send() 仍然先还原 SAL descriptor。S2

c 复制代码
int recv(int s, void *mem, size_t len, int flags)
{
    int socket = dfs_net_getsocket(s);

    return sal_recvfrom(socket, mem, len, flags, NULL, NULL);
}
c 复制代码
int send(int s, const void *dataptr, size_t size, int flags)
{
    int socket = dfs_net_getsocket(s);

    return sal_sendto(socket, dataptr, size, flags, NULL, 0);
}

进入 sal_recvfrom() 后,当前 socket 对象决定 backend。S3

c 复制代码
int sal_recvfrom(int socket, void *mem, size_t len, int flags,
                 struct sockaddr *from, socklen_t *fromlen)
{
    struct sal_socket *sock;
    const struct sal_proto_family *pf;

    /* get the socket object by socket descriptor */
    SAL_SOCKET_OBJ_GET(sock, socket);

    /* check the network interface is up status  */
    SAL_NETDEV_IS_UP(sock->netdev);
    /* check the network interface socket opreation */
    SAL_SOCKETOPS_VALID(sock, pf, recvfrom);

#ifdef SAL_USING_TLS
    if (SAL_SOCKOPS_PROTO_TLS_VALID(sock, recv))
    {
        int ret;

        if ((ret = proto_tls->ops->recv(sock->user_data_tls, mem, len)) < 0)
        {
            return -1;
        }
        return ret;
    }
    else
    {
        return pf->skt_ops->recvfrom((int)(size_t)sock->user_data, mem, len, flags, from, fromlen);
    }
#else
    return pf->skt_ops->recvfrom((int)(size_t)sock->user_data, mem, len, flags, from, fromlen);
#endif
}

发送方向同样通过 sock->user_data:S3

c 复制代码
int sal_sendto(int socket, const void *dataptr, size_t size, int flags,
               const struct sockaddr *to, socklen_t tolen)
{
    struct sal_socket *sock;
    const struct sal_proto_family *pf;

    /* get the socket object by socket descriptor */
    SAL_SOCKET_OBJ_GET(sock, socket);

    /* check the network interface is up status  */
    SAL_NETDEV_IS_UP(sock->netdev);
    /* check the network interface socket opreation */
    SAL_SOCKETOPS_VALID(sock, pf, sendto);

#ifdef SAL_USING_TLS
    if (SAL_SOCKOPS_PROTO_TLS_VALID(sock, send))
    {
        int ret;

        if ((ret = proto_tls->ops->send(sock->user_data_tls, dataptr, size)) < 0)
        {
            return -1;
        }
        return ret;
    }
    else
    {
        return pf->skt_ops->sendto((int)sock->user_data, dataptr, size, flags, to, tolen);
    }
#else
    return pf->skt_ops->sendto((int)(size_t)sock->user_data, dataptr, size, flags, to, tolen);
#endif
}

对当前 lwIP family,这两条最终进入 lwip_recvfrom() / lwip_sendto()。SAL 没有重新实现 TCP/UDP payload queue,只负责分发。

13. read() / write() 为什么也能操作 socket:DFS file operations 直接桥到同一组 SAL API

由于 socket() 已将 vnode 初始化为 FT_SOCKET 并绑定 dfs_net_get_fops(),POSIX read/write 会进入 dfs_net.c 的 network file operations。S2

继续阅读 dfs_net_read():

c 复制代码
#ifdef RT_USING_DFS_V2
static ssize_t dfs_net_read(struct dfs_file* file, void *buf, size_t count, off_t *pos)
#else
static ssize_t dfs_net_read(struct dfs_file* file, void *buf, size_t count)
#endif
{
    int ret;
    int socket = (int)(size_t)file->vnode->data;

    ret = sal_recvfrom(socket, buf, count, 0, NULL, NULL);
    if (ret < 0)
    {
        ret = rt_get_errno();
        return (ret > 0) ? (-ret) : ret;
    }

    return ret;
}

继续阅读 dfs_net_write():S2

c 复制代码
#ifdef RT_USING_DFS_V2
static ssize_t dfs_net_write(struct dfs_file *file, const void *buf, size_t count, off_t *pos)
#else
static ssize_t dfs_net_write(struct dfs_file *file, const void *buf, size_t count)
#endif
{
    int ret;
    int socket = (int)(size_t)file->vnode->data;

    ret = sal_sendto(socket, buf, count, 0, NULL, 0);
    if (ret < 0)
    {
        ret = rt_get_errno();
        return (ret > 0) ? (-ret) : ret;
    }

    return ret;
}

因此两类 API 在 SAL 层汇合:
#mermaid-svg-BkCN8MrdFujXW8Ru{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BkCN8MrdFujXW8Ru .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BkCN8MrdFujXW8Ru .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BkCN8MrdFujXW8Ru .error-icon{fill:#552222;}#mermaid-svg-BkCN8MrdFujXW8Ru .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BkCN8MrdFujXW8Ru .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BkCN8MrdFujXW8Ru .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BkCN8MrdFujXW8Ru .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BkCN8MrdFujXW8Ru .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BkCN8MrdFujXW8Ru .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BkCN8MrdFujXW8Ru .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BkCN8MrdFujXW8Ru .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BkCN8MrdFujXW8Ru .marker.cross{stroke:#333333;}#mermaid-svg-BkCN8MrdFujXW8Ru svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BkCN8MrdFujXW8Ru p{margin:0;}#mermaid-svg-BkCN8MrdFujXW8Ru .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-BkCN8MrdFujXW8Ru .cluster-label text{fill:#333;}#mermaid-svg-BkCN8MrdFujXW8Ru .cluster-label span{color:#333;}#mermaid-svg-BkCN8MrdFujXW8Ru .cluster-label span p{background-color:transparent;}#mermaid-svg-BkCN8MrdFujXW8Ru .label text,#mermaid-svg-BkCN8MrdFujXW8Ru span{fill:#333;color:#333;}#mermaid-svg-BkCN8MrdFujXW8Ru .node rect,#mermaid-svg-BkCN8MrdFujXW8Ru .node circle,#mermaid-svg-BkCN8MrdFujXW8Ru .node ellipse,#mermaid-svg-BkCN8MrdFujXW8Ru .node polygon,#mermaid-svg-BkCN8MrdFujXW8Ru .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BkCN8MrdFujXW8Ru .rough-node .label text,#mermaid-svg-BkCN8MrdFujXW8Ru .node .label text,#mermaid-svg-BkCN8MrdFujXW8Ru .image-shape .label,#mermaid-svg-BkCN8MrdFujXW8Ru .icon-shape .label{text-anchor:middle;}#mermaid-svg-BkCN8MrdFujXW8Ru .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-BkCN8MrdFujXW8Ru .rough-node .label,#mermaid-svg-BkCN8MrdFujXW8Ru .node .label,#mermaid-svg-BkCN8MrdFujXW8Ru .image-shape .label,#mermaid-svg-BkCN8MrdFujXW8Ru .icon-shape .label{text-align:center;}#mermaid-svg-BkCN8MrdFujXW8Ru .node.clickable{cursor:pointer;}#mermaid-svg-BkCN8MrdFujXW8Ru .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-BkCN8MrdFujXW8Ru .arrowheadPath{fill:#333333;}#mermaid-svg-BkCN8MrdFujXW8Ru .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-BkCN8MrdFujXW8Ru .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-BkCN8MrdFujXW8Ru .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BkCN8MrdFujXW8Ru .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-BkCN8MrdFujXW8Ru .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BkCN8MrdFujXW8Ru .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-BkCN8MrdFujXW8Ru .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-BkCN8MrdFujXW8Ru .cluster text{fill:#333;}#mermaid-svg-BkCN8MrdFujXW8Ru .cluster span{color:#333;}#mermaid-svg-BkCN8MrdFujXW8Ru div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-BkCN8MrdFujXW8Ru .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-BkCN8MrdFujXW8Ru rect.text{fill:none;stroke-width:0;}#mermaid-svg-BkCN8MrdFujXW8Ru .icon-shape,#mermaid-svg-BkCN8MrdFujXW8Ru .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-BkCN8MrdFujXW8Ru .icon-shape p,#mermaid-svg-BkCN8MrdFujXW8Ru .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-BkCN8MrdFujXW8Ru .icon-shape .label rect,#mermaid-svg-BkCN8MrdFujXW8Ru .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-BkCN8MrdFujXW8Ru .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-BkCN8MrdFujXW8Ru .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-BkCN8MrdFujXW8Ru :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} recv()/send()
dfs_net_getsocket(fd)
read()/write()
dfs_file.vnode->data
SAL socket id
sal_recvfrom()/sal_sendto()
saved protocol_family + user_data
lwip_recvfrom()/lwip_sendto()

DFS 只是让 Socket 复用统一 fd/file-operation infrastructure,并不改变 lwIP socket 的网络对象语义。

14. poll() 的异步桥:为什么 inet_socket() 必须替换 Netconn callback

同步 send/recv 只需调用 operation table;poll() 还需要"网络状态变化时唤醒等待线程"。这条链的注册点就是前面 inet_socket() 中:

c 复制代码
lwsock->conn->callback = event_callback;
rt_wqueue_init(&lwsock->wait_head);

当 lwIP Netconn 发生 receive/send/error event 时,会进入 event_callback()。S4

c 复制代码
static void event_callback(struct netconn *conn, enum netconn_evt evt, u16_t len)
{
    int s;
    struct lwip_sock *sock;
    uint32_t event = 0;
    SYS_ARCH_DECL_PROTECT(lev);

    LWIP_UNUSED_ARG(len);

    /* Get socket */
    if (conn)
    {
        s = conn->socket;
        if (s < 0)
        {
            /* Data comes in right away after an accept, even though
             * the server task might not have created a new socket yet.
             * Just count down (or up) if that's the case and we
             * will use the data later. Note that only receive events
             * can happen before the new socket is set up. */
            SYS_ARCH_PROTECT(lev);
            if (conn->socket < 0)
            {
                if (evt == NETCONN_EVT_RCVPLUS)
                {
                    conn->socket--;
                }
                SYS_ARCH_UNPROTECT(lev);
                return;
            }
            s = conn->socket;
            SYS_ARCH_UNPROTECT(lev);
        }

        sock = lwip_tryget_socket(s);
        if (!sock)
        {
            return;
        }
    }
    else
    {
        return;
    }

    SYS_ARCH_PROTECT(lev);
    /* Set event as required */
    switch (evt)
    {
    case NETCONN_EVT_RCVPLUS:
        sock->rcvevent++;
        break;
    case NETCONN_EVT_RCVMINUS:
        sock->rcvevent--;
        break;
    case NETCONN_EVT_SENDPLUS:
        sock->sendevent = 1;
        break;
    case NETCONN_EVT_SENDMINUS:
        sock->sendevent = 0;
        break;
    case NETCONN_EVT_ERROR:
        sock->errevent = 1;
        break;
    default:
        LWIP_ASSERT("unknown event", 0);
        break;
    }

#if LWIP_VERSION >= 0x20100ff
    if ((void*)(sock->lastdata.pbuf) || (sock->rcvevent > 0))
#else
    if ((void*)(sock->lastdata) || (sock->rcvevent > 0))
#endif
        event |= POLLIN;
    if (sock->sendevent)
        event |= POLLOUT;
    if (sock->errevent)
        event |= POLLERR;

    SYS_ARCH_UNPROTECT(lev);

    if (event)
    {
        rt_wqueue_wakeup(&sock->wait_head, (void*)(size_t)event);
    }
}

这里完成的是:

text 复制代码
lwIP Netconn event
  -> 更新 lwip_sock rcvevent/sendevent/errevent
  -> 转成 POLLIN/POLLOUT/POLLERR
  -> rt_wqueue_wakeup()

另一方面,DFS 的 poll 入口只做桥接:S2

c 复制代码
static int dfs_net_poll(struct dfs_file *file, struct rt_pollreq *req)
{
    extern int sal_poll(struct dfs_file *file, struct rt_pollreq *req);

    return sal_poll(file, req);
}

进入 sal_poll():S3

c 复制代码
int sal_poll(struct dfs_file *file, struct rt_pollreq *req)
{
    struct sal_socket *sock;
    const struct sal_proto_family *pf;
    int socket = (int)(size_t)file->vnode->data;

    /* get the socket object by socket descriptor */
    SAL_SOCKET_OBJ_GET(sock, socket);

    /* check the network interface is up status  */
    SAL_NETDEV_IS_UP(sock->netdev);
    /* check the network interface socket opreation */
    SAL_SOCKETOPS_VALID(sock, pf, poll);

    return pf->skt_ops->poll(file, req);
}

对 lwIP family,下一步进入 inet_poll()。S4

c 复制代码
static int inet_poll(struct dfs_file *file, struct rt_pollreq *req)
{
    int mask = 0;
    struct lwip_sock *sock;
    struct sal_socket *sal_sock;

    sal_sock = sal_get_socket((int)(size_t)file->vnode->data);
    if(!sal_sock)
    {
        return -1;
    }

    sock = lwip_tryget_socket((int)(size_t)sal_sock->user_data);
    if (sock != NULL)
    {
        rt_base_t level;

        rt_poll_add(&sock->wait_head, req);

        level = rt_spin_lock_irqsave(&_spinlock);

#if LWIP_VERSION >= 0x20100ff
        if ((void*)(sock->lastdata.pbuf) || sock->rcvevent)
#else
        if ((void*)(sock->lastdata) || sock->rcvevent)
#endif
        {
            mask |= POLLIN;
        }
        if (sock->sendevent)
        {
            mask |= POLLOUT;
        }
        if (sock->errevent)
        {
            mask |= POLLERR;
            /* clean error event */
            sock->errevent = 0;
        }
        rt_spin_unlock_irqrestore(&_spinlock, level);
    }

    return mask;
}

于是 poll 的完整异步关系是:
#mermaid-svg-kICEGdcxcxYpzXIM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kICEGdcxcxYpzXIM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kICEGdcxcxYpzXIM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kICEGdcxcxYpzXIM .error-icon{fill:#552222;}#mermaid-svg-kICEGdcxcxYpzXIM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kICEGdcxcxYpzXIM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kICEGdcxcxYpzXIM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kICEGdcxcxYpzXIM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kICEGdcxcxYpzXIM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kICEGdcxcxYpzXIM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kICEGdcxcxYpzXIM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kICEGdcxcxYpzXIM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kICEGdcxcxYpzXIM .marker.cross{stroke:#333333;}#mermaid-svg-kICEGdcxcxYpzXIM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kICEGdcxcxYpzXIM p{margin:0;}#mermaid-svg-kICEGdcxcxYpzXIM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kICEGdcxcxYpzXIM .cluster-label text{fill:#333;}#mermaid-svg-kICEGdcxcxYpzXIM .cluster-label span{color:#333;}#mermaid-svg-kICEGdcxcxYpzXIM .cluster-label span p{background-color:transparent;}#mermaid-svg-kICEGdcxcxYpzXIM .label text,#mermaid-svg-kICEGdcxcxYpzXIM span{fill:#333;color:#333;}#mermaid-svg-kICEGdcxcxYpzXIM .node rect,#mermaid-svg-kICEGdcxcxYpzXIM .node circle,#mermaid-svg-kICEGdcxcxYpzXIM .node ellipse,#mermaid-svg-kICEGdcxcxYpzXIM .node polygon,#mermaid-svg-kICEGdcxcxYpzXIM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kICEGdcxcxYpzXIM .rough-node .label text,#mermaid-svg-kICEGdcxcxYpzXIM .node .label text,#mermaid-svg-kICEGdcxcxYpzXIM .image-shape .label,#mermaid-svg-kICEGdcxcxYpzXIM .icon-shape .label{text-anchor:middle;}#mermaid-svg-kICEGdcxcxYpzXIM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kICEGdcxcxYpzXIM .rough-node .label,#mermaid-svg-kICEGdcxcxYpzXIM .node .label,#mermaid-svg-kICEGdcxcxYpzXIM .image-shape .label,#mermaid-svg-kICEGdcxcxYpzXIM .icon-shape .label{text-align:center;}#mermaid-svg-kICEGdcxcxYpzXIM .node.clickable{cursor:pointer;}#mermaid-svg-kICEGdcxcxYpzXIM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kICEGdcxcxYpzXIM .arrowheadPath{fill:#333333;}#mermaid-svg-kICEGdcxcxYpzXIM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kICEGdcxcxYpzXIM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kICEGdcxcxYpzXIM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kICEGdcxcxYpzXIM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kICEGdcxcxYpzXIM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kICEGdcxcxYpzXIM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kICEGdcxcxYpzXIM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kICEGdcxcxYpzXIM .cluster text{fill:#333;}#mermaid-svg-kICEGdcxcxYpzXIM .cluster span{color:#333;}#mermaid-svg-kICEGdcxcxYpzXIM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kICEGdcxcxYpzXIM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kICEGdcxcxYpzXIM rect.text{fill:none;stroke-width:0;}#mermaid-svg-kICEGdcxcxYpzXIM .icon-shape,#mermaid-svg-kICEGdcxcxYpzXIM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kICEGdcxcxYpzXIM .icon-shape p,#mermaid-svg-kICEGdcxcxYpzXIM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kICEGdcxcxYpzXIM .icon-shape .label rect,#mermaid-svg-kICEGdcxcxYpzXIM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kICEGdcxcxYpzXIM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kICEGdcxcxYpzXIM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kICEGdcxcxYpzXIM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} application poll(fd)
DFS poll
sal_poll()
inet_poll(): register wait_head
lwIP Netconn event
event_callback()
set POLLIN/POLLOUT/POLLERR state
rt_wqueue_wakeup()

这说明 SAL POSIX 集成不仅是 descriptor 映射,还包含事件模型适配。

15. 为什么标准 socket() 与 lwIP 自己的兼容别名不会冲突

RT-Thread lwipopts.h 在 SAL_USING_POSIX 条件下显式关闭 lwIP 的 BSD compatibility aliases:S5

c 复制代码
/*
 * LWIP_COMPAT_SOCKETS==1: Enable BSD-style sockets functions names.
 * (only used if you use sockets.c)
 */
#ifdef SAL_USING_POSIX
#define LWIP_COMPAT_SOCKETS             0
#else
#ifndef LWIP_COMPAT_SOCKETS
#define LWIP_COMPAT_SOCKETS             1
#endif
#endif

因此分层明确:

text 复制代码
应用可见: socket() / connect() / send() / recv()
              ↓ SAL/DFS
backend:    lwip_socket() / lwip_connect() / lwip_sendto() / lwip_recvfrom()

这不是两套 Socket API 同时竞争同名符号,而是 SAL 有意占据标准 BSD 名称,lwIP 保留 lwip_* backend 名称。

16. closesocket():按 DFS → SAL → lwIP 的反方向释放

创建顺序是:

text 复制代码
DFS fd
  -> SAL socket
  -> lwIP socket
  -> Netconn

关闭时从最外层开始。先进入 net_sockets.c::closesocket()。S2

c 复制代码
int closesocket(int s)
{
    int error = 0;
    int socket = -1;
    struct dfs_file *d;

    socket = dfs_net_getsocket(s);
    if (socket < 0)
    {
        rt_set_errno(-ENOTSOCK);
        return -1;
    }

    d = fd_get(s);
    if (d == RT_NULL)
    {
        rt_set_errno(-EBADF);
        return -1;
    }

    if (!d->vnode)
    {
        rt_set_errno(-EBADF);
        return -1;
    }

#ifdef RT_USING_DFS_V2
    if (dfs_file_close(d) == 0)
#else
    if (sal_closesocket(socket) == 0)
#endif
    {
        error = 0;
    }
    else
    {
        rt_set_errno(-ENOTSOCK);
        error = -1;
    }

    /* socket has been closed, delete it from file system fd */
    fd_release(s);

    return error;
}

DFS V2 的 socket fops 会进入 dfs_net_close();只有 vnode 最后一个引用才真正关闭 SAL socket。S2

c 复制代码
static int dfs_net_close(struct dfs_file* file)
{
    int socket;
    int ret = 0;

    if (file->vnode->ref_count == 1)
    {
        socket = (int)(size_t)file->vnode->data;
        ret = sal_closesocket(socket);
    }
    return ret;
}

继续进入 sal_closesocket():S3

c 复制代码
int sal_closesocket(int socket)
{
    struct sal_socket *sock;
    const struct sal_proto_family *pf;
    int error = 0;

    /* get the socket object by socket descriptor */
    SAL_SOCKET_OBJ_GET(sock, socket);

    /* clsoesocket operation not need to vaild network interface status */
    /* valid the network interface socket opreation */
    SAL_SOCKETOPS_VALID(sock, pf, closesocket);

    if (pf->skt_ops->closesocket((int)(size_t)sock->user_data) == 0)
    {
#ifdef SAL_USING_TLS
        if (SAL_SOCKOPS_PROTO_TLS_VALID(sock, closesocket))
        {
            if (proto_tls->ops->closesocket(sock->user_data_tls) < 0)
            {
                return -1;
            }
        }
#endif
        error = 0;
    }
    else
    {
        error = -1;
    }

    /* delete socket */
    socket_delete(socket);

    return error;
}

对 lwIP family,pf->skt_ops->closesocket 就是 lwip_close()。进入 lwIP:S6

c 复制代码
int
lwip_close(int s)
{
  struct lwip_sock *sock;
  int is_tcp = 0;
  err_t err;

  LWIP_DEBUGF(SOCKETS_DEBUG, ("lwip_close(%d)\n", s));

  sock = get_socket(s);
  if (!sock) {
    return -1;
  }

  if (sock->conn != NULL) {
    is_tcp = NETCONNTYPE_GROUP(netconn_type(sock->conn)) == NETCONN_TCP;
  } else {
    LWIP_ASSERT("sock->lastdata == NULL", sock->lastdata.pbuf == NULL);
  }

#if LWIP_IGMP
  /* drop all possibly joined IGMP memberships */
  lwip_socket_drop_registered_memberships(s);
#endif /* LWIP_IGMP */
#if LWIP_IPV6_MLD
  /* drop all possibly joined MLD6 memberships */
  lwip_socket_drop_registered_mld6_memberships(s);
#endif /* LWIP_IPV6_MLD */

  err = netconn_prepare_delete(sock->conn);
  if (err != ERR_OK) {
    sock_set_errno(sock, err_to_errno(err));
    done_socket(sock);
    return -1;
  }

  free_socket(sock, is_tcp);
  set_errno(0);
  return 0;
}

因此 teardown 的真实顺序是:

text 复制代码
application fd
 -> DFS file/vnode
 -> SAL socket
 -> lwIP socket
 -> Netconn prepare/delete path

sal_closesocket() 特意不检查 NetDev 是否 UP,因为即使链路已经断开,应用仍必须能够释放 socket 资源。

17. Stage 40 的完整心智模型

把整个应用生命周期串起来:
#mermaid-svg-nF1MraKoSbE64zme{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nF1MraKoSbE64zme .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nF1MraKoSbE64zme .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nF1MraKoSbE64zme .error-icon{fill:#552222;}#mermaid-svg-nF1MraKoSbE64zme .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nF1MraKoSbE64zme .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nF1MraKoSbE64zme .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nF1MraKoSbE64zme .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nF1MraKoSbE64zme .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nF1MraKoSbE64zme .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nF1MraKoSbE64zme .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nF1MraKoSbE64zme .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nF1MraKoSbE64zme .marker.cross{stroke:#333333;}#mermaid-svg-nF1MraKoSbE64zme svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nF1MraKoSbE64zme p{margin:0;}#mermaid-svg-nF1MraKoSbE64zme .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nF1MraKoSbE64zme .cluster-label text{fill:#333;}#mermaid-svg-nF1MraKoSbE64zme .cluster-label span{color:#333;}#mermaid-svg-nF1MraKoSbE64zme .cluster-label span p{background-color:transparent;}#mermaid-svg-nF1MraKoSbE64zme .label text,#mermaid-svg-nF1MraKoSbE64zme span{fill:#333;color:#333;}#mermaid-svg-nF1MraKoSbE64zme .node rect,#mermaid-svg-nF1MraKoSbE64zme .node circle,#mermaid-svg-nF1MraKoSbE64zme .node ellipse,#mermaid-svg-nF1MraKoSbE64zme .node polygon,#mermaid-svg-nF1MraKoSbE64zme .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nF1MraKoSbE64zme .rough-node .label text,#mermaid-svg-nF1MraKoSbE64zme .node .label text,#mermaid-svg-nF1MraKoSbE64zme .image-shape .label,#mermaid-svg-nF1MraKoSbE64zme .icon-shape .label{text-anchor:middle;}#mermaid-svg-nF1MraKoSbE64zme .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nF1MraKoSbE64zme .rough-node .label,#mermaid-svg-nF1MraKoSbE64zme .node .label,#mermaid-svg-nF1MraKoSbE64zme .image-shape .label,#mermaid-svg-nF1MraKoSbE64zme .icon-shape .label{text-align:center;}#mermaid-svg-nF1MraKoSbE64zme .node.clickable{cursor:pointer;}#mermaid-svg-nF1MraKoSbE64zme .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nF1MraKoSbE64zme .arrowheadPath{fill:#333333;}#mermaid-svg-nF1MraKoSbE64zme .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nF1MraKoSbE64zme .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nF1MraKoSbE64zme .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nF1MraKoSbE64zme .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nF1MraKoSbE64zme .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nF1MraKoSbE64zme .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nF1MraKoSbE64zme .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nF1MraKoSbE64zme .cluster text{fill:#333;}#mermaid-svg-nF1MraKoSbE64zme .cluster span{color:#333;}#mermaid-svg-nF1MraKoSbE64zme div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nF1MraKoSbE64zme .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nF1MraKoSbE64zme rect.text{fill:none;stroke-width:0;}#mermaid-svg-nF1MraKoSbE64zme .icon-shape,#mermaid-svg-nF1MraKoSbE64zme .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nF1MraKoSbE64zme .icon-shape p,#mermaid-svg-nF1MraKoSbE64zme .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nF1MraKoSbE64zme .icon-shape .label rect,#mermaid-svg-nF1MraKoSbE64zme .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nF1MraKoSbE64zme .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nF1MraKoSbE64zme .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nF1MraKoSbE64zme :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} socket()
DFS fd + FT_SOCKET vnode
sal_socket()
socket_init(): choose NetDev + protocol_family
lwip_inet_family.skt_ops
inet_socket()
lwip_socket()
Netconn
connect/recv/send/read/write/poll/close
DFS fd -> SAL socket id
saved protocol_family + user_data
lwip_connect/send/recv/poll/close

Stage 39 解决"Driver → lwIP",Stage 40 解决"Application → lwIP"。两条路径最终都汇入同一个 lwIP Core。

下一篇 Stage 41 继续展开这里唯一暂时保留的黑盒:socket_init() 为什么会选中某个 NetDev,以及系统同时存在 lwIP Ethernet 与 AT Wi-Fi/4G 时,netdev_default、family/sec_family 和 netdev_get_by_family() 怎样共同决定 backend。

资料来源

S1 RT-Thread SAL / DFS 配置与官方组件说明

  • 类型:RT-Thread 官方仓库源码与官方文档
  • 版本:commit dc8aaa73f2dbea255325ec058a083aeeb5381d0a
  • 定位:components/net/sal/Kconfig、components/dfs/Kconfig、documentation/6.components/sal/sal.md、documentation/6.components/filesystem/README.md
  • URL/文档:SAL Kconfig、SAL documentation、DFS documentation
  • 使用位置:"开场概念桥""SAL/DFS 在系统中的职责边界"
  • 支撑内容:SAL=Socket Abstraction Layer;DFS 提供 RT-Thread 的文件描述符/虚拟文件系统基础设施

S2 RT-Thread BSD Socket 与 DFS bridge

  • 类型:RT-Thread 官方仓库源码
  • 版本:同上
  • 定位:components/net/sal/socket/net_sockets.c:socket()、connect()、recv()、send()、closesocket();components/net/sal/dfs_net/dfs_net.c:dfs_net_getsocket()、dfs_net_read()、dfs_net_write()、dfs_net_close()、dfs_net_poll()
  • URL/文档:net_sockets.c、dfs_net.c
  • 使用位置:"DFS fd 创建与回查""read/write/poll/close bridge"
  • 支撑内容:证明标准 BSD/POSIX API 如何从 DFS descriptor 进入 SAL

S3 RT-Thread SAL Core

  • 类型:RT-Thread 官方仓库源码
  • 版本:同上
  • 定位:components/net/sal/include/sal_low_lvl.h:struct sal_socket、struct sal_socket_ops;components/net/sal/src/sal_socket.c:socket_init()、sal_socket()、sal_connect()、sal_sendto()、sal_recvfrom()、sal_poll()、sal_closesocket()
  • URL/文档:sal_low_lvl.h、sal_socket.c
  • 使用位置:"SAL socket 对象""backend selection""per-socket dispatch context""运行时操作与 teardown"
  • 支撑内容:证明 netdev/protocol_family/user_data 在创建时保存、后续操作复用

S4 RT-Thread lwIP SAL Adapter

  • 类型:RT-Thread 官方仓库源码
  • 版本:同上
  • 定位:components/net/sal/impl/af_inet_lwip.c:event_callback()、inet_socket()、inet_poll()、lwip_socket_ops、lwip_inet_family、sal_lwip_netdev_set_pf_info()
  • URL/文档:af_inet_lwip.c
  • 使用位置:"SAL → lwIP operation table""poll/event bridge"
  • 支撑内容:证明 SAL 怎样调用 lwip_*,以及 POSIX poll 如何接入 lwIP Netconn event

S5 RT-Thread lwIP lwipopts.h

  • 类型:RT-Thread 官方仓库源码
  • 版本:同上
  • 定位:components/net/lwip/port/lwipopts.h:LWIP_COMPAT_SOCKETS
  • URL/文档:RT-Thread lwipopts.h
  • 使用位置:"标准 BSD 名称与 lwIP backend 名称为什么不冲突"
  • 支撑内容:证明 SAL_USING_POSIX 下关闭 lwIP compatibility socket aliases

S6 RT-Thread vendored lwIP 2.1.2 Socket / Netconn

  • 类型:RT-Thread 仓库内置 lwIP 源码
  • 版本:RT-Thread commit dc8aaa73f2dbea255325ec058a083aeeb5381d0a 中的 lwIP 2.1.2
  • 定位:components/net/lwip/lwip-2.1.2/src/api/sockets.c:lwip_socket()、lwip_connect()、lwip_close()
  • URL/文档:lwIP sockets.c
  • 使用位置:"lwIP socket 创建/连接/关闭""重新接回 Netconn 主线"
  • 支撑内容:证明 SAL 最终进入标准 lwIP Socket → Netconn → Core 路径

S7 RT-Thread 官方 SAL 文档

  • 类型:RT-Thread 官方在线文档
  • 版本:访问日期 2026-10-03;用于框架导读,目标实现仍固定到本文 commit
  • URL/文档:Socket Abstraction Layer: SAL
  • 使用位置:"阅读源码前""SAL/VFS/协议栈分层""标准 BSD API 到 backend 的总体关系"
  • 支撑内容:官方说明 SAL 的统一 BSD Socket API、protocol family/backend abstraction 与 POSIX/文件描述符集成定位

S8 RT-Thread 官方 Virtual File System 文档

  • 类型:RT-Thread 官方在线文档
  • 版本:访问日期 2026-10-03
  • URL/文档:Virtual File System
  • 使用位置:"阅读源码前""read/write 复用 fd/file-operation infrastructure"
  • 支撑内容:提供 RT-Thread VFS/设备抽象的通用背景;本文具体 socket vnode/fops 行为仍由 [S2] 目标源码证明
相关推荐
中伟视界1 小时前
边缘端行为识别部署实战:AI算法分析盒子落地皮带场景
人工智能·算法
落樱弥城1 小时前
FlashAttention 完全解析:从 Roofline 诊断到 CUDA 实现
人工智能·算法
FelixZhang0282 小时前
不懂金融或不会编程,如何开始量化?从策略搭建到回测验证
人工智能·python·算法·数学建模·均值算法·自然语言处理·金融
Mortalbreeze2 小时前
0 - 1 BFS 算法详解:从基本原理到经典题目建模
数据结构·c++·算法·图论·宽度优先
飞Link4 小时前
什么是 PX4?一文了解 PX4 在空地协同中的作用
算法·无人机
一去不复返的通信er10 小时前
力扣-图论-岛屿数量
算法·leetcode·图论
程序 代码狂人13 小时前
切片表与分区表的区别(数据开发面试版)
java·大数据·开发语言·数据仓库·算法