C 语言网络编程 — 内核协议栈初始化流程
创始人
2025-05-28 21:26:40
0

目录

文章目录

  • 目录
  • 1、内核启动流程
  • 2、协议栈初始化流程
  • 3、网络设备驱动程序初始化流程
  • 4、网络设备初始化流程
  • 5、启动网络设备流程

1、内核启动流程

  1. 起电:开启主机硬件电源。

  2. 固件:主板固件加载 BIOS 或 UEFI,进行硬件自检和初始化,检查系统配置是否正确。

  3. BIOS/UEFI:BIOS 或 UEFI 开始寻找可启动介质,读取磁盘的 MBR 或 GBT 引导分区,启动 Bootloader。

  4. Bootloader:Bootloader 执行 GRUB2 引导程序,GRUB2 通过 /boot/grub2/grub.cfg 配置文件的内容,从 /boot 目录中读取 /boot/vmlinuz-3.10.0-1160.83.1.el7.x86_64 内核文件,并加载到内存中。管理员可以通过 /boot/grub2/grub.cfg 配置文件,设置系统启动选项。

  5. Initramfs:Kernel 启动过程中,首先加载 /boot/initramfs-3.10.0-1160.83.1.el7.x86_64.img 镜像文件,这是 initramfs(initial RAM filesystem),作为临时文件系统用于进行基本的系统初始化工作。包括加载 /usr/lib/modules/3.10.0-1160.83.1.el7.x86_64/kernel/fs/xfs 驱动程序。有了 xfs 驱动程序之后,Kernel 才可以挂载 xfs 格式的 / 根分区并访问文件。

  6. Init 系统:Kernel 挂载根分区后,开始运行 init 或 systemd 进程,这是第一个 User Process。init 进程会读取 /etc/inittab 配置文件,根据不同的运行级别,开始启动相应的各种程序和服务。包括:各种设备驱动程序、进程管理、内存管理等系统服务。

Init 系统的入口在 linux/init/main.c start_kernel(),是 Kernel 真正的初始化流程入口,start_kerenl() 将会调用一系列的初始化函数,包括:CPU 平台初始化,Memory 初始化,Interrupt 初始化,Process Scheduling 初始化,TCP/IP Stack 初始化等,目的是最终建立起基本完整的 Linux Kernel ENV。

在这里插入图片描述

2、协议栈初始化流程

start_kernel() 调用 sock_init() 进入协议栈初始化流程。

  1. sock_init() Socket 初始化:创建 sk_buff SLAB cache,注册 Socket filesystem。

  2. proto_init() 协议栈初始化:在 /proc/net/ 目录下创建各类协议文件,注册相关的协议文件操作函数。

  3. dev_init() 网络设备初始化

    1. 在 /proc/sys/net/ 目录下创建 Network device 和 Protocols 相关的数据结构文件;
    2. 开启 Netdev 的 Hardware Rx/Tx Interrupt;
    3. 为每个 CPU 初始化一个 Rx queue,注册报文接收的回调函数;
    4. 注册 loopback 本地回环操作函数;
  4. inet_proto_init INET Socket 初始化:注册 INET 协议族的 Socket 接口函数,例如:TCP、UDP、ICMP、IGMP 等协议类型的基本收包函数。

  5. unix_proto_init UNIX Socket 初始化:注册 UNIX 的 Socket 接口函数。

在这里插入图片描述

3、网络设备驱动程序初始化流程

Net device 的 Driver(驱动程序)会调用 module_init() 向 Kernel 注册 init() 函数,dev_init() 过程中加载 Driver 时,Kernel 就会调用它。

以 Intel I350 PCIe 网卡的 IGB Driver(Intel Gigabit Ethernet)为例,它的初始化函数为 linux/drivers/net/ethernet/intel/igb/igb_main.c igb_init_module()。

/***  igb_init_module - Driver Registration Routine**  igb_init_module is the first routine called when the driver is*  loaded. All it does is register with the PCI subsystem.**/
static int __init igb_init_module(void)
{int ret;pr_info("%s\n", igb_driver_string);pr_info("%s\n", igb_copyright);#ifdef CONFIG_IGB_DCAdca_register_notify(&dca_notifier);
#endifret = pci_register_driver(&igb_driver);return ret;
}module_init(igb_init_module);

IGB Driver 初始化流程的核心是 pci_register_driver(),它维护了一个 pci_device_id 映射表,通过读取 Net device PCI configuration space 中的 Vendor ID 和 Device ID 来并识别出 Net device 具体的型号以及对应的驱动程序。例如 Intel I350 的主板型号为 board_82575。

struct pci_device_id {__u32 vendor, device;		/* Vendor and device ID or PCI_ANY_ID*/__u32 subvendor, subdevice;	/* Subsystem ID's or PCI_ANY_ID */__u32 class, class_mask;	/* (class,subclass,prog-if) triplet */kernel_ulong_t driver_data;	/* Data private to the driver */__u32 override_only;
};static const struct pci_device_id igb_pci_tbl[] = {
...{ PCI_VDEVICE(INTEL, E1000_DEV_ID_I350_COPPER), board_82575 },
...{ PCI_VDEVICE(INTEL, E1000_DEV_ID_82580_COPPER), board_82575 },
...

pci_register_driver() 继续将 IGB Driver 的各种回调函数注册到一个 pci_driver 结构体。

static struct pci_driver igb_driver = {.name     = igb_driver_name,.id_table = igb_pci_tbl,.probe    = igb_probe,.remove   = igb_remove,
#ifdef CONFIG_PM.driver.pm = &igb_pm_ops,
#endif.shutdown = igb_shutdown,.sriov_configure = igb_pci_sriov_configure,.err_handler = &igb_err_handler
};

pci_register_driver() 执行完毕后,Kernel 就完成了 IGB Driver igb_driver_name() 和 igb_probe() 的注册。

当计算机插入一张 Intel I350 PCIe 网卡后 Kernel 就可以通过 PCI ID(Vendor ID 和 Device ID)来匹配到对应的 Probe(探针)函数,然后获取 PCI 的 BAR,继续对其进行操作和访问。

4、网络设备初始化流程

在这里插入图片描述

Net device 的初始化流程从 Probe 函数开始,典型的流程如下:

  1. PCI 设备上电。
  2. Kernel 识别 PCI ID 并调用对应 Driver 注册的 Probe 函数。
  3. 获取 MAC 地址。
  4. 申请 DMA 内存空间和 I/O 端口。
  5. 注册 Driver 提供的 ethtool、watchdog 等函数。
  6. 初始化 net_device 结构体,包含了 Net device 的详细信息,例如:BAR 等。
  7. 初始化 net_device_ops 结构体,包含了 Net device 的各种操作回调函数,例如:设备启动函数、收/发包函数等。
  8. 注册 NAPI(中断 + 轮询)收包机制所必须的 poll() 函数到 ksoftirqd 内核线程。

如此的,Kernel 就掌握了 Net device 的详细信息以及各类操作函数入口,并以此完成对 Net device 控制。

在这里插入图片描述

还是以 IGB Driver 的 igb_probe() 为例。

  • 注册 ethtool 函数:ethtool 命令行工具用于查看和修改 Net device 的配置信息。ethtool 指令通过 ioctl I/O SCI 与和 Net device 注册的 ethtool 函数交互。
$ ethtool -i eth0driver: virtio_net
version: 1.0.0
firmware-version:
expansion-rom-version:
bus-info: 0000:00:03.0
supports-statistics: no
supports-test: no
supports-eeprom-access: no
supports-register-dump: no
supports-priv-flags: no
  • 初始化 net_device 结构体
struct igb_adapter {/* 网络设备的标准属性和状态 */struct net_device       *netdev;/* PCI 设备相关信息 */struct pci_dev          *pdev;   // PCI 设备详细信息。const struct pci_device_id *id;  // PCI 设备 ID。/* 记录软硬件状态信息 */struct igb_hw           hw;struct igb_ring         rx_ring;                     // 接收队列。struct igb_ring         tx_ring[IGB_MAX_TX_QUEUES];  // 发送队列。struct igb_ring         test_ring;                   // 测试队列。u16                     vfs_allocated_count;         // 已分配的虚拟网卡数量。/* 记录网络设备的 MAC 地址 */u8                      perm_addr[ETH_ALEN];  // MAC 地址u8                      mc_addr[IGB_MAX_MULTICAST_ADDRS][ETH_ALEN];  // 多播地址。u32                     num_mc_addrs;                                // 多播地址数量。u32                     flags;u32                     wol;unsigned long           state;  // 设备状态。/* 记录统计信息 */struct igb_stats        stats;             // 数据包统计信息。u64                     tx_timeout_count;  // 发送超时次数。u32                     watchdog_timer;    // watchdog 计时器。u32                     link_speed;        // 链路速度。u8                      link_duplex;       // 链路双工模式。bool                    link_up;           // 链路状态。/* 记录中断相关信息 */int                     num_vectors;cpumask_var_t           active_vlans;struct igb_q_vector    *q_vector[0];
};struct net_device {/* 网络设备名称及类型 */char                    name[IFNAMSIZ];const struct net_device_ops *netdev_ops;  // net_device_ops 结构体struct device           dev;/* 网络设备状态和属性 */struct net_device_stats stats;unsigned                flags;unsigned short          gflags;/* 网络设备的地址和路由信息 */struct net_device *     master;struct net_device *     real_dev;struct netdev_hw_addr_list   hw_addr_list;unsigned int            ifindex;struct hlist_node       name_hlist;/* 网络设备队列相关信息 */struct list_head        napi_list;struct netdev_queue     *tx_queue;struct Qdisc            *qdisc;struct netdev_rx_queue  *rx_cpu_rmap;struct rps_map          *rps_cpu_map;struct xps_map          *xps_cpu_map;/* 网络设备的回调函数 */struct netdev_features  *features;struct net_device_ops   *ethtool_ops;const struct ethtool_link_ksettings *link_ksettings;struct phy_device       *phydev;struct netdev_adjacent  *adj_list;
};
  • 初始化 net_device_ops 结构体
static const struct net_device_ops igb_netdev_ops = {/* 初始化网络设备 */.ndo_init           = igb_init_netdev,/* 停止网络设备 */.ndo_uninit         = igb_uninit_netdev,/* 发送网络数据包 */.ndo_start_xmit     = igb_xmit_frame,/* 收到网络数据包 */.ndo_rx_handler     = igb_rx_handler,/* 收到网络数据包后的处理函数 */.ndo_rx_callback    = igb_rx_cleanup,/* 取消发送数据包 */.ndo_tx_timeout     = igb_tx_timeout,/* 获取网络设备统计信息 */.ndo_get_stats64    = igb_get_stats64,...
};
  • 注册 NAPI 规定的 poll 函数到 ksoftirqd 内核线程:NAPI 是一种 “中断 + 轮训” 收包机制,比单一的 “硬中断 + 软中断“ 的方式效率更高。
    1. 数据包到达网卡。
    2. 网卡通过 DMA 将数据包写入内存。
    3. 网卡出发硬中断,进入硬中断处理函数。
    4. 进入软中断,唤醒 NAPI 子系统,新建 ksoftirqd 内核线程。
    5. ksoftirqd 内核线程调用 Net device 注册的 poll 函数完成收包。
    6. 同时,Driver 禁用 Net device 硬中断,开始切换到 NAPI “轮训“ 收包工作模式。
    7. 直到一段时间内没有数据到达为止,关闭 ksoftirqd 内核线程,重新切换到 NAPI “中断” 收包工作模式。

可见,和传统方式相比,NAPI 一次中断就可以接收多个包,因此可以减少硬件中断的数量。

5、启动网络设备流程

  1. 通过 ifconfig eth0 up 指令启动指定的 Net device;
  2. Kernel 调用 net_device_ops 中注册的 open() 函数;
  3. 在 DMA Memory 中为 Net device 分配 Rx/Tx queue(Ring Buffer)。
  4. 注册 poll 函数到 NAPI。
  5. 开启硬中断,等待数据包到达。

在这里插入图片描述

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...