Linux作为应战微软独占的强有力兵器,日益遭到我们的喜欢。真期望她能在我国敏捷生长。把程序文档贴出来,期望和我们讨论Linux技能和运用,促进Linux在我国的遍及。
Linux操作体系网络驱动程序编写
一.Linux体系设备驱动程序概述
1.1 Linux设备驱动程序分类
1.2 编写驱动程序的一些根本概念
二.Linux体系网络设备驱动程序
2.1 网络驱动程序的结构
2.2 网络驱动程序的根本办法
2.3 网络驱动程序中用到的数据结构
2.4 常用的体系支撑
三。编写Linux网络驱动程序中或许遇到的问题
3.1 中止同享
3.2 硬件发送忙时的处理
3.3 流量操控(flow control)
3.4 调试
四。进一步的阅览
五。杂项
一.Linux体系设备驱动程序概述
1.1 Linux设备驱动程序分类
Linux设备驱动程序在Linux的内核源代码中占有很大的份额,源代码的长度日益增加,首要是驱动程序的增加。在Linux内核的不断晋级过程中,驱动程序的结构仍是相对安稳 。在2.0.xx到2.2.xx的变化里,驱动程序的编写做了一些改动,可是从2.0.xx的驱动到2.2.xx的移植只需做少数的作业。
Linux体系的设备分为字符设备(char device),块设备(block device)和网络设备(net work device)三种。字符设备是指存取时没有缓存的设备。块设备的读写都有缓存来支 持,而且块设备有必要能够随机存取(random access),字符设备则没有这个要求。典型的 字符设备包含鼠标,键盘,串行口等。块设备首要包含硬盘软盘设备,CD-ROM等。一个 文件体系要装置进入操作体系有必要在块设备上。 网络设备在Linux里做专门的处理。Linux的网络体系首要是依据BSD unix的socket机制。在体系和驱动程序之间界说有专门的数据结构(sk_buff)进行数据的传递。体系里支撑对发送数据和接纳数据的缓存,供给流量操控机制,供给对多协议的支撑。
1.2 编写驱动程序的一些根本概念
不管是什么操作体系的驱动程序,都有一些通用的概念。操作体系供给给驱动程序的支撑也大致相同。下面简略介绍一下网络设备驱动程序的一些根本要求。
1.2.1 发送和接纳
这是一个网络设备最根本的功用。一块网卡所做的无非便是收发作业。所以驱动程序里要告知体系你的发送函数在哪里,体系在有数据要发送时就会调用你的发 送程序。还有驱动程序由所以直接操作硬件的,所以网络硬件有数据收到最先能得到这个数据的也就 是驱动程序,它担任把这些原始数据进行必要的处理然后送给体系。这儿,操作体系必 需求供给两个机制,一个是找到驱动程序的发送函数,一个是驱动程序把收到的数据送 给体系。
1.2.2 中止
中止在现代计算机结构中有重要的位置。操作体系有必要供给驱动程序呼应中止的才能。 一般是把一个中止处理程序注册到体系中去。操作体系在硬件中止产生后 调用驱动程序 的处理程序。Linux支撑中止的同享,即多个设备同享一个中止。
1.2.3 时钟
在完结驱动程序时,许多当地会用到时钟。如某些协议里的超时处理,没有中止机制的 硬件的轮询等。操作体系应为驱动程序供给守时机制。一般是在预订的时 间过了今后回 调注册的时钟函数。在网络驱动程序中,假如硬件没有中止功用,守时器能够供给轮询 (poll)办法对硬件进行存取。或许是完结某些协议时需求的超时重传等。
二.Linux体系网络设备驱动程序
2.1 网络驱动程序的结构
一切的Linux网络驱动程序遵从通用的接口。设计时选用的是面向方针的办法。一个设备 便是一个方针(device 结构),它内部有自己的数据和办法。每一个设备的办法被调用时 的第一个参数都是这个设备方针自身。这样这个办法就能够存取自身的数据(相似面向对 象程序设计时的this引证)。 一个网络设备最根本的办法有初始化、发送和接纳。
——————- ———————
|deliver packets | |receive packets queue|
|(dev_queue_xmit()) | |them(netif_rx()) |
——————- ———————
| | /
/ | |
——————————————————-
| methods and variables(iniTIalize,open,close,hard_xmit,|
| interrupt handler,config,resources,status.。.) |
——————————————————-
| | /
/ | |
—————– ———————-
|send to hardware | |receivce from hardware|
—————– ———————-
| | /
/ | |
—————————————————–
| hardware media |
—————————————————–
初始化程序完结硬件的初始化、device中变量的初始化和体系资源的请求。发送程序是 在驱动程序的上层协议层有数据要发送时主动调用的。一般驱动程序中不对发送数据进 行缓存,而是直接运用硬件的发送功用把数据发送出去。接纳数据一般是经过硬件中止 来告诉的。在中止处理程序里,把硬件帧信息填入一个skbuff结构中,然后调用neTIf_ rx()传递给上层处理。
2.2 网络驱动程序的根本办法
网络设备做为一个方针,供给一些办法供体系拜访。正是这些有一致接口的办法,掩蔽 了硬件的详细细节,让体系对各种网络设备的拜访都选用一致的办法,做到硬件无关性 。
下面解说最根本的办法。
2.2.1 初始化(iniTIalize)
驱动程序有必要有一个初始化办法。在把驱动程序载入体系的时分会调用这个初始化程序 。它做以下几方面的作业。检测设备。在初始化程序里你能够依据硬件的特征查看硬件 是否存在,然后决议是否发动这个驱动程序。装备和初始化硬件。在初始化程序里你可 以完结对硬件资源的装备,比方即插即用的硬件就能够在这个时分进行装备(Linux内核 对PnP功用没有很好的支撑,能够在驱动程序里完结这个功用)。装备或洽谈好硬件占用 的资源今后,就能够向体系请求这些资源。有些资源是能够和其他设备同享的,如中止 。有些是不能同享的,如IO、DMA。接下来你要初始化device结构中的变量。最终,你可 以让硬件正式开端作业。
2.2.2 翻开(open)
open这个办法在网络设备驱动程序里是网络设备被激活的时分被调用(即设备状况由down–》up)。所以实际上许多在iniTIalize中的作业能够放到这儿来做。比方资源的请求, 硬件的激活。假如dev-》open回来非0(error),则硬件的状况仍是down。 open办法另一个作用是假如驱动程序做为一个模块被装入,则要避免模块卸载时设备处 于翻开状况。在open办法里要调MOD_INC_USE_COUNT宏。
2.2.3 封闭(stop)
close办法做和open相反的作业。能够开释某些资源以削减体系担负。close是在设备状 态由up转为down时被调用的。其他假如是做为模块装入的驱动程序,close里应该调用M OD_DEC_USE_COUNT,削减设备被引证的次数,以使驱动程序能够被卸载。 其他close办法有必要回来成功(0==success)。
2.2.4 发送(hard_start_xmit)
一切的网络设备驱动程序都有必要有这个发送办法。在体系调用驱动程序的xmit时,发送 的数据放在一个sk_buff结构中。一般驱动程序把数据传给硬件发出去。也有一些特别 的设备比方loopback把数据组成一个接纳数据再回送给体系,或许dummy设备直接丢掉数 据。
假如发送成功,hard_start_xmit办法里开释sk_buff,回来0(发送成功)。假如设备暂时 无法处理,比方硬件忙,则回来1。这时假如dev-》tbusy置为非0,则体系认为硬件忙, 要比及dev-》tbusy置0今后才会再次发送。tbusy的置0使命一般由中止完结。硬件在发送 完毕后产生中止,这时能够把tbusy置0,然后用mark_bh()调用告诉体系能够再次发送。 在发送不成功的状况下,也能够不置dev-》tbusy为非0,这样体系会不断测验重发。假如 hard_start_xmit发送不成功,则不要开释sk_buff。传送下来的sk_buff中的数据现已包 含硬件需求的帧头。所以在发送办法里不需求再填充硬件帧头,数据能够直接提交给硬 件发送。sk_buff是被锁住的(locked),保证其他程序不会存取它。
2.2.5 接纳(reception)
驱动程序并不存在一个接纳办法。有数据收到应该是驱动程序来告诉体系的。一般设备 收到数据后都会产生一个中止,在中止处理程序中驱动程序请求一块sk_buff(skb),从 硬件读出数据放置到请求好的缓冲区里。接下来填充sk_buff中 的一些信息skb-》dev = dev,判别收到帧的协议类型,填入skb-》protocol(多协 议的支撑)。把指针skb-》m ac.raw指向硬件数据然后丢掉硬件帧头(skb_pull)。还要设置skb-》pkt_type,标明第二 层(链路层)数据类型。能够是以下类型:
PACKET_BROADCAST : 链路层播送
PACKET_MULTICAST : 链路层组播
PACKET_SELF : 发给自己的帧
PACKET_OTHERHOST : 发给他人的帧(监听办法时会有这种帧)
最终调用netif_rx()把数据传送给协议层。netif_rx()里数据放入处理行列然后回来, 真实的处理是在中止回来今后,这样能够削减中止时刻。调用netif_rx()今后, 驱动程序就不能再存取数据缓冲区skb。
2.2.6 硬件帧头(hard_header)
硬件一般都会在上层数据发送之前加上自己的硬件帧头,比方以太网(Ethernet)就有14 字节的帧头。这个帧头是加在上层ip、ipx等数据包的前面的。驱动程序供给一个hard_ header办法,协议层(ip、ipx、arp等)在发送数据之前会调用这段程序。 硬件帧头的长度有必要填在dev-》hard_header_len,这样协议层回在数据之前保存好硬件 帧头的空间。这样hard_header程序只需调用skb_push然后正确填入硬件帧头就能够了。
在协议层调用hard_header时,传送的参数包含(2.0.xx):数据的sk_buff,device指针 ,protocol,意图地址(daddr),源地址(saddr),数据长度(len)。数据长度不要运用s k_buff中的参数,由于调用hard_header时数据或许还没彻底安排好。saddr是NULL的话 是运用缺省地址(default)。daddr是NULL标明协议层不知道硬件意图地址。假如hard_h eader彻底填好了硬件帧头,则回来增加的字节数。假如硬件帧头中的信息还不彻底(比 如daddr为NULL,可是帧头中需求意图硬件地址。典型的状况是以太网需求地址解析(ar p)),则回来负字节数。hard_header回来负数的状况下,协议层会做进一步的build he ader的作业。现在Linux体系里便是做arp (假如hard_header回来正,dev-》arp=1,标明 不需求做arp,回来负,dev-》arp=0,做arp)。
对hard_header的调用在每个协议层的处理程序里。如ip_output。
2.2.7 地址解析(xarp)
有些网络有硬件地址(比方Ethernet),而且在发送硬件帧时需求知道意图硬件地址。这 样就需求上层协议地址(ip、ipx)和硬件地址的对应。这个对应是经过地址解析完结的。 需求做arp的的设备在发送之前会调用驱动程序的rebuild_header办法。调用的首要参数 包含指向硬件帧头的指针,协议层地址。假如驱动程序能够解析硬件地址,就回来1,如 果不能,回来0。
对rebuild_header的调用在net/core/dev.c的do_dev_queue_xmit()里。
2.2.8 参数设置和计算数据
在驱动程序里还供给一些办法供体系对设备的参数进行设置和读取信息。一般只要超级 用户(root)权限才能对设备参数进行设置。设置办法有: dev-》set_mac_address() 当用户调用ioctl类型为SIOCSIFHWADDR时是要设置这个设备的mac地址。一般对mac地址 的设置没有太大含义的。
dev-》set_config() 当用户调用ioctl时类型为SIOCSIFMAP时,体系会调用驱动程序的set_config办法。用户 会传递一个ifmap结构包含需求的I/O、中止等参数。
dev-》do_ioctl()
假如用户调用ioctl时类型在SIOCDEVPRIVATE和SIOCDEVPRIVATE+15之间,体系会调用驱 动程序的这个办法。一般是设置设备的专用数据。 读取信息也是经过ioctl调用进行。除次之外驱动程序还能够供给一个 dev-》get_stats办法,回来一个enet_statistics结构,包含发送接纳的计算信息。ioctl的处理在net/core/dev.c的dev_ioctl()和dev_ifsioc()里。
@263.net“》linuxman@263.net
.3 网络驱动程序中用到的数据结构
最重要的是网络设备的数据结构。界说在include/linux/netdevice.h里。它的注释现已
满足翔实。
struct device
{
/*
* This is the first field of the ”visible“ part of this structure
* (i.e. as seen by users in the ”Space.c“ file)。 It is the name
* the interface.
*/
char *name;
/* I/O specific fields – FIXME: Merge these and struct ifmap into one */
unsigned long rmem_end; /* shmem ”recv“ end */
unsigned long rmem_start; /* shmem ”recv“ start */
unsigned long mem_end; /* shared mem end */
unsigned long mem_start; /* shared mem start */
unsigned long base_addr; /* device I/O address */
unsigned char irq; /* device IRQ number */
/* Low-level status flags. */
volatile unsigned char start, /* start an operation */
interrupt; /* interrupt arrived */
/* 在处理中止时interrupt设为1,处理完清0。 */
unsigned long tbusy; /* transmitter busy must be long for
bitops */
struct device *next;
/* The device initialization function. Called only once. */
/* 指向驱动程序的初始化办法。 */
int (*init)(struct device *dev);
/* Some hardware also needs these fields, but they are not part of the
usual set specified in Space.c. */
/* 一些硬件能够在一块板上支撑多个接口,或许用到if_port。 */
unsigned char if_port; /* Selectable AUI, TP,。.*/
unsigned char dma; /* DMA channel */
struct enet_statistics* (*get_stats)(struct device *dev);
/*
* This marks the end of the ”visible“ part of the structure. All
* fields hereafter are internal to the system, and may change at
* will (read: may be cleaned up at will)。
*/
/* These may be needed for future network-power-down code. */
/* trans_start记载最终一次成功发送的时刻。能够用来确认硬件是否作业正常。*/
unsigned long trans_start; /* Time (in jiffies) of last Tx */
unsigned long last_rx; /* Time of last Rx */
/* flags里边有许多内容,界说在include/linux/if.h里。*/
unsigned short flags; /* interface flags (a la BSD) */
unsigned short family; /* address family ID (AF_INET) */
unsigned short metric; /* routing metric (not used) */
unsigned short mtu; /* interface MTU value */
/* type标明物理硬件的类型。首要阐明硬件是否需求arp。界说在
include/linux/if_arp.h里。 */
unsigned short type; /* interface hardware type */
/* 上层协议层依据hard_header_len在发送数据缓冲区前面预留硬件帧头空间。*/
unsigned short hard_header_len; /* hardware hdr length */
/* priv指向驱动程序自己界说的一些参数。*/
void *priv; /* pointer to private data */
/* Interface address info. */
unsigned char broadcast[MAX_ADDR_LEN]; /* hw bcast add */
unsigned char pad; /* make dev_addr aligned to 8
bytes */
unsigned char dev_addr[MAX_ADDR_LEN]; /* hw address */
unsigned char addr_len; /* hardware address length */
unsigned long pa_addr; /* protocol address */
unsigned long pa_brdaddr; /* protocol broadcast addr */
unsigned long pa_dstaddr; /* protocol P-P other side addr */
unsigned long pa_mask; /* protocol netmask */
unsigned short pa_alen; /* protocol address length */
struct dev_mc_list *mc_list; /* Multicast mac addresses */
int mc_count; /* Number of installed mcasts */
struct ip_mc_list *ip_mc_list; /* IP multicast filter chain */
__u32 tx_queue_len; /* Max frames per queue allowed */
/* For load balancing driver pair support */
unsigned long pkt_queue; /* Packets queued */
struct device *slave; /* Slave device */
struct net_alias_info *alias_info; /* main dev alias info */
struct net_alias *my_alias; /* alias devs */
/* Pointer to the interface buffers. */
struct sk_buff_head buffs[DEV_NUMBUFFS];
/* Pointers to interface service routines. */
int (*open)(struct device *dev);
int (*stop)(struct device *dev);
int (*hard_start_xmit) (struct sk_buff *skb,
struct device *dev);
int (*hard_header) (struct sk_buff *skb,
struct device *dev,
unsigned short type,
void *daddr,
void *saddr,
unsigned len);
int (*rebuild_header)(void *eth, struct device *dev,
unsigned long raddr, struct sk_buff *skb);
#define HAVE_MULTICAST
void (*set_multicast_list)(struct device *dev);
#define HAVE_SET_MAC_ADDR
int (*set_mac_address)(struct device *dev, void *addr);
#define HAVE_PRIVATE_IOCTL
int (*do_ioctl)(struct device *dev, struct ifreq *ifr, int cmd);
#define HAVE_SET_CONFIG
int (*set_config)(struct device *dev, struct ifmap *map);
#define HAVE_HEADER_CACHE
void (*header_cache_bind)(struct hh_cache **hhp, struct device
*dev, unsigned short htype, __u32 daddr);
void (*header_cache_update)(struct hh_cache *hh, struct device
*dev, unsigned char * haddr);
#define HAVE_CHANGE_MTU
int (*change_mtu)(struct device *dev, int new_mtu);
struct iw_statistics* (*get_wireless_stats)(struct device *dev);
};
2.4 常用的体系支撑
2.4.1 内存请求和开释
include/linux/kernel.h里声明晰kmalloc()和kfree()。用于在内核办法下请求和开释
内存。
void *kmalloc(unsigned int len,int priority);
void kfree(void *__ptr);
与用户办法下的malloc()不同,kmalloc()请求空间有巨细约束。长度是2的整次方。可
以请求的最大长度也有约束。其他kmalloc()有priority参数,一般运用时能够为GFP_K
ERNEL,假如在中止里调用用GFP_ATOMIC参数,由于运用GFP_KERNEL 则调用者或许进入
sleep状况,在处理中止时是不答应的。
kfree()开释的内存有必要是kmalloc()请求的。假如知道内存的巨细,也能够用kfree_s(
)开释。
2.4.2 request_irq()、free_irq()
这是驱动程序请求中止和开释中止的调用。在include/linux/sched.h里声明。
request_irq()调用的界说:
int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
unsigned long irqflags,
const char * devname,
void *dev_id);
irq是要请求的硬件中止号。在Intel渠道,规模0–15。handler是向体系挂号的中止处
理函数。这是一个回调函数,中止产生时,体系调用这个函数,传入的参 数包含硬件中
断号,device id,寄存器值。dev_id便是下面的request_irq时传递 给体系的参数dev
_id。irqflags是中止处理的一些特色。比较重要的有SA_INTERRUPT,
标明中止处理程序是快速处理程序(设置SA_INTERRUPT)仍是慢速处理程序(不设置SA_IN
TERRUPT)。快速处理程序被调用时屏蔽一切中止。慢速处理程序不屏蔽。还有 一个SA_
SHIRQ特色,设置了今后运转多个设备同享中止。dev_id在中止同享时会用到。一般设置
为这个设备的device结构自身或许NULL。中止处理程序能够用dev_id 找到相应的操控这
个中止的设备,或许用irq2dev_map找到中止对应的设备。
void free_irq(unsigned int irq,void *dev_id);
2.4.3 时钟
时钟的处理相似中止,也是挂号一个时刻处理函数,在预订的时刻往后,体系会调用这
个函数。在include/linux/timer.h里声明。
struct timer_list {
struct timer_list *next;
struct timer_list *prev;
unsigned long expires;
unsigned long data;
void (*function)(unsigned long);
};
void add_timer(struct timer_list * timer);
int del_timer(struct timer_list * timer);
void init_timer(struct timer_list * timer);
运用时钟,先声明一个timer_list结构,调用init_timer对它进行初始化。
time_list结构里expires是标明这个时钟的周期,单位选用jiffies的单位。
jiffies是Linux一个全局变量,代表时刻。它的单位随硬件渠道的不同而不同。
体系里界说了一个常数HZ,代表每秒种最小时刻距离的数目。这样jiffies的单位便是1
/HZ。Intel渠道jiffies的单位是1/100秒,这便是体系所能分辩的最小时刻距离了。所
以expires/HZ便是以秒为单位的这个时钟的周期。
function便是时刻到了今后的回调函数,它的参数便是timer_list中的data。data这个
参数在初始化时钟的时分赋值,一般赋给它设备的device结构指针。
在预置时刻到体系调用function,一起体系把这个time_list从守时行列里铲除。所以如
果需求一向运用守时函数,要在function里再次调用add_timer()把这个timer_list加进
守时行列。
2.4.4 I/O
I/O端口的存取运用:
inline unsigned int inb(unsigned short port);
inline unsigned int inb_p(unsigned short port);
inline void outb(char value, unsigned short port);
inline void outb_p(char value, unsigned short port);
在include/adm/io.h里界说。
inb_p()、outb_p()与inb()、outb_p()的不同在于前者在存取I/O时有等候(pause)一适
应慢速的I/O设备。
为了避免存取I/O时产生冲突,Linux供给对端口运用状况的操控。在运用端口之前,可
以查看需求的I/O是否正在被运用,假如没有,则把端口标记为正在运用,运用完后再释
放。体系供给以下几个函数做这些作业。
int check_region(unsigned int from, unsigned int extent);
void request_region(unsigned int from, unsigned int extent,const char *name)
;
void release_region(unsigned int from, unsigned int extent);
其间的参数from表明用到的I/O端口的开端地址,extent标明从from开端的端口数目。n
ame为设备称号。
2.4.5 中止翻开封闭
体系供给给驱动程序敞开和封闭呼应中止的才能。是在include/asm/system.h中的两个
界说。
#define cli() __asm__ __volatile__ (”cli“::)
#define sti() __asm__ __volatile__ (”sti“::)
2.4.6 打印信息
相似一般程序里的printf(),驱动程序要输出信息运用printk()。在include/linux/ke
rnel.h里声明。
int printk(const char* fmt, 。..);
其间fmt是格局化字符串。。..是参数。都是和printf()格局相同的。
2.4.7 注册驱动程序
假如运用模块(module)办法加载驱动程序,需求在模块初始化时把设备注册 到体系设备
表里去。不再运用时,把设备从体系中卸除。界说在drivers/net/net_init.h里的两个
函数完结这个作业。
int register_netdev(struct device *dev);
void unregister_netdev(struct device *dev);
dev便是要注册进体系的设备结构指针。在register_netdev()时,dev结构一般填写前面
11项,即到init,后边的暂时能够不必初始化。最重要的是name指针和init办法。name
指针空(NULL)或许内容为或许name[0]为空格(space),则体系把你的设备做为以太网设
备处理。以太网设备有一致的命名格局,ethX。对以太网这么特别对待大约和Linux的历
史有关。
init办法一定要供给,register_netdev()会调用这个办法让你对硬件检测和设置。
register_netdev()回来0表明成功,非0不成功。
2.4.8 sk_buff
Linux网络各层之间的数据传送都是经过sk_buff。sk_buff供给一套办理缓冲区的办法,
是Linux体系网络高效运转的要害。每个sk_buff包含一些操控办法和一块数据缓冲区。
操控办法按功用分为两种类型。一种是操控整个buffer链的办法,
另一种是操控数据缓冲区的办法。sk_buff安排成双向链表的办法,依据网络运用的特色
,对链表的操作首要是删去链表头的元素和增加到链表尾。sk_buff的操控
办法都很矮小以尽量削减体系负荷。(translated from article written by Alan Cox
)
常用的办法包含:
.alloc_skb() 请求一个sk_buff并对它初始化。回来便是请求到的sk_buff。
.dev_alloc_skb()相似alloc_skb,在请求好缓冲区后,保存16字节的帧头空间。首要用
在Ethernet驱动程序。
.kfree_skb() 开释一个sk_buff。
.skb_clone() 仿制一个sk_buff,但不仿制数据部分。
.skb_copy()彻底仿制一个sk_buff。
.skb_dequeue() 从一个sk_buff链表里取出第一个元素。回来取出的sk_buff,假如链表
空则回来NULL。这是常用的一个操作。
.skb_queue_head() 在一个sk_buff链表头放入一个元素。
.skb_queue_tail() 在一个sk_buff链表尾放入一个元素。这也是常用的一个操作。网络
数据的处理首要是对一个先进先出行列的办理,skb_queue_tail()
和skb_dequeue()完结这个作业。
.skb_insert() 在链表的某个元素前刺进一个元素。
.skb_append() 在链表的某个元素后刺进一个元素。一些协议(如TCP)对没按次序抵达的
数据进行重组时用到skb_insert()和skb_append()。
.skb_reserve() 在一个请求好的sk_buff的缓冲区里保存一块空间。这个空间一般是用
做下一层协议的头空间的。
.skb_put() 在一个请求好的sk_buff的缓冲区里为数据保存一块空间。在
alloc_skb今后,请求到的sk_buff的缓冲区都是处于空(free)状况,有一个tail指针指
向free空间,实际上开端时tail就指向缓冲区头。skb_reserve()
在free空间里请求协议头空间,skb_put()请求数据空间。见下面的图。
.skb_push() 把sk_buff缓冲区里数据空间往前移。即把Head room中的空间移一部分到
Data area。
.skb_pull() 把sk_buff缓冲区里Data area中的空间移一部分到Head room中。
————————————————–
| Tail room(free) |
————————————————–
After alloc_skb()
————————————————–
| Head room | Tail room(free) |
————————————————–
After skb_reserve()
————————————————–
| Head room | Data area | Tail room(free) |
————————————————–
After skb_put()
————————————————–
|Head| skb_ | Data | Tail room(free) |
|room| push | | |
| | Data area | |
————————————————–
After skb_push()
————————————————–
| Head | skb_ | Data area | Tail room(free) |
| | pull | | |
| Head room | | |
————————————————–
After skb_pull()
三。编写Linux网络驱动程序中需求留意的问题
3.1 中止同享
Linux体系运转几个设备同享同一个中止。需求同享的话,在请求的时分指明同享办法。
体系供给的request_irq()调用的界说:
int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
unsigned long irqflags,
const char * devname,
void *dev_id);
假如同享中止,irqflags设置SA_SHIRQ特色,这样就答应其他设备请求同一个中止。需
要留意一切用到这个中止的设备在调用request_irq()都有必要设置这个特色。体系在回调
每个中止处理程序时,能够用dev_id这个参数找到相应的设备。一 般dev_id就设为dev
ice结构自身。体系处理同享中止是用各自的dev_id参数顺次调用每一个中止处理程序。
3.2 硬件发送忙时的处理
主CPU的处理才能一般比网络发送要快,所以经常会遇到体系有数据要发,但上一包数据
网络设备还没发送完。由于在Linux里网络设备驱动程序一般不做数据缓存,不能发送的
数据都是告诉体系发送不成功,所以有必要要有一个机制在硬件不忙时及时告诉体系接着
发送下面的数据。
一般对发送忙的处理在前面设备的发送办法(hard_start_xmit)里现已描绘过,即假如发
送忙,置tbusy为1。处理完发送数据后,在发送完毕中止里清tbusy,一起用mark_bh()
调用告诉体系持续发送。
但在详细完结我的驱动程序时发现,这样的处理体系好象并不能及时地知道硬件现已空
闲了,即在mark_bh()今后,体系要等一段时刻才会接着发送。构成发送功率很低。2M线
路只要10%不到的运用率。内核版别为2.0.35。
我最终的完结是不把tbusy置1,让体系始终认为硬件闲暇,可是陈述发送不成功。体系
会一向测验重发。这样处理就运转正常了。可是遍循内核源码中的网络驱动程序,好像
没有这样处理的。不知道症结在哪里。
3.3 流量操控(flow control)
网络数据的发送和接纳都需求流量操控。这些操控是在体系里完结的,不需求驱动程序
做作业。每个设备数据结构里都有一个参数dev-》tx_queue_len,这个参数标明发送时最
多缓存的数据包。在Linux体系里以太网设备(10/100Mbps)tx_queue_len一般设置为100
,串行线路(异步串口)为10。实际上假如看源码能够知道,设置了dev-》tx_queue_len并
不是为缓存这些数据请求了空间。这个参数只是在收到协议层的数据包时判别发送行列
里的数据是不是到了tx_queue_len的极限,以决议这一包数据加不加进发送行列。发送
时另一个方面的流控是更高层协议的发送窗口(TCP协议里就有发送窗口)。达到了窗口大
小,高层协议就不会再发送数据。
接纳流控也分两个层次。netif_rx()缓存的数据包有约束。其他高层协议也会有一个最
大的等候处理的数据量。
发送和接纳流控处理在net/core/dev.c的do_dev_queue_xmit()和netif_rx()中。
3.4 调试
许多Linux的驱动程序都是编译进内核的,构成一个大的内核文件。但对调试来说,这是
适当费事的。调试驱动程序能够用module办法加载。支撑模块办法的驱动程序有必要供给
两个函数:int init_module(void)和void cleanup_module(void)。init_module()在加
载此模块时调用,在这个函数里能够register_netdev()注册设备。init_module()回来
0表明成功,回来负表明失利。cleanup_module()在驱动程序被卸载时调用,铲除占用的
资源,调用unregister_netdev()。
模块能够动态地加载、卸载。在2.0.xx版别里,还有kerneld主动加载模块,可是2.2.x
x中现已取消了kerneld。手艺加载运用insmod指令,卸载用rmmod指令,看内核中的模块
用lsmod指令。
编译驱动程序用gcc,首要指令行参数-DKERNEL -DMODULE。而且作为模块加载的驱动程
序,只编译成obj办法(加-c参数)。编译好的方针文件放在/lib/modules/2.x.xx/misc下
,在发动文件里用insmod加载。
四。进一步的阅览
Linux程序设计材料能够从网上取得。这便是敞开源代码的优点。