网络驱动程序snull

2023-11-05

由于下次开会要做报告,所以就先把字符设备驱动程序那块搁浅了,先来复习下网络驱动这块.虽然以前看过,学习过网络驱动的相关内容,但是当再次学习这块的时候,仍然有新的收获,或许是时间长不看了,有些东西的印象浅了,或者应了那句话:温故而知新.ok,下面步入正题.
一,snull
为了加深对网络驱动的理解,下面以一个 基于内存的模块化接口实例来说明如何编写网路驱动程序,称之snull.为了简化讨论,做如下假设:
1,snull接口使用以太网硬件协议.
2,snull接口只传输ip数据包.
3,snull不依赖于任何硬件.
snull的设计:
模拟了与远程主机会话的过程.snull模块创建了两个接口,通过 其中一个接口传输的任何数据,都将出现在另外一个接口上.示意图如下:
如图所示,snullnet0是连接到sn0接口的网络,snullnet1是连接到sn1接口的网络.local0是sn0的ip地址,local1是sn1的ip地址.romote0属于snullnet0.romote1属于snullnet1.
为了达到我们的设计目标----从一个接口发送的数据将出现在另一个接口上.在赋予ip地址时需注意以下原则:
local0的主机部分和remote1的主机部分一样,而local1的主机部分和remote0的主机部分一样.
基于以上假设,加入我们ip地址的分配如下:
snullnet0: 192.168.0.0
snullnet1: 192.168.1.0
local0: 192.168.0.1
remote0: 192.168.0.2
local1: 192.168.1.2
remote1: 192.168.1.1
这样,为实现咱们的目的,snull的发送函数只需要把源地址和目的地址的网络地址部分的0==>1,1==>0就可以了.
eg:模拟一个数据包发往remote0,则
源地址:192.168.0.1 目的地址:192.168.0.2 经过snull0后变为:
源地址:192.168.1.1 目的地址:192.168.1.2
这样数据包就会发送到snull1接口,而源地址为192.168.1.1,则模拟了从remote1发送数据到local1的过程.
ok,下面来看看如何编写网络驱动程序,来实现我们的snull.以增进我们对网络驱动程序结构的理解.
二,设备的分配,初始化与注册
显然,想让一个设备可用,必须要将其注册到内核中去.而在把我们的设备注册到内核中之前,有两件事要完成:给设备分配内存空间并初始化.
这里,每个接口用一个net_device结构来描述.其定义在<linux/netdevice.h>中.因为在我们的例子中,有两个接口,snull0和snull1.所以snull在一个数组里保存了两个指向该结构的指针.
struct net_device *snull_devs[2];
分配内存空间:
函数原型:
struct net_device * alloc_netdev(int sizeof_priv, const char *name, void (*setup)(struct net_device *) )
snull的相应代码如下:

snull_devs[0]=alloc_netdev(sizeof(structsnull_priv),"sn%d",snull_init);
snull_devs[1]=alloc_netdev(sizeof(structsnull_priv),"sn%d",snull_init);
if(snull_devs[0]==NULL||snull_devs[1]==NULL)
gotoout;

这里将下一个可用的接口号代替%d.

网络子系统针对alloc_netdev函数,为不同种类的接口封装了许多函数.最常用的是alloc_etherdev,它在<linux/etherdevice.h>中定义.比如说"

以太网设备:alloc_etherdev; 光纤通道设备:alloc_fcdev; FDDI设备:alloc_fddidev; 令牌环设备: alloc_trdev.

现在已经为设备分配了内存空间,下面则应该将其进行初始化.

设备的初始化:

设备初始化最主要的作用是完成了设备操作的对应关系.即把内核所能看见的操作(dev->open)与我们自己所定义的函数(snull_open)进行对应.snull的初始化代码snull_init的核心代码如下:

ether_setup(dev);/*因为我们将snull设计为以太网设备,所以可以用此函数初始化我们的设备.此函数会以'以太网'方式来初始化一些成员.*/

dev->open=snull_open;
dev->stop=snull_release;
dev->set_config=snull_config;
dev->hard_start_xmit=snull_tx;
dev->do_ioctl=snull_ioctl;
dev->get_stats=snull_stats;
dev->rebuild_header=snull_rebuild_header;
dev->hard_header=snull_header;
dev->tx_timeout=snull_tx_timeout;
dev->watchdog_tiomeo=timeout;

比如,当网络子系统调用ifconfig打开一个网络接口时,内核会调用dev->open,而由我们的初始化函数可以看到,我们将dev->open映射为snull_open.所以将执行snull_open操作.同理,当网络子系统需要通过一个网路接口发送数据时,会调用dev->hard_start_xmit函数,而该函数则会对应到我们自定义的snull_tx函数,所以真正执行发送数据的函数是我们自己定义的函数,这里的作用跟字符设备驱动程序的file_operations结构体的作用差不多.

上面完成了内存分配和初始化的工作,下面要做的就是把我们的设备注册到内核中去.

网络设备的注册:

网络接口的注册是通过函数register_netdev来完成的.每一个注册过的netdevice都保存在一个由dev_base指向的链表中.下面是snull的注册部分代码:

for(i=0;i<2;i++)
{
if((result=register_netdev(snull_devs[i])))/*注册成功时返回0*/
printk("snull register error../n");
}

注意:当调用register_netdev函数后,就可以调用驱动程序操作设备了.所以,必须在初始化一切事情结束后再进行注册.也就是说,应该把一切都准备好了以后再进行设备的注册.

相应的,如果我们不在使用网络接口,则应该将其注销掉并释放相关的内存.相应的注销函数为unregister_netdev.


三,设备方法

在经过上面的操作后,我们的设备已经注册到网络子系统中了,下面来看看一个网络接口的每个设备方法都应该完成什么样的工作.

int (*open)(struct net_device *dev);

打开接口.在ifconfig激活接口时,接口将被打开.open函数应该注册所有的系统资源(I/O端口,IRQ,DMA等等),打开硬件,并对设备执行其他所需的设置与操作.

int (*stop)(struct net_device *dev);

停止接口.在接口终止时应该将其停止.在该函数中执行的操作与打开时执行的操作相反.

int (*hard_start_xmit)(struct sk_buff *skb,struct net_device *dev);

数据包的发送函数.完整的数据包(协议头和数据)包含在一个套节字缓冲区中(sk_buff).

int (*hard_header) (struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr, void *saddr, unsigned len);

该函数根据先前检索到的源和目的硬件地址建立硬件头.应该在调用hard_start_xmit之前被调用.eth_header是以太网类型接口的默认函数,ether_setup将该成员赋值成eth_header.

int (*rebuild_header)(struct sk_buff *skb);

该函数用来在传输数据包之前,完成arp解析之后,重新构建硬件头.

void (*tx_timeout)(struct net_device *dev);

如果数据包的传输在合理的时间段内失败,则网络子系统调用此函数,负责解决问题并重新开始传输数据包.

structnet_device_stats*(*get_stats)(struct net_device *dev);

当应用程序需要获得接口的统计信息时,将调用该函数.例如,在运行ifconfig或netstat -i时将利用该方法.

int (*do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);

执行接口特有的ioctl命令.如果接口不需要实现任何接口特有的命令,则net_device中的对应成员可保持为NULL.

....


四,snull网络接口的设备方法

下面通过snull的几个设备方法的具体实现代码,来增强对其的了解.

打开操作

intsnull_open(structnet_device*dev)
{
/* request_region(), request_irq(), .... (like fops->open) */

/*
* Assign the hardware address of the board: use "/0SNULx", where
* x is 0 or 1. The first byte is '/0' to avoid being a multicast
* address (the first byte of multicast addrs is odd).
*/
memcpy(dev->dev_addr,"/0SNUL0",ETH_ALEN);
if(dev==snull_devs[1])
dev->dev_addr[ETH_ALEN-1]++;/* /0SNUL1 */
netif_start_queue(dev);
return0;
}

首先,在接口能够和外界通讯之前,要将mac地址从硬件设备复制到dev->dev_addr。硬件地址可以在打开期间拷贝到设备中。snull设计成是与硬件无关的,所以这里赋给了一个虚拟的地址。一旦接口准备好开始传输数据后,open方法应该启动接口的传输队列。

由于与硬件无关,所以snull的open操作做的事情很少。对stop而言,也是这样,它是open操作的逆过程。

关闭操作

intsnull_release(structnet_device*dev)
{
/* release ports, irq and such -- like fops->close */

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

注意,在接口被关闭时,必须调用netif_stop_queue函数,用来停止接口的队列传输。

数据包的传输

当一个接口被打开之后,就可以用来传输数据了,相关函数如下:

intsnull_tx(structsk_buff*skb,structnet_device*dev)
{
intlen;
char*data,shortpkt[ETH_ZLEN];/*ETH_ZLEN为snull支持传输的最小数据长度*/
structsnull_priv*priv=netdev_priv(dev);

data=skb->data;
len=skb->len;
if(len<ETH_ZLEN){
memset(shortpkt,0,ETH_ZLEN);
memcpy(shortpkt,skb->data,skb->len);
len=ETH_ZLEN;
data=shortpkt;
}
dev->trans_start=jiffies;/* save the timestamp */

/* Remember the skb, so we can free it at interrupt time */
priv->skb=skb;

/* actual deliver of data is device-specific, and not shown here */
snull_hw_tx(data,len,dev);

return0;/* Our simple device can not fail */
}

通过代码我们可以看到,这个函数只是对数据包的长度做了检查,然后调用硬件相关的函数进行数据包的传输。

/*
* Transmit a packet (low level interface)
*/
staticvoidsnull_hw_tx(char*buf,intlen,structnet_device*dev)
{
/*
* This function deals with hw details. This interface loops
* back the packet to the other snull interface (if any).
* In other words, this function implements the snull behaviour,
* while all other procedures are rather device-independent
*/
structiphdr*ih;
structnet_device*dest;
structsnull_priv*priv;
u32*saddr,*daddr;
structsnull_packet*tx_buffer;

/* I am paranoid. Ain't I? */
if(len<sizeof(structethhdr)+sizeof(structiphdr)){
printk("snull: Hmm... packet too short (%i octets)/n",
len);
return;
}
/*
* Ethhdr is 14 bytes, but the kernel arranges for iphdr
* to be aligned (i.e., ethhdr is unaligned)
*/
ih=(structiphdr*)(buf+sizeof(structethhdr));/*获得ip头的位置,注意此方法*/
saddr=&ih->saddr;
daddr=&ih->daddr;

((u8*)saddr)[2]^=1;/* change the third octet (class C) */
((u8*)daddr)[2]^=1;/* 进行异或运算,相同为0,不同为1。*/

ih->check=0;/* and rebuild the checksum (ip needs it) */
ih->check=ip_fast_csum((unsignedchar*)ih,ih->ihl);/* 重新计算ip校验和,正常情况下还应该重新计算tcp头校验和,icmp头检验和。。 */

if(dev==snull_devs[0])
PDEBUGG("%08x:%05i --> %08x:%05i/n",
ntohl(ih->saddr),ntohs(((structtcphdr*)(ih+1))->source),
ntohl(ih->daddr),ntohs(((structtcphdr*)(ih+1))->dest));
else
PDEBUGG("%08x:%05i <-- %08x:%05i/n",
ntohl(ih->daddr),ntohs(((structtcphdr*)(ih+1))->dest),
ntohl(ih->saddr),ntohs(((structtcphdr*)(ih+1))->source));

/*
* Ok, now the packet is ready for transmission: first simulate a
* receive interrupt on the twin device, then a
* transmission-done on the transmitting device
*/
/*除了上面修改网络地址的代码外,以下为snull功能实现的核心代码*/

/*在接收端产生一个接收中断*/
dest=snull_devs[dev==snull_devs[0]?1:0];
priv=netdev_priv(dest);
tx_buffer=snull_get_tx_buffer(dev);
tx_buffer->datalen=len;
memcpy(tx_buffer->data,buf,len);
snull_enqueue_buf(dest,tx_buffer);/*将发送的数据放到接收设备的缓冲队列*/
if(priv->rx_int_enabled){
priv->status|=SNULL_RX_INTR;
snull_interrupt(0,dest,NULL);/*产生中断----接收*/
}

/*在发送端产生一个发送完成中断*/
priv=netdev_priv(dev);
priv->tx_packetlen=len;
priv->tx_packetdata=buf;
priv->status|=SNULL_TX_INTR;
if(lockup&&((priv->stats.tx_packets+1)%lockup)==0){
/* Simulate a dropped transmit interrupt */
netif_stop_queue(dev);
PDEBUG("Simulate lockup at %ld, txp %ld/n",jiffies,
(unsignedlong)priv->stats.tx_packets);
}
else
snull_interrupt(0,dev,NULL);/*产生中断----发送*/
}

数据包的接收

/*
* Receive a packet: retrieve, encapsulate and pass over to upper levels
*/
voidsnull_rx(structnet_device*dev,structsnull_packet*pkt)
{
structsk_buff*skb;
structsnull_priv*priv=netdev_priv(dev);

/*
* The packet has been retrieved from the transmission
* medium. Build an skb around it, so upper layers can handle it
* 已经从传输介质中获得数据包,为数据包分配sk_buff缓冲区,以便上层进行操作。
*/
skb=dev_alloc_skb(pkt->datalen+2);/*以太网头为14个字节,对齐*/
if(!skb){
if(printk_ratelimit())/*控制printk被调用的速度,当向控制台发送大量信息时,printk_ratelimit返回0*/
printk(KERN_NOTICE"snull rx: low on mem - packet dropped/n");
priv->stats.rx_dropped++;
gotoout;
}
skb_reserve(skb,2);/* align IP on 16B boundary */
memcpy(skb_put(skb,pkt->datalen),pkt->data,pkt->datalen);/*skb_put改变tail指针并更新skb->len的值*/

/* Write metadata, and then pass to the receive level */
skb->dev=dev;
skb->protocol=eth_type_trans(skb,dev);/*主要任务是返回skb的协议号*/
skb->ip_summed=CHECKSUM_UNNECESSARY;/* don't check it */
priv->stats.rx_packets++;
priv->stats.rx_bytes+=pkt->datalen;
netif_rx(skb);
out:
return;
}

在能够处理数据包之前,网络层必须知道数据包的一些信息。为此必须在将skb缓冲区传递到上层之前,对dev和protocol成员正确赋值。以太网设备支持eth_type_trans函数来查找填入protocol中的正确值。然后指定如何求得校验和。接收数据包的最后一个步骤由netif_rx执行,它将skb缓冲区传递给上层处理。

这里再来看一下skb_put()函数的作用:

可见,skb_put()更新了tail指针并且增加了skb->len。

中断处理程序

网络接口在两种可能的事件下中断处理器。也就是说有两种情况会引起中断的发生:新数据包的到达和外发数据包的传输已完成。

通常情况下,中断处理程序通过检查物理设备中的状态寄存器,以区分是新数据包的到达产生的中断还是数据传输完毕产生的中断。

snull接口的工作原理也是这样,只是因为其与硬件无关,所以它的状态值是通过软件来实现的,其保存在dev->priv中。snull的中断处理程序如下:

staticvoidsnull_regular_interrupt(intirq,void*dev_id,structpt_regs*regs)
{
intstatusword;
structsnull_priv*priv;
structsnull_packet*pkt=NULL;
/*
* As usual, check the "device" pointer to be sure it is
* really interrupting.
* Then assign "struct device *dev"
*/
structnet_device*dev=(structnet_device*)dev_id;/*如果对应的中断号允许共享,则触发中断后可能有多个中断处理程序与其对应,
这时就需要查看中断是否是与自己的接口对应的*/
/* ... and check with hw if it's really ours */

/* paranoid */
if(!dev)
return;

/* Lock the device */
priv=netdev_priv(dev);
spin_lock(&priv->lock);

/* retrieve statusword: real netdevices use I/O instructions */
statusword=priv->status;
priv->status=0;
if(statusword&SNULL_RX_INTR){/*如果是新数据包的到达,触发接收中断*/
/* send it to snull_rx for handling */
pkt=priv->rx_queue;
if(pkt){
priv->rx_queue=pkt->next;
snull_rx(dev,pkt);/*调用接收函数*/
}
}
if(statusword&SNULL_TX_INTR){/*如果是发送数据包完成,触发发送中断*/
/* a transmission is over: free the skb */
priv->stats.tx_packets++;
priv->stats.tx_bytes+=priv->tx_packetlen;
dev_kfree_skb(priv->skb);
}

/* Unlock the device and we are done */
spin_unlock(&priv->lock);
if(pkt)snull_release_buffer(pkt);/* Do this outside the lock! */
return;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

网络驱动程序snull 的相关文章

随机推荐

  • SkylineGlobe TEPro 6.6.1 二次开发导出KML或者KMZ文件示例代码

    其实Skyline的fly文件跟kml文件很像很像 只不过一个是编码加密的 另一个早已经成为OGC的通用标准 喜欢Skyline的小伙伴们试试下面的代码吧 细心的人能发现彩蛋哦
  • ElementUI 日期范围选择器设置选择跨度一年、30天、7天

    一 思路 利用组件的picker options object 属性以及属性下的disabledDate和onPick来实现选择日期范围跨度设置 二 代码实现 设置选择范围跨度为一年
  • 操作系统王道考研复习——第三章(内存管理/存储器管理)

    操作系统王道考研复习 第三章 内存管理 存储器管理 3 内存管理 存储器管理 3 1 内存管理概念 3 1 1 内存管理的基本原理和要求 1 可执行程序的形成 2 程序的链接 1 静态链接 2 装入时动态链接 3 运行时动态链接 3 程序的
  • eosjs-ecc eos密钥和签名的javascript开发包中文文档

    eosjs ecc是eos官方处理密钥和签名的javascript开发包 eosjs ecc中文文档由汇智网翻译整理 访问地址 eosjs ecc中文手册
  • 学习总结——小熊派+ESP8266

    文章目录 一 知识点 1 小熊派 2 ESP8266 二 感想 1 小熊派 2 ESP8266 三 经验教训 反思 一 知识点 1 小熊派 1 HAL库的使用 2 点亮LED灯 3 配置GPIO 4 按键输入 循环查询 外部中断 5 串口
  • 【华为OD统一考试A卷

    在线OJ 已购买本专栏用户 请私信博主开通账号 在线刷题 运行出现 Runtime Error 0Aborted 请忽略 华为OD统一考试A卷 B卷 新题库说明 2023年5月份 华为官方已经将的 2022 0223Q 1 2 3 4 统一
  • 编程:数据开发经验

    基本框架 select field1 field2 field3 field4 数据合成 T4 from 基础数据 select sum count case when condition1 condition2 as field1
  • WinSCP安装教程

    WinSCP是适用于Windows的免费开源SFTP FTP WebDAV S3 SCP客户端 它的主要功能是在本地计算机和远程计算机之间进行文件传输 除此之外 WinSCP还提供脚本和基本文件管理器功能
  • .net @什么意思_U-Net:基于小样本的高精度医学影像语义分割模型

    原论文地址 U Net Convolutional Networks for Biomedical Image Segmentation Pytorch 实现 https github com milesial Pyt orch UNet
  • elasticsearch地理位置总结

    参考 https blog csdn net tang jian dong article details 104446526 https blog csdn net u013041642 article details 94416631
  • linux内核编译问题解决方法

    在linux内核编译过程中 可能会遇到有些文件没有的可能 下面是我用gcc4 6 2编译linux2 6 30 5内核过程中提示的错误 gcc error elf i386 No such file or directory OBJCOPY
  • linux下select()函数

    1 I O处理的模型有5种 1 gt 阻塞I O模型 在这种模型下 若所调用的I O函数没有完成相关的功能 则会使进程挂起 直到相关数据到达才会返回 如常见的对管道 终端 网络设备进行读写时经常会出现这种情况 2 gt 非阻塞I O模型 在
  • Python——基础学习xpath(爬虫)

    python三种解析方法 1 正则 2 bs4 3 xpath Python xpath解析 xpath解析是最常用且最便捷高效的一种解析方法 通用性最强 xpath解析原理 1 实例一个etree的对象 且需要将被解析的页面的源码数据加载
  • 转载,envi对高光谱进行辐射定标和大气校正

    原文地址 http blog sina com cn s blog 764b1e9d0102v59e html 目录 辐射定标和大气校正 概述 辐射定标 多光谱数据FLAASH大气校正 3 1 FLAASH输入数据要求 3 2 详细操作步骤
  • 去除list of list中的引号与括号

    经常分词后是List of list的结构 输出后会存在很多引号与方括号 它其实是一个list的嵌套结构 可以通过将内层的List转化为str再放入新的list中实现转化 a 我要发 SCI 然后 去 四川大学 读博士 result for
  • Laya 实操十七:物理引擎

    物理引擎 export default class test extends Laya Script constructor super Laya Scene3D load res LayaScene SampleScene Convent
  • Python清洗数据具体操作

    数据清洗是指在处理数据之前对数据进行预处理的过程 这个过程通常包括检查数据的完整性 清除数据中的缺失值 异常值和重复值 以及对数据进行格式转换和数据转换等 在 Python 中 可以使用 pandas 库来方便地进行数据清洗 下面是一些常见
  • Android 禁止RecycleView的滑动

    1 问题 使用RecycleView 时 如果数据量很少只有几个 需求不需要它上下左右滑动 在xml配置中加上Android scrollbars none 这只是去掉了滑动bar 但是RecycleView 上下还是能滑动 且有阴影 2
  • 【message:发生错误 Error: D:\微信小程序\second-demo\package.json 对应的 node_modules 不存在】

    微信小程序安装vant weapp报 node module不存在 提示 这里简述项目相关背景 想使用vant weapp写小程序 在安装的时候踩了一些坑 记录一下 1 安装 vant weapp 提示 这里一个一个来描述遇到的问题 我是根
  • 网络驱动程序snull

    由于下次开会要做报告 所以就先把字符设备驱动程序那块搁浅了 先来复习下网络驱动这块 虽然以前看过 学习过网络驱动的相关内容 但是当再次学习这块的时候 仍然有新的收获 或许是时间长不看了 有些东西的印象浅了 或者应了那句话 温故而知新 ok