网络驱动移植之解析Linux网络驱动的基本框架
内核源码:linux-2.6.38.8.tar.bz2
概括而言,编写Linux网络驱动其实只要完成两件事即可,一是分配并初始化网络设备,二是注册网络设备。
1、分配并初始化网络设备
动态分配网络设备(从C语言角度来看,其实就是定义了一个struct net_device结构体变量,并对这个结构体变量的某些成员进行了初始化而已)及其私有数据的大致过程如下图(以以太网设备为例):

下面将结合linux-2.6.38.8中的代码详细分析网络设备的分配和初始化过程。
- /* linux-2.6.38.8/include/linux/etherdevice.h */
- #define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)
- #define alloc_etherdev_mq(sizeof_priv, count) alloc_etherdev_mqs(sizeof_priv, count, count)
- /* linux-2.6.38.8/net/ethernet/eth.c */
- struct net_device *alloc_etherdev_mqs(int sizeof_priv, unsigned int txqs,
- unsigned int rxqs)
- {
- return alloc_netdev_mqs(sizeof_priv, "eth%d", ether_setup, txqs, rxqs);
- }
- void ether_setup(struct net_device *dev)
- {
- dev->header_ops = ð_header_ops;
- dev->type = ARPHRD_ETHER;
- dev->hard_header_len = ETH_HLEN;
- dev->mtu = ETH_DATA_LEN;
- dev->addr_len = ETH_ALEN;
- dev->tx_queue_len = 1000; /* Ethernet wants good queues */
- dev->flags = IFF_BROADCAST|IFF_MULTICAST;
- memset(dev->broadcast, 0xFF, ETH_ALEN);
- }
以前各类网络设备的分配函数(如以太网设备的alloc_etherdev)都只是alloc_netdev函数的封装而已,但对于linux-2.6.38.8而言已经不是这样了。
- /* linux-2.6.38.8/include/linux/netdevice.h */
- #define alloc_netdev(sizeof_priv, name, setup) \
- 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和私有数据分配内存空间
- alloc_size = sizeof(struct net_device);
- if (sizeof_priv) {
- alloc_size = ALIGN(alloc_size, NETDEV_ALIGN); //#define NETDEV_ALIGN 32
- alloc_size += sizeof_priv;
- }
- alloc_size += NETDEV_ALIGN - 1;
- p = kzalloc(alloc_size, GFP_KERNEL);
- if (!p) {
- printk(KERN_ERR "alloc_netdev: Unable to allocate device.\n");
- return NULL;
- }
- dev = PTR_ALIGN(p, NETDEV_ALIGN);
- dev->padded = (char *)dev - (char *)p;
对齐操作相关宏:
- /* linux-2.6.38.8/include/linux/kernel.h */
- #define ALIGN(x, a) __ALIGN_KERNEL((x), (a))
- #define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1)
- #define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask))
- #define PTR_ALIGN(p, a) ((typeof(p))ALIGN((unsigned long)(p), (a)))
(2)、动态分配per-CPU变量
- dev->pcpu_refcnt = alloc_percpu(int);
- if (!dev->pcpu_refcnt)
- goto free_p;
(3)、初始化硬件地址链表dev->dev_addrs,并把首元素赋给dev->dev_addr
- if (dev_addr_init(dev))
- goto free_pcpu;
(4)、初始化组播和单播地址链表
- dev_mc_init(dev);
- dev_uc_init(dev);
(5)、设置网络命名空间
- dev_net_set(dev, &init_net);
(6)、设置GSO最大值
- dev->gso_max_size = GSO_MAX_SIZE;
(7)、初始化各种链表
- INIT_LIST_HEAD(&dev->ethtool_ntuple_list.list);
- dev->ethtool_ntuple_list.count = 0;
- INIT_LIST_HEAD(&dev->napi_list);
- INIT_LIST_HEAD(&dev->unreg_list);
- INIT_LIST_HEAD(&dev->link_watch_list);
(8)、设置priv_flags值
- dev->priv_flags = IFF_XMIT_DST_RELEASE;
(9)、执行默认初始化函数(以太网设备默认为ether_setup)
- setup(dev);
(10)、初始化数据包发送队列
- dev->num_tx_queues = txqs;
- dev->real_num_tx_queues = txqs;
- if (netif_alloc_netdev_queues(dev))
- goto free_all;
(11)、初始化数据包接收队列
- dev->num_rx_queues = rxqs;
- dev->real_num_rx_queues = rxqs;
- if (netif_alloc_rx_queues(dev))
- goto free_all;
(12)、设置网络设备名称
- strcpy(dev->name, name);
2、注册网络设备
通过register_netdev函数把已完成部分初始化的net_device结构体变量(即某个网络设备实例)注册到Linux内核中,大致过程如下图:

下面将结合linux-2.6.38.8中的代码详细分析网络设备的注册过程。
(1)、获得rtnl信号量
- rtnl_lock();
(2)、分配网络设备名(即%d对应的数字)
- if (strchr(dev->name, '%')) {
- err = dev_alloc_name(dev, dev->name);
- if (err < 0)
- goto out;
- }
(3)、调用实际注册函数
- err = register_netdevice(dev);
3.1、初始化dev->addr_list_lock自旋锁并根据dev->type设置其类别
- spin_lock_init(&dev->addr_list_lock);
- netdev_set_addr_lockdep_class(dev);
3.2、调用init函数
- if (dev->netdev_ops->ndo_init) {
- ret = dev->netdev_ops->ndo_init(dev);
- if (ret) {
- if (ret > 0)
- ret = -EIO;
- goto out;
- }
- }
3.3、检测网络设备名是否有效
- ret = dev_get_valid_name(dev, dev->name, 0);
- if (ret)
- goto err_uninit;
3.4、为网络设备分配唯一的索引号
- dev->ifindex = dev_new_index(net);
- if (dev->iflink == -1)
- dev->iflink = dev->ifindex;
3.5、设置网络设备特性(dev->features)
- if ((dev->features & NETIF_F_HW_CSUM) &&
- (dev->features & (NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {
- printk(KERN_NOTICE "%s: mixed HW and IP checksum settings.\n",
- dev->name);
- dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM);
- }
- if ((dev->features & NETIF_F_NO_CSUM) &&
- (dev->features & (NETIF_F_HW_CSUM|NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {
- printk(KERN_NOTICE "%s: mixed no checksumming and other settings.\n",
- dev->name);
- dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM|NETIF_F_HW_CSUM);
- }
- dev->features = netdev_fix_features(dev->features, dev->name);
- if (dev->features & NETIF_F_SG)
- dev->features |= NETIF_F_GSO;
- dev->vlan_features |= (NETIF_F_GRO | NETIF_F_HIGHDMA);
3.6、通过通知链告知内核其他子系统某种事件的发生(如注册网络设备)
- ret = call_netdevice_notifiers(NETDEV_POST_INIT, dev);
- ret = notifier_to_errno(ret);
- if (ret)
- goto err_uninit;
- ret = call_netdevice_notifiers(NETDEV_REGISTER, dev);
- ret = notifier_to_errno(ret);
- if (ret) {
- rollback_registered(dev);
- dev->reg_state = NETREG_UNREGISTERED;
- }
3.7、创建网络设备在sysfs文件系统中的入口
- ret = netdev_register_kobject(dev);
- if (ret)
- goto err_uninit;
3.8、设置网络设备为已注册状态
- dev->reg_state = NETREG_REGISTERED;
3.9、设置网络设备状态为可用
- set_bit(__LINK_STATE_PRESENT, &dev->state);
3.10、初始化网络设备的队列规则
- dev_init_scheduler(dev);
3.11、增加网络设备的引用计数
- dev_hold(dev);
3.12、加入到设备链表(如dev->dev_list、dev->name_hlist、dev->index_hlist)
- list_netdevice(dev);
3.13、发送netlink(RFC 3549协议)信息
- if (!dev->rtnl_link_ops ||
- dev->rtnl_link_state == RTNL_LINK_INITIALIZED)
- rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U);
网络驱动移植之解析Linux网络驱动的基本框架的更多相关文章
- 网络驱动移植之简述CS8900A网络芯片的基本原理
CS8900A数据手册:http://www.cirrus.com/cn/products/cs8900a.html 1.概述 CS8900A是CIRRUS LOGIC公司生产的低功耗.性能优越的16 ...
- 网络数据包头部在linux网络协议栈中的变化
接收时使用skb_pull()不断去掉各层协议头部:发送时使用skb_push()不断添加各层协议头部. 先说说接收: * eth_type_trans - determine the packet' ...
- linux 设备驱动概述
linux 设备驱动概述 目前,Linux软件工程师大致可分为两个层次: (1)Linux应用软件工程师(Application Software Engineer): 主要利用C库函数和 ...
- Linux 网络子系统
今天记录一下Linux网络子系统相关的东西. 因为感觉对这一块还是有一个很大的空白,这件事情太可怕了. 摘抄多份博客进行总结一下Linux网络子系统的相关东西. 一. Linux网络子系统体系结构 L ...
- 理解 Linux 网络栈(1):Linux 网络协议栈简单总结
本系列文章总结 Linux 网络栈,包括: (1)Linux 网络协议栈总结 (2)非虚拟化Linux环境中的网络分段卸载技术 GSO/TSO/UFO/LRO/GRO (3)QEMU/KVM + Vx ...
- AM335x(TQ335x)学习笔记——WM8960声卡驱动移植
经过一段时间的调试,终于调好了TQ335x的声卡驱动.TQ335x采用的Codec是WM8960,本文来总结下WM8960驱动在AM335x平台上的移植方法.Linux声卡驱动架构有OSS和ALSA两 ...
- I.MX6 AW-NB177NF WIFI 驱动移植问题
/******************************************************************************** * I.MX6 AW-NB177NF ...
- 网络管理监视很重要!学编程的你知道哪些不错的网络监控工具?2020 最好的Linux网络监控工具分享给你
以下文章来源于新钛云服 翻译:侯明明 前言 虽然这个清单包含开源的和闭源的产品,但它着重于介绍基于 Linux 的网络监控工具, 少数常用工具只能在 Windows,Pandora 或其他系统上运行, ...
- MySQL 调优基础(五) Linux网络
1. TCP/IP模型 我们一般知道OSI的网络参考模型是分为7层:“应表会传网数物”——应用层,表示层,会话层,传输层,网络层,数据链路层,物理层.而实际的Linux网络层协议是参照了OSI标准,但 ...
随机推荐
- ADO.NET怎删改+vs 2013 C#
一.删除 string constr = "server=.;database=test;uid=sa;pwd=sa"; SqlConnection myco ...
- ubuntu各种软件安装-装机整套系列
首先声明,本人系统ubuntu 14.04.1 LTS, 以下所有软件均安装于该系统. 一. 首先在windows下删除ubuntu,删除方法如下: 1.进入win7,下载个软件MbrFix,放在C: ...
- Java throw throws try...catch区别
java里的异常多种多样,这是一种非常有用的机制,它能帮助我们处理那些我们未知的错误,在java里,关于异常的有throw throws,还有一个try catch 程序块.接下来我们挨个看看这几个的 ...
- PHP视频教程 字符串处理函数(三)
字符串替换函数: str_replace() 替换字符串或数组元素,区分大小,第四个参数可选用于统计替换次数. str_ireplace() 不区分大小写替换 字符串函数比较 strcmp()比较字符 ...
- Hibernate fetching strategies(抓取策略)
抓取策略(fetching strategies)是指:当应用程序需要在(Hibernate实体对象图的)关联关系间进行导航的时候,Hibernate如何获取关联对象的策略.抓取策略可以在O/R映射的 ...
- Hibernate *.hbm.xml对象关系映射文件详解
在hibernate中表与pojo对象是一一对应的,通过hbm文件将数据库表与实体关联起来,本文将对hbm文件进行介绍. pojo对象:提供了公共的无参构造方法 ,通过反射产生对象. ...
- 几何:pick定理详解
一.概念 假设P的内部有I(P)个格点,边界上有B(P)个格点,则P的面积A(P)为:A(P)=I(P)+B(P)/2-1. 二.说明 Pick定理主要是计算格点多边形(定点全是格点的不自交图形)P的 ...
- 「BZOJ 4502」串
「BZOJ 4502」串 题目描述 兔子们在玩字符串的游戏.首先,它们拿出了一个字符串集合 \(S\),然后它们定义一个字符串为"好"的,当且仅当它可以被分成非空的两段,其中每一段 ...
- BZOJ 3238: [Ahoi2013]差异 后缀自动机 树形dp
http://www.lydsy.com/JudgeOnline/problem.php?id=3238 就算是全局变量,也不要忘记,初始化(吐血). 长得一副lca样,没想到是个树形dp(小丫头还有 ...
- 【MPI】矩阵向量乘法
输入作乘法的次数K 然后输入矩阵和向量的维度n 然后输入一个n维向量 然后输入K个n阶方阵 程序会给出该向量连续与此K个方阵做乘法后的结果 主要用了MPI_Gather, MPI_Allgather, ...