关于源码分析的文章,网络上有很多,所以我不想通过源码来分析网卡驱动,而是换个别的角度。
当编写Linux驱动的时候,主要应该解决几个问题:
- linux驱动框架的使用(增强移植性,并且对熟悉linux驱动框架的人来说,有更高的可读性)
- 针对具体的设备,该如何对驱动进行控制(如对寄存器的操作),这里当然就是dm9000网卡
- 操作逻辑(中断 + 流控)
- 以及,对整个linux网络子系统框架的理解(当然,在编写驱动的时候,可能感觉不到其重要性,但是一旦出现问题,只有了解了整个框架的原理和细节,才能更好的快速定位问题,并对其进行解决)
1.linux网络驱动的框架设计
平台总线
分别在 驱动中注册平台驱动,板文件中注册平台设备(主要包括使用到的resource)
net_device
通用的描述网络设备的对象,该对象的私有数据中往往是一个设备的私有结构体,用来描述具体设备中特殊的部分。
首先内存分配
初始化
通过register_netdev()函数向内核进行注册(注意,在注册之前要进行基本初始化,因为调用了该函数,用户就能操作该设备)
sk_buff
在整个网络子系统中,通过sk_buff进行数据的传输,通过对sk_buff结构体的操作,对数据完成打包和解包的工作(比如添加头部和删除头部)
2.dm9000的操作
dm9000的初始化
dm9000的数据发送操作
dm9000的数据接收操作
dm9000的各种设置
3.dm9000整体操作逻辑
因为dm9000的主要功能是接收发数据,所以这里的操作逻辑是关于数据收发的。
数据收发主要通过中断 + 流控来进行。
dm9000设备的数据缓冲区共有16K,其中前3K为发送缓冲区(0x0000 - 0x02FF),后13K是接收缓冲区(0x0C00-03FFF)
发送缓冲区只能允许存储两个packet,这个一定要考虑进整个收发逻辑中。
发送逻辑
- 上层以包的形式发送数据,数据的大小(46-1500字节)
- 每次dm9000会发送一个包
- 当包成功发送后,会产生一个发送成功中断,从而进入中断处理程序(进行判断)
- 根据一个计数变量判断缓冲区中是否还有数据,如果有,则发送数据,如果没有,则返回
- 重启发送队列()
接收逻辑
- 当缓冲区有中断的时候,会产生接收中断
- 进入中断处理程序,判断,并进入dm9000_rx,进行接收处理
- 根据包头部四个字节(下图是包的结构)进行判断,如果包是好的,则继续
- 将data按照固定的格式放入sk_buff结构体中,将sk_buff通过netif_rx()提交出去(这里需要继续讨论)
- 异步返回、
当然,别忘了超时处理
4.网络子系统
在驱动中,网络子系统涉及到的主要对象就是sk_buff
sk_buff中对数据的长度的描述主要是
head和end(表示整个数据区的头和位),在sk_buff中是指针
data和tail(有效数据的头和尾),不是指针
关于sk_buff的操作
alloc_skb()
采用了slab内存分配
skb_put()
该函数是在数据区的末端添加某协议的尾部
/**
* skb_put - add data to a buffer
* @skb: buffer to use
* @len: amount of data to add
*
* This function extends the used data area of the buffer. If this would
* exceed the total buffer size the kernel will panic. A pointer to the
* first byte of the extra data is returned.
*/
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
{
unsigned char *tmp = skb_tail_pointer(skb);
//return skb->head + skb->tail,head是指针,而end和tail都是int
SKB_LINEAR_ASSERT(skb);
skb->tail += len;
skb->len += len;
if (unlikely(skb->tail > skb->end))
skb_over_panic(skb, len, __builtin_return_address(0));
return tmp;
}
EXPORT_SYMBOL(skb_put);
返回添加部分的头部
skb_trim()
从缓冲区的尾部删除数据
void skb_trim (struct sk_buff * skb, unsigned int len); len是新的长度,通过从尾部删除数据达到新的长度skb_push()
在数据的开始部分添加
/**
* skb_push - add data to the start of a buffer
* @skb: buffer to use
* @len: amount of data to add
*
* This function extends the used data area of the buffer at the buffer
* start. If this would exceed the total buffer headroom the kernel will
* panic. A pointer to the first byte of the extra data is returned.
*/
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
{
skb->data -= len;
skb->len += len;
if (unlikely(skb->data<skb->head))
skb_under_panic(skb, len, __builtin_return_address(0));
return skb->data;
//返回头部
}
EXPORT_SYMBOL(skb_push);
skb_pull()
和push相反,从缓冲区的开始删除数据
skb_reserve()
该函数的作用是在数据区创建存储协议头部的空间,函数实现很简单。
- static inline void skb_reserve(struct sk_buff *skb, int len)
- {
- skb->data += len;
- skb->tail += len;
- }
更多:
NAPI的添加,netif_rx背后的原理(使用了软中断)
还有很多不完善的地方,未完待续;
参考:
Linux内核--网络协议栈深入分析(二)--sk_buff的操作函数 http://www.linuxidc.com/Linux/2012-09/70439p2.htm