网络驱动移植之解析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. /* linux-2.6.38.8/net/ethernet/eth.c */
  5. struct net_device *alloc_etherdev_mqs(int sizeof_priv, unsigned int txqs,
  6. unsigned int rxqs)
  7. {
  8. return alloc_netdev_mqs(sizeof_priv, "eth%d", ether_setup, txqs, rxqs);
  9. }
  10. void ether_setup(struct net_device *dev)
  11. {
  12. dev->header_ops      = &eth_header_ops;
  13. dev->type        = ARPHRD_ETHER;
  14. dev->hard_header_len     = ETH_HLEN;
  15. dev->mtu     = ETH_DATA_LEN;
  16. dev->addr_len        = ETH_ALEN;
  17. dev->tx_queue_len    = 1000; /* Ethernet wants good queues */
  18. dev->flags       = IFF_BROADCAST|IFF_MULTICAST;
  19. memset(dev->broadcast, 0xFF, ETH_ALEN);
  20. }

以前各类网络设备的分配函数(如以太网设备的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. p = kzalloc(alloc_size, GFP_KERNEL);
  8. if (!p) {
  9. printk(KERN_ERR "alloc_netdev: Unable to allocate device.\n");
  10. return NULL;
  11. }
  12. dev = PTR_ALIGN(p, NETDEV_ALIGN);
  13. 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. #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.\n",
  4. dev->name);
  5. dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM);
  6. }
  7. if ((dev->features & NETIF_F_NO_CSUM) &&
  8. (dev->features & (NETIF_F_HW_CSUM|NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM))) {
  9. printk(KERN_NOTICE "%s: mixed no checksumming and other settings.\n",
  10. dev->name);
  11. dev->features &= ~(NETIF_F_IP_CSUM|NETIF_F_IPV6_CSUM|NETIF_F_HW_CSUM);
  12. }
  13. dev->features = netdev_fix_features(dev->features, dev->name);
  14. if (dev->features & NETIF_F_SG)
  15. dev->features |= NETIF_F_GSO;
  16. 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. ret = call_netdevice_notifiers(NETDEV_REGISTER, dev);
  6. ret = notifier_to_errno(ret);
  7. if (ret) {
  8. rollback_registered(dev);
  9. dev->reg_state = NETREG_UNREGISTERED;
  10. }

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);

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-07-31 04:18:47

网络驱动移植之解析Linux网络驱动的基本框架的相关文章

网络数据包头部在linux网络协议栈中的变化

接收时使用skb_pull()不断去掉各层协议头部:发送时使用skb_push()不断添加各层协议头部. 先说说接收: 150 * eth_type_trans - determine the packet's protocol ID. 151 * @skb: received socket data 152 * @dev: receiving network device 153 * 154 * The rule here is that we 155 * assume 802.3 if th

Linux设备驱动开发学习(1):前言

虽然网络上已经有很多Linux设备驱动开发学习的文章和博客,更是有很多经典的Linux设备驱动开 发的书籍,写这些博文似乎意义不大,但把自己的学习过程.学习心得记录下来,一方面有着强化巩固的 意义,另一方面也是把所学知识转化为自己所得的必要途径之一,这是我写这些的博客的原始动力.

理解 Linux 网络栈 (Linux networking stack)(1):Linux 网络协议栈简单总结

本系列文章总结 Linux 网络栈,包括: (1)Linux 网络协议栈总结 (2)非虚拟化Linux环境中的网络分段卸载技术 GSO/TSO/UFO/LRO/GRO (3)QEMU/KVM虚拟化 Linux 环境中的网络分段卸载技术 GSO/TSO/UFO/LRO/GRO 1. Linux 网络路径 1.1 发送端 1.1.1 应用层 (1) Socket 应用层的各种网络应用程序基本上都是通过 Linux Socket 编程接口来和内核空间的网络协议栈通信的.Linux Socket 是从

AM335x(TQ335x)学习笔记——WM8960声卡驱动移植

经过一段时间的调试,终于调好了TQ335x的声卡驱动.TQ335x采用的Codec是WM8960,本文来总结下WM8960驱动在AM335x平台上的移植方法.Linux声卡驱动架构有OSS和ALSA两种架构,目前最常用的架构是ALSA,本文也使用ALSA架构对WM8960驱动进行移植. ASoC是对ALSA驱动架构的进一步封装.ASoC将ALSA驱动中的各模块抽象为三部分:Platform.Codec和Machine.Platform主要是平台硬件驱动,包括SoC的IIS模块.DMA等,在本文中

I.MX6 AW-NB177NF WIFi 驱动移植问题

/******************************************************************************** * I.MX6 AW-NB177NF WIFi 驱动移植问题 * 说明: * 之前驱动移植,但看不到有wlan设备产生,于是感觉是wifi驱动出了问题,于是开始 * 分析厂家提供的wifi驱动,发现id对应的信息不对,经过与FAE确认之后并验证,确实是 * 这个问题导致的. * * 2016-6-20 深圳 南山平山村 曾剑锋 ***

简述linux网络配置

linux是一种专为服务器打造的操作系统,服务器的主要功能是提供与网络相关服务.因此配置linux网络属性和查看网络属性是linux系统管理员的必备技能.linux的网络功能是由内核中的tcp/ip协议栈提供的.配置linux网络属性主要需要配置ip/netmask.gateway.DNS.route.hostname相关信息,正确的配置这些信息之后,linux服务器就可以接入互联网中了. 配置linux网络属性有临时生效和永久生效(下一次开机时生效)两种方式,下面我来介绍一下如何配置linux

Linux网络配置基础篇

Linux网络配置基础篇 一.如何实现linux网络通信? 1)指定IP/NETMASK可实现本地通信: 2)指定路由(网关)可实现跨网络通信: 3)指定DNS服务器地址可实现基于主机名的通信, 主DNS服务器地址(当前服务器不在线时,启用备用DNS服务器地址) 备用DNS服务器地址 第三备份DNS服务器地址 二.配置网络方式及网络接口命名 linux 网络属于内核的功能, 配置方式: 静态指定:使用命令直接指定或修改配置文件 动态分配:依赖于本地网络中有DHCP服务 网络接口命名方式: 传统命

linux网络协议栈--路由流程分析

转:http://blog.csdn.net/hsly_support/article/details/8797976 来吧,路由 路由是网络的核心,是linux网络协议栈的核心,我们找个入口进去看看 还记得在笔记5-IP层的处理1中ip_rcv_finish走到过一个岔口 ->ip_rcv_finish() ->ip_route_input()  查找路由信息 ->if (iph->ihl > 5 && ip_rcv_options(skb)) 如果IP头部

在linux内核文件中添加自己的驱动,添加自己的linux驱动,编译自己的linux驱动程序方法和例子图文

此文章为done原创,转载请写明出处,尊重原创. 写这个文章,我参考了网上的一些博客: http://bbs.chinaunix.net/thread-3634524-1-1.html http://www.bkjia.com/gjrj/800182.html 也参考了参考了 <linux驱动开发详解>3.4章节,实现建立自己驱动目录. 文章中的驱动范例是:linux设备驱动开发详解第二版 的 global mem tow,宋宝华主编的书本光盘源码 在我们学习了linux的驱动之后,我们都想编