网络驱动开发样例snull详解(基于3.10.0)

简介:

  网络驱动开发样例snull详解(基于3.10.0)

  本章素材为ldd3书中的网络驱动snull部分。由于现在内核的更新,导致其在最新的内核中无法编译该网络驱动,需要针对修改,顾为此文(内核3.10.0)。另外,网络驱动的开发对理解linux内核网络协议栈有较大帮助,文中涉及设备虽为虚拟设备,但提供了网络驱动必备的知识。

1.1.1 snull编译问题

1、CFLAGS变为EXTRA_CFLAGS

2、config.h头文件替换注释掉

3、struct net_device 的结构在新版本内核中发生了变化,删去了quota成员;将(open,stop,set_config,hard_start_xmit等)封装进了 struct net_device_ops;将(poll,weight等)封装进了 struct napi_struct;将(hard_header,hard_header_cache,rebuild等)封装进了struct header_ops。并对部分函数名和参数进行了变动。

4、netif_rx_complete和netif_rx_schedule属于poll机制中的函数,接口名称已经变为napi_complete和napi_schedule。

       具体修改过的文件可以从:

https://download.csdn.net/download/notbaron/10285251中下载

1.1.2 snull使用

  回环地址的实现在drivers/net/loopback.c文件中。

在/etc/network文件中增加如下内容:

snullnet0 172.168.0.0

snullnet1 172.168.1.0

在/etc/hosts中:

172.168.0.1 local0

172.168.0.2 remote0

172.168.1.2 local1

172.168.1.1 remote1

配置虚拟网络设备:sn0和sn1.

#ifconfig sn0 local0

#ifconfig sn1 local1

测试虚拟设备:

#ping local0

#ping local1

 

1.1.3 驱动链接到内核

网络驱动通过模块初始化函数注册的方式与字符和块驱动是不同的,驱动为接口在一个全局的网络设备列表里插入一个数据结构。

每个接口由一个结构net_device项描述,定义在

include/linux/netdevice.h文件中。

通过内核函数alloc_netdev来进行分配,定义如下:

3.10.0-514中为如下:

struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,

                                    void (*setup)(struct net_device *),

                                    unsigned int txqs, unsigned int rxqs);

  在4.14.14的内核中如下:

struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,

                                    unsigned char name_assign_type,

                                    void (*setup)(struct net_device *),

                                    unsigned int txqs, unsigned int rxqs);

其中sizeof_priv表示私有数据区的大小。name是接口的名字。setup是设备初始化后的回调函数指针。如果是在该版本内核下那么snull.c中源码就存在问题了。

也可以直接调用alloc_etherdev(定义在文件net/ethernet/eth.c)函数,该函数也是调用alloc_netdev,只是把相关参数给你设置好了,例如回调函数为ether_setup,名字为eth开头。初始化函数主要是对net_device结构的例行初始化,大部分是存储我们的各种驱动函数指针。主要特点是禁用的ARP,因为是模拟的远程系统并不存在,所以去掉arp.

 dev->flags  |= IFF_NOARP; (定义在文件include/uapi/linux/if.h  中,4.14.14代码中可以看到该变量可以被sysfs进行设置,但是不是持久的,重启后失效)

       完成net_device结构初始化后,将该结构传递给register_netdev,来注册设备。

        for (i = 0; i < 2;  i++)

                if ((result = register_netdev(snull_devs[i])))

                        printk("snull: error %i registering device \"%s\"\n",

                                        result, snull_devs[i]->name);

                else

                        ret = 0;

       分配net_device之后,需要进行初始化。因为net_device 结构庞大,内核负责以太网范围中的缺省值,通过ether_setup函数(由alloc_etherdev调用)。

当初始化完设备后,调用register_netdev时候,驱动可能马上被调用来操作设备。

       其中函数snull_rx_ints用于使能接收中断(设置snull_priv中的使能变量)。

   snull_setup_pool设置包(snull_packet)缓冲池,是一个单向链表结构。

         netif_napi_add函数依赖于模块参数use_napi,主要是实现实现napi结构的初始化以及与设备的关联。定义如下net/core/dev.c

void netif_napi_add(struct net_device *dev, struct napi_struct *napi,

                    int (*poll)(struct napi_struct *, int), int weight)

1.1.4 卸载驱动

调用unregister_netdev调用,从系统中去除接口,free_netdev归还net_device结构给内核。释放初始化时候申请的缓冲区。

       调用函数:snull_cleanup

释放缓冲区的函数为:snull_teardown_pool,该函数在注销了设备后才能进行.而且需要在net_device结构返回给系统之间进行,因为一旦调用free_netdev就不能再使用设备或者自己的私有数据做任何引用了。

1.1.5 设备方法

除了设备的初始化,注册和卸载方法之外,还需要定义设备相关的方法。网络设备能声明操作它的函数。

       基本方法包括那些必须能够使用接口的,例如。

int dev_open(struct net_device *dev);

struct sk_buff *dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,

                                    struct netdev_queue *txq, int *ret);

1.1.5.1  基本方法

打开函数请求任何它需要的系统资源并且告知接口启动;关闭接口并释放系统资源。

int dev_open(struct net_device *dev);

  硬件地址在open是复制到设备.同时启动接口的发送队列。内核提供函数netif_start_queue来启动队列,定义在include/linux/netdevice.h文件中

int snull_open(struct net_device *dev)

{

        memcpy(dev->dev_addr, "\0SNUL0", ETH_ALEN);

        if (dev == snull_devs[1])

                dev->dev_addr[ETH_ALEN-1]++; /* \0SNUL1 */

        netif_start_queue(dev);

        return 0;

}

    netif_start_queue函数来告诉内核网络子系统,现在可以开始数据包的发送。

    在虚拟设备中open操作动作很有限,就是初始化设备的MAC地址,调用netif_start_queue启动队列。

    关闭函数就是逆操作:

int snull_release(struct net_device *dev)

{

        netif_stop_queue(dev); /* can't transmit any more */

        return 0;

}

    调用netif_stop_queue后,就不能再继续发送了。在接口关闭时调用。

 

1.1.5.2  可选方法

 例如:NAPI驱动提供的方法,在中断关闭下,用来在查询模式下操作接口。

       change_mtu方法支持改变硬件地址的能力。

1.1.5.3  公用方法

例如:由接口使用来持有有用的状态信息。有些是ifconfig和netstat用来提供给用户关于当前配置的信息。

       watchdog_timeo是设置的一个超时值,超时后调用驱动tx_timeout函数。

 

1.1.6 报文传送

驱动可以在模块加载时或者内核启动时探测接口。但是在接口能承载报文前,内核必须打开并分配一个地址给接口。使用的命令为ifconfig命令。需要做两个步骤,一个通过ioctl(SIOSCIFADDR)是安排地址,另一个通过iocatl(SIOCSIFFLAGS)设置IFF_UP来打开接口。

  关闭时候调用iocatl(SIOCSIFFLAGS)来清除IFF_UP。

       其发送函数为snull_tx,函数中保存发送时间,同时保存该skb结构体用于在中断时候释放。snull_tx最后调用snull_hw_tx函数,该函数本与硬件相关,而本snull是在虚拟设备上的,所以未有硬件相关细节,只是将包转发送其他的虚拟接口上,该函数的功能就是snull驱动的主要功能。

       监视检测包的合法性,然后修改包中的源和目的ip地址,然后重新计算ip检验值。

       其中函数snull_get_tx_buffer从缓冲池中获取包,过程中会进制软中断。如果缓冲池为空,则调用netif_stop_queue函数。

   获取后,往获取的结构体中复制数据包内容,然后调用snull_enqueue_buf

snull_enqueue_buf函数,将包放入到发送队列中(定义在私有数据结构snull_priv中的*rx_queue),此过程中会禁止软中断。

如果中断使能,则调用:snull_interrupt(0, dest, NULL);在未定义use_napi时使用snull_regular_interrupt函数,否则使用snull_napi_interrupt函数。定义分别如下:

static void snull_regular_interrupt(int irq, void *dev_id, struct pt_regs *regs)

static void snull_napi_interrupt(int irq, void *dev_id, struct pt_regs *regs)

中断中会去检查是接收中断还是发送中断。终端中会更新统计数值,并释放skb,最后调用snull_release_buffer函数。snull_release_buffer函数释放pkt缓存。

       snull_napi_interrupt函数,在接收触发中断时,禁用中断(snull_regular_interrupt中并未禁用中断),然后调用napi_schedule函数使用轮询函数snull_poll。在发送时,统计包的发送个情况,调用dev_kfree_skb函数来释放skb.

   snull_poll循环从发送队列中获取需要发送的包。最后使用napi_complete函数来结束查询,并使能中断。

       超时时候调用驱动的tx_timeout方法。超时设置在watchdog_timeo即可,驱动不用去检测,由网络层来最终调用这个tx_timeout函数。

 

1.1.7 接收发送

网络的接收报文比发送要难一点。中断处理会调用函数snull_rx函数。

       先分配一个缓存区来保存报文,就是skb,调用的函数是dev_alloc_skb,该函数的返回值必须检查,不然可能会遭殃。然后复制包内容到分配的skb内存结构中。

       驱动更新统计计数来记录收到的一个报文。统计结构主要由以下几个成员组成:rx_packet,rx_bytes和tx_bytes,表示收到的报文数目,发送的数目和发送的字节总数。

       最后有netif_rx函数将skb给上层。

1.1.8 链接状态改变

  大部分涉及实际的物理连接的网络技术提供有一个载波状态。当驱动检测到一个设备载波丢失,它应当调用netif_carrier_off来通知内核这个改变。当载波回来时,应当调用netif_carrier_on。

1.1.9 统计信息

调用net_device_stats函数可以返回统计信息。

1.1.10          查看当前网卡驱动

查看网卡生产厂商和信号

# lspci | grep -i ethernet

使用lspci -vvv可以查看到驱动信息。当然pci设备也是可以查看到的。

 

 

 

1.1.11          查看驱动实例

appletalk驱动如下:

drivers/net/appletalk/cops.c

e1000驱动如下:

drivers/net/ethernet/intel/e1000/

 

 

1.1.12          加载驱动参数

module_param(name, type, perm);

其中,name:表示参数的名字;

type:表示参数的类型;

perm:表示参数的访问权限;

 

目录
相关文章
|
2月前
|
安全 网络安全 Android开发
安卓与iOS开发:选择的艺术网络安全与信息安全:漏洞、加密与意识的交织
【8月更文挑战第20天】在数字时代,安卓和iOS两大平台如同两座巍峨的山峰,分别占据着移动互联网的半壁江山。它们各自拥有独特的魅力和优势,吸引着无数开发者投身其中。本文将探讨这两个平台的特点、优势以及它们在移动应用开发中的地位,帮助读者更好地理解这两个平台的差异,并为那些正在面临选择的开发者提供一些启示。
116 56
|
2月前
|
C++
C++ Qt开发:QUdpSocket网络通信组件
QUdpSocket是Qt网络编程中一个非常有用的组件,它提供了在UDP协议下进行数据发送和接收的能力。通过简单的方法和信号,可以轻松实现基于UDP的网络通信。不过,需要注意的是,UDP协议本身不保证数据的可靠传输,因此在使用QUdpSocket时,可能需要在应用层实现一些机制来保证数据的完整性和顺序,或者选择在适用的场景下使用UDP协议。
75 2
|
2月前
|
小程序 数据安全/隐私保护
Taro@3.x+Vue@3.x+TS开发微信小程序,网络请求封装
在 `src/http` 目录下创建 `request.ts` 文件,并配置 Taro 的网络请求方法 `Taro.request`,支持多种 HTTP 方法并处理数据加密。
Taro@3.x+Vue@3.x+TS开发微信小程序,网络请求封装
|
2月前
|
数据采集 存储 前端开发
豆瓣评分9.0!Python3网络爬虫开发实战,堪称教学典范!
今天我们所处的时代是信息化时代,是数据驱动的人工智能时代。在人工智能、物联网时代,万物互联和物理世界的全面数字化使得人工智能可以基于这些数据产生优质的决策,从而对人类的生产生活产生巨大价值。 在这个以数据驱动为特征的时代,数据是最基础的。数据既可以通过研发产品获得,也可以通过爬虫采集公开数据获得,因此爬虫技术在这个快速发展的时代就显得尤为重要,高端爬虫人才的收人也在逐年提高。
Qt开发网络嗅探器02
Qt开发网络嗅探器02
|
2月前
|
存储 运维 监控
Qt开发网络嗅探器01
Qt开发网络嗅探器01
|
2月前
|
网络协议 容器
Qt开发网络嗅探器03
Qt开发网络嗅探器03
|
2月前
|
机器学习/深度学习 数据采集 人工智能
未来的守护神:AI驱动的网络安全之盾,如何用智慧的光芒驱散网络黑暗势力?揭秘高科技防御系统背后的惊天秘密!
【8月更文挑战第13天】随着网络技术的发展,网络安全问题愈发严峻,传统防御手段已显乏力。本文探讨构建AI驱动的自适应网络安全防御系统,该系统能自动调整策略应对未知威胁。通过数据采集、行为分析、威胁识别及响应决策等环节,利用Python工具如Scapy、scikit-learn和TensorFlow实现网络流量监控、异常检测及自动化响应,从而提升网络安全防护的效率和准确性。随着AI技术的进步,未来的网络安全防御将更加智能和自动化。
42 6
|
2月前
|
安全 网络安全 数据安全/隐私保护
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享安卓与iOS开发中的线程管理比较
【8月更文挑战第30天】本文将探讨网络安全与信息安全的重要性,并分享关于网络安全漏洞、加密技术和安全意识的知识。我们将了解常见的网络攻击类型和防御策略,以及如何通过加密技术和提高安全意识来保护个人和组织的信息安全。
|
2月前
|
安全 网络安全 Android开发
探索安卓开发之旅:从新手到专家网络安全与信息安全:防范网络威胁,保护数据安全
【8月更文挑战第29天】在这篇技术性文章中,我们将踏上一段激动人心的旅程,探索安卓开发的世界。无论你是刚开始接触编程的新手,还是希望提升技能的资深开发者,这篇文章都将为你提供宝贵的知识和指导。我们将从基础概念入手,逐步深入到安卓开发的高级主题,包括UI设计、数据存储、网络通信等方面。通过阅读本文,你将获得一个全面的安卓开发知识体系,并学会如何将这些知识应用到实际项目中。让我们一起开启这段探索之旅吧!
下一篇
无影云桌面