深入明白Linux网络随笔(一):内核是怎样吸取网络包的(上篇)

[复制链接]
发表于 4 天前 | 显示全部楼层 |阅读模式
深入明白Linux网络随笔(一):内核是怎样吸取网络包的(上篇)



1、TCP/IP模子概述

从Linux视角看,TCP/IP网络分层模子包罗用户空间和内核空间。用户空间(应用层)负责HTTP、FTP等协议的网络服务。内核空间则实现了各个协议层:


  • 传输层:TCP确保可靠传输,UDP提供无毗连快速传输。
  • 网络层:IP负责数据包路由,ICMP用于网络诊断。
  • 链路层:通过网络装备驱动处理处罚数据帧和硬件交互。
  • 物理层:网卡实现物理信号收发。
各层通过封装与解封装协作完成数据传输,内核通过socket接口支持用户进程与网络交互。


2、内核收包过程

内核收包路径如下图所示,其焦点步调小结如下:
1、网卡NIC吸取到网络数据包
由于网卡位于物理层,此时吸取的数据以电信号情势存在,网卡将电信号转换为数字信号,并举行数据帧的封装和预处理处罚。
2、以DMA方式将数据帧写放入内存
采取直接内存访问(DMA)技能,将数据帧直接写入内核中预定的缓冲区,淘汰CPU负担并进步数据传输服从。
3、网卡触发硬停止关照CPU
4、软停止处理处罚
CPU收到停止哀求,调用网络装备驱动注册的停止处理处罚函数,将停止哀求转为软停止,提交给软停止处理处罚机制。硬停止的目标是尽快相应硬件,而软停止则允许体系举行更复杂的处理处罚。
5、ksoftirqd内核线程处理处罚,poll轮询收包


  • 软停止哀求由ksoftirqd内核线程处理处罚,通过poll轮询的方式处理处罚全部网络相干的软停止使命。批量处理处罚多个网络数据包,淘汰停止上下文切换的开销。
  • 网卡的吸取缓冲区(Ring Buffer)中取出数据,将其封装为Socket缓冲区(skb)对象。skb是Linux内核中用于形貌网络数据包的数据布局。
6、协议栈处理处罚网络帧


  • 协议栈开始处理处罚网路帧,处理处罚完成的data存于socket的吸取队列(每个进程通过socket与内核网络栈通讯,吸取到的数据会被放入该进程对应的吸取队列)。
  • socket充当用户进程和内核通讯桥梁
7、内核唤醒用户进程


  • 调用如recv()或read()等体系调用读取数据,数据会从吸取队列中取出并交给用户空间。

2.1Linux启动

2.1.1 创建ksoftirqd内核线程

ksoftirqd 是一种 per-CPU 内核线程,意味着每个 CPU 焦点都会有一个独立的 ksoftirqd 线程来处理处罚该焦点的软停止。在 Linux 内核中,ksoftirqd 的优先级相对较低。当体系中有更高优先级的使命(如硬停止、实时使命或其他告急进程)必要处理处罚时,ksoftirqd 线程会被调理器推迟实行,等候 CPU 资源空闲。只有当 CPU 处于空闲状态,大概软停止的处理处罚已经无法继承推迟时,ksoftirqd 线程才会被调理实行。这种耽误处理处罚机制,使得ksoftirqd 只会在 CPU 空闲大概软停止处理处罚不能再推迟时才会被调理实行,最大化 CPU 的利用率,制止不须要的资源浪费。
ps下令检察**ksoftirq内核线程, **ps -eLf | grep ksoftirqd 下令检察到了 [ksoftirqd/0]、[ksoftirqd/1]、[ksoftirqd/2]、[ksoftirqd/3] 这 4 个 ksoftirq 内核线程,利用 nproc 下令检察到当前呆板的 CPU 焦点数为 4 。ksoftirqd线程数量便是呆板的CPU焦点数

Linux内核启动过程中,体系初始化的时间在kernel/smpboot.c中调用了smpboot_register_percpu_thread为每个CPU焦点启动一个内核线程,该函数会进一步实行到spawn_ksoftirqd(位于kernel/ksoftirqd.c)来创建softirqd线程。
  1. //kernel/softirq.c
  2. static __init int spawn_ksoftirqd(void)
  3. {
  4.         cpuhp_setup_state_nocalls(CPUHP_SOFTIRQ_DEAD, "softirq:dead", NULL,
  5.                                   takeover_tasklets);//注册CPU的热插拔状态,确保在CPU下线时能够正确处理软中断
  6.         BUG_ON(smpboot_register_percpu_thread(&softirq_threads));//负责在每一个CPU上注册并启动软中断线程softirqd
  7.         return 0;
  8. }
  9. early_initcall(spawn_ksoftirqd);
复制代码
spawn_ksoftirqd会在内核启动时创建并初始化每个CPU上的软停止线程,并为每个CPU设置适当的热插拔状态。通过这种方式,体系可以在多核情况下并行处理处罚软停止。
  1. static struct smp_hotplug_thread softirq_threads = {
  2.         .store                        = &ksoftirqd,
  3.         .thread_should_run        = ksoftirqd_should_run,
  4.         .thread_fn                = run_ksoftirqd,
  5.         .thread_comm                = "ksoftirqd/%u",
  6. };
复制代码
smp_hotplug_thread布局体形貌热插拔线程(hotplug thread),即在体系中随着 CPU 的启停而创建或烧毁的线程。softirq_threads 专门用来形貌软停止线程,设置回调函数ksoftirqd_should_run,决定线程是否必要继承实行;设置回调函数run_ksoftirqd处理处罚软停止使命。(软停止焦点流程不做表明)
  1. // include/linux/interrupt.h
  2. enum
  3. {
  4.     HI_SOFTIRQ = 0,               // 高优先级软中断,通常用于处理紧急任务
  5.     TIMER_SOFTIRQ,                // 定时器软中断,处理内核定时器相关的任务
  6.     NET_TX_SOFTIRQ,               // 网络传输发送软中断,用于处理网络发送操作
  7.     NET_RX_SOFTIRQ,               // 网络传输接收软中断,用于处理网络接收操作
  8.     BLOCK_SOFTIRQ,                // 块设备软中断,处理与块设备(如硬盘)相关的任务
  9.     BLOCK_IOPOLL_SOFTIRQ,         // 块 I/O 轮询软中断,负责检查 I/O 操作是否完成
  10.     TASKLET_SOFTIRQ,              // tasklet 软中断,处理延迟任务
  11.     SCHED_SOFTIRQ,                // 调度软中断,处理任务调度相关的任务
  12.     HRTIMER_SOFTIRQ,              // 高分辨率定时器软中断,处理高精度定时器任务
  13.     RCPU_SOFTIRQ,                 // RCPU 同步软中断,涉及到 RCPU 的更新和回调处理
  14.     NR_SOFTIRQS                   // 软中断的总数,表示系统中所有软中断类型的数量
  15. };
复制代码
软停止范例中,网络传输发送软停止NET_TX_SOFTIRQ,网络传输吸取软停止NET_RX_SOFTIRQ。
2.1.2网络子体系初始化

Linux内核通过调用subsys_initcall来初始化各个子体系,利用subsys_initcall(net_dev_init)初始化网络装备。

  1. static int __init net_dev_init(void)
  2. {
  3. ......
  4.         /*
  5.          *        Initialise the packet receive queues.
  6.          */
  7.    
  8.     // *初始化每个 CPU 上的网络接收队列和软中断相关的数据结构。
  9.         for_each_possible_cpu(i) {
  10.                 struct work_struct *flush = per_cpu_ptr(&flush_works, i);
  11.                 struct softnet_data *sd = &per_cpu(softnet_data, i);
  12.                 INIT_WORK(flush, flush_backlog);
  13.                 skb_queue_head_init(&sd->input_pkt_queue);//输入数据包队列
  14.                 skb_queue_head_init(&sd->process_queue);//处理队列
  15. ......
  16.                 init_gro_hash(&sd->backlog);//哈希表
  17.                 sd->backlog.poll = process_backlog;//回调
  18.                 sd->backlog.weight = weight_p;//Gro权值
  19.         }
  20.     //初始化阶段结束
  21.         dev_boot_phase = 0;
  22.         /* The loopback device is special if any other network devices
  23.          * is present in a network namespace the loopback device must
  24.          * be present. Since we now dynamically allocate and free the
  25.          * loopback device ensure this invariant is maintained by
  26.          * keeping the loopback device as the first device on the
  27.          * list of network devices.  Ensuring the loopback devices
  28.          * is the first device that appears and the last network device
  29.          * that disappears.
  30.          */
  31.     //注册回环设备
  32.         if (register_pernet_device(&loopback_net_ops))
  33.                 goto out;
  34.     //注册默认网路设备
  35.         if (register_pernet_device(&default_device_ops))
  36.                 goto out;
  37.     //启动网络传输发送软中断
  38.         open_softirq(NET_TX_SOFTIRQ, net_tx_action);
  39.     //启动网络传输接收软中断
  40.         open_softirq(NET_RX_SOFTIRQ, net_rx_action);
  41.     // 设置 CPU 热插拔状态,注册 CPU 下线时的清理函数
  42.         rc = cpuhp_setup_state_nocalls(CPUHP_NET_DEV_DEAD, "net/dev:dead",
  43.                                        NULL, dev_cpu_dead);
  44.         WARN_ON(rc < 0);
  45.         rc = 0;
  46. out:
  47.         return rc;
  48. }
复制代码
网络子体系初始化过程,为每个CPU初始化softnet_data布局体,举行软停止处理处罚和管理网络数据包队列,为软停止注册处理处罚函数利用方法open_softirq,网络传输发送软停止NET_TX_SOFTIRQ处理处罚注册回调处理处罚函数net_tx_action,网络传输吸取软停止NET_RX_SOFTIRQ处理处罚注册回调处理处罚函数net_rx_action。
  1. struct softnet_data {
  2.         struct list_head        poll_list;
  3.         struct sk_buff_head        process_queue;
  4.         /* stats */
  5.         unsigned int                processed;
  6.         unsigned int                time_squeeze;
  7. #ifdef CONFIG_RPS
  8.         struct softnet_data        *rps_ipi_list;
  9. #endif
  10. #ifdef CONFIG_NET_FLOW_LIMIT
  11.         struct sd_flow_limit __rcu *flow_limit;
  12. #endif
  13.         struct Qdisc                *output_queue;
  14.         struct Qdisc                **output_queue_tailp;
  15.         struct sk_buff                *completion_queue;
  16. #ifdef CONFIG_XFRM_OFFLOAD
  17.         struct sk_buff_head        xfrm_backlog;
  18. #endif
  19.         /* written and read only by owning cpu: */
  20.         struct {
  21.                 u16 recursion;
  22.                 u8  more;
  23. #ifdef CONFIG_NET_EGRESS
  24.                 u8  skip_txqueue;
  25. #endif
  26.         } xmit;
  27. #ifdef CONFIG_RPS
  28.         /* input_queue_head should be written by cpu owning this struct,
  29.          * and only read by other cpus. Worth using a cache line.
  30.          */
  31.         unsigned int                input_queue_head ____cacheline_aligned_in_smp;
  32.         /* Elements below can be accessed between CPUs for RPS/RFS */
  33.         call_single_data_t        csd ____cacheline_aligned_in_smp;
  34.         struct softnet_data        *rps_ipi_next;
  35.         unsigned int                cpu;
  36.         unsigned int                input_queue_tail;
  37. #endif
  38.         unsigned int                received_rps;
  39.         unsigned int                dropped;
  40.         struct sk_buff_head        input_pkt_queue;
  41.         struct napi_struct        backlog;
  42.         /* Another possibly contended cache line */
  43.         spinlock_t                defer_lock ____cacheline_aligned_in_smp;
  44.         int                        defer_count;
  45.         int                        defer_ipi_scheduled;
  46.         struct sk_buff                *defer_list;
  47.         call_single_data_t        defer_csd;
  48. };
复制代码
在每个 softnet_data 布局体中,poll_list 用来链接必要轮询的网络装备接口。每个 CPU 上的 softnet_data 布局体都有一个 poll_list,记录了当前必要处理处罚的网络装备。如允许以通过轮询这些装备的接口来处理处罚网络数据包。
在 Linux 网络栈的工作流程中,NAPI 会定期轮询装备(而不是每次包都停止),有用地淘汰停止处理处罚的负载。每个网络装备的 NAPI 布局体都会被放入这个链表中,表现这个装备正在举行轮询,等候数据包的处理处罚。
  1. void open_softirq(int nr, void (*action)(struct softirq_action *))
  2. {
  3.         softirq_vec[nr].action = action;
  4. }
复制代码
软停止处理处罚函数绑定软停止号通过softirq_vec数组实现,每一个元素对应一种软停止范例。
2.1.3协议栈注册

Linux内核实现了网络层的IP协议,同样也实现了TCP、UDP协议,协议栈初始化入口函数为fs_initcall(inet_init),调用inet_init举行网络协议栈注册。

  1. static int __init inet_init(void)
  2. {
  3.     ......
  4.     if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
  5.                 pr_crit("%s: Cannot add ICMP protocol\n", __func__);
  6.         if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
  7.                 pr_crit("%s: Cannot add UDP protocol\n", __func__);
  8.         if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
  9.                 pr_crit("%s: Cannot add TCP protocol\n", __func__);  
  10.     ......
  11.     dev_add_pack(&ip_packet_type);
  12. }
复制代码
利用inet_add_protocol向协议栈注册协议,协议界说布局体如下:
  1. static struct packet_type ip_packet_type __read_mostly = {
  2.         .type = cpu_to_be16(ETH_P_IP),
  3.         .func = ip_rcv,
  4.         .list_func = ip_list_rcv,
  5. };
  6. static const struct net_protocol icmp_protocol = {
  7.         .handler =        icmp_rcv,
  8.         .err_handler =        icmp_err,
  9.         .no_policy =        1,
  10. };
  11. static const struct net_protocol udp_protocol = {
  12.         .handler =        udp_rcv,
  13.         .err_handler =        udp_err,
  14.         .no_policy =        1,
  15. };
  16. static const struct net_protocol tcp_protocol = {
  17.         .handler        =        tcp_v4_rcv,//回调处理函数
  18.         .err_handler        =        tcp_v4_err,
  19.         .no_policy        =        1,
  20.         .icmp_strict_tag_validation = 1,
  21. };
复制代码
dev_add_pack负责将新的packet_type布局体添加于协议范例链表。
  1. void dev_add_pack(struct packet_type *pt)
  2. {
  3.         struct list_head *head = ptype_head(pt);// 获取对应协议类型的链表头
  4.         spin_lock(&ptype_lock);
  5.         list_add_rcu(&pt->list, head);// 将新的 packet_type 结构体添加到链表中
  6.         spin_unlock(&ptype_lock);
  7. }
复制代码
ptype_head 是一个内联函数,根据传入的 packet_type 布局体 (pt) 获取对应协议范例的链表头。这个链表存储了与该协议范例相干的处理处罚步调。它根据协议范例和装备信息决定返回哪个链表的头部。
  1. static inline struct list_head *ptype_head(const struct packet_type *pt)
  2. {
  3.     if (pt->type == htons(ETH_P_ALL))  // 如果协议类型是 ETH_P_ALL
  4.         return pt->dev ? &pt->dev->ptype_all : &ptype_all;  // 返回设备特定的链表或全局链表
  5.     else  // 如果协议类型不是 ETH_P_ALL
  6.         return pt->dev ? &pt->dev->ptype_specific :
  7.                          &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK];  // 返回设备特定的链表或根据协议类型哈希的链表
  8. }
复制代码
协议范例通过 ntohs(pt->type) & PTYPE_HASH_MASK 举行哈希盘算,决定将其对应的 packet_type 布局体插入到 ptype_base 数组中的哪个位置,在这里IP协议会注册到ptype_base 哈希表,存储ip_rcv函数处理处罚地点。
  1. int inet_add_protocol(const struct net_protocol *prot, unsigned char protocol)
  2. {
  3.         return !cmpxchg((const struct net_protocol **)&inet_protos[protocol],
  4.                         NULL, prot) ? 0 : -1;
  5. }
  6. EXPORT_SYMBOL(inet_add_protocol);
复制代码
TCP、UDP、ICMP协议注册方式与IP协议差异,通过inet_add_protocol函数调用完成协议注册,将协议范例protocol对应的处理处罚步调添加于inet_protos数组。
2.1.4网卡驱动初始化

每一个驱动步调(不但仅包罗网卡驱动步调)会利用module_init向内核注册一个初始化函数,当驱动步调被加载时,内核会调用这个函数,以igb网卡驱动步调为例,其初始化函数如下。

  1. static int __init igb_init_module(void)
  2. {
  3. ......
  4.         ret = pci_register_driver(&igb_driver);
  5.         return ret;
  6. }
  7. module_init(igb_init_module);
复制代码
  1. //驱动相关信息
  2. static struct pci_driver igb_driver = {
  3.         .name     = igb_driver_name,
  4.         .id_table = igb_pci_tbl,
  5.         .probe    = igb_probe,
  6.         .remove   = igb_remove,
  7. #ifdef CONFIG_PM
  8.         .driver.pm = &igb_pm_ops,
  9. #endif
  10.         .shutdown = igb_shutdown,
  11.         .sriov_configure = igb_pci_sriov_configure,
  12.         .err_handler = &igb_err_handler
  13. };
复制代码
注册PCI驱动步调在pci_register_driver函数完成,函数调用关系pci_register_driver–>__pci_register_driver。
  1. int __pci_register_driver(struct pci_driver *drv, struct module *owner,
  2.                           const char *mod_name)
  3. {
  4.         /* initialize common driver fields */
  5.         drv->driver.name = drv->name;             // 驱动名称
  6.         drv->driver.bus = &pci_bus_type;          // 绑定到 PCI 总线类型
  7.         drv->driver.owner = owner;                // 驱动模块所有者
  8.         drv->driver.mod_name = mod_name;          // 模块名称
  9.         drv->driver.groups = drv->groups;         // 驱动的分组(若有)
  10.         drv->driver.dev_groups = drv->dev_groups; // 设备的分组(若有)
  11.         spin_lock_init(&drv->dynids.lock);        // 初始化锁,用于动态分配的设备 ID
  12.         INIT_LIST_HEAD(&drv->dynids.list);        // 初始化动态设备列表
  13.         /* register with core */
  14.         return driver_register(&drv->driver);    // 注册驱动到内核的核心驱动模型中
  15. }
  16. EXPORT_SYMBOL(__pci_register_driver);
复制代码
__pci_register_driver调用完成后,Linux内核就会知道驱动相干信息(如igb网卡驱动名称、igb_probe地点等),当网卡装备被辨认以后,Linux内核会调用.probe方法(igb_probe方法)。
  1. static int igb_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
  2. {
  3.     ......
  4.     //获取MAC地址
  5.     if (eth_platform_get_mac_address(&pdev->dev, hw->mac.addr)) {
  6.                 /* copy the MAC address out of the NVM */
  7.                 if (hw->mac.ops.read_mac_addr(hw))
  8.                         dev_err(&pdev->dev, "NVM Read Error\n");
  9.         }
  10.     //DMA初始化
  11.     err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
  12.     //注册ethtool函数
  13.     igb_set_ethtool_ops(netdev);
  14.     //注册net_device_ops、netdev等
  15.     netdev = alloc_etherdev_mq(sizeof(struct igb_adapter),
  16.                                    IGB_MAX_TX_QUEUES);
  17.         if (!netdev)
  18.                 goto err_alloc_etherdev;
  19.         SET_NETDEV_DEV(netdev, &pdev->dev);
  20.         pci_set_drvdata(pdev, netdev);
  21.         adapter = netdev_priv(netdev);
  22.         adapter->netdev = netdev;
  23.         adapter->pdev = pdev;
  24.         hw = &adapter->hw;
  25.         hw->back = adapter;
  26.    ......
  27.         netdev->netdev_ops = &igb_netdev_ops;
  28.     // 注册NAPI相关的资源分配
  29.     err = igb_alloc_q_vector(adapter);
  30.     if (err)
  31.         goto err_alloc_q_vector;
  32.     ......       
  33. }
复制代码
igb_probe初始化过程中,调用igb_alloc_q_vector–>netif_napi_add,初始化NAPI机制,对于igb网卡来说,NAPI机制的poll函数是igb_poll。
  1. static int igb_alloc_q_vector(struct igb_adapter *adapter,
  2.                               int v_count, int v_idx,
  3.                               int txr_count, int txr_idx,
  4.                               int rxr_count, int rxr_idx)
  5. {
  6.     ....
  7.     /* initialize NAPI */
  8.         netif_napi_add(adapter->netdev, &q_vector->napi, igb_poll);
  9.     ....
  10. }
复制代码
2.1.5启动网卡

  1. static const struct net_device_ops igb_netdev_ops = {
  2.         .ndo_open                = igb_open,
  3.         .ndo_stop                = igb_close,
  4.         .ndo_start_xmit                = igb_xmit_frame,
  5.         .ndo_get_stats64        = igb_get_stats64,
  6.         .ndo_set_rx_mode        = igb_set_rx_mode,
  7.         .ndo_set_mac_address        = igb_set_mac,
  8.         .ndo_change_mtu                = igb_change_mtu,
  9.         .ndo_eth_ioctl                = igb_ioctl,
  10.         .ndo_tx_timeout                = igb_tx_timeout,
  11.         .ndo_validate_addr        = eth_validate_addr,
  12.         .ndo_vlan_rx_add_vid        = igb_vlan_rx_add_vid,
  13.         .ndo_vlan_rx_kill_vid        = igb_vlan_rx_kill_vid,
  14.         .ndo_set_vf_mac                = igb_ndo_set_vf_mac,
  15.         .ndo_set_vf_vlan        = igb_ndo_set_vf_vlan,
  16.         .ndo_set_vf_rate        = igb_ndo_set_vf_bw,
  17.         .ndo_set_vf_spoofchk        = igb_ndo_set_vf_spoofchk,
  18.         .ndo_set_vf_trust        = igb_ndo_set_vf_trust,
  19.         .ndo_get_vf_config        = igb_ndo_get_vf_config,
  20.         .ndo_fix_features        = igb_fix_features,
  21.         .ndo_set_features        = igb_set_features,
  22.         .ndo_fdb_add                = igb_ndo_fdb_add,
  23.         .ndo_features_check        = igb_features_check,
  24.         .ndo_setup_tc                = igb_setup_tc,
  25.         .ndo_bpf                = igb_xdp,
  26.         .ndo_xdp_xmit                = igb_xdp_xmit,
  27. };
复制代码
igb_netdev_ops布局体界说了网络装备常见的实行操纵,当网卡被启动时调用回调函数igb_open。函数调用关系igb_open–>_igb_open。

  1. static int __igb_open(struct net_device *netdev, bool resuming)
  2. {
  3.     ......
  4.         netif_carrier_off(netdev);
  5.         /* allocate transmit descriptors */
  6.         err = igb_setup_all_tx_resources(adapter);
  7.         if (err)
  8.                 goto err_setup_tx;
  9.         /* allocate receive descriptors */
  10.         err = igb_setup_all_rx_resources(adapter);
  11.         if (err)
  12.                 goto err_setup_rx;
  13.    
  14.         err = igb_request_irq(adapter);
  15.         if (err)
  16.                 goto err_req_irq;
  17.     ......
  18.     igb_irq_enable(adapter);
  19.         for (i = 0; i < adapter->num_q_vectors; i++)
  20.                 napi_enable(&(adapter->q_vector[i]->napi));
  21.     ......
  22.    
  23. }
复制代码
__igb_open中调用igb_setup_all_tx_resources分配TX队列内存,调用igb_setup_all_rx_resources分配RX队列内存,调用igb_request_irq注册停止处理处罚函数,igb_irq_enable启用硬停止, napi_enable启用NAPI机制。
  1. static int igb_setup_all_rx_resources(struct igb_adapter *adapter)
  2. {
  3.         struct pci_dev *pdev = adapter->pdev;
  4.         int i, err = 0;
  5.         for (i = 0; i < adapter->num_rx_queues; i++) {
  6.                 err = igb_setup_rx_resources(adapter->rx_ring[i]);
  7.                 ......
  8.                 }
  9.         }
  10.         return err;
  11. }
复制代码
循环创建多少个吸取队列,调用igb_setup_rx_resources为RX队分配资源,前期预备工作已完成,等候网络数据包到来~~。
  1. int igb_setup_rx_resources(struct igb_ring *rx_ring)
  2. {
  3. ......
  4.     //申请igb_rx_buffer数组内存供内核使用
  5.         size = sizeof(struct igb_rx_buffer) * rx_ring->count;
  6.         rx_ring->rx_buffer_info = vmalloc(size);
  7.         if (!rx_ring->rx_buffer_info)
  8.                 goto err;
  9.     //申请e1000_adv_rx_desc数组内存供网卡使用
  10.         /* Round up to nearest 4K */
  11.         rx_ring->size = rx_ring->count * sizeof(union e1000_adv_rx_desc);
  12.         rx_ring->size = ALIGN(rx_ring->size, 4096);
  13.         rx_ring->desc = dma_alloc_coherent(dev, rx_ring->size,
  14.                                            &rx_ring->dma, GFP_KERNEL);
  15.         if (!rx_ring->desc)
  16.                 goto err;
  17.     //初始化
  18.         rx_ring->next_to_alloc = 0;
  19.         rx_ring->next_to_clean = 0;
  20.         rx_ring->next_to_use = 0;
  21. ......
  22.         return 0;
  23. }
复制代码
参考资料:《深入明白linux网络》

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

×
登录参与点评抽奖,加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表