• 网络驱动移植之解析Linux网络驱动的基本框架


      内核源码:Linux-2.6.38.8.tar.bz2

        概括而言,编写Linux网络驱动其实只要完成两件事即可,一是分配并初始化网络设备,二是注册网络设备。

        1、分配并初始化网络设备

        动态分配网络设备(从C语言角度来看,其实就是定义了一个struct net_device结构体变量,并对这个结构体变量的某些成员进行了初始化而已)及其私有数据的大致过程如下图(以以太网设备为例):

     

        下面将结合linux-2.6.38.8中的代码详细分析网络设备的分配和初始化过程。 

    [cpp] view plain copy
     
    1. /* linux-2.6.38.8/include/linux/etherdevice.h */  
    2. #define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)  
    3. #define alloc_etherdev_mq(sizeof_priv, count) alloc_etherdev_mqs(sizeof_priv, count, count)  
    4.   
    5. /* linux-2.6.38.8/net/ethernet/eth.c */  
    6. struct net_device *alloc_etherdev_mqs(int sizeof_priv, unsigned int txqs,  
    7.                       unsigned int rxqs)  
    8. {  
    9.     return alloc_netdev_mqs(sizeof_priv, "eth%d", ether_setup, txqs, rxqs);  
    10. }  
    11.   
    12. void ether_setup(struct net_device *dev)  
    13. {  
    14.     dev->header_ops      = &eth_header_ops;  
    15.     dev->type        = ARPHRD_ETHER;  
    16.     dev->hard_header_len     = ETH_HLEN;  
    17.     dev->mtu     = ETH_DATA_LEN;  
    18.     dev->addr_len        = ETH_ALEN;  
    19.     dev->tx_queue_len    = 1000; /* Ethernet wants good queues */  
    20.     dev->flags       = IFF_BROADCAST|IFF_MULTICAST;  
    21.   
    22.     memset(dev->broadcast, 0xFF, ETH_ALEN);  
    23.   
    24. }  

        以前各类网络设备的分配函数(如以太网设备的alloc_etherdev)都只是alloc_netdev函数的封装而已,但对于linux-2.6.38.8而言已经不是这样了。 

    [cpp] view plain copy
     
    1. /* linux-2.6.38.8/include/linux/netdevice.h */  
    2. #define alloc_netdev(sizeof_priv, name, setup)   
    3.     alloc_netdev_mqs(sizeof_priv, name, setup, 1, 1)  

        alloc_netdev_mqs函数的五个参数分别为私有数据大小、设备名称、默认初始化函数、发送队列数目和接收队列数目。

        以太网设备的名称设为eth%d,默认初始化函数设为ether_setup,发送和接收队列数目都设为1。

        函数alloc_netdev_mqs定义在linux-2.6.38.8/net/core/dev.c文件中,大概会完成以下各种操作:

        (1)、为struct net_device和私有数据分配内存空间 

    [cpp] view plain copy
     
    1. alloc_size = sizeof(struct net_device);  
    2. if (sizeof_priv) {  
    3.     alloc_size = ALIGN(alloc_size, NETDEV_ALIGN);  //#define NETDEV_ALIGN 32  
    4.     alloc_size += sizeof_priv;  
    5. }  
    6. alloc_size += NETDEV_ALIGN - 1;  
    7.   
    8. p = kzalloc(alloc_size, GFP_KERNEL);  
    9. if (!p) {  
    10.     printk(KERN_ERR "alloc_netdev: Unable to allocate device. ");  
    11.     return NULL;  
    12. }  
    13.   
    14. dev = PTR_ALIGN(p, NETDEV_ALIGN);  
    15. dev->padded = (char *)dev - (char *)p;  

        对齐操作相关宏: 

    [cpp] view plain copy
     
    1. /* linux-2.6.38.8/include/linux/kernel.h */  
    2. #define ALIGN(x, a)     __ALIGN_KERNEL((x), (a))  
    3. #define __ALIGN_KERNEL(x, a)        __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1)  
    4. #define __ALIGN_KERNEL_MASK(x, mask)    (((x) + (mask)) & ~(mask))  
    5.       
    6. #define PTR_ALIGN(p, a)     ((typeof(p))ALIGN((unsigned long)(p), (a)))  

        (2)、动态分配per-CPU变量 

    [cpp] view plain copy
     
    1. dev->pcpu_refcnt = alloc_percpu(int);  
    2. if (!dev->pcpu_refcnt)  
    3.     goto free_p;  

        (3)、初始化硬件地址链表dev->dev_addrs,并把首元素赋给dev->dev_addr 

    [cpp] view plain copy
     
    1. if (dev_addr_init(dev))  
    2.     goto free_pcpu;  

        (4)、初始化组播和单播地址链表 

    [cpp] view plain copy
     
    1. dev_mc_init(dev);  
    2. dev_uc_init(dev);  

        (5)、设置网络命名空间 

    [cpp] view plain copy
     
    1. dev_net_set(dev, &init_net);  

        (6)、设置GSO最大值 

    [cpp] view plain copy
     
    1. dev->gso_max_size = GSO_MAX_SIZE;  

        (7)、初始化各种链表 

    [cpp] view plain copy
     
    1. INIT_LIST_HEAD(&dev->ethtool_ntuple_list.list);  
    2. dev->ethtool_ntuple_list.count = 0;  
    3. INIT_LIST_HEAD(&dev->napi_list);  
    4. INIT_LIST_HEAD(&dev->unreg_list);  
    5. INIT_LIST_HEAD(&dev->link_watch_list);  

        (8)、设置priv_flags值 

    [cpp] view plain copy
     
    1. dev->priv_flags = IFF_XMIT_DST_RELEASE;  

        (9)、执行默认初始化函数(以太网设备默认为ether_setup) 

    [cpp] view plain copy
     
    1. setup(dev);  

        (10)、初始化数据包发送队列 

    [cpp] view plain copy
     
    1. dev->num_tx_queues = txqs;  
    2. dev->real_num_tx_queues = txqs;  
    3. if (netif_alloc_netdev_queues(dev))  
    4.     goto free_all;  

        (11)、初始化数据包接收队列 

    [cpp] view plain copy
     
    1. dev->num_rx_queues = rxqs;  
    2. dev->real_num_rx_queues = rxqs;  
    3. if (netif_alloc_rx_queues(dev))  
    4.     goto free_all;  

        (12)、设置网络设备名称 

    [cpp] view plain copy
     
    1. strcpy(dev->name, name);  

        2、注册网络设备

        通过register_netdev函数把已完成部分初始化的net_device结构体变量(即某个网络设备实例)注册到Linux内核中,大致过程如下图:

     

        下面将结合linux-2.6.38.8中的代码详细分析网络设备的注册过程。

        (1)、获得rtnl信号量 

    [cpp] view plain copy
     
    1. rtnl_lock();  

        (2)、分配网络设备名(即%d对应的数字) 

    [cpp] view plain copy
     
    1. if (strchr(dev->name, '%')) {  
    2.     err = dev_alloc_name(dev, dev->name);  
    3.     if (err < 0)  
    4.         goto out;  
    5. }  

        (3)、调用实际注册函数 

    [cpp] view plain copy
     
    1. err = register_netdevice(dev);  

        3.1、初始化dev->addr_list_lock自旋锁并根据dev->type设置其类别 

    [cpp] view plain copy
     
    1. spin_lock_init(&dev->addr_list_lock);  
    2. netdev_set_addr_lockdep_class(dev);  

        3.2、调用init函数 

    [cpp] view plain copy
     
    1. if (dev->netdev_ops->ndo_init) {  
    2.     ret = dev->netdev_ops->ndo_init(dev);  
    3.     if (ret) {  
    4.         if (ret > 0)  
    5.             ret = -EIO;  
    6.         goto out;  
    7.     }  
    8. }  

        3.3、检测网络设备名是否有效 

    [cpp] view plain copy
     
    1. ret = dev_get_valid_name(dev, dev->name, 0);  
    2. if (ret)  
    3.     goto err_uninit;  

        3.4、为网络设备分配唯一的索引号 

    [cpp] view plain copy
     
    1. dev->ifindex = dev_new_index(net);  
    2. if (dev->iflink == -1)  
    3.     dev->iflink = dev->ifindex;  

        3.5、设置网络设备特性(dev->features) 

    [cpp] view plain copy
     
    1. if ((dev->features & NETIF_F_HW_CSUM) &&  
    2.     (dev->features & (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {  
    3.     printk(KERN_NOTICE "%s: mixed HW and IP checksum settings. ",  
    4.            dev->name);  
    5.     dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM);  
    6. }  
    7.   
    8. if ((dev->features & NETIF_F_NO_CSUM) &&  
    9.     (dev->features & (NETIF_F_HW_CSUM|NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {  
    10.     printk(KERN_NOTICE "%s: mixed no checksumming and other settings. ",  
    11.            dev->name);  
    12.     dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM|NETIF_F_HW_CSUM);  
    13. }  
    14.   
    15. dev->features = netdev_fix_features(dev->features, dev->name);  
    16.   
    17.   
    18. if (dev->features & NETIF_F_SG)  
    19.     dev->features |= NETIF_F_GSO;  
    20.   
    21.   
    22. dev->vlan_features |= (NETIF_F_GRO | NETIF_F_HIGHDMA);  

        3.6、通过通知链告知内核其他子系统某种事件的发生(如注册网络设备) 

    [cpp] view plain copy
     
    1. ret = call_netdevice_notifiers(NETDEV_POST_INIT, dev);  
    2. ret = notifier_to_errno(ret);  
    3. if (ret)  
    4.     goto err_uninit;  
    5.   
    6. ret = call_netdevice_notifiers(NETDEV_REGISTER, dev);  
    7. ret = notifier_to_errno(ret);  
    8. if (ret) {  
    9.     rollback_registered(dev);  
    10.     dev->reg_state = NETREG_UNREGISTERED;  
    11. }  

        3.7、创建网络设备在sysfs文件系统中的入口 

    [cpp] view plain copy
     
    1. ret = netdev_register_kobject(dev);  
    2. if (ret)  
    3.     goto err_uninit;  

        3.8、设置网络设备为已注册状态 

    [cpp] view plain copy
     
    1. dev->reg_state = NETREG_REGISTERED;  

        3.9、设置网络设备状态为可用 

    [cpp] view plain copy
     
    1. set_bit(__LINK_STATE_PRESENT, &dev->state);  

        3.10、初始化网络设备的队列规则 

    [cpp] view plain copy
     
    1. dev_init_scheduler(dev);  

        3.11、增加网络设备的引用计数 

    [cpp] view plain copy
     
    1. dev_hold(dev);  

        3.12、加入到设备链表(如dev->dev_list、dev->name_hlist、dev->index_hlist) 

    [cpp] view plain copy
     
    1. list_netdevice(dev);  

        3.13、发送netlink(RFC 3549协议)信息

    [cpp] view plain copy
     
    1. if (!dev->rtnl_link_ops ||  
    2.     dev->rtnl_link_state == RTNL_LINK_INITIALIZED)  
    3.     rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U);  
  • 相关阅读:
    一个关于STL list使用 小示例
    c++几个通用工具
    修改pc机的mac地址 以及 mac地址的组成
    win7 下 arp 绑定mac和Ip
    wireshark如何抓取别人电脑的数据包
    WPA破解原理简要——无线网络破解续
    Apache配置代理服务器的方法(2)
    Apache配置代理服务器的方法(1)
    在Linux系统中如何设置APACHE服务器里的后台页面只允许某个IP地址访问
    Apache服务器配置技巧
  • 原文地址:https://www.cnblogs.com/Ph-one/p/6422472.html
Copyright © 2020-2023  润新知