260 likes | 494 Views
Linux 操作系统分析与实践 第九讲:网络设备驱动程序. 《 Linux 操作系统分析与实践 》 课程建设小组 北京大学 二零零八年春季 *致谢:感谢 Intel 对本课程项目的资助. 本讲主要内容. 网络接口驱动程序 网络接口例子 —snull. 9.1 网络接口驱动程序. 网络接口驱动程序和字符设备、块设备驱动程序都不同 本次课介绍一个不和真实硬件相关的“虚拟”网络接口. 设备注册. <linux/netdevice.h> struct net_device 这个结构中包括了网络设备接口需要的很多信息 必须动态分配,例如
E N D
Linux操作系统分析与实践第九讲:网络设备驱动程序 《Linux操作系统分析与实践》课程建设小组 北京大学 二零零八年春季 *致谢:感谢Intel对本课程项目的资助
本讲主要内容 • 网络接口驱动程序 • 网络接口例子—snull
9.1 网络接口驱动程序 • 网络接口驱动程序和字符设备、块设备驱动程序都不同 • 本次课介绍一个不和真实硬件相关的“虚拟”网络接口
设备注册 • <linux/netdevice.h> • struct net_device • 这个结构中包括了网络设备接口需要的很多信息 • 必须动态分配,例如 • struct net_device *snull_devs[2]; • struct net_device *alloc_netdev(int sizeof_priv, const char *name, void (*setup)(struct net_device *)); • struct net_device *alloc_netdev(int sizeof_priv, /*私有数据区长度 ,驱动程序设计者设计*/ const char *name,/*接口名 */ void (*setup)(struct net_device *) /* 网络接口初始化函数 */ );
例子 snull_devs[0] = alloc_netdev(sizeof(struct snull_priv), "sn%d", snull_init); snull_devs[1] = alloc_netdev(sizeof(struct snull_priv), "sn%d", snull_init); if (snull_devs[0] = = NULL || snull_devs[1] = = NULL) goto out;
alloc_etherdev • 除了alloc_netdev,也有一些简化工作的函数,比如: • struct net_device *alloc_etherdev(int sizeof_priv); • 这个函数不用驱动程序提供接口初始化函数,网络接口名是 eth%d 这样的形式 • 在<linux/etherdevice.h>中定义
register_netdev • 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);
初始化设备 ether_setup(dev); /* 对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->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->features |= NETIF_F_NO_CSUM; dev->hard_header_cache = NULL; /* Disable caching */ 网络设备的一系列操作和 状态标志
程序员定义的结构snull_priv struct snull_priv { struct net_device_stats stats; //统计信息 int status;//状态 struct snull_packet *ppool; struct snull_packet *rx_queue; /* List of incoming packets */ int rx_int_enabled; int tx_packetlen; u8 *tx_packetdata; struct sk_buff *skb; spinlock_t lock; }; 通过netdev_priv()来获得设备 的私有数据区
清理工作 void snull_cleanup(void) { int i; for (i = 0; i < 2; i++) { if (snull_devs[i]) { unregister_netdev(snull_devs[i]); snull_teardown_pool(snull_devs[i]); free_netdev(snull_devs[i]); } } return; }
net_device 结构中重要数据介绍 • char name[IFNAMSIZ]; • 网络接口名,如果名字中包括 %d,那么 register_netdev 替换它为唯一名字,从0开始分配数字 • unsigned long state; • 驱动状态 • struct net_device *next; • 在全局接口链表中指向下一个接口结构 • int (*init)(struct net_device *dev); • 初始化函数。被register_netdev调用完成初始化。现在一般不使用这个函数,而是在调用register_netdev前就对结构进行初始化
Cont. • unsigned long rmem_end; • unsigned long rmem_start; • unsigned long mem_end; • unsigned long mem_start; • Device memory information. These fields hold the beginning and ending addresses of the shared memory used by the device. Rmem 代表接收内存;mem_代表发送内存 • unsigned long base_addr; • The I/O base address of the network interface. • unsigned char irq; • The assigned interrupt number. • unsigned char if_port; • The port in use on multiport devices. • unsigned char dma; • The DMA channel allocated by the device.
比较重要的网络设备驱动方法 • int (*open)(struct net_device *dev); • Opens the interface. The interface is opened whenever ifconfig activates it. The open method should register any system resource it needs (I/O ports, IRQ, DMA, etc.), turn on the hardware, and perform any other setup your device requires. • int (*stop)(struct net_device *dev); • Stops the interface. The interface is stopped when it is brought down. This function should reverse operations performed at open time
Cont. • int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev); • Method that initiates the transmission of a packet. The full packet (protocol headers and all) is contained in a socket buffer (sk_buff) structure. Socket buffers are introduced later in this chapter. • int (*hard_header) (struct sk_buff *skb, struct net_device *dev, unsigned • short type, void *daddr, void *saddr, unsigned len); • Function (called before hard_start_xmit) that builds the hardware header from the source and destination hardware addresses that were previously retrieved; its job is to organize the information passed to it as arguments into an appropriate, device-specific hardware header. eth_header is the default function for Ethernet-like interfaces, and ether_setup assigns this field accordingly.
Cont. • void (*tx_timeout)(struct net_device *dev); • Method called by the networking code when a packet transmission fails to complete within a reasonable period, on the assumption that an interrupt has been missed or the interface has locked up. It should handle the problem and resume packet transmission. • struct net_device_stats *(*get_stats)(struct net_device *dev); • Whenever an application needs to get statistics for the interface, this method is called. This happens, for example, when ifconfig or netstat -i is run. • int (*set_mac_address)(struct net_device *dev, void *addr); • int (*change_mtu)(struct net_device *dev, int new_mtu); • maximum transfer unit (MTU)
9.2 网络接口例子 snull • 打开设备 int snull_open(struct net_device *dev) { /* request_region( ), request_irq( ), .... (like fops->open) * Assign the hardware address of the board: use "\0SNULx", where * x is 0 or 1. The first byte is '\0' to avoid being a multicast * address (the first byte of multicast addrs is odd). */ memcpy(dev->dev_addr, "\0SNUL0", ETH_ALEN); if (dev = = snull_devs[1]) dev->dev_addr[ETH_ALEN-1]++; /* \0SNUL1 */ netif_start_queue(dev); return 0; } netif_start_queue()使能处理数据; netif_stop_queue()停止处理数据
Cont. • 关闭设备 int snull_release(struct net_device *dev) { /* release ports, irq and such -- like fops->close */ netif_stop_queue(dev); /* can't transmit any more */ return 0; }
发送数据 int snull_tx(struct sk_buff *skb, struct net_device *dev) { int len; char *data, shortpkt[ETH_ZLEN]; struct snull_priv *priv = netdev_priv(dev); data = skb->data; len = skb->len; if (len < ETH_ZLEN) { memset(shortpkt, 0, ETH_ZLEN); memcpy(shortpkt, skb->data, skb->len); len = ETH_ZLEN; data = shortpkt; } 要发送的数据保存在 Skb->data 指向的位置 从skb->data拷贝数据到要保存 发送数据的缓冲区shortpkt
Cont. dev->trans_start = jiffies; /* save the timestamp */ /* Remember the skb, so we can free it at interrupt time */ priv->skb = skb; /* actual deliver of data is device-specific, and not shown here */ snull_hw_tx(data, len, dev); return 0; /* Our simple device can not fail */ } 在snull_hw_tx中,实际上我们可以 编写程序实现具体硬件的发送数据 的控制,这里的snull_hw_tx并 没有对哪个具体的硬件进行操作。
snull_hw_tx /* * Transmit a packet (low level interface) */ static 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; struct net_device *dest; struct snull_priv *priv; u32 *saddr, *daddr; struct snull_packet *tx_buffer;
/* I am paranoid. Ain't I? */ 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; /* change the third octet (class C) */ ((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[0]) 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[0] ? 1 : 0]; priv = netdev_priv(dest); tx_buffer = snull_get_tx_buffer(dev); tx_buffer->datalen = len; memcpy(tx_buffer->data, buf, len); snull_enqueue_buf(dest, tx_buffer);
Cont. if (priv->rx_int_enabled) { priv->status |= SNULL_RX_INTR; snull_interrupt(0, dest, NULL); } priv = netdev_priv(dev); priv->tx_packetlen = len; priv->tx_packetdata = buf; priv->status |= SNULL_TX_INTR; 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); }
模拟真实中断的中断处理程序 static void snull_napi_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 */ /* paranoid */ if (!dev) return;
Cont. /* Lock the device */ priv = netdev_priv(dev); spin_lock(&priv->lock); /* retrieve statusword: real netdevices use I/O instructions */ statusword = priv->status; priv->status = 0; if (statusword & SNULL_RX_INTR) { snull_rx_ints(dev, 0); /* Disable further interrupts */ netif_rx_schedule(dev); } 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); } /* Unlock the device and we are done */ spin_unlock(&priv->lock); return; }
Q&A 本讲结束 !