学会安装Linux的网络驱动

1.概述
linux系统多用于服务器上,linux非常牢固的支持网络。在linux,网络分为两个层,分别是网络堆栈协议支持层,以及接收和发送网络协议的设备驱动程序层。网络堆栈是硬件中独立出来的部分,主要用来支持tcp/ip等多种协议,而网络设备驱动层是连接网络堆栈协议层和网络硬件的中间层。
网络设备驱动程序的主要功能是:
(1)模块加载或内核启动相关的初始化处理
(2)清除模块时的处理
(3)网络设备的检索和探测
(4)网络设备的初始化和注册
(5)打开或关闭网络设备
(6)发送网络数据
(7)接收网络数据
(8)中断处理(在发送完数据时,硬件向内核产生一个中断,告诉内核数据已经发送完毕,在网络设备接收到数据时,也要发生一个中断,告诉内核,数据已经到达,请及时处理)
(9)超时处理
(10)多播处理
(11)网络设备的控制ioctl
而linux网络设备驱动的主要功能就是网络设备的初始化,网络设备的配置,数据包的收发。
2. linux网络设备驱动的接口函数
net_device结构体存储一个网络接口的重要信息,它是系统中网络设备的代表。
sk_buff是socket buffer,在网络传输过程中起着重要的作用,内核把数据包封装成socket buffer向网络硬件发送,当网络硬件接收到数据包时,再把数据包封装成socket buffer向内核传送。
注册网络设备:
int register_netdev(struct net_device *dev);//网络设备与字符设备,块设备不同,没有主,次设备号
注销网络设备:
void unregister_netdev(struct net_device *dev);
返回网络设备结构体的private data:
void *netdev_priv(struct net_device *dev);
即返回我们定义的设备结构体。
保存设备统计信息的结构体
struct net_device_stats
打开发送队列,能够发送数据包,在open()中调用
netif_start_queue(struct net_device *dev);
关闭发送队列,在stop()中调用
netif_stop_queue(struct net_device* dev);
重新打开队列,一般在关闭队列之后重启队列
netif_wake_queue(struct net_device *dev);
当数据到达时,通知内核数据包到达
void netif_rx(struct sk_buff *skb);
分配一个sk_buff结构体
struct sk_buff *dev_alloc_skb(unsigned int len);
释放sk_buff结构体
void dev_kfree_skb(struct sk_buff *skb);
从数据的尾部扩展len长度的空间,为了把数据放到skb的尾部
unsigned char* skb_put(struct sk_buff *skb,int len);
siocdevprivate 可用ioctl执行的16个命令的第一个命令
最后一个是siocdevprivate+15
3.下面给出一个虚拟硬件的网络驱动的例子
#undef pdebug /* undef it, just in case */
#ifdef snull_debug
# ifdef __kernel__
/* this one if debugging is on, and kernel space */
# define pdebug(fmt, args...) printk( kern_debug snull: fmt, ## args)
# else
/* this one for user space */
# define pdebug(fmt, args...) fprintf(stderr, fmt, ## args)
# endif
#else
# define pdebug(fmt, args...) /* not debugging: nothing */
#endif
#undef pdebugg
#define pdebugg(fmt, args...) /* nothing: it's a placeholder */
/* these are the flags in the statusword */
#define snull_rx_intr 0x0001
#define snull_tx_intr 0x0002
/* default timeout period */
#define snull_timeout 6 /* in jiffies */
#include
#include
#include /* printk() */
#include /* kmalloc() */
#include /* error codes */
#include /* size_t */
#include /* mark_bh */
#include
#include /* struct device, and other headers */
#include /* eth_type_trans */
#include /* struct iphdr */
#include /* struct tcphdr */
#include
#include
#include
#include
#include
static int lockup = 0;
static int timeout = snull_timeout;
struct net_device snull_devs[2];//这里定义两个设备,一个是snull0,一个是snull1
//网络设备结构体,作为net_device->priv
struct snull_priv {
struct net_device_stats stats;//有用的统计信息
int status;//网络设备的状态信息,是发完数据包,还是接收到网络数据包
int rx_packetlen;//接收到的数据包长度
u8 *rx_packetdata;//接收到的数据
int tx_packetlen;//发送的数据包长度
u8 *tx_packetdata;//发送的数据
struct sk_buff *skb;//socket buffer结构体,网络各层之间传送数据都是通过这个结构体来实现的
spinlock_t lock;//自旋锁
};
void snull_tx_timeout (struct net_device *dev);
//网络接口的打开函数
int snull_open(struct net_device *dev)
{
printk(call snull_open/n);
memcpy(dev->dev_addr, /0snul0, eth_alen);//分配一个硬件地址,eth_alen是网络设备硬件地址的长度
netif_start_queue(dev);//打开传输队列,这样才能进行数据传输
return 0;
}
int snull_release(struct net_device *dev)
{
printk(call snull_release/n);
netif_stop_queue(dev); //当网络接口关闭的时候,调用stop方法,这个函数表示不能再发送数据
return 0;
}
//接包函数
void snull_rx(struct net_device *dev, int len, unsigned char *buf)
{
struct sk_buff *skb;
struct snull_priv *priv = (struct snull_priv *) dev->priv;
/*
* the packet has been retrieved from the transmission
* medium. build an skb around it, so upper layers can handle it
*/
skb = dev_alloc_skb(len+2);//分配一个socket buffer,并且初始化skb->data,skb->tail和skb->head
if (!skb) {
printk(snull rx: low on mem - packet dropped/n);
priv->stats.rx_dropped++;
return;
}
skb_reserve(skb, 2); /* align ip on 16b boundary */
memcpy(skb_put(skb, len), buf, len);//skb_put是把数据写入到socket buffer
/* write metadata, and then pass to the receive level */
skb->dev = dev;
skb->protocol = eth_type_trans(skb, dev);//返回的是协议号
skb->ip_summed = checksum_unnecessary; //此处不校验
priv->stats.rx_packets++;//接收到包的个数+1
priv->stats.rx_bytes += len;//接收到包的长度
netif_rx(skb);//通知内核已经接收到包,并且封装成socket buffer传到上层
return;
}
/*
* the typical interrupt entry point
*/
//中断处理,此程序中没有硬件,因此,没有真正的硬件中断,只是模拟中断,在发送完网络数据包之后,会产生中断
//用来通知内核已经发送完数据包,当新的数据包到达网络接口时,会发生中断,通知新的数据包已经到来了
void snull_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
int statusword;//用来标识是发送完毕还是接收到新的数据包
struct snull_priv *priv;
/*
* as usual, check the device pointer for shared handlers.
* then assign struct device *dev
*/
struct net_device *dev = (struct net_device *)dev_id;
/* ... and check with hw if it's really ours */
if (!dev /*paranoid*/ ) return;
/* lock the device */
priv = (struct snull_priv *) dev->priv;
spin_lock(&priv->lock);
/* retrieve statusword: real netdevices use i/o instructions */
statusword = priv->status;
if (statusword & snull_rx_intr) {//如果是接收
/* send it to snull_rx for handling */
snull_rx(dev, priv->rx_packetlen, priv->rx_packetdata);
}
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);//释放skb 套接字缓冲区
}
/* unlock the device and we are done */
spin_unlock(&priv->lock);
return;
}
/*
* transmit a packet (low level interface)
*/
//真正的处理的发送数据包
//模拟从一个网络向另一个网络发送数据包
void snull_hw_tx(char *buf, int len, struct net_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
*/
struct iphdr *ih;//ip头部
struct net_device *dest;//目标设备结构体,net_device存储一个网络接口的重要信息,是网络驱动程序的核心
struct snull_priv *priv;
u32 *saddr, *daddr;//源设备地址与目标设备地址
/* i am paranoid. ain't i? */
if (len saddr;
daddr = &ih->daddr;
//在同一台机器上模拟两个网络,不同的网段地址,进行发送网络数据包与接收网络数据包
((u8 *)saddr)[2] ^= 1; /* change the third octet (class c) ^是位异或操作符把第三个部分的网络地址与1进行异或,由于同一网络的数据不进行转发*/
((u8 *)daddr)[2] ^= 1;
ih->check = 0; /* and rebuild the checksum (ip needs it) */
ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
if (dev == snull_devs)
pdebugg(%08x:%05i --> %08x:%05i/n,
ntohl(ih->saddr),ntohs(((struct tcphdr *)(ih+1))->source),
ntohl(ih->daddr),ntohs(((struct tcphdr *)(ih+1))->dest));
else
pdebugg(%08x:%05i daddr),ntohs(((struct tcphdr *)(ih+1))->dest),
ntohl(ih->saddr),ntohs(((struct tcphdr *)(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
*/
dest = snull_devs + (dev==snull_devs ? 1 : 0);//如果dev是0,那么dest就是1,如果dev是1,那么dest是0
priv = (struct snull_priv *) dest->priv;//目标dest中的priv
priv->status = snull_rx_intr;
priv->rx_packetlen = len;
priv->rx_packetdata = buf;
snull_interrupt(0, dest, null);
priv = (struct snull_priv *) dev->priv;
priv->status = snull_tx_intr;
priv->tx_packetlen = len;
priv->tx_packetdata = buf;
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,
(unsigned long) priv->stats.tx_packets);
}
else
snull_interrupt(0, dev, null);
}
/*
* transmit a packet (called by the kernel)
*/
//发包函数
int snull_tx(struct sk_buff *skb, struct net_device *dev)
{
int len;
char *data;
struct snull_priv *priv = (struct snull_priv *) dev->priv;
if ( skb == null) {
pdebug(tint for %p, skb %p/n, dev, skb);
snull_tx_timeout (dev);
if (skb == null)
return 0;
}
len = skb->len len;//eth_zlen是所发的最小数据包的长度
data = skb->data;//将要发送的数据包中数据部分
dev->trans_start = jiffies; //保存当前的发送时间
priv->skb = skb;
snull_hw_tx(data, len, dev);//真正的发送函数
return 0; /* our simple device can not fail */
}
/*
* deal with a transmit timeout.
*/
//一旦超出watchdog_timeo就会调用snull_tx_timeout
void snull_tx_timeout (struct net_device *dev)
{
printk(call snull_tx_timeout/n);
struct snull_priv *priv = (struct snull_priv *) dev->priv;
pdebug(transmit timeout at %ld, latency %ld/n, jiffies,
jiffies - dev->trans_start);
priv->status = snull_tx_intr;
snull_interrupt(0, dev, null);//超时后发生中断
priv->stats.tx_errors++;//发送的错误数
netif_wake_queue(dev); //为了再次发送数据,调用此函数,重新启动发送队列
return;
}
/*
* ioctl commands
*/
int snull_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
pdebug(ioctl/n);
return 0;
}
/*
* return statistics to the caller
*/
struct net_device_stats *snull_stats(struct net_device *dev)
{
struct snull_priv *priv = (struct snull_priv *) dev->priv;
return &priv->stats;//得到统计资料信息
}
//设备初始化函数
int snull_init(struct net_device *dev)
{
printk(call snull_init/n);
/*
* then, assign other fields in dev, using ether_setup() and some
* hand assignments
*/
ether_setup(dev);//填充一些以太网中的设备结构体的项
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->change_mtu = snull_change_mtu;
// dev->rebuild_header = snull_rebuild_header;
//dev->hard_header = snull_header;
dev->tx_timeout = snull_tx_timeout;//超时处理
dev->watchdog_timeo = timeout;
/* keep the default flags, just add noarp */
dev->flags |= iff_noarp;
dev->hard_header_cache = null; /* disable caching */
set_module_owner(dev);
/*
* then, allocate the priv field. this encloses the statistics
* and a few private fields.
*/
//为priv分配内存
dev->priv = kmalloc(sizeof(struct snull_priv), gfp_kernel);
if (dev->priv == null)
return -enomem;
memset(dev->priv, 0, sizeof(struct snull_priv));
spin_lock_init(& ((struct snull_priv *) dev->priv)->lock);
return 0;
}
struct net_device snull_devs[2] = {
{ init: snull_init, }, /* init, nothing more */
{ init: snull_init, }
};
int snull_init_module(void)
{
int i,result=0;
strcpy(snull_devs[0].name,snull0);//net_device结构体中的name表示设备名
strcpy(snull_devs[1].name,snull1);//即定义了两个设备,snull0与snull1
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);
return 0;
}
void snull_cleanup(void)
{
int i;
for (i=0; i<2; i++) {
kfree(snull_devs[i].priv);
unregister_netdev(snull_devs+i);
}
return;
}
module_init(snull_init_module);
module_exit(snull_cleanup);
分析:
这个例子中包括了以下部分:
(1)网络设备初始化 snull_init
(2)发送数据包函数snull_tx,而真正的发送数据包函数是snull_hw_tx,在snull_hw_tx,目标设备dest收到数据包产生中断,然后再向源设备发送数据包,发送完之后也产生中断
(3)接收数据包的函数snull_rx
(4)中断处理snull_interrupt
(5)网络超时处理snull_timeout
(6)网络设备的打开snull_open
测试:
(1)生成snull.ko, insmod snull.ko
(2)为两个网络设备分配ip:
ifconfig snull0 192.168.0.1
ifconfig snull1 192.168.1.2
可以看出,两个网络设备在不同的网段
ping 192.168.0.2 由于目标daddr经过((u8 *)daddr)[2] ^= 1,变成 192.168.1.2,相当于ping 192.168.1.2.
而源ip 192.168.0.1 经过((u8 *)saddr)[2] ^= 1,变成192.168.1.1,那么dest设备发送的数据包地址是192.168.1.1,相当于发送给192.168.0.1
如果不经过这样处理,直接ping 192.168.1.2 是不能ping 通的,由于不在同一个网段上。
可以测试一下car /var/log/messages.
关于linux网络驱动就介绍到这里了。

纵行科技发布新一代LPWAN2.0产品”ZETA泛工业物联网关”
工频机UPS不间断电源的应用
英国下血本,意图成为自动驾驶技术及其应用领域的全球领导者
欧姆龙血压计内部结构解析
Linux和Android车载系统比较
学会安装Linux的网络驱动
使用无功补偿装置调整不平衡电流的基本原理
讨论一下RS485总线上下拉电阻的选择问题
Spring状态机存在的问题
使用通用电源IC实现电源时序控制的电路-前言
微内核中的电源管理
东莞南城代理记账选东莞南城佰达会计公司,轻松,安全
TCP协议拥塞控制的滑动窗口协议解析
iphone8什么时候上市?iphone8最新消息:iphone 8外观确定真机上手,就长这样了,买买买?
SK海力士与大连市人民政府签署合作谅解备忘录,助力中国信息技术产业发展及区域经济繁荣
黑科技切入新战场 全面薄冰箱登陆AWE
影响电源EMI滤波器插入损耗的原因及改进方法研究
CPSE安博会将于10月举办,预计将有超过6万种数字城市产品亮相
AMD未来五年将在印度投资4亿美元,建立最大的设计中心
GaAs器件2020年营收达64.92亿美元