# socket / bind 内核实现 —— socket 四件套的前两步

> 上一篇 [listen-kernel-internals.md](/concepts/network/listen-kernel-internals.md) 讲 listen 怎么建两队列，下一篇 [accept-kernel-internals.md](/concepts/network/accept-kernel-internals.md) 讲 accept 怎么取连接。本篇聚焦服务端启动的**前两步**：`socket()` 在内核里分配什么、`bind()` 做了什么、为什么这两步**不改变 TCP 状态**。全文呼应 TCP 四件套 `socket → bind → listen → accept` 的生命线。

## 一、socket 和 bind 各自做了什么

一句话：**`socket()` 造壳（分配结构 + 挂 vtable），`bind()` 贴标签（注册 IP:Port 到内核 hash 表）。两者都不改变 TCP 状态。**

| 步骤 | 做了什么 | TCP 状态 | 关键数据结构 |
|------|---------|---------|-------------|
| `socket(AF_INET, SOCK_STREAM, 0)` | 分配 `struct socket` + `struct sock`（含 `inet_connection_sock`），挂好 [`inet_stream_ops`](#vfs-视角socket-为什么是一个文件) vtable（详见 §二 VFS 视角），返回 fd | [`TCP_CLOSE`](/concepts/network/listen-kernel-internals.md) | `socket.ops = &inet_stream_ops`、`socket.sk → struct sock` |
| `bind(sockfd, &addr, ...)` | 把 IP:Port 写入 `sock`，注册到全局 bind hash 表（端口冲突检查） | [`TCP_CLOSE`](/concepts/network/listen-kernel-internals.md)（不变） | `inet->inet_sport`、`inet->inet_num`、bind hash 节点 |

后面的 `listen()` 才把状态推到 `TCP_LISTEN` 并创建两队列。在这之前，socket 只是一个"有名字但还不接客"的空壳。

## 二、socket 和 sock 的双层结构

这是理解 socket 内核实现的核心：**两个结构，两层抽象。**

```plantuml
@startuml
left to right direction
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<user>> #C8E6C9
  BorderColor<<user>>     #388E3C
  BackgroundColor<<kern>> #BBDEFB
  BorderColor<<kern>>     #1976D2
  BackgroundColor<<struct>> #FFF9C4
  BorderColor<<struct>>   #F9A825
}
rectangle "用户态 fd = 5" <<user>> as FD
rectangle "struct file" <<kern>> as FILE
rectangle "struct socket\n  .type = SOCK_STREAM\n  .state = SS_UNCONNECTED\n  .ops = &inet_stream_ops  ← vtable\n  .sk" <<struct>> as SOCKET
rectangle "struct sock (sk)\n  .sk_prot = &tcp_prot\n  .sk_state = TCP_CLOSE\n  .sk_family = AF_INET\n\n struct inet_connection_sock (icsk)\n        .icsk_accept_queue  ← 两队列在此！\n        .icsk_bind_hash     ← bind hash 节点" <<struct>> as SOCK
FD -down-> FILE : fd → file
FILE -down-> SOCKET : file->private_data
SOCKET -right-> SOCK : socket.sk →
@enduml
```

用户态拿到的是整数 fd，但 fd 背后有五层沿路指针，各结构体之间的引用与继承关系如下：

```plantuml
@startuml
left to right direction
skinparam shadowing false
skinparam class {
  BackgroundColor #FFF9C4
  BorderColor     #F9A825
}
class "fd (进程fd表下标)" as fd #C8E6C9
class "struct file
─────
private_data → socket
f_count (引用计数)" as file
class "struct socket
─────
ops → proto_ops
sk → sock" as socket
class "struct sock
─────
sk_state (TCP状态机)
sk_protinfo → inet" as sock
class "inet_sock
─────
sport/dport
saddr/daddr" as inet
class "inet_connection_sock
─────
accept_queue (两队列)
ca_state/rto" as icsk
together {
  class "proto_ops <<vtable>>" as proto_ops {
    bind / listen / accept
    sendmsg / recvmsg
  }
  class "proto <<vtable>>" as prot {
    connect / sendmsg / recvmsg
    hash / unhash
  }
}
fd --> file
file --> socket
socket --> sock
socket ..> proto_ops : ops
sock ..> prot : sk_prot
sock --> inet : sk_protinfo
inet <|-- icsk
note bottom of icsk : fd → file → socket → sock → inet → icsk  共5层指针跳转
@enduml
```

**关系分析**：

fd 不是结构体，而是进程 fd 表（`files_struct`）中的数组下标。真正的数据结构链如下：

- **`fd → struct file`**：进程的 `files_struct.fd_array[fd]` 拿到 `struct file *`。这一步由 `sock_map_fd()` 分配 fd 并建立映射（进程 fd 表的结构详见 [`files-struct.md`](/concepts/process/task-resources/files-struct.md)）。`file` 是 VFS 层的通用对象，内核用引用计数（`f_count`）管理生命周期——fork 时父子共享同一 file，close 时计数减 1，归零才真正释放。
- **`file.private_data → struct socket`**：`private_data` 是 `void *` 类型的"万能钩子"。对普通文件它指向 inode/目录等，对 socket 文件它指向 `struct socket`。这一层把通用 VFS 接口和网络协议栈桥接起来——用户调用 `read(sockfd)` 时，VFS 通过 `file→f_op→read` 进入 socket 文件操作，再通过 `socket→ops→recvmsg` 进入协议栈。
- **`socket.sk → struct sock`**：socket 通过 `.sk` 指针指向 sock，两者是**组合关系**——socket 管接口、sock 管数据，`sk_alloc()` 先分配 sock，`sock_init_data()` 再把两层关联。这层分离使得同一套协议栈（sock）可以复用给不同 socket 接口。
- **`sock.sk_protinfo → struct inet_sock`**：`sk_protinfo` 是协议族私有数据的入口，`AF_INET` 下它指向 `inet_sock`（通过 `inet_sk(sk)` 宏转换）。IP 和端口信息就存在这里，`bind()` 写入时同步更新 `inet_sport` 和 `inet_num`。
- **`inet_sock → inet_connection_sock`（继承）**：`inet_connection_sock` 内嵌在 `inet_sock` 之后，形成 C 风格继承——`sk_alloc()` 一次性分配整块内存，包含 sock + inet_sock + inet_connection_sock 三个结构。`listen()` 时在 `icsk_accept_queue` 里初始化半连接和全连接两队列。

**为什么不能省略中间层**：

| 如果省略... | 后果 |
|------------|------|
| `struct file` | fd 失去引用计数，fork/dup 后无法管理共享，close 时无法判断是否真正释放 |
| `struct socket` | VFS 的 read/write/poll 无法找到网络协议栈的入口函数，每一类文件都要硬编码分发 |
| `struct sock` | TCP/UDP/RAW 各自实现一套缓冲区管理和状态机，代码重复，无法复用 |
| `struct inet_sock` | IPv4 和 IPv6 的地址信息混在 sock 里，协议族扩展需要改核心结构 |
| `struct inet_connection_sock` | 连接队列、拥塞控制状态无处安放，只能靠条件编译或运行时判断是否"是 TCP"
| 结构 | 职责 | 类比 |
|------|------|------|
| `struct socket` | 面向用户态 API，持有 ops 函数指针表（`bind`/`listen`/`accept`/`sendmsg`/`recvmsg` 全部指向 `inet_*`） | fd 的"接口层" |
| `struct sock` | 面向内核网络栈，协议状态机、缓冲区、队列、bind hash 节点 | fd 的"实现层" |
| `struct inet_connection_sock` | TCP 特有扩展，内嵌于 sock，含 `icsk_accept_queue`（两队列的容器） | TCP 的"连接管理附加件" |

### 结构体成员详解

以上 PlantUML 和职责表给出了结构体的大框架，下面把每个结构体的关键成员逐一展开。

#### `struct socket` —— 接口层

| 成员 | 类型 | 描述 |
|------|------|------|
| `type` | `short` | socket 类型：`SOCK_STREAM`(TCP)、`SOCK_DGRAM`(UDP)、`SOCK_RAW` |
| `state` | `socket_state` | socket 状态机：`SS_UNCONNECTED` → `SS_CONNECTING` → `SS_CONNECTED` → `SS_DISCONNECTING` |
| `ops` | `struct proto_ops *` | 操作函数指针表（vtable），指向 `inet_stream_ops`，含 `bind` / `listen` / `accept` / `sendmsg` / `recvmsg` / `poll` 等 |
| `sk` | `struct sock *` | 核心指针：指向 `struct sock`，是 socket 到网络协议栈的唯一桥梁 |
| `file` | `struct file *` | 反向指针：指向关联的 `struct file`（通过 `sock_map_fd` 建立） |
| `wq` | `wait_queue_head_t` | 等待队列头，供 poll / epoll 阻塞等待事件 |
| `flags` | `unsigned long` | socket 标志位（`SOCK_NOSPACE` / `SOCK_ASYNC_NOSPACE` 等） |

#### `struct sock` —— 实现层（核心）

| 成员 | 类型 | 描述 |
|------|------|------|
| `sk_prot` | `struct proto *` | 协议操作函数表，`SOCK_STREAM` 时指向 `tcp_prot`：含 `connect` / `sendmsg` / `recvmsg` / `hash` / `unhash` / `setsockopt` 等 |
| `sk_state` | `__u8` | **TCP 状态机**：`TCP_CLOSE` → `TCP_LISTEN` → `TCP_SYN_RECV` → `TCP_ESTABLISHED` → `TCP_FIN_WAIT1` → ... |
| `sk_family` | `sa_family_t` | 地址族：`AF_INET`(IPv4) / `AF_INET6`(IPv6) / `AF_UNIX`(Unix Domain) |
| `sk_type` | `unsigned short` | socket 类型（与 `socket.type` 一致） |
| `sk_protocol` | `__u8` | 协议号：`IPPROTO_TCP`(6) / `IPPROTO_UDP`(17) |
| `sk_rcvbuf` | `int` | 接收缓冲区大小（字节），可由 `SO_RCVBUF` 设置（调优见 [`kernel-tuning-net.md`](/concepts/network/kernel-tuning-net.md)） |
| `sk_sndbuf` | `int` | 发送缓冲区大小（字节），可由 `SO_SNDBUF` 设置（调优见 [`kernel-tuning-net.md`](/concepts/network/kernel-tuning-net.md)） |
| `sk_refcnt` | `refcount_t` | 引用计数：fd 关闭 / fork 复制时增减，减到 0 才真正释放 |
| `sk_reuse` | `__u8` | `SO_REUSEADDR` 标志位（用法见 [accept-bottleneck.md](/concepts/network/accept-bottleneck.md)） |
| `sk_reuseport` | `__u8` | `SO_REUSEPORT` 标志位（Linux 3.9+，深入见 [accept-bottleneck.md](/concepts/network/accept-bottleneck.md)） |
| `sk_bound_dev_if` | `int` | `SO_BINDTODEVICE` 绑定的网卡接口索引 |
| `sk_err` | `int` | 异步错误码（`SO_ERROR`），连接失败 / RST 时置位 |
| `sk_shutdown` | `unsigned char` | shutdown() 状态：`SEND_SHUTDOWN` / `RCV_SHUTDOWN` |
| `sk_backlog` | `struct sk_buff_head` | backlog 队列：内核空间积压的数据包链表（两队列模型详见 [`listen-kernel-internals.md`](/concepts/network/listen-kernel-internals.md)） |
| `sk_protinfo` | `void *` | 指向协议族私有扩展——`AF_INET` 下指向 `struct inet_sock` |

#### `struct inet_sock` —— IPv4 地址信息

`sock` 通过 `sk_protinfo` / `inet_sk(sk)` 宏拿到：

| 成员 | 类型 | 描述 |
|------|------|------|
| `inet_sport` | `__be16` | 源端口（网络字节序），`bind()` 时由 `htons(port)` 赋值 |
| `inet_dport` | `__be16` | 目的端口（网络字节序），`connect()` / `accept()` 时赋值 |
| `inet_saddr` | `__be32` | 源 IP（网络字节序） |
| `inet_daddr` | `__be32` | 目的 IP（网络字节序） |
| `inet_num` | `__be16` | 本地端口号（主机字节序），与 `inet_sport` 对应，`bind()` 同时写入 |
| `tos` | `__u8` | IP TOS（Type of Service）字段 |
| `uc_ttl` | `__u8` | 单播 TTL |
| `inet_id` | `__u16` | IP 分片 ID |

#### `struct inet_connection_sock`（icsk）—— TCP 连接管理

继承自 `inet_sock`，是 TCP 特化扩展：

| 成员 | 类型 | 描述 |
|------|------|------|
| `icsk_accept_queue` | `struct request_sock_queue` | **接受队列容器**：内含半连接队列（`syn_table`）和全连接队列（`accept_queue`），`listen()` 时初始化 |
| `icsk_bind_hash` | `struct inet_bind_bucket *` | **bind hash 表节点指针**：`bind()` 时注册，用于端口冲突检测；桶内以链表串联同 IP:Port 的所有 sock |
| `icsk_af_ops` | `struct inet_connection_sock_af_ops *` | 地址族差异操作：`queue_xmit` / `send_check` / `rebuild_header` / `mtu_reduced`（IPv4 vs IPv6） |
| `icsk_syn_retries` | `__u8` | SYN 重试次数上限（`tcp_syn_retries`，默认 6） |
| `icsk_rto` | `__u32` | 重传超时（RTO），动态计算，初始约 1s |
| `icsk_pmtu_cookie` | `__u32` | 路径 MTU 发现缓存值 |
| `icsk_ca_state` | `__u8` | 拥塞控制状态：`open` / `disorder` / `cwr` / `recovery` / `loss`（详见 [`tcp-flow-congestion-control.md`](/concepts/network/tcp-flow-congestion-control.md)） |
| `icsk_retransmits` | `__u8` | 当前重传次数 |
| `icsk_backoff` | `__u8` | 退避指数 |
| `icsk_user_timeout` | `__u32` | `TCP_USER_TIMEOUT`（ms），用户态设置的最大等待时间 |
| `icsk_delack_max` | `__u32` | 延迟 ACK 最大超时（μs）（调优见 [`tcp-nodelay.md`](/concepts/network/tcp-nodelay.md)） |

#### `struct file` —— fd 与 socket 的桥梁

用户态 fd 背后的内核对象，`socket()` 的 `sock_map_fd` 负责绑定（进程 fd 表全貌见 [`../process/task-resources/files-struct.md`](/concepts/process/task-resources/files-struct.md)）：

| 成员 | 类型 | 描述 |
|------|------|------|
| `private_data` | `void *` | **关键指针**：指向 `struct socket`，是 `fd → socket` 的唯一路径 |
| `f_op` | `struct file_operations *` | 文件操作表：socket 文件指向 `socket_file_ops`，read / write / poll 最终转发到 `sock->ops` |
| `f_mode` | `fmode_t` | 访问模式（读 / 写 / 读写） |
| `f_flags` | `fmode_t` | 文件标志：`O_NONBLOCK` / `O_CLOEXEC` / `O_APPEND` |
| `f_count` | `refcount_t` | 引用计数：fork / dup 时递增，close 时递减，减到 0 释放 |
| `f_pos` | `loff_t` | 文件偏移量（对 socket 无意义，始终为 0） |
| `f_path` | `struct path` | 文件路径（对 socket 显示为 `socket:[inode_number]`） |

关系总结：`fd → struct file (private_data) → struct socket (sk) → struct sock (sk_protinfo) → struct inet_sock → struct inet_connection_sock`，从 fd 到 icsk 共经过 **5 层指针跳转**。

**整个 TCP 服务端四件套的函数指针都在 `inet_stream_ops` 这张 vtable 里**——`socket()` 选协议族时挂好表，之后 `bind`/`listen`/`accept` 都通过 `sock->ops->xxx()` 分发到 `inet_bind`/`inet_listen`/`inet_accept`。

### VFS 视角：socket 为什么是一个"文件"

socket 能通过 `read()` / `write()` / `poll()` / `close()` 操作，源自 Linux 的 **VFS（Virtual File System）** 多态框架。

| vtable | 所属层 | 指向对象 | 职责 |
|--------|--------|---------|------|
| `file_operations` | VFS 层 | `socket_file_ops` | 文件语义：`read` / `write` / `poll` / `mmap` / `ioctl` |
| `proto_ops` | 协议族层 | `inet_stream_ops` | 协议语义：`bind` / `listen` / `accept` / `sendmsg` / `recvmsg` |

`file_operations` 让 socket 复用文件系统的全部基础设施——`read()` 通过 3 层函数指针跳转（`file_operations → proto_ops → proto`）进入 TCP 收包，`close()` 通过引用计数（`f_count`）确保 fork 后只有双方都 close 才触发四次挥手，`epoll` / `sendfile` / `fcntl` 全部因为 VFS 的 fd 抽象而能在 socket 上统一工作。

> VFS 的完整论述已独立成文，详见 [`../vfs/`](/concepts/vfs)（总纲 [`README.md`](/concepts/vfs/README.md)）：

> - [`../vfs/vfs-overview.md`](/concepts/vfs/vfs-overview.md) —— VFS 架构全景：四大对象、多态分发

> - [`../vfs/vfs-file-operations.md`](/concepts/vfs/vfs-file-operations.md) —— `file_operations` vtable 逐字段详解

> - [`../vfs/vfs-and-socket.md`](/concepts/vfs/vfs-and-socket.md) —— socket 与 VFS 深度融合：两套 vtable、`read()` 3 层分发、`close()` 引用计数、三大好处

## 三、socket() 执行流程

```plantuml
@startuml
skinparam shadowing false
participant "用户进程" as U #C8E6C9
participant "__sys_socket" as SYS #BBDEFB
participant "inet_create" as INET #FFECB3
participant "内核对象\n(socket + sock)" as OBJ #FFF9C4
U -> SYS : socket(AF_INET, SOCK_STREAM, 0)
SYS -> SYS : __sock_create()\n查 net_families[AF_INET]\n→ inet_family_ops
SYS -> INET : inet_create()
INET -> INET : sock->ops = &inet_stream_ops\n根据 SOCK_STREAM 选 vtable
INET -> INET : sk_alloc() → 分配 sock +\ninet_connection_sock
INET -> OBJ : sock_init_data(sock, sk)\n关联 socket ↔ sock
INET --> SYS : return 0
SYS -> SYS : sock_map_fd(sock)\n分配新 fd → 绑定 file → socket
SYS --> U : return sockfd
note right of U : 状态：TCP_CLOSE\n壳已造好，还没有名字
@enduml
```

`socket()` 的核心动作只有三件事：

1. **选 vtable**：根据协议族和类型——`AF_INET + SOCK_STREAM` → `inet_stream_ops`，这一步决定了后续所有操作走 TCP 路径。
2. **分配双层结构**：先 `sk_alloc` 出来 `struct sock`（含 `inet_connection_sock`），再 `sock_init_data` 把 socket 和 sock 双向关联。
3. **分配 fd 并挂到进程 fd 表**：`sock_map_fd` 把 `socket → file → fd` 这条链串起来。

三步走完，socket 还处于 `TCP_CLOSE`，不知道自己是哪个 IP、哪个端口——只是一个有数据结构但"无名无分"的空壳。

### 协议族怎么选

`socket()` 传入的 `AF_INET` 在 `__sock_create` 里通过 `net_families[AF_INET]` 查表，拿到 `inet_family_ops`，其 `.create = inet_create`。`inet_create` 再根据 socket 类型选 ops：

| socket 类型 | `sock->ops` | 协议 |
|------------|-------------|------|
| `SOCK_STREAM` | `inet_stream_ops` | TCP |
| `SOCK_DGRAM` | `inet_dgram_ops` | UDP |
| `SOCK_RAW` | `inet_sockraw_ops` | RAW |

## 四、bind() 执行流程

```plantuml
@startuml
skinparam shadowing false
participant "用户进程" as U #C8E6C9
participant "__sys_bind" as BIND #BBDEFB
participant "inet_bind" as IBIND #FFECB3
participant "bind hash 表\n(全局)" as HASH #FFCCBC
U -> BIND : bind(sockfd, &addr, ...)
BIND -> BIND : sockfd_lookup_light()\nfd → struct socket
BIND -> BIND : move_addr_to_kernel()\n用户态地址 → 内核态
BIND -> IBIND : sock->ops->bind()\n→ inet_bind()
IBIND -> HASH : 遍历 bind hash 表\n检查 (IP, Port) 冲突
note right of HASH
  完全匹配 → EADDRINUSE
  SO_REUSEPORT → 允许共用
  port=0 → 自动分配
end note
IBIND -> IBIND : inet->inet_sport = htons(port)\ninet->inet_num = port
IBIND -> HASH : sk->sk_prot->hash(sk)\n注册到 bind hash 表
note right of HASH : 此后别人 bind 同 IP:Port\n会在此表中查到冲突
IBIND --> BIND : return 0
BIND --> U : return 0
note right of U : 状态仍是 TCP_CLOSE\nsocket 有了名字\n下一步：listen()
@enduml
```

`bind()` 的核心是对**全局 bind hash 表**的查 + 写操作：

1. **查冲突**：遍历 hash 表，找相同 IP:Port 的已有绑定。如果精确匹配且无 `SO_REUSEPORT`，返回 `EADDRINUSE`。
2. **写入**：如果通过检查，把新的 IP:Port 注册进 hash 表。

`bind()` **不改变 TCP 状态**，socket 仍是 `TCP_CLOSE`——它只是给 socket 贴了个名字，让外界能"找到"它。真正的"开张"得等 `listen()` 标记 `TCP_LISTEN`。

### bind 的端口冲突规则

| 标志 | 行为 |
|------|------|
| 无特殊标志 | 一个 IP:Port 只能被一个 socket 绑定 |
| `SO_REUSEADDR` | `TIME_WAIT` 状态的端口可复用；非 `TIME_WAIT` 不行 |
| `SO_REUSEPORT`（Linux 3.9+） | 多个 socket 可绑定同 IP:Port，内核做分发——是实现无锁 accept 的基础（详见 [`accept-bottleneck.md`](/concepts/network/accept-bottleneck.md)） |
| `端口=0` | 内核从 `ip_local_port_range` 自动分配（通常 32768~60999，参数详解见 [`kernel-tuning-net.md`](/concepts/network/kernel-tuning-net.md)） |

## 五、观测：怎么看到 socket 和 bind

| 手段 | 命令 | 看什么 |
|------|------|--------|
| 跟踪系统调用 | `strace -e trace=socket,bind ./program`（详见 [`../../tools/code/strace.md`](/tools/code/strace.md)） | 看到 `socket(AF_INET, SOCK_STREAM, ...)` 和 `bind(...)` |
| 查看已创建 socket | `ss -tlnp`（详见 [`../../tools/network/ss.md`](/tools/network/ss.md)） | `State` 列：`LISTEN`（listen 后）或空（socket 后 bind 前不显示） |
| 查看 socket 详细信息 | `ss -tlnp -o` | `Send-Q` / `Recv-Q` 显示全连接队列状态 |
| /proc | `ls -l /proc/<PID>/fd` | 看到 `socket:[inode]` 类型的 fd |

延伸阅读：

- [listen-kernel-internals.md](/concepts/network/listen-kernel-internals.md) — 四件套第三步：`listen()` 建两队列、标记 `TCP_LISTEN`
- [accept-kernel-internals.md](/concepts/network/accept-kernel-internals.md) — 四件套第四步：`accept()` 阻塞等待 → 取出连接 → 返回 fd
- [epoll.md](/concepts/network/epoll.md) — epoll 原理：红黑树 + 就绪队列、LT/ET 模式
- [thread-models.md](/concepts/network/thread-models.md) — Reactor / Proactor 线程模型
- [zero-copy.md](/concepts/network/zero-copy.md) / [zero-copy-deep.md](/concepts/network/zero-copy-deep.md) — 零拷贝技术：mmap / sendfile / splice
- [tcp-flow-congestion-control.md](/concepts/network/tcp-flow-congestion-control.md) — TCP 流控与拥塞控制
- [kernel-tuning-net.md](/concepts/network/kernel-tuning-net.md) — 内核网络参数调优
- [accept-bottleneck.md](/concepts/network/accept-bottleneck.md) — accept 瓶颈分析与 SO_REUSEPORT 解决方案
- [../io/read-write-process.md](/concepts/io/read-write-process.md) — VFS `read()` 完整 10 层调用链
- [../vfs/vfs-overview.md](/concepts/vfs/vfs-overview.md) — VFS 架构全景：四大对象、多态分发
- [../vfs/vfs-file-operations.md](/concepts/vfs/vfs-file-operations.md) — `file_operations` vtable 逐字段拆解
- [../vfs/vfs-and-socket.md](/concepts/vfs/vfs-and-socket.md) — socket 与 VFS 深度融合
- [../process/task-resources/files-struct.md](/concepts/process/task-resources/files-struct.md) — 进程 fd 表（`files_struct`）结构
- [../../tools/code/strace.md](/tools/code/strace.md) — strace 用法详解
- [../../tools/network/ss.md](/tools/network/ss.md) — ss 命令详解

## 一句话总结

**`socket()` 分配双层结构（`struct socket` 挂 vtable + `struct sock` 管状态）并返回 fd，`bind()` 把 IP:Port 注册到内核全局 hash 表。这两步不改变 TCP 状态（始终 `TCP_CLOSE`），只是为服务端准备好"我是谁、我能做什么"的数据结构——真正的"开张"要等 `listen()` 标记 `TCP_LISTEN` 并建两队列。**
