大家好,我是极客范的本期栏目编辑小友,现在为大家讲解学会安装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是套接字缓冲区,在网络传输过程中起着重要的作用。内核将数据包封装为套接字缓冲区,并将其发送到网络硬件。当网络硬件接收到数据包时,它将数据包封装为套接字缓冲区,并将其发送给内核。
注册网络设备:
int register _ net dev(struct net _ device * dev);//网络设备不同于字符设备和块设备,因为它们没有主设备号和辅助设备号。
注销网络设备:
void unregister _ net dev(struct net _ device * dev);
返回网络设备结构的私有数据:
void * net dev _ priv(struct net _ device * dev);
也就是说,返回我们定义的设备结构。
保存设备统计信息的结构。
结构网络设备统计
打开发送队列,能够发送数据包,并调用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 #ifdef SNULL_DEBUG# ifdef __KERNEL__ # define PDEBUG(fmt, args...) printk( KERN_DEBUG"snull:"fmt, ## args)# else # define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)# endif#else# define PDEBUG(fmt, args...) #endif#undef PDEBUGG#define PDEBUGG(fmt, args...) #define SNULL_RX_INTR 0x0001#define SNULL_TX_INTR 0x0002#define SNULL_TIMEOUT 6 #include#include#include #include #include #include #include #include#include #include #include #include #include#include#include#include#includestatic int lockup = 0;static int timeout = SNULL_TIMEOUT;struct net_device snull_devs[2];//这里定义两个设备,一个是snull0,一个是snull1//网络设备结构体,作为net_device->privstruct 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); memcpy(skb_put(skb, len), buf, len);//skb_put是把数据写入到socket buffer 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; if (!dev ) return; priv = (struct snull_priv *) dev->priv; spin_lock(&priv->lock); statusword = priv->status; if (statusword & SNULL_RX_INTR) {//如果是接收 snull_rx(dev, priv->rx_packetlen, priv->rx_packetdata); } if (statusword & SNULL_TX_INTR) {//如果发送完毕 priv->stats.tx_packets++; priv->stats.tx_bytes += priv->tx_packetlen; dev_kfree_skb(priv->skb);//释放skb 套接字缓冲区 } 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;//源设备地址与目标设备地址 if (len < sizeof(struct ethhdr) + sizeof(struct iphdr)) { 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 = (struct iphdr *)(buf+sizeof(struct ethhdr)); saddr = &ih->saddr; daddr = &ih->daddr;//在同一台机器上模拟两个网络,不同的网段地址,进行发送网络数据包与接收网络数据包 ((u8 *)saddr)[2] ^= 1; ((u8 *)daddr)[2] ^= 1; ih->check = 0; 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 <-- %08x:%05i/n", ntohl(ih->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) { 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; }/* * Deal with a transmit timeout. *///一旦超出watchdog_timeo就会调用snull_tx_timeoutvoid 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; dev->flags |= IFF_NOARP; dev->hard_header_cache = NULL; 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: 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网络驱动就介绍到这里了。