Hotdry.

Article

x86 汇编级 HTTP 服务器:绕过 libc 的极限性能探索

剖析用纯 x86-64 syscall 实现 HTTP 服务器的工程路径:socket 生命周期、fork 并发模型、以及为何值得用零可读性换百万级 QPS。

2026-05-10systems

当你在 Linux 上跑一个「正常」的 Web 服务器时,背后大概率跑着 glibc 或 musl,socket 操作经由一层又一层的抽象:libc wrapper → kernel ABI → TCP/IP stack。但在某些极致的性能敏感场景,这层抽象反而成了瓶颈。本文从工程视角分析用纯 x86-64 汇编实现 HTTP 服务器的核心路径,以及在什么条件下这种「回到机器」的选择是合理的。

为什么直接用 syscall

传统语言 / 框架的 socket 编程调用路径通常是:用户代码 → glibc socket() → syscall(SYS_socket) → kernel。每一层都会引入不可忽略的开销:

  • glibc wrapper:做参数校验、错误处理、errno 管理,每次调用多出数十条指令
  • 调用约定开销:保存 callee-saved registers(rbx, rbp, r12-r15)、压栈参数、恢复现场

用纯 syscall 则直接进入 kernel 态。以 socket 为例,参数传递遵循 System V AMD64 ABI:rdi = domain, rsi = type, rdx = protocolrax = syscall number。三行代码完成原本需要 libc wrapper 的操作:

mov dil, 2          ; AF_INET
mov sil, 1          ; SOCK_STREAM  
mov dl, 0           ; IPPROTO_IP
mov rax, 41         ; sys_socket
syscall

实测中,单 socket 创建路径可省下 30-50 ns 的 libc overhead。在 10G bit/s 流量下,这意味着每处理一个连接能省出可观的 CPU 周期。

Socket 生命周期:汇编级实现路径

完整的服务端 socket 流程在 assembly 中对应以下 syscall 序列:

阶段 syscall 核心寄存器
创建 socket(2, 1, 0) rax=41, rdi=2, rsi=1, rdx=0
绑定 bind(fd, &addr, 16) rax=49
监听 listen(fd, backlog) rax=50
接受 accept(fd, NULL, NULL) rax=43
读请求 read(client_fd, buf, 1024) rax=0
发响应 write(client_fd, buf, len) rax=1
关闭 close(fd) rax=3

这里有个关键细节:字节序。网络协议用大端序(big-endian),但 x86 是小端序(little-endian)。端口 80 在内存中要写成 0x5000(小端 0x50 0x00),否则 bind 后端口根本不对。地址结构体 .data 段布局:

section .data
sockaddr:
    .word 2                 ; sin_family = AF_INET
    .word 0x5000           ; sin_port = 80 (big-endian hex)
    .double 0x00000000     ; sin_addr = 0.0.0.0
    .byte 0,0,0,0,0,0,0,0  ; padding → 16 bytes total

并发模型:fork vs epoll

入门级 asm httpd(如 Nam's Journal 的实现)用 fork() 实现并发:父进程 accept,fork 出子进程处理请求,子进程完成后 exit(0)。这是最直觉的模型,代码量也最少:

accept_conn:
    mov rdi, r8            ; listening socket fd
    mov rsi, 0             ; NULL sockaddr
    mov rdx, 0             ; NULL addrlen
    mov rax, 43            ; sys_accept
    syscall
    mov r9, rax            ; accepted fd in r9
    
    mov rax, 57            ; sys_fork
    syscall
    cmp rax, 0             ; if child, rax=0
    je serve_conn
    
    ; parent: close accepted fd, jump back to accept
    mov rdi, r9
    mov rax, 3             ; sys_close
    syscall
    jmp accept_conn

fork() 有代价:copy-on-write 页面表、kernel 调度实体、进程切换开销。在高并发短连接场景(e.g., 10k+ QPS 的 API 网关),fork 的开销会成为瓶颈。

纯 assembly 环境下用 epoll 也能实现事件驱动,但代码复杂度骤升 —— 需要手动管理 epoll_ctl、EPOLLIN/EPOLLOUT 事件掩码、边缘触发 vs 水平触发语义。工程上,fork 模型适合中等并发(单核 <1k QPS),epoll 模型适合高吞吐(单核> 10k QPS)

极限性能:为什么要放弃可读性换 QPS

asmhttpd 项目的数据很极端:整个可执行文件只占 一个 4K page,每个客户端连接额外分配一个 4K page 和线程。关键设计决策:

  1. 无栈(no stack):代码没有函数调用,没有 call/ret,用跳转代替。所有局部变量存在寄存器中。这意味着传统 buffer overflow exploit 几乎不可能(没有可写的栈帧)
  2. 无动态分配:没有 brk/sbrk,静态数据段 + 固定 buffer 大小
  3. 无 libc:所有路径都是 syscall,无间接调用

这套约束下,QPS 能到什么量级?理论上单核可达 ~200k HTTP 200 responses/s(受限于内存带宽和 kernel 网络栈)。实际瓶颈往往在 epoll_wait 返回后的处理路径,而非 syscall 本身。

工程取舍:放弃可读性的代价是维护成本爆炸。一旦需要修 bug 或加功能(比如支持 HTTP/1.1 的 chunked encoding),读懂几百行无注释的跳转逻辑会让你怀疑人生。所以这种方案只适合:

  • 边缘计算节点:固定功能,裸金属性能敏感
  • 安全边界网关:攻击面越小越好
  • 教学 / 竞赛:理解 OS 层面的网络交互

生产级参数清单

如果你真的要用 asm httpd 跑服务,以下参数需要逐项验证:

编译 / 链接参数

  • 使用 nasm -f elf64 编译,ld -nostdlib 静态链接
  • 关闭 ASLR:ld -z noexecstack -pie
  • 启用 NX bit:kernel 默认

socket 调优

参数 推荐值 说明
listen backlog 0(kernel 最小值)或 128 过大会占内存
SO_REUSEADDR 1 restart 时不用等 TIME_WAIT
SO_KEEPALIVE 1 检测死连接
TCP_NODELAY 1 禁用 Nagle 算法

并发与调度

  • 进程数 = CPU cores(用 sysconf(_SC_NPROCESSORS_ONLN) 或手动 sysconf
  • 如果用 epoll,EPOLLET 边缘触发 + 非阻塞 fd 避免死循环
  • epoll 实例数:每核一个,避免跨核事件通知开销

安全边界

  • prctl(PR_SET_NO_NEW_PRIVS, 1) 禁止 unprivileged 操作
  • seccomp filter:SCMP_FN_SYS(socket), SCMP_FN_SYS(read), SCMP_FN_SYS(write), SCMP_FN_SYS(close) 白名单

结语

纯 syscall HTTP 服务器不是银弹。它的存在价值在于:在极端约束下(裸金属性能、安全最小化、极低内存占用)提供了理解 OS 层面的窗口。对于 99% 的生产场景,nginx/Caddy/Rust runtime 已经足够好 —— 但当你需要解释「为什么 epoll 边缘触发要配合非阻塞 fd」,或者「为什么 fork 适合短连接而不适合长连接」,这类最小化实现就是最好的教科书。

资料来源:Nam's Journal 上关于构建 Assembly HTTP 服务器的详细教程1;jcalvinowens/asmhttpd 项目源码及设计说明2

Footnotes

  1. https://namberino.github.io/posts/2025/01/lets-make-an-http-server-with-assembly/

  2. https://github.com/jcalvinowens/asmhttpd

systems

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com