图解Linux网络包接收过程( 三 )

上面的代码中我们可以看到,udp_protocol结构体中的handler是udp_rcv,tcp_protocol结构体中的handler是tcp_v4_rcv,通过inet_add_protocol被初始化了进来 。
int inet_add_protocol(const struct net_protocol *prot, unsigned char protocol){    if (!prot->netns_ok) {        pr_err("Protocol %u is not namespace aware, cannot register.n",            protocol);        return -EINVAL;    }    return !cmpxchg((const struct net_protocol **)&inet_protos[protocol],            NULL, prot) ? 0 : -1;}inet_add_protocol函数将tcp和udp对应的处理函数都注册到了inet_protos数组中了 。再看dev_add_pack(&ip_packet_type);这一行,ip_packet_type结构体中的type是协议名,func是ip_rcv函数,在dev_add_pack中会被注册到ptype_base哈希表中 。
//file: net/core/dev.cvoid dev_add_pack(struct packet_type *pt){    struct list_head *head = ptype_head(pt);    ......}static inline struct list_head *ptype_head(const struct packet_type *pt){    if (pt->type == htons(ETH_P_ALL))        return &ptype_all;    else        return &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK];}这里我们需要记住inet_protos记录着udp,tcp的处理函数地址,ptype_base存储着ip_rcv()函数的处理地址 。后面我们会看到软中断中会通过ptype_base找到ip_rcv函数地址,进而将ip包正确地送到ip_rcv()中执行 。在ip_rcv中将会通过inet_protos找到tcp或者udp的处理函数,再而把包转发给udp_rcv()或tcp_v4_rcv()函数 。
扩展一下,如果看一下ip_rcv和udp_rcv等函数的代码能看到很多协议的处理过程 。例如,ip_rcv中会处理netfilter和iptable过滤,如果你有很多或者很复杂的 netfilter 或 iptables 规则,这些规则都是在软中断的上下文中执行的,会加大网络延迟 。再例如,udp_rcv中会判断socket接收队列是否满了 。对应的相关内核参数是net.core.rmem_max和net.core.rmem_default 。如果有兴趣,建议大家好好读一下inet_init这个函数的代码 。
2.4 网卡驱动初始化每一个驱动程序(不仅仅只是网卡驱动)会使用 module_init 向内核注册一个初始化函数,当驱动被加载时,内核会调用这个函数 。比如igb网卡驱动的代码位于drivers/net/ethernet/intel/igb/igb_main.c
//file: drivers/net/ethernet/intel/igb/igb_main.cstatic struct pci_driver igb_driver = {    .name     = igb_driver_name,    .id_table = igb_pci_tbl,    .probe    = igb_probe,    .remove   = igb_remove,    ......};static int __init igb_init_module(void){    ......    ret = pci_register_driver(&igb_driver);    return ret;}驱动的pci_register_driver调用完成后,Linux内核就知道了该驱动的相关信息,比如igb网卡驱动的igb_driver_name和igb_probe函数地址等等 。当网卡设备被识别以后,内核会调用其驱动的probe方法(igb_driver的probe方法是igb_probe) 。驱动probe方法执行的目的就是让设备ready,对于igb网卡,其igb_probe位于drivers/net/ethernet/intel/igb/igb_main.c下 。主要执行的操作如下:

图解Linux网络包接收过程

文章插图
 
图6 网卡驱动初始化
第5步中我们看到,网卡驱动实现了ethtool所需要的接口,也在这里注册完成函数地址的注册 。当 ethtool 发起一个系统调用之后,内核会找到对应操作的回调函数 。对于igb网卡来说,其实现函数都在drivers/net/ethernet/intel/igb/igb_ethtool.c下 。相信你这次能彻底理解ethtool的工作原理了吧?这个命令之所以能查看网卡收发包统计、能修改网卡自适应模式、能调整RX 队列的数量和大小,是因为ethtool命令最终调用到了网卡驱动的相应方法,而不是ethtool本身有这个超能力 。
第6步注册的igb_netdev_ops中包含的是igb_open等函数,该函数在网卡被启动的时候会被调用 。
//file: drivers/net/ethernet/intel/igb/igb_main.cstatic const struct net_device_ops igb_netdev_ops = {  .ndo_open               = igb_open,  .ndo_stop               = igb_close,  .ndo_start_xmit         = igb_xmit_frame,  .ndo_get_stats64        = igb_get_stats64,  .ndo_set_rx_mode        = igb_set_rx_mode,  .ndo_set_mac_address    = igb_set_mac,  .ndo_change_mtu         = igb_change_mtu,  .ndo_do_ioctl           = igb_ioctl, ......


推荐阅读