起电:开启主机硬件电源。
固件:主板固件加载 BIOS 或 UEFI,进行硬件自检和初始化,检查系统配置是否正确。
BIOS/UEFI:BIOS 或 UEFI 开始寻找可启动介质,读取磁盘的 MBR 或 GBT 引导分区,启动 Bootloader。
Bootloader:Bootloader 执行 GRUB2 引导程序,GRUB2 通过 /boot/grub2/grub.cfg 配置文件的内容,从 /boot 目录中读取 /boot/vmlinuz-3.10.0-1160.83.1.el7.x86_64 内核文件,并加载到内存中。管理员可以通过 /boot/grub2/grub.cfg 配置文件,设置系统启动选项。
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 格式的 / 根分区并访问文件。
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。
start_kernel() 调用 sock_init() 进入协议栈初始化流程。
sock_init() Socket 初始化:创建 sk_buff SLAB cache,注册 Socket filesystem。
proto_init() 协议栈初始化:在 /proc/net/ 目录下创建各类协议文件,注册相关的协议文件操作函数。
dev_init() 网络设备初始化:
inet_proto_init INET Socket 初始化:注册 INET 协议族的 Socket 接口函数,例如:TCP、UDP、ICMP、IGMP 等协议类型的基本收包函数。
unix_proto_init UNIX Socket 初始化:注册 UNIX 的 Socket 接口函数。
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,继续对其进行操作和访问。
Net device 的初始化流程从 Probe 函数开始,典型的流程如下:
如此的,Kernel 就掌握了 Net device 的详细信息以及各类操作函数入口,并以此完成对 Net device 控制。
还是以 IGB Driver 的 igb_probe() 为例。
$ 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
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;
};
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 一次中断就可以接收多个包,因此可以减少硬件中断的数量。