关键词搜索

源码搜索 ×
×

Linux 操作系统原理 — 网卡驱动程序初始化流程

发布2023-04-10浏览611次

详情内容

目录

源码版本

  • Linux Kernel v3.10-rc7 版本。
  • Intel I350 IGB 网卡驱动程序。

1、内核启动流程

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

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

  3. BIOS/UEFIBIOS 或 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 的 main 函数,是 Kernel 真正的初始化流程入口,start_kerenl() 将会调用一系列的初始化函数,包括:CPU 初始化,Main Memory 初始化,Interrupt 初始化,Process Scheduling 初始化,TCP/IP Stack 初始化等,目的是最终建立起基本完整的 Linux Kernel ENV。

asmlinkage __visible void __init start_kernel(void)
{
    ...
    rest_init();  // 最后一个函数。
}

    rest_init() 的最后会进入 0 号进程,并开始永循环。

    在这里插入图片描述

    2、内核协议栈初始化流程

    start_kernel() 过程中调用 linux/net/socket.c sock_init() 进入协议栈初始化流程。

    static int __init sock_init(void)
    {
    ...
        err = net_sysctl_init();
    ...
        skb_init();
    ...
        init_inodecache();
    ...
        err = register_filesystem(&sock_fs_type);
    ...
        sock_mnt = kern_mount(&sock_fs_type);
    ...
        netfilter_init();
    ...
        skb_timestamping_init();
    ...
    }
    
      6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    1. sock_init() Socket 初始化:使用 Slab 内存分配算法创建 sk_buff 的 Cache 空间,并注册 Socket filesystem。

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

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

      1. 在 /proc/sys/net/ 目录下创建 Ethernet Device 和 TCP/IP Protocols 相关的数据结构文件;
      2. 开启 Device 的 Hardware Rx/Tx Interrupt;
      3. 为每个 CPU 初始化一个 Rx Queues 并绑定硬中断号,同时注册接收报文的软中断回调函数;
      4. 注册 loopback 本地回环操作函数;
    4. inet_proto_init INET Socket 初始化:注册 INET Socket 接口函数,例如:TCP、UDP、ICMP、IGMP 等协议类型的基本收包处理函数。

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

    在这里插入图片描述

    3、网卡驱动程序注册流程

    Driver 会调用 module_init() 向 Kernel 注册 init() 函数,然后在 dev_init() 过程中初始化 Device 和 Driver 时,Kernel 就会调用它。以 Intel I350 网卡的 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_DCA
        dca_register_notify(&dca_notifier);
    #endif
        ret = pci_register_driver(&igb_driver);
        return ret;
    }
    
    module_init(igb_init_module);
    
      6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    IGB Driver 初始化流程的核心是 pci_register_driver(),它维护了一个 pci_device_id 映射表,通过读取 Device PCI configuration space(如下图)中的 Vendor ID 和 Device ID 来并识别出 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 },
    ...
    
      6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    pci_register_driver() 还会将 IGB Driver 的各种回调函数注册到一个 pci_driver 结构体实例中,例如:IGB Driver 具体实现的 igb_driver_name() 和 igb_probe() 等函数。如此的,IGB Driver 就被注册到了 Kernel 的设备接口层(Device agnostic interface)中。

    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
    };
    
      6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    后面,当计算机插入一张 Intel I350 PCIe 网卡后,Kernel 就可以通过 PCIe Device 的 Vendor ID 和 Device ID 来匹配到对应的 Probe(探针)函数 igb_probe(),然后进入 IGB Driver 的初始化流程。

    4、网卡驱动程序初始化流程

    IGB Driver 的初始化流程从 linux/drivers/net/ethernet/intel/igb/igb_main.c igb_probe() 函数开始,如下图所示。

    /**
     *  igb_probe - Device Initialization Routine
     *  @pdev: PCI device information struct
     *  @ent: entry in igb_pci_tbl
     *
     *  Returns 0 on success, negative on failure
     *
     *  igb_probe initializes an adapter identified by a pci_dev structure.
     *  The OS initialization, configuring of the adapter private structure,
     *  and a hardware reset occur.
     **/
    static int igb_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
    
      6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. dma_set_mask() 申请 DMA 内存空间和 I/O 端口。

    2. pci_request_selected_regions() 获取 PCIe 设备的 Resource,包括:Memory BAR、I/O BAR 和 MSI-X BAR 这 3 个 Regions,并通过这些 BARs 完成一系列访问和初始化,例如更新 Linux 文件系统 /sys/bus/pci/devices/{BDF}/。

    3. alloc_etherdev_mq() => alloc_netdev_mq():

      1. 实例化 Kernel 的 net_device(网络设备管理,包含了 Device 的详细信息)结构体和 IGB 私有的 igb_adapter(包含了 IGB Driver 的特性信息)结构体。
      2. 实例化 Kernel 的 netdev_queue(网络设备队列管理)结构体,并关联到 net_device。
      3. 初始化 net_device 实例。
    4. 设置 net_device->netdev_ops 设备操作函数集(包含了 Device 的各种操作回调函数,例如:igb_open 设备启动函数、收/发包函数等)和 net_device->ethtool_ops ethtool 操作函数集。

    5. igb_sw_init() => igb_init_interrupt_scheme():

      1. igb_set_interrupt_capability():设置网卡的发送队列数量、接收队列数量、中断描述符数量,调用 pci_enable_msix() 获得网络的 MSIX 中断号。并将其保存到 pci_dev->msi_list 的每一项 msi_desc.irq 中。
      2. igb_alloc_q_vectors():根据之前设置的中断描述符个数,初始化中断描述符 igb_q_vector,并加入到 igb_adapter->q_vector[] 列表中。同时初始化 igb_q_vector 中的 napi 结构体(注册 NAPI 收包机制所必须的 poll() 函数),然后将 napi 实例挂载到 net_device->napi_list 链表中。
      3. igb_alloc_queues():根据之前设置的发送队列个数,实例化 igb_ring 结构体,然后添加到 igb_adapter->tx_ring[] 列表中。同样的,为接收队列实例化 igb_ring 结构体,并添加到 igb_adapter->rx_ring[] 列表。
      4. igb_map_ring_to_vector():将 Rx Ring、Tx Ring 实例和 igb_q_vector 关联起来,即:igb_q_vector->tx_ring 和 igb_q_vector->rx_ring。
    6. igb_init_hw_timer() 设置网卡硬件定时器。

    7. igb_probe_vfs() 设置 SR-IOV 特性,如果没有开启则将 igb_adapter->vfs_allocated_count 设置为 0。

    8. igb_irq_disable() 关闭网卡设备的中断。

    9. 设置网卡特性标志 net_device->features 和 net_device->vlan_features。

    10. 获取 Ethernet Ports 的 MAC 地址并保存到 net_device->dev_addr 中。

    11. register_netdev() 将新建的 net_device 实例注册到 Kernel 中。

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

    net_device 结构体

    struct igb_adapter {
        /* 网络设备的标准属性和状态 */
        struct net_device       *netdev;  // net_device 结构体
    
        /* 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;
    };
    
      6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    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,    /* 获取网络设备统计信息 */
        ...
    };
    
      6
    • 7
    • 8
    • 9
    • 10

    初始化数据结构全景图

    在这里插入图片描述

    5、创建网络接口

    在 igb_probe() 的后期调用了 linux/include/linux/netdevice.h register_netdev() 将 net_device 实例注册到 Kernel 中,并会创建对应的 Network Interface。然后我们在 Shell 中就可以看见对应的网卡设备了。

    extern int register_netdev(struct net_device *dev);
    
    • 1

    register_netdev() 读取 net_device 实例提供的信息,并根据 IGB Driver 设定的 Network interface name prefix(前缀),为其生成一个唯一的 Interface name。例如:ethX(Ethernet)。

    ifconfig 指令

    当我们执行指令 ifconfig eth0 时,就可以查看到 net_device 实例提供的 Name、MAC、Mask、MTU 等信息。这些信息。这实际上是 ifconfig 指令通过调用 Socket I/O SCI 来实现的。

    $ ifconfig eth0
    
    eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
            inet 192.168.1.3  netmask 255.255.255.0  broadcast 192.168.1.255
            inet6 fe80::a6d6:97f7:7a9a:4c71  prefixlen 64  scopeid 0x20<link>
            ether 52:54:00:08:ea:e0  txqueuelen 1000  (Ethernet)
            RX packets 1072779  bytes 591984204 (564.5 MiB)
            RX errors 0  dropped 0  overruns 0  frame 0
            TX packets 308802  bytes 80454446 (76.7 MiB)
            TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
    
      6
    • 7
    • 8
    • 9
    • 10
    • RX errors:NIC 总的收包错误数量,包括 too-long-frames 错误,Rx Ring 溢出错误,CRC 校验错误,Frame 同步错误,FIFO Overruns 错误、Missed pkg 错误等。

    • RX dropped:NIC 总的丢包数量,通常是由于 skb_buffer 内存空间不足导致的,表示 CPU 处理能力低于 NIC 带宽。

    • RX overruns:NIC 总的 FIFO Overruns 错误数量,通常是由于 CPU 无法及时处理 NIC 发出的硬件中断导致的,表示硬件中断可能没有均衡的分布在多个 CPU Cores 上。

    • RX frame:表示 Misaligned 的 Frames。

    ethtool 指令

    也可以使用 ethtool 命令行工具用于查看并 net_device 结构体的配置信息。ethtool 指令则是通过调用 ioctl I/O SCI 与 Net device 注册的 ethtool 函数进行交互来实现的。

    • 查看 NIC 的基础信息,包括 Supported ports(TP 电口、Fiber 光口)、Supported link modes、Speed(速率)、Duplex(双工)、Link detected 等。
    $ ethtool enp2s0
    Settings for enp2s0:
    	Supported ports: [ TP MII ]
    	Supported link modes:   10baseT/Half 10baseT/Full
    	                        100baseT/Half 100baseT/Full
    	                        1000baseT/Half 1000baseT/Full
    	Supported pause frame use: No
    	Supports auto-negotiation: Yes
    	Supported FEC modes: Not reported
    	Advertised link modes:  10baseT/Half 10baseT/Full
    	                        100baseT/Half 100baseT/Full
    	                        1000baseT/Half 1000baseT/Full
    	Advertised pause frame use: No
    	Advertised auto-negotiation: Yes
    	Advertised FEC modes: Not reported
    	Link partner advertised link modes:  10baseT/Half 10baseT/Full
    	                                     100baseT/Half 100baseT/Full
    	Link partner advertised pause frame use: Symmetric Receive-only
    	Link partner advertised auto-negotiation: Yes
    	Link partner advertised FEC modes: Not reported
    	Speed: 100Mb/s
    	Duplex: Full
    	Port: MII
    	PHYAD: 0
    	Transceiver: internal
    	Auto-negotiation: on
    	Supports Wake-on: pumbg
    	Wake-on: d
    	Current message level: 0x00000033 (51)
    			       drv probe ifdown ifup
    	Link detected: yes
    
      6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 查看 NIC 的数据统计。
    $ ethtool -S enp2s0
    
    NIC statistics:
         tx_packets: 3255
         rx_packets: 39837
         tx_errors: 0
         rx_errors: 0
         rx_missed: 0
         align_errors: 0
         tx_single_collisions: 0
         tx_multi_collisions: 0
         unicast: 3094
         broadcast: 7873
         multicast: 36743
         tx_aborted: 0
         tx_underrun: 0
    
      6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 监控 NIC 的 errors、dropped、overruns、frame 等错误信息。
    $ for i in `seq 1 100`; do ifconfig enp2s0 | grep RX | grep overruns; sleep 1; done
    
    • 1

    相关技术文章

    点击QQ咨询
    开通会员
    返回顶部
    ×
    微信扫码支付
    微信扫码支付
    确定支付下载
    请使用微信描二维码支付
    ×

    提示信息

    ×

    选择支付方式

    • 微信支付
    • 支付宝付款
    确定支付下载