Linux 网络协议栈梳理

前言 本文梳理常见报文格式、联网流程及 Linux 网络协议栈的收发路径。 网络分层与报文格式 OSI 七层模型用于理解分层;TCP/IP 模型通常将 L5~L7 合为应用层。学习 Linux 网络时,重点掌握 L2、L3、L4、L7,以及以太网、IP、TCP/UDP 的报文格式。 层级 名称 典型协议或功能 L7 应用层 HTTP、DNS、FTP、SMTP、SSH L6 表示层 编码、加密、压缩 L5 会话层 会话建立、管理与终止 L4 传输层 TCP、UDP L3 网络层 IPv4、IPv6、ICMP、IPsec L2 数据链路层 Ethernet、PPP、PPPoE、802.1Q L1 物理层 电气、光学信号及其传输介质 L2 数据链路层 Ethernet II 以太网帧 普通、未带 VLAN 标签的帧格式如下: 目的 MAC 源 MAC EtherType 数据及填充 FCS(帧校验序列) 6 字节 6 字节 2 字节 46~1500 字节 4 字节 帧长与 MTU:帧长为 64~1518 字节(含 FCS);IP MTU 通常为 1500 字节,不含以太网首部、填充和 FCS。数据不足 46 字节时需填充,填充不属于 IP 报文。RFC 894 前导码与帧间隙:帧前为 7 字节前导码和 1 字节帧开始定界符(SFD)。帧间隙是空闲时间,通常按 96 bit times(12 字节的发送时间)计算,不属于帧内容。IEEE 802.3 工作组说明 EtherType 与 VLAN:0x0800 为 IPv4,0x0806 为 ARP,0x86DD 为 IPv6。802.1Q 在源 MAC 后插入 4 字节标签(TPID + TCI),常用 TPID 为 0x8100,标签后仍有实际 EtherType。IANA 注册表 ARP 用于解析 IPv4 地址对应的 MAC 地址,在以太网中直接封装,不经过 IP。 ...

October 16, 2026 · 5 min · zhumouren

x86_64 Linux A/B 系统设计:从 GRUB 选槽到 OverlayFS 根文件系统

1. 设计目标与启动全貌 设备在线升级时,写入镜像只是其中一步。更新可能在断电时中止,新内核可能无法挂载根目录,业务也可能在系统启动后才暴露问题。A/B 设计的目标,是在候选版本尚未被确认时保留一套可启动的旧系统,并为失败后的重启指定明确的去向。 本文面向熟悉 Linux 分区、挂载与 systemd 的读者,讨论 Legacy BIOS + GPT 的 x86_64 设备:GRUB 2 选择启动槽位,自定义 BusyBox initramfs 组装根文件系统,systemd 启动主系统并组织健康检查。RootFS 使用只读 SquashFS,运行时写入由持久化的 OverlayFS 可写层(upper)承接。 这是一篇架构设计与关键机制说明:给出分区约定、状态转换、挂载顺序和验收条件。GRUB 选槽使用伪代码,/init 展示核心片段;完整部署还需实现错误分支、升级程序、超时监督和恢复环境。文中没有提供目标硬件实测记录,因此不据此宣称已经达到生产级可靠性。 设计遵循三个约定: 成套更新:一个槽位包含匹配的 kernel、initramfs 和 RootFS,始终一起选择、一起升级。 保留回退对象:当前运行槽与已确认的稳定槽一致时,才允许更新另一槽;候选版本经过健康检查后才成为默认版本。 分离系统与数据:系统基线保存在 SquashFS;系统写入进入槽位独立的可写层;需要跨版本保留的业务数据单独存放。 选择 SquashFS,是为了把系统基线做成可整体校验、替换的只读镜像;选择 OverlayFS,是为了兼容需要可写根目录的软件。代价是 upper 会保存偏离基线的内容,必须与镜像版本一起管理。普通重启不会恢复出厂状态;本文也不覆盖纯 UEFI 启动、磁盘冗余或完整的可信启动链。 设备上电 → BIOS 自检、初始化启动所需硬件,选择磁盘 → 执行磁盘 LBA 0(保护性 MBR)中的 GRUB 引导代码 → 加载 p1 内嵌的 GRUB core.img,访问 p2 的模块与 grub.cfg → grub.cfg 读取启动状态,选择 A/B,将该槽 kernel 和 initramfs 装入内存 → GRUB 按 Linux 启动协议传参并跳转到内核入口 → kernel 解压、初始化内存和驱动,将 initramfs 解包到初始根文件系统 → 执行 initramfs 中的 /init(PID 1,本文为 BusyBox shell 脚本) → /init 挂载 SquashFS 和 DATA,再挂载 OverlayFS 到 /newroot → exec switch_root /newroot /sbin/init → systemd 接管 PID 1,按依赖启动服务,开放登录或业务接口 → 独立健康检查通过后,才确认试启动版本 initramfs 是文件系统归档,不是独立执行的程序;OverlayFS 是内核文件系统,由早期用户态请求挂载。 /init 与 systemd 属于同一个内核下的前后两个用户态阶段,切根不会重新启动内核。若改用 systemd 构建 initramfs,它会更早成为 PID 1,并通过 systemctl switch-root 交接,不能原样套用本文的 BusyBox 脚本。两种方式见 systemd initrd 接口。 ...

September 14, 2026 · 9 min · zhumouren

将Ubuntu20.04变为一个路由器

前言 本文记录的是作者手头上的设备搭建情况,不同的芯片架构、系统版本搭建情况可能有所不同。以下是设备情况介绍 芯片架构: aarch64 Linux内核版本: 4.19.232 发行版: Ubuntu20.04 设备路由设置 修改内核参数 vi /etc/sysctl.conf net.ipv4.ip_forward = 1 net.ipv6.conf.all.forwarding = 1 net.ipv6.conf.all.accept_ra = 2 修改物理网口名称 在ubuntu中用ifconfig查看网口信息有可能有enP1p17s0这种网口,想要修改为规律的ethx可以按下面的方式做 udevadm info -a -p /sys/class/net/eth0 # 可以通过这个命令查看网口属性以编写下面的配置 vi /etc/udev/rules.d/70-persistent-net.rules SUBSYSTEM=="net", ACTION=="add", SUBSYSTEMS=="platform", KERNELS=="fe2a0000.ethernet", NAME="eth0" SUBSYSTEM=="net", ACTION=="add", SUBSYSTEMS=="platform", KERNELS=="fe010000.ethernet", NAME="eth1" SUBSYSTEM=="net", ACTION=="add", SUBSYSTEMS=="pci", KERNELS=="0001:11:00.0", NAME="eth2" SUBSYSTEM=="net", ACTION=="add", SUBSYSTEMS=="pci", KERNELS=="0002:21:00.0", NAME="eth3" SUBSYSTEM=="net", ACTION=="add", SUBSYSTEMS=="pci", KERNELS=="0000:01:00.0", NAME="eth4" 这里可以关注SUBSYSTEMS有platform、pci两种值但实际还有更多,看具体以太网口的属性。KERNELS代表设备硬件位置,可以通过udevadm命令来查看。 设置lan口、wan口 以下设置为eth0为wan0口,其他为lan口。 vi /etc/network/interfaces.d/wan auto wan0 iface wan0 inet dhcp bridge_ports eth0 metric 30 vi /etc/network/interfaces.d/lan auto lan iface lan inet static bridge_ports eth1 eth2 eth3 eth4 address 192.168.1.1 netmask 255.255.255.0 监听wan口拔插 在wan口插入网线时需要自动启动dhcp clinet获取ip地址并设置route,在wan口拔出网线时需要清空wan口的ip并更新route。默认情况下ubuntu并不会做这些事情 ...

June 6, 2025 · 1 min · zhumouren

UNIX环境高级编程-进程操作

1. 进程环境 #include <stdlib.h> void exit(int status); exit(0) 与 return 0 是等价的。 2. 进程标识和创建子进程 每个进程都有一个非负整型表示的唯一进程ID,但进程ID是可以被复用的。当一个进程终止后进程ID会进入候选池,并且一般新的进程不会使用最近终止进程的进程ID。 #include <unistd.h> pid_t getpid(void); // 返回值: 调用进程的进程ID pid_t getppid(void); // 返回值: 调用进程的父进程ID fork() 函数可以创建一个新进程,子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获得父进程数据空间、堆和栈的副本。并且在修改时使用了写时复制(Copy-On-Write,COW)技术。 #include <unistd.h> pid_t fork(void); // 返回值: 子进程返回0, 父进程返回子进程ID,若出错返回-1 以下是一个示例代码,用于创建一个子进程并展示父进程和子进程的ID与父进程ID,以及如何通过信号避免僵尸进程。 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> #include <sys/types.h> void sigchld_handler(int sig) { int status; pid_t id = waitpid(-1, NULL, WNOHANG); if (WIFEXITED(status)) { printf("remove proc id: %d \n", id); printf("Child send: %d \n", WEXITSTATUS(status)); } } int main(int argc, char *argv[]) { struct sigaction sa; sa.sa_handler = sigchld_handler; sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; // 初始化信号屏蔽集,确保不阻塞任何额外信号 sigemptyset(&sa.sa_mask); // 注册 SIGCHLD 信号处理 sigaction(SIGCHLD, &sa, NULL); pid_t pid; int val = 24; if ((pid = fork()) < 0) { fprintf(stderr, "fork err: %d\n", pid); exit(1); } else if (pid == 0) { val++; printf("child: pid = %d, ppid = %d\n", getpid(), getppid()); printf("child: val = %d\n", val); _exit(0); } else { sleep(5); printf("root: pid = %d, ppid = %d, child_pid = %d\n", getpid(), getppid(), pid); printf("root: val = %d\n", val); sleep(5); } exit(0); } 以下是执行程序后的结果,一个有意思点是程序的父进程ID是bash,子进程修改的数据并不会影响到父进程的数据。 ...

November 27, 2024 · 3 min · zhumouren

OpenWrt开发入门实战(一)Docker环境搭建

1. 环境准备 一台带有Windows10(64位)及以上专业版的电脑(其他操作系统也行,只要能用Docker就好了 CPU为X86_64 Docker(用作OpenWrt的编译环境和测试环境) 2. 使用Docker搭建OpenWrt编译环境 本文使用docker-compse构建,构建脚本目录环境为 docker-linux-env/ | docker-compose.yml | ----ubuntu-compile-openwrt/ | | Dockerfile | | sources.list | - 以下为各文件的具体内容 docker-compose.yml version: '3' services: ubuntu-compile-openwrt: build: ./ubuntu-compile-openwrt environment: TZ: Asia/Shanghai volumes: - compile-openwrt:/root # compile-openwrt 是数据卷 - compile-openwrt-home:/home ports: - "2211:22" volumes: compile-openwrt: compile-openwrt-home: Dockerfile # 以最新的Ubuntu镜像为模板 FROM ubuntu:24.04 # 将本目录下的sources.list作为容器的一个文件 ADD sources.list /root/sources.list # 使用国内Ubuntu源,更新快 RUN mv /etc/apt/sources.list /etc/apt/sources.list_bak RUN cp /root/sources.list /etc/apt/sources.list RUN apt update # 安装常用工具 RUN apt install -y vim git nano # 安装编译OpenWrt官方实例相关工具 RUN apt install -y build-essential clang flex bison g++ gawk \ gcc-multilib g++-multilib gettext git libncurses5-dev libssl-dev \ python3-setuptools rsync swig unzip zlib1g-dev file wget # 安装当前镜像对当前OpenWrt编译所需要库 RUN apt install -y libelf-dev locales # 设置LOCALE RUN locale-gen en_US.UTF-8 # 修改root密码 RUN echo 'root:pw' | chpasswd # 添加自定义用户 RUN adduser buildbot \ && echo 'buildbot:pw' | chpasswd # 安装ssh RUN apt install -y openssh-server RUN mkdir -p /var/run/sshd # 开放22端口 EXPOSE 22 #设置自启动命令 CMD ["/usr/sbin/sshd", "-D"] sources.list ...

October 10, 2024 · 2 min · zhumouren