嵌入式学习-驱动开发-lesson7.1-网卡驱动架构分析驱动及CS8900流程分析
2016-08-10 21:43
295 查看
PART1 网卡驱动架构分析
1.SCI
2.Pm
3.MM
4.Arch
5.Vfs
6.Network stack
7.DD
并且在前面已经讲过内存管理子系统和进程管子系统,今天主要讲解linux网络子系统。
首先看一下网络子系统的主要组成部分,如下图:
如上图所示,处于内核空间的linux网络子系统主要由5个部分组成:
1.系统调用接口
面向应用程序,提供一些 socket send等网络系统调用接口
2.协议无关接口
系统调用接口要访问网络协议栈,不同的接口有不同的协议,如IP,tcp,udp等,太繁杂,利用协议无关接口将下层的所有协议统一起来,提供给系统调用接口一个统一的接口
3.网络协议栈
实现一些具体的网络协议 TCP UDP ip协议等
4.设备无关接口
利用此接口将驱动统一起来,不管驱动程序如何,或者说是如何写的,函数接口都是统一的
5.设备驱动程序:网卡驱动程序位于这一部分,例如 cs8900 dm9000驱动
char name[IFNAMSIZ]
设备名,如:eth%d
unsigned long base_addr
I/O 基地址
const struct net_device_ops *netdev_ops;
网卡操作函数集
下面是dm9000的操作函数集:
static const struct net_device_ops dm9000_netdev_ops =
{
.ndo_open = dm9000_open,
.ndo_stop = dm9000_stop,
.ndo_start_xmit = dm9000_start_xmit,
.ndo_do_ioctl = dm9000_ioctl,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = eth_mac_addr,
};
下面为skb的组成部分:
head:包最开始的起点
data:有效数据的开始
tail:有效数据的结尾
end:包的终点
在cs8900.c中,在其入口函数中,使用函数alloc_etherdev为其分配结构
2初始化net_device结构
在接下来的过程中,主要工作是对net_device的结构进行了初始化,主要包括以下几个方面:
中断号irq和基地址base_addr的初始化
MAC地址
netdev_ops的初始化
3初始化硬件
这里暂且不分析硬件相关的
4注册网卡驱动
使用register_netdev函数注册网卡驱动
1告诉上层协议,在发送数据,暂停接收其它数据
netif_stop_queue(dev);
2将skb中的数据写入网卡寄存器,发送走
3释放skb结构
dev_kfree_skb (skb);
4 当发送完数据后,网卡会产生一个中断,因此在中断处理函数net_interrupt中,通知上层协议,可以接收数据
netif_wake_queue
1读取接收状态
status = readword(ioaddr, RX_FRAME_PORT);
2读取接收的数据长度
length = readword(ioaddr, RX_FRAME_PORT);
3构造一个skb结构,存放接收到的数据
skb = dev_alloc_skb(length + 2);
4从网卡寄存器中读出数据,存入skb
5将收到的数据包skb交给协议栈处理
netif_rx(skb);
流程图如下:
PART2 网络子系统深度剖析
本次需要解决两个问题:
1.用户程序怎样把数据经过linux内核交给网卡去发送
2.网卡收到的数据怎样经过linux内核交给用户态程序
如上图所示,一个安转了Linux系统的PC1,有两个网卡W1 W2,W1的IP为192.168.1.10,他接到了一个路由器Y1上,这个路由器Y1的网关为192.168.1.1,这个路由器又接到了另外一个网络,这个网络中有一个PC2,这个PC2的IP为168.1.1.10,第二个网卡W2,IP地址10.0.0.30,接到了路由器Y2上面,IP地址为10.0.0.1,路由器Y2又接到了另外的网络上,并且有一个PC3.
现在PC1上有一个数据包,要发送到PC2上面去,现在问题来了?这个数据包如何发送出去,是经过W1?W2?PC1如何决定发送路径?
Linux系统会根据系统中的一个东西—路由表来决定,在这个路由表中,有一些路由信息,例如,会存储PC2 168.1.1.10的数据由路由器网关192.168.1.1转发,那Linux会根据这条路由信息选择相应的路径
因此上面的主要流程为:
1.选择路由,即选择好路径,由哪一个路由器发送
2.选择邻居子系统,建立邻居信息
Linux将数据发给PC2,即将数据发给路由器,然后路由器再将数据发送给PC2,路由器在发送数据的过程中,充当一个发送路径上的邻居的角色。
注意:数据给路由器,那么就需要知道路由器的Mac地址,若不知道Mac地址,则需要通过arp获取Mac地址
如上图,socket_file_ops便是socket对应的fileoperation,
在fileoperation中 sock_aio_write 函数便是整个发包函数,在Linux内核中的入口,
sock_aio_write又调用do_sock_write函数,
do_sock_write函数又调用__sock_sendmsg函数,
上面这三个函数便是sci和协议无关层,同时这两层也没有涉及到具体的协议,即不管发送的是UDP、TCP或者IP,所调用的函数都是一致的,而在下一层网络协议栈,则根据不同的协议构造不同的数据包。
udp_sendmsg便是网络协议栈的入口,在这个函数中做了很多事情,其中。重要的有:
调用ip_route_output_flow这个函数,这个函数的重要作用是选择路由
又调用udp_push_pending_frames函数,udp_push_pending_frames又调用
有点小疑问,现在是UDP数据的发送流程,为何出现了与IP有关的函数?
一个数据进来之后,先经过udp协议的处理,然后交给IP协议处理,其处理入口就是Ip_push_pending_frames
先上一张高清无码的大图
接下来分析ip协议是如何处理的?
ip_push_pending经过一系列的调用的过程中,在ip_finish_output2函数中完成了建立邻居信息这个任务
在函数ip_finish_output2函数中,建立邻居信息
判断路由是否有邻居的信息,如果有,则直接发送,如果没有,则调用邻居子系统,建立邻居信息。
调用neigh_resolve_output函数
首先将网卡的的操作函数集赋值给ops,然后ops调用操作函数集里面的指针,ndo_start_xmit,实现数据的发送,这一点就涉及到驱动层了,可以联系上面的cs8900的ndo_start_xmit进行分析。
总结:要发送一个数据包,需要调用write系统调用,依次经过 SCI、协议无关接口、协议栈、设备无关接口、驱动。
在此过程中,有两个重要的地方:
1.选路由
在协议栈中,UDP的协议实现部分,
2.邻居子系统
在协议栈中,IP部分,建立邻居信息
将上面的流程绘制如下:
2.网卡收到包之后,如何交给用户处理
在中断处理程序中会判断是否是接收中断,如果是则从硬件中读取数据,并且放入到skb中去,然后函数netif_rx会把包丢给上层去处理,netif_rx会触发软中断,等Linux内核方便的时候,再处理这个中断,那么谁会处理这个软中断?net_rx_action
下图是具体的流程。
新手一枚,如有错误,多多指教。。。
一、网络子系统
在内核相关一课中,已经介绍过,linux内核一共有7个子系统(模块):1.SCI
2.Pm
3.MM
4.Arch
5.Vfs
6.Network stack
7.DD
并且在前面已经讲过内存管理子系统和进程管子系统,今天主要讲解linux网络子系统。
首先看一下网络子系统的主要组成部分,如下图:
如上图所示,处于内核空间的linux网络子系统主要由5个部分组成:
1.系统调用接口
面向应用程序,提供一些 socket send等网络系统调用接口
2.协议无关接口
系统调用接口要访问网络协议栈,不同的接口有不同的协议,如IP,tcp,udp等,太繁杂,利用协议无关接口将下层的所有协议统一起来,提供给系统调用接口一个统一的接口
3.网络协议栈
实现一些具体的网络协议 TCP UDP ip协议等
4.设备无关接口
利用此接口将驱动统一起来,不管驱动程序如何,或者说是如何写的,函数接口都是统一的
5.设备驱动程序:网卡驱动程序位于这一部分,例如 cs8900 dm9000驱动
二、重要数据结构
1) 网卡描述结构
在Linux内核中,每个网卡都由一个net_device结构来描述,其中的一些重要成员有:char name[IFNAMSIZ]
设备名,如:eth%d
unsigned long base_addr
I/O 基地址
const struct net_device_ops *netdev_ops;
网卡操作函数集
2) 网卡操作集合
类似于字符设备驱动中的file_operations结构,net_device_ops结构记录了网卡所支持的操作。下面是dm9000的操作函数集:
static const struct net_device_ops dm9000_netdev_ops =
{
.ndo_open = dm9000_open,
.ndo_stop = dm9000_stop,
.ndo_start_xmit = dm9000_start_xmit,
.ndo_do_ioctl = dm9000_ioctl,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = eth_mac_addr,
};
3) 网络数据包
Linux内核中的每个网络数据包(由网络协议栈产生)都由一个套接字缓冲区结构 struct sk_buff 描述,即一个sk_buff结构就是一个网络包,指向sk_buff的指针通常被称做skb。通常为:struct sk_buff skb;下面为skb的组成部分:
head:包最开始的起点
data:有效数据的开始
tail:有效数据的结尾
end:包的终点
三、CS8900分析
1).初始化
1分配net_device结构在cs8900.c中,在其入口函数中,使用函数alloc_etherdev为其分配结构
2初始化net_device结构
在接下来的过程中,主要工作是对net_device的结构进行了初始化,主要包括以下几个方面:
中断号irq和基地址base_addr的初始化
MAC地址
netdev_ops的初始化
3初始化硬件
这里暂且不分析硬件相关的
4注册网卡驱动
使用register_netdev函数注册网卡驱动
2)网卡发送函数
在net_device_ops函数中,有网卡发送函数ndo_start_xmit,进入到这个函数,分析其数据发送流程1告诉上层协议,在发送数据,暂停接收其它数据
netif_stop_queue(dev);
2将skb中的数据写入网卡寄存器,发送走
3释放skb结构
dev_kfree_skb (skb);
4 当发送完数据后,网卡会产生一个中断,因此在中断处理函数net_interrupt中,通知上层协议,可以接收数据
netif_wake_queue
3)数据接收函数
对于网卡的数据接收,不像网卡的数据发送那样,在操作函数集中有一个发送函数,而是会产生一个接收中断,在接收中断中则进行接收数据的处理1读取接收状态
status = readword(ioaddr, RX_FRAME_PORT);
2读取接收的数据长度
length = readword(ioaddr, RX_FRAME_PORT);
3构造一个skb结构,存放接收到的数据
skb = dev_alloc_skb(length + 2);
4从网卡寄存器中读出数据,存入skb
5将收到的数据包skb交给协议栈处理
netif_rx(skb);
流程图如下:
PART2 网络子系统深度剖析
本次需要解决两个问题:
1.用户程序怎样把数据经过linux内核交给网卡去发送
2.网卡收到的数据怎样经过linux内核交给用户态程序
一、网络发包模型
如上图所示,一个安转了Linux系统的PC1,有两个网卡W1 W2,W1的IP为192.168.1.10,他接到了一个路由器Y1上,这个路由器Y1的网关为192.168.1.1,这个路由器又接到了另外一个网络,这个网络中有一个PC2,这个PC2的IP为168.1.1.10,第二个网卡W2,IP地址10.0.0.30,接到了路由器Y2上面,IP地址为10.0.0.1,路由器Y2又接到了另外的网络上,并且有一个PC3.
现在PC1上有一个数据包,要发送到PC2上面去,现在问题来了?这个数据包如何发送出去,是经过W1?W2?PC1如何决定发送路径?
Linux系统会根据系统中的一个东西—路由表来决定,在这个路由表中,有一些路由信息,例如,会存储PC2 168.1.1.10的数据由路由器网关192.168.1.1转发,那Linux会根据这条路由信息选择相应的路径
因此上面的主要流程为:
1.选择路由,即选择好路径,由哪一个路由器发送
2.选择邻居子系统,建立邻居信息
Linux将数据发给PC2,即将数据发给路由器,然后路由器再将数据发送给PC2,路由器在发送数据的过程中,充当一个发送路径上的邻居的角色。
注意:数据给路由器,那么就需要知道路由器的Mac地址,若不知道Mac地址,则需要通过arp获取Mac地址
二、UDP数据发送流程分析
在前面已经介绍过,Linux网络子系统主要由5个部分组成,现在假设有一个UDP的包,要发送,观察这个包在这5个部分的主要流程是如何?1).SCI和协议无关层
对于要发送udp包,首先要创建一个socket系统调用,然后使用write发送数据,既然要使用socket,那么我们就需要首先知道socket对应的fileoperation,然后通过fileoperation找到在Linux系统中的入口点如上图,socket_file_ops便是socket对应的fileoperation,
在fileoperation中 sock_aio_write 函数便是整个发包函数,在Linux内核中的入口,
sock_aio_write又调用do_sock_write函数,
do_sock_write函数又调用__sock_sendmsg函数,
上面这三个函数便是sci和协议无关层,同时这两层也没有涉及到具体的协议,即不管发送的是UDP、TCP或者IP,所调用的函数都是一致的,而在下一层网络协议栈,则根据不同的协议构造不同的数据包。
2).网络协议栈
因为我们是发送UDP,所以要找到udp相关的数据发送函数。udp_sendmsg便是网络协议栈的入口,在这个函数中做了很多事情,其中。重要的有:
调用ip_route_output_flow这个函数,这个函数的重要作用是选择路由
又调用udp_push_pending_frames函数,udp_push_pending_frames又调用
有点小疑问,现在是UDP数据的发送流程,为何出现了与IP有关的函数?
一个数据进来之后,先经过udp协议的处理,然后交给IP协议处理,其处理入口就是Ip_push_pending_frames
先上一张高清无码的大图
接下来分析ip协议是如何处理的?
ip_push_pending经过一系列的调用的过程中,在ip_finish_output2函数中完成了建立邻居信息这个任务
在函数ip_finish_output2函数中,建立邻居信息
判断路由是否有邻居的信息,如果有,则直接发送,如果没有,则调用邻居子系统,建立邻居信息。
调用neigh_resolve_output函数
3)设备无关接口
首先将网卡的的操作函数集赋值给ops,然后ops调用操作函数集里面的指针,ndo_start_xmit,实现数据的发送,这一点就涉及到驱动层了,可以联系上面的cs8900的ndo_start_xmit进行分析。
总结:要发送一个数据包,需要调用write系统调用,依次经过 SCI、协议无关接口、协议栈、设备无关接口、驱动。
在此过程中,有两个重要的地方:
1.选路由
在协议栈中,UDP的协议实现部分,
2.邻居子系统
在协议栈中,IP部分,建立邻居信息
将上面的流程绘制如下:
2.网卡收到包之后,如何交给用户处理
在中断处理程序中会判断是否是接收中断,如果是则从硬件中读取数据,并且放入到skb中去,然后函数netif_rx会把包丢给上层去处理,netif_rx会触发软中断,等Linux内核方便的时候,再处理这个中断,那么谁会处理这个软中断?net_rx_action
下图是具体的流程。
新手一枚,如有错误,多多指教。。。
相关文章推荐
- 嵌入式学习-驱动开发-lesson6.2-UART驱动初始化和open流程分析
- 嵌入式学习-驱动开发-lesson6.3-UART驱动send和receive流程分析
- 嵌入式学习-驱动开发-lesson7.2-DM9000驱动流程分析
- 嵌入式学习-驱动开发-lesson3-混杂设备驱动模型与linux中断处理流程
- 嵌入式学习-uboot-lesson3-6410uboot启动流程分析
- 嵌入式学习-驱动开发前奏-lesson1-内核模块相关知识
- 嵌入式学习-驱动开发前奏-lesson2-内存管理与进程管理子系统
- 嵌入式学习-驱动开发前奏-lesson3-linux内核链表
- 嵌入式学习-驱动开发前奏-lesson4-驱动分类和硬件访问相关
- 嵌入式学习-驱动开发-lesson1-字符设备驱动模型
- 嵌入式学习-驱动开发-lesson2-LED字符设备驱动
- 嵌入式学习-驱动开发-lesson4-按键混杂设备驱动
- 嵌入式学习-驱动开发-lesson5-总线设备驱动模型及平台总线驱动
- 嵌入式学习-驱动开发-lesson6.1-TTY驱动架构分析
- 【嵌入式linux驱动开发】第十节 LCD 背光驱动代码架构分析(1)
- linux spi驱动开发学习(四)-----spi驱动程序完整流程分析
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】PowerPC + Linux2.6.25平台下的I2C驱动架构分析
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】PowerPC + Linux2.6.25平台下的SPI驱动架构分析
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】PowerPC + Linux2.6.25平台下的I2C驱动架构分析
- 多CPU下基于e1000e驱动的数据包以及网卡中断流程分析.doc