linux网卡驱动的加载流程

简介:    在linux系统中,其网卡驱动大多通过PCI总线与系统相连,同时,内核对于所有PCI总线上的设备是通过PCI子系统来进行管理,通过PCI子系统提供各种PCI设备驱动程序共同的所有通用功能。
   在linux系统中,其网卡驱动大多通过PCI总线与系统相连,同时,内核对于所有PCI总线上的设备是通过PCI子系统来进行管理,通过PCI子系统提供各种PCI设备驱动程序共同的所有通用功能。因此,作为linux系统中的PCI网卡设备,其加载的流程必然分为两个部分:作为PCI设备向PCI子系统注册;作为网卡设备向网络子系统注册。
   下面也将从两个方面,分析一下网卡驱动在内核加载的两个流程。

PCI设备驱动程序的注册:
   PCI设备驱动程序使用pci_register_driver函数完成内核的注册,其定义在include/linux/pci.h文件内
  1. /*
  2.  * pci_register_driver must be a macro so that KBUILD_MODNAME can be expanded
  3.  */
  4. #define pci_register_driver(driver)        \
  5.     __pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)
    其主要结构是名为driver的pci_driver结构体,
  1. struct pci_driver {
  2.     struct list_head node;
  3.     const char *name;
  4.     /* 驱动所关联的PCI设备 */
  5.     const struct pci_device_id *id_table; /* must be non-NULL for probe to be called */
  6.     int (*probe) (struct pci_dev *dev, const struct pci_device_id *id); /* New device inserted */
  7.     void (*remove) (struct pci_dev *dev); /* Device removed (NULL if not a hot-plug capable driver) */
  8.     int (*suspend) (struct pci_dev *dev, pm_message_t state); /* Device suspended */
  9.     int (*suspend_late) (struct pci_dev *dev, pm_message_t state);
  10.     int (*resume_early) (struct pci_dev *dev);
  11.     int (*resume) (struct pci_dev *dev); /* Device woken up */
  12.     void (*shutdown) (struct pci_dev *dev);
  13.     struct pci_error_handlers *err_handler;
  14.     struct device_driver driver;
  15.     struct pci_dynids dynids;
  16. };
   在pri_driver结构体中有两个比较关键的函数,分别是struct pci_device_id *id_table、以及probe函数。其中id_table是一个id向量表,内核通过其来把一些相关的设备关联到此驱动程序,PCI设备独一无二的识别方式是通过一些参数的组合,包含开发商以及模型等,这些参数都存储在内核的pci_device_id数据结构中:
  1. struct pci_device_id {
  2.     __u32 vendor, device;        /* Vendor and device ID or PCI_ANY_ID*/
  3.     __u32 subvendor, subdevice;    /* Subsystem ID's or PCI_ANY_ID */
  4.     __u32 class, class_mask;    /* (class,subclass,prog-if) triplet */
  5.     kernel_ulong_t driver_data;    /* Data private to the driver */
  6. };
   对于每一个PCI设备驱动程序,在注册的时候都会把一个pci_device_id实例注册到内核中,这个实例向量就包含了此驱动程序所能处理的所有设备ID。
   另外一个probe函数,主要用于当PCI子系统发现它正在搜寻驱动程序的设备ID与前面所提到的id_table匹配,就会调用此函数。对于网络设备而言,此函数会开启硬件、分配net_device结构、初始化并注册新设备。
   总的来说,对于所有的PCI设备,在系统引导时,会建立一种数据库,把每个总线都关联一份已侦测并且使用该总线的设备列表。对于PCI设备来说,系统中就存在着这样一个数据库,其中保存着所有使用PCI总线的设备ID,此ID即上文提到的pci_device_id。以下图为例,来说明当设备驱动程序加载时会发生什么。
22227409_1329659246MNQq.jpg
   此时,(a)图就代表着所有使用PCI总线的设备数据库。当设备驱动程序A被加载时,会调用pci_register_driver并提供pci_driver实例与PCI层注册,同时pci_driver结构中包含一个驱动程序所能处理的设备ID表;接着,PCI子系统使用该表去查在已经保存的设备数据库中是否存在匹配,于是会建立该驱动程序的设备列表,如图(b)所示;此外,对每个匹配的设备而言,PCI层会调用相匹配的驱动程序中的pci_driver结构中所提供的probe函数,由probe函数完成必须的操作,如对于网络设备来说,probe函数就会建立并注册相关联的网络设备。

网络设备的注册:
   作为一个PCI网络设备,再其完成PCI总线的注册后,剩下的工作就是通过probe函数来完成作为网络设备的注册流程。对于每一个网络设备在内核中都通过一个net_device结构来表示(此结构涉及内容较多,就不再细说),因此网络设备的注册流程从字面上也应该分为两个部分,net_device结构的分配、完成分配到的dev结构向网络子系统的注册。
   下面,首先说一下net_device结构的分配,在内核中通过alloc_netdev函数来完成分配
  1. #define alloc_netdev(sizeof_priv, name, setup) \
  2.     alloc_netdev_mqs(sizeof_priv, name, setup, 1, 1)
  3. struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
  4. void (*setup)(struct net_device *),
  5. unsigned int txqs, unsigned int rxqs)
   此函数的参数主要有三个:sizeof_priv,私有数据结构的大小,因为作为任一个网络设备都有一个net_device结构,但又由于设备的多样性,很难在dev结构中包含所有必需的字段,因此每一个设备一般都需要一个私有结构来保存一些自己的信息;name,设备名称,此名称可能只是一部分名称,由内核通过某种规则来完成,比如对于以太网设备,只知道是eth设备,但不知道具体的标号,此时可通过传递eth%d作为name参数,由内核完成具体的分配流程;setup,设置函数,此函数用于初始化net_device的部分字段,主要用于一些具有某种共同特性字段的初始化,比如对于以太网设备,其MTU等字段具有相同的值,因此可通过ether_setup来完成公共字段的初始化。
img_953f8f50bd15623814d5502f03cc618a.jpg

    对于Ethernet设备为例,其注册流程如上图所示,首先通过调用alloc_netdev的包裹函数,alloc_etherdev,来完成net_device函数的分配以及部分数据的初始化;接下来,通过netdev_boot_setup_check函数来检查加载内核时用户是否提供了任何引导期间参数;最终,新的设备net_device实例会利用register_netdevice插入至网络设备数据库。
   PS:对于系统中的所有net_device数据结构存在于三张表中,一个是全局的列表dev_base,另外两张hash表dev_name_head、dev_index_head。dev_base实例的全局列表能够方便内核浏览设备;dev_name_head是以设备名为索引,方面通过dev的名称搜寻到设备的dev结构;dev_index_head是以设备的ID,dev->ifindex为索引,方面通过设备的ID来搜寻设备的。
   对于一个完整的net_device结构的注册,register_netdevice实际上只完成了一部分工作,而将剩余部分的工作让netdev_run_todo予以完成。
img_396f109bc463cc370b7f3c4444dde097.jpg
   对于register_netdev函数的功能主要包括:

  1. int register_netdev(struct net_device *dev)
  2. {
  3.     int err;

  4.     rtnl_lock();

  5.     /*
  6.      * If the name is a format string the caller wants us to do a
  7.      * name allocation.
  8.      */
  9.     if (strchr(dev->name, '%')) {
  10.         err = dev_alloc_name(dev, dev->name);
  11.         if (err 0)
  12.             goto out;
  13.     }
  14.     /* register_netdev 的注册流程分为两部分*/
  15.     /* 1) register_netdevice*/
  16.     /* 2) 在rtnl_unlock 解锁操作中,由netdev_run_todo 函数完成*/
  17.     err = register_netdevice(dev);
  18. out:
  19.     rtnl_unlock();
  20.     return err;
  21. }

  22. void rtnl_unlock(void)
  23. {
  24.     /* This fellow will unlock it for us. */
  25.     netdev_run_todo();
  26. }
下面是register_netdevice的函数过程:
  1. int register_netdevice(struct net_device *dev)
  2. {
  3.     int ret;
  4.     struct net *net = dev_net(dev);

  5.     BUG_ON(dev_boot_phase);
  6.     ASSERT_RTNL();

  7.     might_sleep();

  8.     /* When net_device's are persistent, this will be fatal. */
  9.     BUG_ON(dev->reg_state != NETREG_UNINITIALIZED);
  10.     BUG_ON(!net);

  11.     /* 初始化相关的锁*/
  12.     spin_lock_init(&dev->addr_list_lock);
  13.     netdev_set_addr_lockdep_class(dev);

  14.     dev->iflink = -1;

  15.     /* Init, if this function is available */
  16.     /* 如果有初始化init 函数则执行init */
  17.     if (dev->netdev_ops->ndo_init) {
  18.         ret = dev->netdev_ops->ndo_init(dev);
  19.         if (ret) {
  20.             if (ret > 0)
  21.                 ret = -EIO;
  22.             goto out;
  23.         }
  24.     }

  25.     /* 对dev 的名称进行处理*/
  26.     /* 如,命名是否合法*/
  27.     /* 对诸如eth%d 的情况分配设备名称*/
  28.     /* 对已经指定名称的设备在链表中查询是否重名等等*/
  29.     ret = dev_get_valid_name(dev, dev->name, 0);
  30.     if (ret)
  31.         goto err_uninit;

  32.     /* 给设备分配一个全局的identifier */
  33.     dev->ifindex = dev_new_index(net);
  34.     if (dev->iflink == -1)
  35.         dev->iflink = dev->ifindex;

  36.     /* Fix illegal checksum combinations */
  37.     /* 检测一些特性的组合是否合法*/
  38.     if ((dev->features & NETIF_F_HW_CSUM) &&
  39.      (dev->features & (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {
  40.         printk(KERN_NOTICE "%s: mixed HW and IP checksum settings.\n",
  41.          dev->name);
  42.         dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM);
  43.     }

  44.     if ((dev->features & NETIF_F_NO_CSUM) &&
  45.      (dev->features & (NETIF_F_HW_CSUM|NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {
  46.         printk(KERN_NOTICE "%s: mixed no checksumming and other settings.\n",
  47.          dev->name);
  48.         dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM|NETIF_F_HW_CSUM);
  49.     }

  50.     dev->features = netdev_fix_features(dev->features, dev->name);

  51.     /* Enable software GSO if SG is supported. */
  52.     if (dev->features & NETIF_F_SG)
  53.         dev->features |= NETIF_F_GSO;

  54.     /* Enable GRO and NETIF_F_HIGHDMA for vlans by default,
  55.      * vlan_dev_init() will do the dev->features check, so these features
  56.      * are enabled only if supported by underlying device.
  57.      */
  58.     dev->vlan_features |= (NETIF_F_GRO | NETIF_F_HIGHDMA);

  59.     /* 调用netdev_chain 通知链,通知其他设备*/
  60.     ret = call_netdevice_notifiers(NETDEV_POST_INIT, dev);
  61.     ret = notifier_to_errno(ret);
  62.     if (ret)
  63.         goto err_uninit;

  64.     /* 初始化设备相关的kobject,创建相关sysfs */
  65.     ret = netdev_register_kobject(dev);
  66.     if (ret)
  67.         goto err_uninit;
  68.     dev->reg_state = NETREG_REGISTERED;

  69.     /*
  70.      *    Default initial state at registry is that the
  71.      *    device is present.
  72.      */

  73.     set_bit(__LINK_STATE_PRESENT, &dev->state);

  74.     /* 初始化流控队列*/
  75.     dev_init_scheduler(dev);
  76.     dev_hold(dev);
  77.     /* 分别将netdevice 加入全局链表以及名称hash 表、index索引哈希表*/
  78.     list_netdevice(dev);

  79.     /* Notify protocols, that a new device appeared. */
  80.     ret = call_netdevice_notifiers(NETDEV_REGISTER, dev);
  81.     ret = notifier_to_errno(ret);
  82.     if (ret) {
  83.         rollback_registered(dev);
  84.         dev->reg_state = NETREG_UNREGISTERED;
  85.     }
  86.     /*
  87.      *    Prevent userspace races by waiting until the network
  88.      *    device is fully setup before sending notifications.
  89.      */
  90.      /* RT netlink 的相关操作*/
  91.     if (!dev->rtnl_link_ops ||
  92.      dev->rtnl_link_state == RTNL_LINK_INITIALIZED)
  93.         rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U);

  94. out:
  95.     return ret;

  96. err_uninit:
  97.     if (dev->netdev_ops->ndo_uninit)
  98.         dev->netdev_ops->ndo_uninit(dev);
  99.     goto out;


   从简单来说,网卡驱动加载大致流程就已经结束了,不过再实际中,对于一个具体的网卡来说,比如最近接触到的igb、ixgbe网卡驱动,在probe函数中要进行的初始化操作要多的多,涉及到诸多的硬件寄存器配置、队列设置等等。所以呢,这篇文章总结起来只能算作是入门而已。。。
目录
相关文章
|
4月前
|
监控 网络协议 Linux
在Linux中,如何查看某个网卡是否连接着交换机?
在Linux中,如何查看某个网卡是否连接着交换机?
|
6天前
|
存储 Oracle 安全
服务器数据恢复—LINUX系统删除/格式化的数据恢复流程
Linux操作系统是世界上流行的操作系统之一,被广泛用于服务器、个人电脑、移动设备和嵌入式系统。Linux系统下数据被误删除或者误格式化的问题非常普遍。下面北亚企安数据恢复工程师简单聊一下基于linux的文件系统(EXT2/EXT3/EXT4/Reiserfs/Xfs) 下删除或者格式化的数据恢复流程和可行性。
|
2月前
|
监控 安全 Java
linux服务器上启动framework应用程序流程
【10月更文挑战第17天】在Linux服务器上启动Framework应用程序需经过准备工作、部署、启动、监控及访问五个步骤。首先确保服务器满足系统要求并安装依赖项;接着上传应用文件,编译构建,配置参数;然后通过脚本、命令行或系统服务启动应用;启动后检查日志,监控性能;最后确认访问地址,验证应用运行状态。具体操作应参照应用文档。
|
2月前
|
监控 Java Linux
linux服务器上启动framework应用程序流程
【10月更文挑战第18天】在 Linux 服务器上启动框架应用程序的流程包括:准备工作(确保访问权限、上传部署文件、了解启动要求)、检查依赖项、配置环境变量、切换到应用程序目录、启动应用程序、监控启动过程以及验证应用程序是否正常运行。具体步骤可能因应用程序类型和框架而异。
|
4月前
|
缓存 NoSQL Linux
【Azure Redis 缓存】Windows和Linux系统本地安装Redis, 加载dump.rdb中数据以及通过AOF日志文件追加数据
【Azure Redis 缓存】Windows和Linux系统本地安装Redis, 加载dump.rdb中数据以及通过AOF日志文件追加数据
139 1
【Azure Redis 缓存】Windows和Linux系统本地安装Redis, 加载dump.rdb中数据以及通过AOF日志文件追加数据
|
4月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
51 6
|
4月前
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
55 5
|
4月前
|
NoSQL Linux Android开发
内核实验(三):编写简单Linux内核模块,使用Qemu加载ko做测试
本文介绍了如何在QEMU中挂载虚拟分区、创建和编译简单的Linux内核模块,并在QEMU虚拟机中加载和测试这些内核模块,包括创建虚拟分区、编写内核模块代码、编译、部署以及在QEMU中的加载和测试过程。
231 0
内核实验(三):编写简单Linux内核模块,使用Qemu加载ko做测试
|
4月前
|
Ubuntu NoSQL Linux
Linux内核和驱动
Linux内核和驱动
32 2
|
4月前
|
安全 Linux 开发者
在Linux中,内核模块是什么以及如何加载和卸载它们?
在Linux中,内核模块是什么以及如何加载和卸载它们?