Linux网络设备驱动架構學習(三)
Linux网络设备驱动架構學習(三)
接下來會從以下幾個方面介紹網絡設備驅動的編寫流程:
1、網絡設備的註冊與註銷
2、網絡設備的初始化
3、網絡設備的打開與釋放
4、網絡數據發送流程
5、網絡數據接收流程
6、網絡連接狀態
7、網絡參數設置和統計數據
瞭解了這幾部份內容,網絡設備驅動的編寫方法也就基本明白了
網絡數據發送流程
Linux 网络子系统在发送数据包时,会调用驱动程序提供的 hard_start_transmit()函数,该函数用于启动数据包的发送。在设备初始化的时候,这个函数指针需被初始化指向设备的 xxx_tx()函数。
网络设备驱动完成数据包发送的流程如下。
(1)网络设备驱动程序从上层协议传递过来的 sk_buff 参数获得数据包的有效数据和长度,将有效数据放入临时缓冲区。
(2)对于以太网,如果有效数据的长度小于以太网冲突检测所要求数据帧的最小长度 ETH_ZLEN,则给临时缓冲区的末尾填充 0。
(3)设置硬件的寄存器,驱使网络设备进行数据发送操作。
完成以上 3 个步骤的网络设备驱动程序的数据包发送函数的模板如代码清单所示。
1 int xxx_tx(struct sk_buff *skb, struct net_device *dev)
2 {
3 int len;
4 char *data, shortpkt[ETH_ZLEN];
5 /* 获得有效数据指针和长度 */
6 data = skb->data;
7 len = skb->len;
8 if (len < ETH_ZLEN)
9 {
10 /* 如果帧长小于以太网帧最小长度,补 0 */
11 memset(shortpkt, 0, ETH_ZLEN);
12 memcpy(shortpkt, skb->data, skb->len);
13 len = ETH_ZLEN;
14 data = shortpkt;
15 }
16
17 dev->trans_start = jiffies; /* 记录发送时间戳 */
18
19 /* 设置硬件寄存器让硬件把数据包发送出去 */
20 xxx_hw_tx(data, len, dev);
21 ...
22 }
当数据传输超时时,意味着当前的发送操作失败,此时,数据包发送超时处理函数 xxx_tx_ timeout()将被调用。这个函数需要调用 Linux 内核提供的 netif_wake_queue()函数重新启动设备发送队列,如代码清单所示。
1 void xxx_tx_timeout(struct net_device *dev)
2 {
3 ...
4 netif_wake_queue(dev); /* 重新启动设备发送队列 */
5 }
網絡數據接收流程
网络设备接收数据的主要方法是由中断引发设备的中断处理函数,中断处理函数判断中断类型,如果为接收中断,则读取接收到的数据,分配 sk_buffer 数据结构和数据缓冲区,将接收到的数据复制到数据缓冲区,并调用 netif_rx()函数将 sk_buffer 传递给上层协议。代码清单所示为完成这一过程的函数模板。
1 static void xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
2 {
3 ...
4 switch (status &ISQ_EVENT_MASK)
5 {
6 case ISQ_RECEIVER_EVENT:
7 /* 获取数据包 */
8 xxx_rx(dev);
9 break;
10 /* 其他类型的中断 */
11 }
12 }
13 static void xxx_rx(struct xxx_device *dev)
14 {
15 ...
16 length = get_rev_len (...);
17 /* 分配新的套接字缓冲区 */
18 skb = dev_alloc_skb(length + 2);
19
20 skb_reserve(skb, 2); /* 对齐 */
21 skb->dev = dev;
22
23 /* 读取硬件上接收到的数据 */
24 insw(ioaddr + RX_FRAME_PORT, skb_put(skb, length), length >> 1);
25 if (length &1)
26 skb->data[length - 1] = inw(ioaddr + RX_FRAME_PORT);
27
28 /* 获取上层协议类型 */
29 skb->protocol = eth_type_trans(skb, dev);
30
31 /* 把数据包交给上层 */
32 netif_rx(skb);
33
34 /* 记录接收时间戳 */
35 dev->last_rx = jiffies;
36 ...
37 }
从上述代码的第 4~7 行可以看出,当设备的中断处理程序判断中断类型为数据包接收中断时,它调用第 13~37 定义的 xxx_rx()函数完成更深入的数据包接收工作。 xxx_rx()函数代码中的第 16 行从硬件读取到接收数据包有效数据的长度,第 17~20 行分配 sk_buff 和数据缓冲区,第 23~26 行读取硬件上接收到的数据并放入数据缓冲区,第 28~29 行解析接收数据包上层协议的类型,最后,第 31~32 行代码将数据包上交给上层协议。
如果是 NAPI 兼容的设备驱动,则可以通过 poll 方式接收数据包。这种情况下,我们需要为该设备驱动提供 xxx_poll()函数,如代码清单所示。
1 static int xxx_poll(struct net_device *dev, int *budget)
2 {
3 int npackets = 0, quota = min(dev->quota, *budget);
4 struct sk_buff *skb;
5 struct xxx_priv *priv = netdev_priv(dev);
6 struct xxx_packet *pkt;
7
8 while (npackets < quota && priv->rx_queue)
9 {
10 /*从队列中取出数据包*/
11 pkt = xxx_dequeue_buf(dev);
12
13 /*接下来的处理,和中断触发的数据包接收一致*/
14 skb = dev_alloc_skb(pkt->datalen + 2);
15 if (!skb)
16 {
17 ...
18 continue;
19 }
20 skb_reserve(skb, 2);
21 memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
22 skb->dev = dev;
23 skb->protocol = eth_type_trans(skb, dev);
24 /*调用 netif_receive_skb 而不是 net_rx 将数据包交给上层协议*/
25 netif_receive_skb(skb);
26
27 /*更改统计数据 */
28 priv->stats.rx_packets++;
29 priv->stats.rx_bytes += pkt->datalen;
30 xxx_release_buffer(pkt);
31 }
32 /* 处理完所有数据包*/
33 *budget -= npackets;
34 dev->quota -= npackets;
35
36 if (!priv->rx_queue)
37 {
38 netif_rx_complete(dev);
39 xxx_enable_rx_int ( … ); /* 再次使能网络设备的接收中断 */
40 return 0;
41 }
42
43 return 1;
44 }
上述代码第 3 行中的 dev->quota 是当前 CPU 能够从所有接口中接收数据包的最大数目,budget 是在初始化阶段分配给接口的 weight 值,poll 函数必须接收两者之间的最小值,表示轮询函数本次要处理的数据包个数。第 8 行的 while()循环读取设备的接收缓冲区,读取数据包并提交给上层。这个过程和中断触发的数据包接收过程一致,但是最后使用 netif_receive_skb()函数而非 netif_rx()函数将数据包提交给上层。这里体
现出了中断处理机制和轮询机制之间的差别。
当网络设备接收缓冲区中的数据包都被读取完后(即 priv->rx_queue 为 NULL),一个轮询过程结束,第 38 行代码调用 netif_rx_complete()把当前指定的设备从 poll 队列中清除,第 39 行代码再次启动网络设备的接收中断。 虽然 NAPI 兼容的设备驱动以 poll 方式接收数据包,但是仍然需要首次数据包接收中断来触发 poll 过程。与数据包的中断接收方式不同的是,以轮询方式接收数据包时,当第一次中断发生后,中断处理程序要禁止设备的数据包接收中断,如代码清单所示。
1 static void xxx_poll_interrupt(int irq, void *dev_id, struct pt_regs *regs)
2 {
3 switch (status &ISQ_EVENT_MASK)
4 {
5 case ISQ_RECEIVER_EVENT:
6 … /* 获取数据包 */
7 xxx_disable_rx_int(...); /* 禁止接收中断 */
8 netif_rx_schedule(dev);
9 break;
10 … /* 其他类型的中断 */
11 }
12 }
上述代码第 8 行的 netif_rx_schedule()函数被轮询方式驱动的中断程序调用,将设备的 poll 方法添加到网络层的 poll 处理队列中,排队并且准备接收数据包,最终触发一个 NET_RX_SOFTIRQ 软中断,通知网络层接收数据包。下图所示为 NAPI 驱动程序各部分的调用关系。
網絡連接狀態
网络适配器硬件电路可以检测出链路上是否有载波,载波反映了网络的连接是否正常。网络设备驱动可以通过 netif_carrier_on()和 netif_carrier_off()函数改变设备的连接状态 , 如果驱动檢測到连接状态发生变化,也应该以netif_carrier_on()和netif_carrier_off()函数显式地通知内核。 除了 netif_carrier_on()和 netif_carrier_off()函数以外,另一个函数 netif_carrier_ok()可用于向调用者返回链路上的载波信号是否存在。 这几个函数都接收一个 net_device 设备结构体指针为参数,原型分别为:
void netif_carrier_on(struct net_device *dev);
void netif_carrier_off(struct net_device *dev);
int netif_carrier_ok(struct net_device *dev);
网络设备驱动程序中往往设置一个定时器来对链路状态进行周期性地检查。当定时器到期之后,在定时器处理函数中读取物理设备的相关寄存器获得载波状态,从而更新设备的连接状态,如代码清单所示。
1 static void xxx_timer(unsigned long data)
2 {
3 struct net_device *dev = (struct net_device*)data;
4 u16 link;
5 …
6 if (!(dev->flags &IFF_UP))
7 {
8 goto set_timer;
9 }
10
11 /* 获得物理上的连接状态 */
12 if (link = xxx_chk_link(dev))
13 {
14 if (!(dev->flags &IFF_RUNNING))
15 {
16 netif_carrier_on(dev);
17 dev->flags |= IFF_RUNNING;
18 printk(KERN_DEBUG "%s: link up\n", dev->name);
19 }
20 }
21 else
22 {
23 if (dev->flags &IFF_RUNNING)
24 {
25 netif_carrier_off(dev);
26 dev->flags &= ~IFF_RUNNING;
27 printk(KERN_DEBUG "%s: link down\n", dev->name);
28 }
29 }
30
31 set_timer:
32 priv->timer.expires = jiffies + 1 * HZ;
33 priv->timer.data = (unsigned long)dev;
34 priv->timer.function = &xxx_timer; /* timer handler */
35 add_timer(&priv->timer);
36 }
上述代码第 12 行调用的 xxx_chk_link()函数用于读取网络适配器硬件的相关寄存器以获得链路连接状态,具体实现由硬件决定。当链路连接上时,第 16 行的netif_carrier_on()函数显式地通知内核链路正常;反之,则第 25 行的 netif_carrier_off()则同样显式地通知内核链路失去连接。 此外,从上述源代码还可以看出,定时器处理函数会不停地利用第 31~35 行代码启动新的定时器以实现周期检测的目的。那么最初启动定时器的地方在哪里呢?很显然,它最适合在设备的打开函数中完成,如代码清单所示。
1 static int xxx_open(struct net_device *dev)
2 {
3 struct xxx_priv *priv = (struct xxx_priv*)dev->priv;
4
5 ...
6 priv->timer.expires = jiffies + 3 * HZ;
7 priv->timer.data = (unsigned long)dev;
8 priv->timer.function = &xxx_timer; /* timer handler */
9 add_timer(&priv->timer);
10 ...
11 }
網絡參數設置和統計數據
在网络设备的驱动程序中还提供一些方法供系统对设备的参数进行设置或读取设备相关的信息。 当用户调用 ioctl()函数,并指定 SIOCSIFHWADDR 命令时,意味着要设置这个设备的 MAC 地址。一般来说,这个操作没有太大的意义。设置网络设备的 MAC 地址可用如代码清单所示的模板。
1 static int set_mac_address(struct net_device *dev, void *addr)
2 {
3 if (netif_running(dev))
4 return -EBUSY; /* 设备忙 */
5
6 /* 设置以太网的 MAC 地址 */
7 xxx_set_ mac(dev, addr);
8
9 return 0;
10 }
上述程序首先用 netif_running()宏判断设备是否正在运行,如果是,则意味着设备忙,此时不允许设置 MAC 地址;否则,调用 xxx_set_ mac()函数在网络适配器硬件内写入新的 MAC 地址。这要求设备在硬件上支持 MAC 地址的修改,而实际上,许多设备并不提供修改 MAC 地址的接口。
netif_running()宏的定义为:
#define netif_running(dev) (dev->flags & IFF_UP)
当用户调用 ioctl()函数时,若命令为 SIOCSIFMAP(如在控制台中运行网络配置命令 ifconfig 就会引发这一调用),系统会调用驱动程序的 set_config()函数。 系统会向 set_config()函数传递一个 ifmap 结构体,该结构体中主要包含用户欲设置的设备要使用的 I/O 地址、中断等信息。注意,并非 ifmap 结构体中给出的所有修改都是可以被接受的。实际上,大多数设备并不宜包含 set_config()函数。set_config()函数的例子如代码清单 所示。
1 int xxx_config(struct net_device *dev, struct ifmap *map)
2 {
3 if (netif_running(dev)) /* 不能设置一个正在运行状态的设备 */
4 return - EBUSY;
5
6 /* 假设不允许改变 I/O 地址 */
7 if (map->base_addr != dev->base_addr)
8 {
9 printk(KERN_WARNING "xxx: Can't change I/O address\n");
10 return - EOPNOTSUPP;
11 }
12
13 /* 假设允许改变 IRQ */
14 if (map->irq != dev->irq)
15 {
16 dev->irq = map->irq;
17 }
18
19 return 0;
20 }
上述代码中的 set_config()函数接受 IRQ 的修改,拒绝设备 I/O 地址的修改。具体的设备是否接收这些信息的修改,要视硬件的设计而定。 如 果 用 户 调 用 ioctl() 时 , 命 令 类 型 在 SIOCDEVPRIVATE 和SIOCDEVPRIVATE+15 之间,系统会调用驱动程序的 do_ioctl()函数,进行设备专用数据的设置。这个设置大多数情况下也并不需要。 驱动程序还应提供 get_stats()函数用以向用户反馈设备状态和统计信息,该函数返回的是一个 net_device_stats 结构体,如代码清单所示。
1 struct net_device_stats *xxx_stats(struct net_device *dev)
2 {
3 struct xxx_priv *priv = netdev_priv(dev);
4 return &priv->stats;
5 }
net_device_stats 结构体定义在内核的 include/linux/netdevice.h 文件中,它包含了比较完整的统计信息,如代码清单所示。
1 struct net_device_stats
2 {
3 unsigned long rx_packets; /* 收到的数据包数 */
4 unsigned long tx_packets; /* 发送的数据包数 */
5 unsigned long rx_bytes; /* 收到的字节数 */
6 unsigned long tx_bytes; /* 发送的字节数 */
7 unsigned long rx_errors; /* 收到的错误数据包数 */
8 unsigned long tx_errors; /* 发生发送错误的数据包数 */
9 ...
10 };
上述代码清单只是列出了 net_device_stats 包含的主项目统计信息,实际上,这些项目还可以进一步细分,net_device_stats 中的其他信息给出了更详细的子项目统计,详见 Linux 源代码。
net_device_stats 结构体适宜包含在设备的私有信息结构体中,而其中统计信息的修改则应该在设备驱动的与发送和接收相关的具体函数中完成,这些函数包括中断处理程序、数据包发送函数、数据包发送超时函数和数据包接收相关函数等。我们应该在这些函数中添加相应的代码,如代码清单所示。
1 /* 发送超时函数 */
2 void xxx_tx_timeout(struct net_device *dev)
3 {
4 struct xxx_priv *priv = netdev_priv(dev);
5 ...
6 priv->stats.tx_errors++; /* 发送错误包数加 1 */
7 ...
8 }
9
10 /* 中断处理函数 */
11 static void xxx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
12 {
13 switch (status &ISQ_EVENT_MASK)
14 {
15 ...
16 case ISQ_TRANSMITTER_EVENT: /
17 priv->stats.tx_packets++; /* 数据包发送成功,tx_packets 信息加1 */
18 netif_wake_queue(dev); /* 通知上层协议 */
19 if ((status &(TX_OK | TX_LOST_CRS | TX_SQE_ERROR |
20 TX_LATE_COL | TX_16_COL)) != TX_OK) /*读取硬件上的出错标志*/
21 {
22 /* 根据错误的不同情况,对 net_device_stats 的不同成员加 1 */
23 if ((status &TX_OK) == 0)
24 priv->stats.tx_errors++;
25 if (status &TX_LOST_CRS)
26 priv->stats.tx_carrier_errors++;
27 if (status &TX_SQE_ERROR)
28 priv->stats.tx_heartbeat_errors++;
29 if (status &TX_LATE_COL)
30 priv->stats.tx_window_errors++;
31 if (status &TX_16_COL)
32 priv->stats.tx_aborted_errors++;
33 }
34 break;
35 case ISQ_RX_MISS_EVENT:
36 priv->stats.rx_missed_errors += (status >> 6);
37 break;
38 case ISQ_TX_COL_EVENT:
39 priv->stats.collisions += (status >> 6);
40 break;
41 }
42 }
上述代码的第 6 行意味着在发送数据包超时时,将发生发送错误的数据包数增 1。而第 13~41 行则意味着当网络设备中断产生时,中断处理程序读取硬件的相关信息以决定修改 net_device_ stats 统计信息中的哪些项目和子项目,并将相应的项目增 1。
Linux网络设备驱动架構學習(三)的更多相关文章
- Linux网络设备驱动架構學習(二)
Linux网络设备驱动架構學習(二) 接下來會從以下幾個方面介紹網絡設備驅動的編寫流程: 1.網絡設備的註冊與註銷 2.網絡設備的初始化 3.網絡設備的打開與釋放 4.網絡數據發送流程 5.網絡數據接 ...
- linux网络设备驱动
Linux网络设备驱动 Linux网络驱动程序的体系结构可划分为4个层次.Linux内核源代码中提供了网络设备接口及以网络子系统的上层的代码,移植特定网络硬件的驱动程序的主要工作就是完成设备驱动功能层 ...
- linux 网络设备驱动
linux 网络驱动 谨以此文纪念过往的岁月 一.前言在linux中网络驱动也是一个大头,如何去理解网络驱动是作为一个linux驱动工程师必备的技能.不过同样的设备,在不同人的手中会有不同的效果,其原 ...
- Linux网络设备驱动(一) _驱动模型
Linux素来以其强大的网络功能著名,同时, 网络设备也作为三大设备之一, 成为Linux驱动学习中必不可少的设备类型, 此外, 由于历史原因, Linux并没有强制对网络设备贯彻其"一切皆 ...
- Linux网络设备驱动架构
Linux网络设备驱动程序体系结构分为四层:网络协议接口层.网络设备接口层.提供实际功能的设备驱动层以及网络设备与媒介层. (1)网络协议接口层向网络层协议提供统一的数据包收发接口,不论上层协议是AR ...
- Linux网络设备驱动 _驱动模型
Linux素来以其强大的网络功能著名,同时, 网络设备也作为三大设备之一, 成为Linux驱动学习中必不可少的设备类型, 此外, 由于历史原因, Linux并没有强制对网络设备贯彻其"一切皆 ...
- Linux 网络设备驱动开发(一) —— linux内核网络分层结构
Preface Linux内核对网络驱动程序使用统一的接口,并且对于网络设备采用面向对象的思想设计. Linux内核采用分层结构处理网络数据包.分层结构与网络协议的结构匹配,既能简化数据包处理流程,又 ...
- 《linux设备驱动开发详解》笔记——14 linux网络设备驱动
14.1 网络设备驱动结构 网络协议接口层:硬件无关,标准收发函数dev_queue_xmit()和netif_rx(); 注意,netif_rx是将接收到的数据给上层,有时也在驱动收到数据以后调用 ...
- Linux网络设备驱动的实现
结论: 1.对一个设备驱动而言,主要从两个方面进行着手,一个是控制流,一个是数据流. 控制流就是如何控制网络设备,数据流则说的是报文的收发流程. 2.网络设备可以是Linux服务器的网卡,也可以是嵌入 ...
随机推荐
- MYSQL insert
准备: create table T4(X int ,Y int); 方法 1. insert [low_priority][high_priority][delayed] into table_na ...
- gdal读写图像分块处理(精华版)
一.gdal进行数据操作在安装好gdal后,即可调用gdal库中的函数.(需要包含的头文件:gdal_priv.h)1.打开数据集使用gdal库进行数据(影像)操作的第一步就是打开一个数据集.对于“数 ...
- mfc窗口,父窗口parentwindow,所有者窗口ownerwindow 区别
一. parent:创建者,owner:所有者 小玉的父母生下小玉,养到8岁,卖给贾府当丫头小玉的父母是parent,贾府是owner 二. 1.Pop-up窗口: 一个弹出窗口是必须具有WS_POP ...
- poj 2975 Nim_最经典的Nim取石子
题意:给你n堆石头,每次只能在一堆取最少一个石子,最后拿走最后一堆的为胜者,问胜者有多少种赢得取法 #include <iostream> #include<cstdio> u ...
- getgrent
http://baike.baidu.com/link?url=JNyoNvukL-LP7ayYlNNWLv2gPOzn-bjiwuX1CE_QwUTyrRGCWu4NhDW-JznHQoG4aIfw ...
- 直播视频插件--sewise player
直播视频插件 -- sewise player 2017-1-9 因为公司要开发一个关于购车直播的新项目,需要在页面引入直播视频,项目组之前都没有做过关于直播类型的项目,所以可以说是真的从各种资源中筛 ...
- Mysql Binlog Dump原理
Register 首先,我们需要伪造一个slave,向master注册,这样master才会发送binlog event.注册很简单,就是向master发送COM_REGISTER_SLAVE命令,带 ...
- mesos博客集
http://dongxicheng.org/category/apache-mesos/
- css3动画之animate
CSS3 动画属性 下面的表格列出了 @keyframes 规则和所有动画属性: 语法:div{animation: 动画名称 一个周期播放时间 速度曲线 延迟时间 下个周期是否逆向} @keyfra ...
- Position详解---转
position有四个属性值: relative absolute fixed static 下面分别讲述这四个属性. 1. relative relative属性,相对定位,我们要搞清它是相对哪个对 ...