Linux素来以其强大的网络功能著名,同时, 网络设备也作为三大设备之一, 成为Linux驱动学习中必不可少的设备类型, 此外, 由于历史原因, Linux并没有强制对网络设备贯彻其"一切皆文件"的思想, 网络设备不以/dev下的设备文件为接口,用户程序通过socket作为访问硬件的接口。本文以Linux3.14.0内核为例, 讨论Linux中的网络驱动模型

Linux的网络设备并不使用文件作为用户程序访问网络设备的接口,所以/sys/dev下和/dev下并没有相应的网络设备文件,在Linux中,用户程序最终使用套接字来访问网络设备。

框架

上图就是经典的OSI 7层模型,Linux的网卡驱动程序处于OSI模型中的数据链路层,他的职责就是将上上层的协议栈传过来的信息通过网卡发送出去,

Linux的网络驱动模型采用4层结构:

  • 协议接口层 向网络协议提供统一的数据包发送接口,上层任何形式的协议都通过dev_queue_xmit()发送,通过netif_rx()接收,都使用sk_buff作为数据的载体
  • 设备接口层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,这个结构从整体规划了具体操作硬件的设备驱动功能层的结构,是设备驱动功能层的各个函数的容器,开发网络驱动的主要工作就是编写驱动功能层的相关函数以填充net_device数据结构的内容并将net_device注册到内核
  • 驱动功能层的各个函数是网络设备接口层net_device数据结构的具体成员,是驱动 网络设备硬件完成相应动作的程序,它通过ndo_start_xmit()函数启动发送动作,并通过网络设备上的中断触发接收操作,通过中断或POLL机制接收
  • 设备与媒介层 是完成数据收发的物理实体,网卡被设备驱动层中的函数在物理上驱动,对于Linux系统而言,网络设备和媒介都可以是虚拟的

第2 and 第3层是驱动开发主要关心的层次

核心类与方法简述

核心类

  • sk_buff是网络驱动框架中信息的载体, 是网络分层模型中对数据进行层层打包以及层层解包的载体
  • net_device对象描述了一个网络设备, **其中的struct net_device_ops *netdev_ops是操作方法集, 向上提供接口的同时也向下操作硬件
  • netdev_ops一个网络设备的操作方法集
  • 私有数据 和其他模型框架一样, net_device对象也提供了承载私有数据的域, 不过不是使用void *, 参见下文alloc_ethdev

核心方法

  • dev_queue_xmit()是网络协议接口层向下发送数据的接口, 内核已经实现, 不需要网络设备驱动实现
  • ndo_start_xmit()是网络设备接口层向下发送数据的接口, 位于net_device->net_device_ops, 会被dev_queue_xmit()回调, 需要网络驱动实现
  • netif_rx()是网络设备接口层向上发送数据的接口, 不需要网络驱动实现
  • 中断处理函数是网络设备媒介层收到数据后向上发送数据的入口, 需要网络驱动实现,最后要调用netif_rx()

核心类与方法详述

sk_buff

套接字缓冲区是数据在多层模型中传输的载体,其被处理的最终结果就是网络数据包, Linux巧妙的使用了移动head/tail指针的方式实现了网络模型中每一层对数据包的加工过程。sk_buff部分定义如下

 427 struct sk_buff {
428 /* These two members must be first. */
429 struct sk_buff *next;
430 struct sk_buff *prev;
432 ktime_t tstamp;
434 struct sock *sk;
435 struct net_device *dev;
443 char cb[48] __aligned(8);
445 unsigned long _skb_refdst;
449 unsigned int len,
450 data_len;
451 __u16 mac_len,
452 hdr_len;
473 __be16 protocol;
534 __u16 inner_transport_header;
535 __u16 inner_network_header;
536 __u16 inner_mac_header;
537 __u16 transport_header;
538 __u16 network_header;
539 __u16 mac_header;
540 /* These elements must be at the end, see alloc_skb() for details. */
541 sk_buff_data_t tail;
542 sk_buff_data_t end;
543 unsigned char *head,
544 *data;
545 unsigned int truesize;
546 atomic_t users;
547 };

struct sk_buff

--435-->对应的net_device

--449-->len有效数据长度

--451-->mac_len表示mac头长度

--473-->protocol协议编号

--537-->transport_header指向传输层协议头

--538-->network_header指向IP头

--539-->mac_header指向以太网头

--541-->tail指向当前数据包的尾地址, 随着各个网络层的加工而变化

--542-->end 指向数据缓冲的内核尾地址, 不变

--543-->head指向数据缓冲(PackertData)的内核首地址, 不变

--544-->data指向当前数据包的首地址, 随着各个网路层的加工而变化

net_device

net_device是设备接口层的核心, 也是编写网络驱动核心的对象

1160 struct net_device {
1167 char name[IFNAMSIZ];
1179 unsigned long mem_end; /* shared mem end */
1180 unsigned long mem_start; /* shared mem start */
1181 unsigned long base_addr; /* device I/O address */
1182 int irq; /* device IRQ number */
1189 unsigned long state;
1190
1191 struct list_head dev_list;
1192 struct list_head napi_list;
1193 struct list_head unreg_list;
1194 struct list_head close_list;
1210 netdev_features_t features;
1212 netdev_features_t hw_features;
1214 netdev_features_t wanted_features;
1243 const struct net_device_ops *netdev_ops;
1244 const struct ethtool_ops *ethtool_ops;
1245 const struct forwarding_accel_ops *fwd_ops;
1248 const struct header_ops *header_ops;
1250 unsigned int flags; /* interface flags (a la BSD) */
1251 unsigned int priv_flags; /* Like 'flags' but invisible to userspace.
1252 * See if.h for definitions. */
1253 unsigned short gflags;
1254 unsigned short padded; /* How much padding added by alloc_netdev() */
1256 unsigned char operstate; /* RFC2863 operstate */
1257 unsigned char link_mode; /* mapping policy to operstate */
1259 unsigned char if_port; /* Selectable AUI, TP,..*/
1260 unsigned char dma; /* DMA channel */
1262 unsigned int mtu; /* interface MTU value */
1263 unsigned short type; /* interface hardware type */
1264 unsigned short hard_header_len; /* hardware hdr length */
1270 unsigned short needed_headroom;
1271 unsigned short needed_tailroom;
1274 unsigned char perm_addr[MAX_ADDR_LEN]; /* permanent hw address */
1275 unsigned char addr_assign_type; /* hw address assignment type */
1276 unsigned char addr_len; /* hardware address length */
1289 struct kset *queues_kset;
1386 int watchdog_timeo; /* used by dev_watchdog() */
1480 };

struct net_device

--1170-->name是网络设备的名称, 网络设备被载入后会出现在ifconfig中, 比如默认的eth0就是这个

--1179-->mem_start和mem_end存储了设备所使用的共享内存起始和结束地址

--1180-->base_addr表示网络设备的IO基地址

--1182-->irq为设备使用的中断号

--1210-->用户层可以修改的特征

--1212-->用户层不能修改的特征

--1230-->网卡的统计信息

--1243-->netdev_ops即网络设备的操作方法集

--1244-->ethtool的方法集

--1248-->header_ops表示协议头操作集

--1250-->用户层可以修改的标准

--1251-->用户层不能修改的标准

--1254-->alloc_netdev()时加入的pad的大小

--1259-->if_port指定多端口设备使用哪一个端口

--1260-->dma即分配给该设备的dma通道

--1264-->hard_header_len表示网络设备的硬件头长度, 在以太网设备的初始化过程中, 该成员被赋值为ETH_HLEN, 即14

--1263-->type是硬件类型

--1262-->mtu即MAX TRANSFER UNIT

--1270-->needed_headroom表示数据包缓冲区中需要的head_room大小

--1271-->数据缓冲区中需要的tailroom的大小

--1274-->mac地址

--1275-->硬件地址类型

--1276-->硬件地址长度

--1289-->设备所属的kset

--1386-->计数值

下面是一些与net_device相关的内核API

分配/释放

//linux/etherdevice.h
/**
* 分配及初始化net_device对象()
* @sizeof_priv - 私有数据大小(单位:字节数)
* 返回值:失败:NULL, 成功:net_device对象的首地址
*/
struct net_device *alloc_etherdev(int sizeof_priv); //linux/netdevice.h
/**
* 分配及初始化net_device对象
* @int sizeof_priv - 私有数据大小(单位:字节数)
* @const char *name - 物理接口名("名称%d")
* @unsigned char name_assign_type - NET_NAME_UNKNOWN
* @void (*setup)(struct net_device *) - 初始化函数
* 返回值:失败:NULL成功:net_device对象的首地址
*/
struct net_device *alloc_netdev(int sizeof_priv, const char *name,unsigned char name_assign_type,void (*setup)(struct net_device *)); //释放
void free_netdev(struct net_device *dev);

通过形参的名字就可以看出, 这个函数其实不止分配了一个net_device对象的空间, 因为net_device中并没有一个存储私有数据的域(dev->platform_data除外), 关于net_device的私有数据的存储方式, 我们可以通过这个函数的定义中看出, 这也就是要使用内核API来分配一个net_device结构的原因

//include/linux/netdevice.h
2897 #define alloc_netdev(sizeof_priv, name, setup) \
2898 alloc_netdev_mqs(sizeof_priv, name, setup, 1, 1)
//net/core/dev.c
6308 struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
6309 void (*setup)(struct net_device *),
6310 unsigned int txqs, unsigned int rxqs)
6311{
6330 alloc_size = sizeof(struct net_device);
6331 if (sizeof_priv) {
6332 /* ensure 32-byte alignment of private area */
6333 alloc_size = ALIGN(alloc_size, NETDEV_ALIGN);
6334 alloc_size += sizeof_priv;
6335 }
6336 /* ensure 32-byte alignment of whole construct */
6337 alloc_size += NETDEV_ALIGN - 1;
6339 p = kzalloc(alloc_size, GFP_KERNEL | __GFP_NOWARN | __GFP_REPEAT);
6340 if (!p)
6341 p = vzalloc(alloc_size);
6342 if (!p)
6343 return NULL;
6344
6345 dev = PTR_ALIGN(p, NETDEV_ALIGN);
6346 dev->padded = (char *)dev - (char *)p; 6406 }

alloc_net_dev_wqs

--6330-->用alloc_size保存一个net_device的大小

--6333-->如果有私有数据的要求, 对alloc_size重新赋值, 新的大小为net_device的32字节对齐大小+请求的私有数据大小

--6337-->确保分配的空间是32字节对齐, 与--6345--配合使用

--6339-->分配空间, 空间的大小=net_device大小+请求的私有数据空间大小+(NETDEV_ALIGN-1)大小 net_device的私有数据是预分配的, 就在net_device对象的下面.

--6345-->6337行的作用在这一行就体现出来了, PTR_ALIGN是向上对齐, 这样久保证了dev指向的空间是32字节对齐的, 之前分配的多余的部分作为padded被添加到net_device域中,所以, 考虑到net_device本身凑巧就是32字节对齐的,最后得到的内存布局是"net_device+priv_data+padded"的一块物理连续空间

初始化

//以太网的初始化
void ether_setup(struct net_device *dev);

这个函数也是一个重头, 在初始化一个以太网设备的时候应该被调用, 它的主要作用就是针对以太网标准对net_device对象进行初始化.

//net/ethernet/eth.c
359 void ether_setup(struct net_device *dev)
360 {
361 dev->header_ops = &eth_header_ops;
362 dev->type = ARPHRD_ETHER;
363 dev->hard_header_len = ETH_HLEN;
364 dev->mtu = ETH_DATA_LEN;
365 dev->addr_len = ETH_ALEN;
366 dev->tx_queue_len = 1000; /* Ethernet wants good queues */
367 dev->flags = IFF_BROADCAST|IFF_MULTICAST;
368 dev->priv_flags |= IFF_TX_SKB_SHARING;
369
370 memset(dev->broadcast, 0xFF, ETH_ALEN);
371
372 }

注册/注销

//注册
int register_netdev(struct net_device *dev);
//注销
void unregister_netdev(struct net_device *dev);

netdevice_ops

1002 struct net_device_ops {
1003 int (*ndo_init)(struct net_device *dev);
1004 void (*ndo_uninit)(struct net_device *dev);
1005 int (*ndo_open)(struct net_device *dev);
1006 int (*ndo_stop)(struct net_device *dev);
1007 netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb,
1008 struct net_device *dev);
1013 void (*ndo_change_rx_flags)(struct net_device *dev,
1014 int flags);
1015 void (*ndo_set_rx_mode)(struct net_device *dev);
1016 int (*ndo_set_mac_address)(struct net_device *dev,
1017 void *addr);
1018 int (*ndo_validate_addr)(struct net_device *dev);
1019 int (*ndo_do_ioctl)(struct net_device *dev,
1020 struct ifreq *ifr, int cmd);
1021 int (*ndo_set_config)(struct net_device *dev,
1022 struct ifmap *map);
1023 int (*ndo_change_mtu)(struct net_device *dev,
1024 int new_mtu);
1025 int (*ndo_neigh_setup)(struct net_device *dev,
1026 struct neigh_parms *);
1027 void (*ndo_tx_timeout) (struct net_device *dev);
1028
1148 };

struct net_device_ops

--1003-->ndo_init是初始化函数指针, 如果这个指针被设置了, 那么在设备被注册的时候会被调用, 用来初始化net_device对象, 相当于C++中的构造函数, 这个指针可以为NULL

--1004-->析构函数,回收清理

--1005-->打开设备, ifconfig xxx up 时会回调

--1006-->关闭设备, ifconfig xxx down 时会回调

--1007-->发送数据,会被dev_queue_xmit()回调

--1016-->设置mac

--1018-->检查mac是否有效

--1019-->对网络设备的ioctl操作

--1021-->配置接口, 用于配置读写参数, 读状态, 控制网卡等

--1023-->设置MTU值

--1027超时重发, 超时后会被回调

ndo_init()模板

下面是一个可以借鉴的ndo_init()的实现

void xxx_init(struct net_device *dev)
{
/* 设备的私有信息结构体 */
struct xxx_priv *priv; /* 检查设备是否存在, 以及设备需要的硬件资源 */
xxx_hw_init(); /* 初始化以太网设备的公用成员 */
ether_setup(dev); /* 设置设备的成员函数指针 */
dev->netdev_ops->ndo_open = xxx_open;
dev->netdev_ops->ndo_stop = xxx_stop;
dev->netdev_ops->ndo_set_config = xxx_set_config;
dev->netdev_ops->ndo_start_xmit = xxx_tx;
dev->netdev_ops->ndo_do_ioctl = xxx_ioctl;
dev->netdev_ops->ndo_get_stats = xxx_stats;
dev->netdev_ops->ndo_change_mtu = xxx_change_mtu;
dev->netdev_ops->ndo_tx_timeout = xxx_tx_timeout;
dev->netdev_ops->ndo_watchdog_timeo = xxx_timeout;
dev->rebuild_header = xxx_rebuild_header;
dev->hard_header = xxx_header; /* 获得私有数据并将其初始化 */
priv = netdev_priv(dev);
/* 初始化priv代码 */
}

ndo_open()/ndo_release()模板

int xxx_open(struct net_device *dev)
{
/* 申请资源 */
ret = request_irq(dev->irq, &xxx_interrupt, 0, dev->name, dev); /* 激活设备的发送队列 */
netif_start_queue(dev);
} init xxx_release(struct net_device *dev)
{
/* 释放资源 */
free_irq(dev->irq,dev); /* 关闭设备的发送队列 */
netif_stop_queue(dev);
}

ndo_start_xmit()模板

int xxx_tx(struct sk_buff *skb, struct net_device *dev)
{
int len;
char *data, shortpkt[ETH_ZLEN];
if(xxx_send_available(...)){ //发送队列未满, 可以发送
/* 获得有效数据指针和长度 */
data = skb->data;
len = skb->len;
if(len < ETH_ZLEN){
/* 如果帧长小于以太网帧最小长度,补0 */
memset(shortpkt,0,ETH_ZLEN);
memcpy(shortpkt,skb->data,skb->len);
len = ETH_ZLEN;
data = shortpkt;
}
dev->trans_start = jiffies; //记录发送时间戳 /* 设置硬件寄存器让硬件将数据发出去 */
xxx_hw_tx(data,len,dev); }else{
netif_stop_queue(dev);
...
}
}

timeout()模板

这个函数会在超时的时候被调用, 通常用来实现重发.

void xxx_tx_timeout(struct net_device *dev)
{
...
netif_wake_queue(dev); //重新启动设备发送队列
}

中断处理函数

是网络设备媒介层相设备驱动功能层发送数据的接口, 网卡接收到数据是通过中断的方式上报的, 所以网络驱动中的中断处理函数就是第一时间队接收到的数据进行处理的地方, 这个函数最终一定要调用netif_rx()将收到的数据上报到协议接口层. 下面是一个简单的接收数据中断处理函数模板

static void xxx_rx(struct xxx_device * dev)
{
...
length = get_rev_len(...);
/* 分配新的套接字缓冲区 */
skb = dev_alloc_skb(length +2);
skb_researve(skb, 2); //对齐
skb->dev = dev; /* 读取硬件上接收到的数据 */
insw(ioaddr +RX_FRAME_PORT, skb_put(skb, length), length >>1);
if(length &1){
skb ->data[length - 1] = inw(ioaddr + RX_FRAME_PORT);
}
/* 获取上层协议类型 */
skb->protocol = eth_type_trans(skb,dev); /* 把数据包交给上层 */
netif_rx(skb); /* 记录接收时间戳 */
dev->last_rx = jiffies;
...
} static void xxx_interrupt(int irq, void *dev_id)
{
...
switch(status & ISQ_EVENT_MASK){
case ISQ_RECEIVER_EVENT: /* 获取数据包 */
xxx_rx(dev);
break;
/* 其他类型中断 */
}
}

其他API

这些API都在"include/linux/netdevice.h"中声明了

得到私有数据指针
void *netdev_priv(const struct net_device *dev); 设置MAC
int eth_mac_addr(struct net_device *dev, void *p);
检查MAC地址是否有效
int eth_validate_addr(struct net_device *dev);
修改MTU值
int eth_change_mtu(struct net_device *dev, int new_mtu); //随机生成mac地址
void eth_hw_addr_random(struct net_device *dev)
void eth_random_addr(u8 *addr); //开启发送队列
void netif_start_queue(struct net_device *dev)
//停止发送队列
void netif_stop_queue(struct net_device *dev) //runing
void netif_carrier_on(struct net_device *dev)
//not runing
void netif_carrier_off(struct net_device *dev)

Linux网络设备驱动(一) _驱动模型的更多相关文章

  1. Linux输入子系统(一) _驱动编码

    输入设备都有共性:中断驱动+字符IO,基于分层的思想,Linux内核将这些设备的公有的部分提取出来,基于cdev提供接口,设计了输入子系统,所有使用输入子系统构建的设备都使用主设备号13,同时输入子系 ...

  2. Linux块设备驱动(一) _驱动模型

    块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,本文以3.14为蓝本,探讨内核中的块设备驱动模型 框架 下图是Linux中的块设备模型示意图,应用层程序有两种方式访问一 ...

  3. 001_linux驱动之_驱动的加载和卸载

    (一)驱动的安装: 1. 可以将驱动程序静态编译进内内核中 2. 也可以将它作为模块在使用的时候再加载 注:在配置内核时候,如果某个配置被设置为m,就表示它将会被编译成模块   (二)加载和卸载驱动使 ...

  4. Linux网络设备驱动 _驱动模型

    Linux素来以其强大的网络功能著名,同时, 网络设备也作为三大设备之一, 成为Linux驱动学习中必不可少的设备类型, 此外, 由于历史原因, Linux并没有强制对网络设备贯彻其"一切皆 ...

  5. Linux Platform驱动模型(二) _驱动方法

    在Linux设备树语法详解和Linux Platform驱动模型(一) _设备信息中我们讨论了设备信息的写法,本文主要讨论平台总线中另外一部分-驱动方法,将试图回答下面几个问题: 如何填充platfo ...

  6. Linux Platform驱动模型(二) _驱动方法【转】

    转自:http://www.cnblogs.com/xiaojiang1025/archive/2017/02/06/6367910.html 在Linux设备树语法详解和Linux Platform ...

  7. Linux MTD子系统 _从模型分析到Flash驱动模板

    MTD(Memory Technology Device)即常说的Flash等使用存储芯片的存储设备,MTD子系统对应的是块设备驱动框架中的设备驱动层,可以说,MTD就是针对Flash设备设计的标准化 ...

  8. 迅为4412开发板Linux驱动教程——总线_设备_驱动注册流程详解

    本文转自:http://www.topeetboard.com 视频下载地址: 驱动注册:http://pan.baidu.com/s/1i34HcDB 设备注册:http://pan.baidu.c ...

  9. Linux网络设备驱动架構學習(三)

    Linux网络设备驱动架構學習(三) 接下來會從以下幾個方面介紹網絡設備驅動的編寫流程: 1.網絡設備的註冊與註銷 2.網絡設備的初始化 3.網絡設備的打開與釋放 4.網絡數據發送流程 5.網絡數據接 ...

随机推荐

  1. DB2 表空间监控

    默认DB2 缓冲池信息监控是OFF, 需要开启(DB2表空间是由缓冲池分配的) CollBufferpool : ============ The CollBufferpool collector c ...

  2. C++用new创建对象和不用new创建对象的区别解析

    在C++用new创建对象和不用new创建对象是有区别的,不知你是否清楚的了解它们到底有什么样的区别呢?下面小编就用示例来告诉大家吧,需要的朋友可以过来参考下   我们都知道C++中有三种创建对象的方法 ...

  3. Dockerfile详解

    Dockerfile详解 利用Dockerfile文件,可以构建docker的image镜像 命令使用 通过-f参数指定Dockerfile路径,进行构建image docker build -f / ...

  4. css3瀑布流

    css3虽然可以实现,不过要是真的运用到项目中还是老老实实写js吧 .container{ /*列的宽度*/ column-width:160px; -webkit-column-width:160p ...

  5. ORACLE-EXP和IMP方法介绍

    说明: 之前经常有人询问数据库导出导入的方法,去百度查询,但是查询的结果却不是很尽如人意,所以在此做个基本的总结,包括 导出:(导出用户,导出表,导出表结构不带数据,QUERY参数),导入(导入数据文 ...

  6. Spring源码解析一:IOC容器设计

    一.IOC接口设计 IOC容器设计的源码主要在spring-beans.jar.spring-context.jar这两个包中.IOC容器主要接口设计如下: 这里的接口设计有两条主线:BeanFact ...

  7. 动态添加删除网卡 - 每天5分钟玩转 OpenStack(156)

    这是 OpenStack 实施经验分享系列的第 6 篇. 在项目实施过程中,经常会有添加删除网卡的需求.比如一个运行数据库的 instance,初始只有一个网卡,数据库服务和备份共用这块网卡,后来为提 ...

  8. Linux实战教学笔记18:linux三剑客之awk精讲

    Linux三剑客之awk精讲(基础与进阶) 标签(空格分隔): Linux实战教学笔记-陈思齐 快捷跳转目录: * 第1章:awk基础入门 * 1.1:awk简介 * 1.2:学完awk你可以掌握: ...

  9. 每天一个Linux命令(18)--locate命令

    locate让使用者可以很快速的搜寻档案系统内是否有指定的档案.器方法是先建立一个包括系统内所有档案名称及路径的数据库,之后当寻找时就需查询这个数据库,而不必实际深入档案系统之中了.在一般的 ditr ...

  10. nginx内置全局变量

    nginx内置全局变量 $args        请求中的参数; $binary_remote_addr        远程地址的二进制表示 $body_bytes_sent        已发送的消 ...