3784 字
约 12 分钟
7
S02-B01 · 从进程到网卡:一台机器如何把数据发出去

适用读者:会写 CRUD,但被问"这个请求到底怎么走到对面"时答不上来的人。本系列第一篇。
前置:S01(能用 curl、ss、ip 等命令)。
实测环境:Ubuntu 24.04.1 / Linux 6.8(内核)/ curl 8.x / iproute2 / openssl 3.x;实测日期 2026-11-05。
覆盖旧文:A-1860(操作系统核心知识点,692 点)、A-1861(计算机网络核心知识点,742 点)= 1,434 点。
覆盖扩展:E-S02-001(虚拟内存与页表)、E-S02-010(HTTP 版本演进,只点名)。
下一篇:S02-B02 · 进程与线程。


1. 一句话结论

发一个请求,本质是"用户态的字节"被一层层套上头部(封装),交给网卡变成电信号;回包再一层层剥掉头部(解封装)。操作系统负责"进程与内存",网络栈负责"投递"。两者在 socket 这个接口上交汇。

   你的进程                     内核                          网卡/线路
 ┌──────────┐   write()   ┌─────────────┐  ┌──────┐  ┌──────┐  ┌────────┐
 │ 应用数据  │ ─────────► │ socket 缓冲区 │→ │ TCP  │→ │ IP   │→ │ 以太网  │→ bits
 │ "GET /"  │  系统调用    │             │  │ 段   │  │ 包   │  │ 帧     │
 └──────────┘             └─────────────┘  └──────┘  └──────┘  └────────┘
        ▲                        │
        └──── read() ←───────────┘   ← 回包按相反方向逐层解封装

记住这个"两层世界":应用层看到的永远是"一次 write 写了 N 字节",而网络上实际发了几个包、怎么分片,应用层并不知道(这正是 S02-B05 粘包的根源)。


2. 用三条命令看同一次请求(实测)

以本博客域名做一次真实请求:

2.1 DNS:先要知道 IP

$ getent hosts clxhxhhr.top
2606:4700:3034::ac43:ab35 clxhxhhr.top
2606:4700:3036::6815:1d03 clxhxhhr.top

拿到的是 IPv6 地址(Cloudflare 边缘节点)。curl 实际连接时会根据本机网络能力选 A/AAAA 记录。

2.2 路由:包从哪张网卡出去

$ ip route
default via 10.0.23.1 dev ens17 proto static
10.0.23.0/24 dev ens17 proto kernel scope link src 10.0.23.56
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1

读法:目的地址不在本地网段 → 走 default 网关 10.0.23.1,从 ens17 出去,源地址是 10.0.23.56。

2.3 HTTP 请求:把连接、加密、协议一次看全

$ curl -sv -o /dev/null https://clxhxhhr.top/

实测输出(截取):

* Connected to clxhxhhr.top (104.21.29.3) port 443
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* Server certificate:
* using HTTP/2
> GET / HTTP/2
> Host: clxhxhhr.top

三条信息量很大的结论:

观察 含义
Connected to ... port 443 TCP 连接建立成功(三次握手已完成,见 B03)
TLSv1.3 ... Client hello / Server hello / Certificate TLS 握手在 TCP 之上又走了一轮(B06 详讲)
using HTTP/2 应用层协议是 HTTP/2,不是 HTTP/1.1

同一台机器、同一个域名,四层协议栈全在现场:IP(路由)→ TCP(连接)→ TLS(加密)→ HTTP/2(应用)。


3. 端口、socket 与四元组

概念 定义 常见误解
端口 16 位数字,标识"主机上的哪个服务" 以为端口是进程的属性——端口只是绑定关系
socket 内核里的一条连接,由四元组唯一确定 以为 socket 就是"一个 IP:端口"
四元组 (源IP, 源端口, 目的IP, 目的端口) 忽略源端口,导致"为什么同一服务能接一万个连接"答不上来

ss 命令就是用来观察这些的(本机实测):

$ ss -s
Total: 239
TCP:   42 (estab 7, closed 8, orphaned 1, timewait 4)

$ ss -ti state established | head -2
Recv-Q Send-Q Local Address:Port   Peer Address:Port
0      0      127.0.0.1:46450      127.0.0.1:mysql
        cubic wscale:7,7 rto:204 rtt:3.145/0.924 ... cwnd:10 ... bytes_sent:245816

第二段那些参数(cwnd、rtt、retrans)是内核在告诉你"这条连接的真实状态"——B04 与 J01 会逐项拆。


4. 一次请求的 12 步(从键盘到回包)

① 浏览器/DNS 解析域名 → IP                       【应用 + DNS】
② 内核选源地址与出口网卡(查路由表)                【IP 层】
③ 若目的在本地网段 → ARP 查 MAC;否则发给网关        【链路层】
④ 应用调 connect() → 内核发起 SYN                 【TCP】
⑤ 三次握手完成,socket 进入 ESTABLISHED            【TCP】
⑥ 应用 write() → 数据进 socket 发送缓冲区           【系统调用】
⑦ TCP 分段(受 MSS 限制)、加序号,交给 IP           【传输层】
⑧ IP 加源/目的地址,查路由,交给链路层               【网络层】
⑨ 链路层加帧头(源/目的 MAC),网卡 DMA 发出          【链路层】
⑩ 对端逐层解封装:帧 → 包 → 段 → 字节流            【内核】
⑪ 对端应用 read() 拿到字节,处理并回包               【应用】
⑫ 回包按 ⑨→⑦ 反向走一遍,本机 read() 拿到响应      【全链路】

为什么这 12 步值得背:排障时"卡在哪一步"决定了你该看哪个命令。

症状 大概率卡在 首查命令
Could not resolve host ① getent hosts / dig
No route to host ②③ ip route
Connection timed out ④⑤ ss -tan、对端防火墙
连上了但无响应 ⑥⑪ 应用日志、ss -ti 的 unacked
数据错乱/粘包 ⑦⑪ 抓包或应用层解码(B05)

5. 操作系统的四个核心概念(旧文 A-1860 的重组)

旧文 A-1860 把 OS 讲了 1,000 多行(中断、内核态、线程、进程、调度、内存、页表……)。这里只留与"发数据"直接相关的四个,其余进 §9 概念索引。

5.1 用户态与内核态

态 能做什么 代价
用户态 跑你的代码,不能直接碰硬件/网卡 快
内核态 能碰硬件、能调度、能管内存 切换有成本(保存/恢复上下文)

read/write/connect 这些系统调用就是"过门"的动作:用户态 → 内核态 → 回到用户态。所以"每次网络收发都有一次态切换",这也是零拷贝、sendfile、io_uring 等技术存在的理由。

5.2 进程与地址空间

  • 每个进程有独立的虚拟地址空间(页表映射到物理内存)。
  • 进程间通信必须经过内核(管道、socket、共享内存)。
  • 线程共享同一地址空间 → 通信成本低,但共享可变状态就是数据竞争的来源(S05 详讲)。

5.3 虚拟内存与页表(E-S02-001)

概念 一句话
虚拟地址 进程看到的地址,不是物理地址
页表 虚拟页 → 物理页的映射表
缺页中断 访问的页不在内存 → 内核去磁盘/交换区取
快表(TLB) 页表的缓存,命中就不用查多级页表

与网络的联系:socket 缓冲区占用的是内核内存(不受进程堆限制),所以"应用内存没满但系统 OOM"是可能的。

5.4 中断与软中断

网卡收到包 → 触发硬中断 → 内核把处理"延后"到软中断(NAPI/softirq)→ 协议栈处理 → 唤醒等待的进程。

这解释了一个常见现象:高流量下 %soft(软中断 CPU 占比)飙高、ksoftirqd 吃满一个核——这不是你的代码慢,是收包路径饱和(J01 会给排查方法)。


6. 网络分层:OSI 七层是教学模型,TCP/IP 四层才是实现

OSI(7 层) TCP/IP(4 层) 实际对应 典型协议
应用 / 表示 / 会话 应用层 你的 HTTP 客户端、TLS 库 HTTP、DNS、SSH、WebSocket
传输层 传输层 内核 socket TCP、UDP、QUIC
网络层 网络层 内核 IP 栈 IP、ICMP
数据链路 / 物理 链路层 网卡驱动、交换机 Ethernet、ARP、Wi-Fi

面试常考的"七层"要会说,但排障要看四层:说"表示层有问题"没人能修,说"TLS 握手失败"才有人能修。

6.1 封装:每层加什么头

层 加的头 里面有什么
传输层(TCP) TCP 首部 20 字节起 源/目的端口、序号、确认号、窗口、标志位
网络层(IP) IP 首部 20 字节起 源/目的 IP、TTL、协议号(6=TCP,17=UDP)
链路层(以太) 帧头 14 字节 源/目的 MAC、类型
物理层 前导码等 比特流

"MTU 1500"的由来:以太网帧最大 1518 字节 − 18 字节帧头尾 = 1500 字节 IP 包;再减 20 IP + 20 TCP = MSS 1460。这就是为什么"一次 send 100KB 会变成几十个包"。


7. 五个高频误区

误区 真相
"TCP 保证消息边界" ❌ TCP 只保证字节流有序可靠,边界要应用层自己划(B05)
"端口属于进程" ❌ 端口是绑定关系;同一端口可以有多个连接(四元组不同)
"SYN 就是连接建立" ❌ 收到 SYN 只说明对端可达;半连接队列满时仍会失败(B03)
"网卡收到的包,进程立刻能读到" ❌ 要经过软中断与协议栈;高负载时延迟来自这里
"带宽大就不卡" ❌ 延迟(RTT)与丢包才是交互体验的决定因素(B04、J01)

8. 自检

# 检查 命令 预期
1 能解析域名 getent hosts <域名> 打印 IP
2 能看出口网卡 ip route | head -1 default via ... dev ...
3 能看到四层协议 curl -sv https://<域名>/ -o /dev/null 2>&1 | grep -E "Connected|TLS|HTTP/" 连接 + TLS + 协议版本
4 能读 socket 统计 ss -s estab / timewait 计数
5 能读 TCP 细节 ss -ti state established 出现 cwnd、rtt

9. 附录 · 概念索引(旧文 A-1860 / A-1861 的其余知识点)

这一节是"索引 + 一句话定义",用来保证旧文的每个概念都有落点;深入展开留给对应系列(S05 并发、S02-J01 网络排障)。

9.1 操作系统(A-1860)

概念 一句话 深入
冯·诺依曼结构 运算器/控制器/存储器/输入输出 + 总线 —
中断 打断当前执行去处理紧急事件;分硬/软中断 §5.4
内核态/用户态 权限级别的两档;系统调用是通道 §5.1
用户态线程 / 内核态线程 前者由库调度、后者由内核调度 B02
协程 用户态轻量线程,切换不经过内核 B02
线程安全 共享可变状态 + 并发访问 = 竞争 S05
进程创建(fork/exec) fork 复制地址空间,exec 换程序 B02
进程状态机 新建/就绪/运行/阻塞/终止 B02
IPC 管道、消息队列、共享内存、信号、socket S05
信号 异步通知机制(SIGTERM/SIGKILL) —
进程同步 互斥量、信号量、条件变量 S05
上下文切换 保存/恢复寄存器与页表,有成本 B02
调度算法 FCFS、SJF、时间片、优先级、多级反馈队列 B02(E-002)
守护进程 脱离终端在后台运行 S15
孤儿/僵尸进程 父进程先死 / 子进程退出未被回收 S15
死锁 互斥、占有等待、不可剥夺、循环等待 S05
fork 与写时复制 共享物理页,写时才复制 B02
磁盘调度 先来先服务、最短寻道、电梯算法 —
虚拟地址 / 虚拟内存 进程视角的连续空间,映射到物理页 §5.3
缺页中断 访问页不在内存时触发 §5.3
页面置换 FIFO、LRU、Clock、OPT S05
局部性原理 时间局部性 + 空间局部性 S05
页表 / 快表 / MMU 映射表 / 表缓存 / 地址转换硬件 §5.3
动态分区分配 首次适应、最佳适应、最坏适应 —
内存覆盖 / 交换 手工复用内存 / 换出到磁盘 —

9.2 计算机网络(A-1861)

概念 一句话 深入
OSI 七层 / TCP-IP 四层 教学模型 / 实现模型 §6
交换机 链路层设备,按 MAC 转发 —
路由器 网络层设备,按 IP 与路由表转发 §2.2
HTTP 状态码 1xx 信息、2xx 成功、3xx 重定向、4xx 客户端错、5xx 服务端错 B06
HTTP/1.1 → 2.0 → 3.0 队头阻塞 → 多路复用 → QUIC(UDP) B06、E-010
HTTPS HTTP over TLS B06、J02
请求/响应报文结构 起始行 + 头部 + 空行 + 体 B06
TCP 报文段 首部字段(序号/确认/窗口/标志) B03、B04
粘包与拆包 字节流无边界,需应用层划 B05
三次握手 / 四次挥手 建立/释放连接 B03
流量控制 接收窗口限制发送速率 B04
拥塞控制 慢启动、拥塞避免、快重传、快恢复 B04、E-006
socket 内核连接对象的编程接口 §3
UDP 报文段 无连接、不可靠、首部 8 字节 B04
MAC 地址 链路层地址 —
IP 地址 / 子网掩码 网络层寻址与网段划分 —
网关 跨网段的出口 §2.2
ping / ICMP 连通性探测 J01
DNS 域名 → IP 的分布式查询系统 §2.1
ARP IP → MAC 的本地解析 §4 ③
数字签名 私钥签、公钥验;保证完整性与来源 B06、J02
加密算法 对称(AES)/ 非对称(RSA、ECDHE) B06、J02
常见攻击(SQL 注入等) 属应用安全,非网络层 S13(Web 安全)

10. 下一篇

S02-B02 · 进程与线程:为什么"多开一个线程"不等于"多一份 CPU"

下一篇把 §5.2–§5.4 的三件事展开:进程与线程的内存模型、上下文切换的真实成本、以及"线程越多越好吗"的实测拐点。旧文 A-1615 的面试口径会改写成"能解释为什么"的机制叙述。


本篇属于系列《S02 · 计算机与网络基础》。旧文来源:A-1860、A-1861;扩展点 E-S02-001/010。DNS、TLS、HTTP/2 与 socket 统计为 2026-11-05 实测。

S02-B01 · 从进程到网卡:一台机器如何把数据发出去
https://www.clxhxhhr.top/posts/5182/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。