1.1 网络设备的特殊性
网络设备作为Linux的三类设备之一,有非常特殊的地方。每一个字符设备或块设备在文件系统中都有一个相应的特殊设备文件来表示该设备,如/dev/hda1、/dev/sda1、/dev/tty1等。网络设备与它们不同,所有网络设备都抽象为一个接口,这个接口提供了对所有网络设备的操作集合。网络接口不存在于Linux的文件系统中,在/dev目录下没有对应的设备名,但每个网络设备有自己的设备名称,从设备名称可以看出设备类型,如lo表示回环设备,eth表示以太网设备。同类型的多个设备从0向上编号,如以太网设备的编号为 eth0、eth1…ethn等。使用ifconfig命令可以查看当前活动的网卡信息。
- [root@/home]ifconfig
- eth0 Link encap:Ethernet HWaddr 00:0C:29:10:26:00
- inet addr:192.168.1.101 Bcast:192.168.1.255 Mask:255.255.255.0
- inet6 addr: fe80::20c:29ff:fe10:2600/64 Scope:Link
- UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
- RX packets:14106 errors:0 dropped:0 overruns:0 frame:0
- TX packets:21977 errors:0 dropped:0 overruns:0 carrier:0
- collisions:0 txqueuelen:1000
- RX bytes:1416177 (1.3 MiB) TX bytes:22373971 (21.3 MiB)
- Interrupt:16 Base address:0x2024
- lo Link encap:Local Loopback
- inet addr:127.0.0.1 Mask:255.0.0.0
- inet6 addr: ::1/128 Scope:Host
- UP LOOPBACK RUNNING MTU:16436 Metric:1
- RX packets:3273 errors:0 dropped:0 overruns:0 frame:0
- TX packets:3273 errors:0 dropped:0 overruns:0 carrier:0
- collisions:0 txqueuelen:0
- RX bytes:6961876 (6.6 MiB) TX bytes:6961876 (6.6 MiB)
网络应用程序通过Socket套接字接口、网络协议层、网络驱动程序访问网络设备。Linux的网络系统主要基于BSD UNIX的socket机制。在网络子系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。Linux网络子系统为系统提供协议层支持、数据缓冲和流量控制等。
1.2 sk_buff结构
sk_buff结构是整个Linux内核网络子系统中最核心的数据结构。Linux网络各层之间的数据传送都要通过sk_buff结构。sk_buff结构定义如下:
- struct sk_buff {
- struct sk_buff *next; //双向链表指针
- struct sk_buff *prev;
- struct sock *sk; // 拥有这个sk_buff的sock结构
- ktime_t tstamp; //到达或发送的时间戳
- struct net_devic e *dev;//关联的网络设备
- struct dst_entry *dst; // 路由子系统中使用
- struct sec_path *sp; // IPSec协议用于跟踪传输的信息
- char cb[48];//各层私有数据
- unsigned int len, //当前协议数据包的长度
- data_len; //分片中数据的长度
- __u16 mac_len,//mac头的长度
- hdr_len;
- union {
- __wsum csum;
- struct {
- __u16 csum_start;
- __u16 csum_offset;
- };
- };
- __u32 priority;
- __u8 local_df:1,
- cloned:1,
- ip_summed:2,
- nohdr:1,
- nfctinfo:3;
- __u8 pkt_type:3,// 帧的类型
- fclone:2,
- ipvs_property:1,
- nf_trace:1;
- __be16 protocol;//协议,包括IP、IPV6、ARP等
- // 在缓冲区释放时完成某些动作的函数
- void (*destructor)(struct sk_buff *skb);
- #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
- struct nf_conntrack *nfct;
- struct sk_buff *nfct_reasm;
- #endif
- #ifdef CONFIG_BRIDGE_NETFILTER //netfilter防火墙使用
- struct nf_bridge_info *nf_bridge;
- #endif
- int iif;
- #ifdef CONFIG_NETDEVICES_MULTIQUEUE
- __u16 queue_mapping;
- #endif
- #ifdef CONFIG_NET_SCHED
- __u16 tc_index;
- #ifdef CONFIG_NET_CLS_ACT
- __u16 tc_verd; /*流量控制机制*/
- #endif
- #endif
- #ifdef CONFIG_NET_DMA
- dma_cookie_t dma_cookie;
- #endif
- #ifdef CONFIG_NETWORK_SECMARK
- __u32 secmark;
- #endif
- __u32 mark;
- sk_buff_data_t transport_header;//传输层头部
- sk_buff_data_t network_header;//网络层头部
- sk_buff_data_t mac_header;//MAC层头部
- sk_buff_data_t tail;
- sk_buff_data_t end;
- unsigned char *head,*data;//数据区
- unsigned int truesize;//真实大小
- atomic_t users; // 引用计数
- };
1.3 Linux网络设备驱动程序架构
网络设备被抽象为统一的接口供系统访问,应用层对各种网络设备的访问都采用统一的形式,也就是套接字形式,它具有硬件无关性。
网络设备驱动程序最重要的结构是struct net_device。struct net_device结构保存一个网络接口的重要信息,是网络驱动程序的核心。struct net_device结构非常庞大,下面介绍该结构中几个最重要的成员:
- struct net_device
- {
- char name[IFNAMSIZ]; //设备名
- struct hlist_node name_hlist;
- unsigned long mem_end; //共享内存的尾地址
- unsigned long mem_start; //共享内存的首地址
- unsigned long base_addr; //设备的I/O地址
- unsigned int irq; //设备的中断号
- unsigned char if_port;
- unsigned char dma; //设备用的DMA通道
- unsigned long state;
- struct list_head dev_list;
- int (*init)(struct net_device *dev);//设备初始化函数
- unsigned long features;//设备特性
- struct net_device *next_sched;
- int ifindex;
- int iflink;
- struct net_device_stats* (*get_stats)(struct net_device *dev);//获取统计数据
- struct net_device_stats stats;//统计数据
- …
- int (*hard_start_xmit) (struct sk_buff *skb,struct net_device *dev);//数据发送
- const struct ethtool_ops *ethtool_ops;
- const struct header_ops *header_ops;
- int (*open)(struct net_device *dev); //打开网络接口
- int (*stop)(struct net_device *dev);//关闭网络接口
- void (*change_rx_flags)(struct net_device *dev,int flags);
- void (*set_rx_mode)(struct net_device *dev);
- //配置多播地址链表
- void (*set_multicast_list)(struct net_device *dev);
- //配置MAC地址
- int (*set_mac_address)(struct net_device *dev,void *addr);
- int (*validate_addr)(struct net_device *dev); //地址验证
- //执行特殊的ioctl命令
- int (*do_ioctl)(struct net_device *dev,struct ifreq *ifr, int cmd);
- //改变接口配置
- int (*set_config)(struct net_device *dev,struct ifmap *map);
- //当MTU改变时驱动程序要做一些特殊的事情,就应该写这个函数
- int (*change_mtu)(struct net_device *dev, int new_mtu);
- void (*tx_timeout) (struct net_device *dev); //发送超时处理
- void (*vlan_rx_register)(struct net_device *dev,struct vlan_group *grp);
- void (*vlan_rx_add_vid)(struct net_device *dev,unsigned short vid);
- void (*vlan_rx_kill_vid)(struct net_device *dev,unsigned short vid);
- int (*neigh_setup)(struct net_device *dev, struct neigh_parms *);
- …
- };
网络设备注册和注销函数原型如下:
- int register_netdev(struct net_device *dev);
- void unregister_netdev(struct net_device *dev);
网络设备驱动程序与网络子系统直接交互。两者交互的基本单位是sk_buff结构。网络系统下发数据要通过dev_queue_xmit函数,而dev_queue_xmit函数会调用网络设备的hard_start_xmit接口。网络设备收到数据后会产生一个中断,在中断处理程序中驱动程序申请一块sk_buff,从硬件读出数据放置到申请好的缓冲区里,接下来填充sk_buff中的一些成员,最后驱动程序调用netif_rx把数据传送给协议层,netif_rx将数据放入处理队列然后返回,真正的处理是在中断返回以后,这样可以减少中断时间。
- int dev_queue_xmit(struct sk_buff *skb);
- int (*hard_start_xmit) (struct sk_buff *skb,struct net_device *dev);
- int netif_rx(struct sk_buff *skb);
网络设备驱动程序的主要功能是实现struct net_device中的函数接口。
(1)open接口
- int (*open)(struct net_device *dev);
open接口在网络设备被激活的时候被调用。它主要完成资源和中断的申请、DMA的注册等工作。使用ifconfig命令可以激活网络设备。
(2)hard_start_xmit接口
- int (*hard_start_xmit) (struct sk_buff *skb,struct net_device *dev);
hard_start_xmit接口用来将网络子系统发来的数据通过网卡发送到物理网络。struct net_device中没有读数据接口,读数据一般在网卡中断中处理。
(3)get_stats接口
- struct net_device_stats* (*get_stats)(struct net_device *dev);
get_stats函数返回一个net_device_stats结构,该结构保存了所管理的网络设备接口的详细的流量与错误统计信息:
- struct net_device_stats
- {
- unsigned long rx_packets;//接收的总包数
- unsigned long tx_packets;//发送的总包数
- unsigned long rx_bytes; //接收总字节数
- unsigned long tx_bytes; //发送总字节数
- unsigned long rx_errors; //收到的错包数量
- unsigned long tx_errors; //发送的错包数量
- unsigned long rx_dropped;//丢弃的接收包数量
- unsigned long tx_dropped; //丢弃的发送包数量
- unsigned long multicast; //接收的多播包数
- unsigned long collisions;
- /*详细的接收错误*/
- unsigned long rx_length_errors;//长度错误
- unsigned long rx_over_errors;//环行缓冲溢出错误
- unsigned long rx_crc_errors; //CRC校验错误
- unsigned long rx_frame_errors;//帧对齐错误
- unsigned long rx_fifo_errors; //接收缓冲溢出错误
- unsigned long rx_missed_errors;//接收者遗漏错误
- /*详细的发送错误*/
- unsigned long tx_aborted_errors;
- unsigned long tx_carrier_errors;
- unsigned long tx_fifo_errors;
- unsigned long tx_heartbeat_errors;
- unsigned long tx_window_errors;
- unsigned long rx_compressed;
- unsigned long tx_compressed;
- };
(4)do_ioctl接口
- int (*do_ioctl)(struct net_device *dev,struct ifreq *ifr, int cmd);
Linux有一些特定的socket ioctl,定义在sockios.h头文件中。网络子系统一般已经实现了这些ioctl命令,也有的命令需要在自定义的驱动程序中实现。常用的ioctl命令有:
- #define SIOCGIFMTU 0x8921 /*获取MTU大小*/
- #define SIOCSIFMTU 0x8922 /*配置MTU大小*/
- #define SIOCSIFNAME 0x8923 /*配置接口名称*/
- #define SIOCSIFHWADDR 0x8924 /*配置硬件地址*/
- #define SIOCGIFENCAP 0x8925 /*获取封装属性*/
- #define SIOCSIFENCAP 0x8926 /*配置封装属性*/
- #define SIOCGIFHWADDR 0x8927 /*获取硬件地址*/
- #define SIOCADDMULTI 0x8931 /*增加广播地址*/
- #define SIOCDELMULTI 0x8932 /*删除广播地址*/
- #define SIOCGIFBRDADDR 0x8919 /*得到广播地址*/
- #define SIOCSIFBRDADDR 0x891a /*配置广播地址*/
例1.11 使用SIOCGIFHWADDR获取MAC地址
本例介绍如何使用SIOCGIFHWADDR获取网卡MAC地址。参考代码如下:
- int fd;
- struct ifreq ifr;
- fd = socket(AF_INET, SOCK_DGRAM, 0);
- ifr.ifr_addr.sa_family = AF_INET;
- strncpy(ifr.ifr_name, "eth0", IFNAMSIZ-1);
- ioctl(fd, SIOCGIFHWADDR, &ifr);
- close(fd);
- printf("%.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n",
- (unsigned char)ifr.ifr_hwaddr.sa_data[0],
- (unsigned char)ifr.ifr_hwaddr.sa_data[1],
- (unsigned char)ifr.ifr_hwaddr.sa_data[2],
- (unsigned char)ifr.ifr_hwaddr.sa_data[3],
- (unsigned char)ifr.ifr_hwaddr.sa_data[4],
- (unsigned char)ifr.ifr_hwaddr.sa_data[5]);
do_ioctl一般用来实现驱动程序私有的ioctl命令,命令的类型在SIOCDEVPRIVATE和SIOCDEVPRIVATE+15之间。
(5)set_multicast_list接口
- void (*set_multicast_list)(struct net_device *dev);
set_multicast_list接口在设备地址更改或dev->flags 被修改时调用。在以太网的默认初始化函数中,dev->flags被配置为IFF_BROADCAST|IFF_MULTICAST,表示以太网卡是可广播的,并且是能够进行组播发送的。对dev->flags配置或清除IFF_PROMISC标志时,会调用set_multicast_list函数通知板卡上的硬件过滤器。
(6)set_mac_address接口
- int set_mac_address(struct net_device *dev, void *p)
该接口用来配置网络设备的MAC地址,MAC地址就存放在p指针中。
(7)stop接口
- int (*stop)(struct net_device *dev);
stop接口在网卡状态由up转为down时被调用,一般用来释放资源。
例1.12 虚拟网络设备驱动程序实例
下面的例子是一个虚拟网卡驱动程序,代码见光盘\src\1drivermodel\1-11net。核心代码如下所示:
- static char netbuffer[100];
- struct net_device *simnetdevs;
- void simnetrx(struct net_device *dev, int len, unsigned char *buf)
- {
- struct sk_buff *skb;
- struct simnetpriv *priv = (struct simnetpriv *) dev->priv;
- skb = dev_alloc_skb(len+2);
- if (!skb) {
- printk("simnetrx can not allocate more memory to store the packet. drop the packet\n");
- priv->stats.rx_dropped++;
- return;
- }
- skb_reserve(skb, 2);
- memcpy(skb_put(skb, len), buf, len);
- skb->devdev = dev;
- skb->protocol = eth_type_trans(skb, dev);
- /* 不需要校验 */
- skb->ip_summed = CHECKSUM_UNNECESSARY;
- priv->stats.rx_packets++;
- netif_rx(skb);//将数据送入内核网络层
- return;
- }
- static irqreturn_t simnet_interrupt (int irq, void *dev_id)
- {
- struct net_device *dev;
- dev = (struct net_device *) dev_id;
- //从硬件获取数据
- simnetrx(dev,100,netbuffer);
- return IRQ_HANDLED;
- }
- int simnetopen(struct net_device *dev)
- {
- int ret=0;
- printk("simnetopen\n");
- //申请中断
- ret = request_irq(IRQ_NET_CHIP, simnet_interrupt, IRQF_SHARED,dev->name, dev);
- if (ret) return ret;
- printk("request_irq ok\n");
- netif_start_queue(dev);
- return 0;
- }
- int simnetrelease(struct net_device *dev)
- {
- printk("simnetrelease\n");
- netif_stop_queue(dev);
- return 0;
- }
- //虚拟硬件发送
- void simnethw_tx(char *buf, int len, struct net_device *dev)
- {
- struct simnetpriv *priv;
- /* 长度检查 */
- if (len < sizeof(struct ethhdr) + sizeof(struct iphdr))
- {
- printk("Bad packet! It's size is less then 34!\n");
- return;
- }
- /* 保存状态*/
- priv = (struct simnetpriv *) dev->priv;
- priv->stats.tx_packets++;
- priv->stats.rx_bytes += len;
- /* 释放内存 */
- dev_kfree_skb(priv->skb);
- }
- //发送数据包
- int simnettx(struct sk_buff *skb, struct net_device *dev)
- {
- int len;
- char *data;
- struct simnetpriv *priv = (struct simnetpriv *) dev->priv;
- len = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len;
- data = skb->data;
- /*添加时间戳 */
- dev->trans_start = jiffies;
- //记录skb以便在simnethw_tx中释放
- priv->skbskb = skb;
- simnethw_tx(data, len, dev);
- return 0;
- }
- //处理发送超时
- void simnettx_timeout (struct net_device *dev)
- {
- struct simnetpriv *priv = (struct simnetpriv *) dev->priv;
- priv->stats.tx_errors++;
- netif_wake_queue(dev);
- return;
- }
- // ioctl控制
- int simnetioctl(struct net_device *dev, struct ifreq *rq, int cmd)
- {
- return 0;
- }
- struct net_device_stats *simnetstats(struct net_device *dev)
- {
- struct simnetpriv *priv = (struct simnetpriv *) dev->priv;
- return &priv->stats;
- }
- //变更mtu
- int simnetchange_mtu(struct net_device *dev, int new_mtu)
- {
- unsigned long flags;
- spinlock_t *lock = &((struct simnetpriv *) dev->priv)->lock;
- if (new_mtu < 68)
- return -EINVAL;
- spin_lock_irqsave(lock, flags);
- dev->mtu = new_mtu;
- spin_unlock_irqrestore(lock, flags);
- return 0;
- }
- //设备初始化
- void simnetinit(struct net_device *dev)
- {
- struct simnetpriv *priv;
- ether_setup(dev);
- dev->open= simnetopen;
- dev->stop= simnetrelease;
- dev->hard_start_xmit = simnettx;
- dev->do_ioctl= simnetioctl;
- dev->get_stats= simnetstats;
- dev->change_mtu = simnetchange_mtu;
- dev->tx_timeout = simnettx_timeout;
- //配置MAC地址,注意如果(0x01 & addr[0])为真,则是multicast地址
- dev->dev_addr[0] = 0x18;
- dev->dev_addr[1] = 0x02;
- dev->dev_addr[2] = 0x03;
- dev->dev_addr[3] = 0x04;
- dev->dev_addr[4] = 0x05;
- dev->dev_addr[5] = 0x06;
- dev->flags|= IFF_NOARP;//不支持ARP
- dev->features|= NETIF_F_NO_CSUM;
- priv = netdev_priv(dev);
- memset(priv, 0, sizeof(struct simnetpriv));
- spin_lock_init(&priv->lock);
- }
- //模块卸载
- void simnetcleanup(void)
- {
- if (simnetdevs)
- {
- unregister_netdev(simnetdevs);
- free_netdev(simnetdevs);
- }
- return;
- }
- //模块初始化
- int simnetinit_module(void)
- {
- int result,ret = -ENOMEM;
- //分配网络设备
- simnetdevs=alloc_netdev(sizeof(struct simnetpriv), "eth%d",
- simnetinit);
- if (simnetdevs == NULL)
- goto out;
- ret = -ENODEV;
- //注册网络设备
- if ((result = register_netdev(simnetdevs)))
- printk("demo: error %i registering device \"%s\"\n",result, simnetdevs->name);
- else
- ret = 0;
- out:
- if (ret)
- simnetcleanup();
- return ret;
- }
- module_init(simnetinit_module);
- module_exit(simnetcleanup);
运行结果如下:
- [root@urbetter /home]# insmod demo.ko
- [root@urbetter /home]# ifconfig eth1 192.168.1.23
- simnetopen
- request_irq ok
- [root@urbetter /home]# ifconfig eth1 up
- [root@urbetter /home]# ping 192.168.1.23
- PING 192.168.1.23 (192.168.1.23): 56 data bytes
- 64 bytes from 192.168.1.23: seq=0 ttl=64 time=1.347 ms
- 64 bytes from 192.168.1.23: seq=1 ttl=64 time=0.301 ms
- 64 bytes from 192.168.1.23: seq=2 ttl=64 time=0.266 ms
- ^C
- --- 192.168.1.23 ping statistics ---
- 3 packets transmitted, 3 packets received, 0% packet loss
- round-trip min/avg/max = 0.266/0.638/1.347 ms
- [root@urbetter /home]# ifconfig eth1
- eth1 Link encap:Ethernet HWaddr 18:02:03:04:05:06
- inet addr:192.168.1.23 Bcast:192.168.1.255 Mask:255.255.255.0
- UP BROADCAST RUNNING NOARP MULTICAST MTU:1500 Metric:1
- RX packets:0 errors:0 dropped:0 overruns:0 frame:0
- TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
- collisions:0 txqueuelen:1000
- RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
- [root@urbetter /home]# ifconfig eth1 192.168.1.26
- [root@urbetter /home]# ifconfig eth1 down
- simnetrelease
- [root@urbetter /home]# ifconfig eth1 192.168.1.26
- simnetopen
- request_irq ok