图书-深入理解Linux内核网络
2018-04-23 11:07:24 79 举报
AI智能生成
《深入理解Linux内核网络》是一本关于Linux内核网络的专业书籍,它详细介绍了Linux内核网络的工作原理和实现细节。这本书涵盖了从基本概念到高级主题的全面内容,包括套接字编程、协议栈实现、网络设备驱动等。读者可以通过阅读这本书来深入了解Linux内核网络的各个方面,掌握如何编写高效的网络应用程序,以及如何优化网络性能。总之,《深入理解Linux内核网络》是一本值得一读的图书,它将为您带来宝贵的知识和技能。
作者其他创作
大纲/内容
4章通知链notify chain
全局介绍
用途: 内核多个子系统之间具有较强的依赖性,
其中1个子系统探测到或产生的事件, 其他子系统可能也感性趣,
linux通过通知链完成这种交互需求
其中1个子系统探测到或产生的事件, 其他子系统可能也感性趣,
linux通过通知链完成这种交互需求
举例:
当1个网口down之后,关联的路由子系统关注了这个事件,
就会通知路由子系统该事件
当1个网口down之后,关联的路由子系统关注了这个事件,
就会通知路由子系统该事件
代码相关实现
主动方是产生事件的
notifyer_block->notify_call 回调函数是要执行的函数
notifyer_block->notify_call 回调函数是要执行的函数
接受方是链注册
notifier_chain_register()
notifier_chain_register()
10章帧的接收
轮询收包模式NAPI
net_rx_action() -> 关联软中断NET_RX_SOFTIRQ
轮询的总体介绍
其实它还是基于单核的思想,
是统一由net_rx_action()调用,让每个轮询poll_list队列中网卡, 去轮询收包dev->poll
是统一由net_rx_action()调用,让每个轮询poll_list队列中网卡, 去轮询收包dev->poll
1 netif_rx(skb) P226
是收包后只把包挂到队列上,并不处理
是收包后只把包挂到队列上,并不处理
1 每个cpu有独立的softnet_data,把收到的包挂到待处理队列(backlog)就结束了
enqueue_to_backlog()函数
__skb_queue_tail(&per_cpu(soft_data,cpu)->input_pkt_queue, skb)
enqueue_to_backlog()函数
__skb_queue_tail(&per_cpu(soft_data,cpu)->input_pkt_queue, skb)
2 处理收到的包
net_rx_action()
1 对于支持轮询的设备NAPI
A 对于共享的CPU专用队列, 从softnet_data->input_pkt_queue取包处理
B 对于设备内存,驱动调用poll函数直接从设备中取包处理
A 对于共享的CPU专用队列, 从softnet_data->input_pkt_queue取包处理
B 对于设备内存,驱动调用poll函数直接从设备中取包处理
2 对于不支持NAPI的设备
从积压队列process_backlog中取下包
从积压队列process_backlog中取下包
3 对包进行协议栈处理
__netif_receive_skb(skb)
__netif_receive_skb(skb)
6章 PCI层和网络接口
涉及的数据结构
pci_device_id 指的是PCI设备生产商的设备标识
pci_dev 每个pci设备都会被内核分配1个实例,
类似于每个网络设备被分配1个net_device
类似于每个网络设备被分配1个net_device
pci_driver PCI设备的驱动结构
char *name 驱动程序名称
struct pci_device_id *id_table 这是1个标识ID表,
它是设备生产商指定的设备标识
它是设备生产商指定的设备标识
int (*probe) (pci_dev *dev, pci_device_id *id)
在PCI层搜寻设备的标识和驱动的匹配,就会调用此驱动探测函数
在PCI层搜寻设备的标识和驱动的匹配,就会调用此驱动探测函数
开启硬件,分配net_device结构,初始化注册新设备
PCI NIC设备驱动程序的注册
pci_register_driver(struct pci_driver *driver)
把驱动结构pci_driver注册到内核上,当检测到物理设备介入
时,匹配设备标识和驱动识别的标识pci_device_id,当相同时
就会调用其probe函数
把驱动结构pci_driver注册到内核上,当检测到物理设备介入
时,匹配设备标识和驱动识别的标识pci_device_id,当相同时
就会调用其probe函数
8章设备的注册和初始化
alloc_netdev() 分配net_device结构
net_device 结构的组织
dev_base 是所有net_device的全局链表
查找网络设备net_device
dev_get_by_name(struct net*net, char *name)
dev_get_by_index(struct net*net, int idx)
dev_get_by_name(struct net*net, char *name)
dev_get_by_index(struct net*net, int idx)
设备注册状态的通知
netdev_chain 通知链
用途其他内核组件想知道何时网络设备注册,注销,关闭,开启事件
用途其他内核组件想知道何时网络设备注册,注销,关闭,开启事件
1通过register_netdevice_notifier 注册标识关注此通知
2 通知的事件类型: 设备开启,将要关闭,已关闭,设备已注册
NETDEV_UP,NETDEV_GOING_DOWN,NETDEV_DOWN
NETDEV_REGISTER,NETDEV_UNREGISTER
NETDEV_UP,NETDEV_GOING_DOWN,NETDEV_DOWN
NETDEV_REGISTER,NETDEV_UNREGISTER
设备注册通过register_netdevice()
设备的引用计数
unregister_netdevice() ->
net_device->refcnt 只有引用计数=0时,才会注销设备
net_device->refcnt 只有引用计数=0时,才会注销设备
register_netdevice() 时refcnt =1
dev_hold和dev_put分别增加和减少引用计数
dev_hold和dev_put分别增加和减少引用计数
上层网络功能
流量控制TC功能
Traffic Control
Traffic Control
宏观介绍
数据流传输需要不同的优先级.
举例: ssh和telnet远程命令操作要高于下载文件,
能避免ssh命令被文件下载吞掉所有流量而无法使用
举例: ssh和telnet远程命令操作要高于下载文件,
能避免ssh命令被文件下载吞掉所有流量而无法使用
实现基本概念:
过滤器(Filter)
分类器(Classic)
数据包队列(Queue)
过滤器(Filter)
分类器(Classic)
数据包队列(Queue)
功能发生的在协议栈位置:
流量控制发生的位置是协议栈的发包出口,
也就是网卡输出output
流量控制发生的位置是协议栈的发包出口,
也就是网卡输出output
发生流量控制的过程:
1数据流先被过滤器Filter处理,
2 进行分类Classic
3 每个分类是一个队列Qdisc
1数据流先被过滤器Filter处理,
2 进行分类Classic
3 每个分类是一个队列Qdisc
使用流量控制器工具
通用步骤介绍:
给网卡分配一个队列
在该队列上建立分类
根据需要配置子队列和子分类
为每个分类建立过滤器,把数据流导向它
具体命令介绍:
为网卡创建队列
命令格式:
tc qdisc [add | change | replace | link] dev DEV
[parent qdisk-id |root] [handle qdisc-id] qdisc
[qdisc specific parameters
tc qdisc [add | change | replace | link] dev DEV
[parent qdisk-id |root] [handle qdisc-id] qdisc
[qdisc specific parameters
具体命令举例: 为网卡eth0创建一个类型是htb的队列
tc qdisc add dev eth0 root handle 1:htb default 11
tc qdisc add dev eth0 root handle 1:htb default 11
解释:
root表示创建的是根队列,是最底层的
handle 1:htb 表示队列的句柄是1
root表示创建的是根队列,是最底层的
handle 1:htb 表示队列的句柄是1
为队列创建类别
命令格式:
tc class [add | change | replace] dev DEV
parent qdisc-id [classid class-id]
qdisc [qdisc specific parameters]
tc class [add | change | replace] dev DEV
parent qdisc-id [classid class-id]
qdisc [qdisc specific parameters]
具体命令举例:
tc class add dev eth0 parent 1: classid 1:11 htb rate 40mbit ceil 40mbit
表示父亲队列为1,创建类别classid 1:11,类别标识是1:11
rate 40mbt 表示分配的带宽是10M
表示父亲队列为1,创建类别classid 1:11,类别标识是1:11
rate 40mbt 表示分配的带宽是10M
为类别创建过滤器
命令格式:
tc filter [add | change | replace]
dev DEV [parent qdisc-id | root]
tc filter [add | change | replace]
dev DEV [parent qdisc-id | root]
具体命令举例:
tc filter add dev eth0 protocol ip
parent 1:0 prio 1
u32 match ip dport 80 0xffff flowid 1:11
tc filter add dev eth0 protocol ip
parent 1:0 prio 1
u32 match ip dport 80 0xffff flowid 1:11
解释:
protocol ip表示过滤的是数据流是IP协议
prio 1表示优先级最高为1
u32 match ip dport 表示用到了u32选择器,
判断目的网口dport和0xfff相与之后=80表示匹配
然后送给类别=1:11
protocol ip表示过滤的是数据流是IP协议
prio 1表示优先级最高为1
u32 match ip dport 表示用到了u32选择器,
判断目的网口dport和0xfff相与之后=80表示匹配
然后送给类别=1:11
2章关键数据结构
sk_buff 数据包管理结构
skb->users是引用计数 P36
主要用途是避免某人仍在引用这个结构时,把这个结构释放掉
=1表示只被创建过,只被当前1个人引用,可以被随意修改和释放
>1表示被多人引用,不能修改和释放
通过atomic_inc()和atomic_dec()增加和递减引用,但大多数情况是
通过skb_get(skb), kfree_skb(skb) 去修改引用
主要用途是避免某人仍在引用这个结构时,把这个结构释放掉
=1表示只被创建过,只被当前1个人引用,可以被随意修改和释放
>1表示被多人引用,不能修改和释放
通过atomic_inc()和atomic_dec()增加和递减引用,但大多数情况是
通过skb_get(skb), kfree_skb(skb) 去修改引用
unsigned char *head;
unsigned char *end;
unsigned char *end;
指向的是内存分配的起始head和结束end
unsigned char *data:
unsigned char *tail
unsigned char *tail
指向的实际数据的有效起始位置(data)和结束位置(tail)
通用字段, 内核编译配置必选
sturct net_device *dev
1 当从网卡接受到数据包时,指向的是接收的网口
2 当要发出数据包时,指向的是发送数据包的网口
struct net_device *input_dev
1 表示被接收的数据包源自那个网口, 对于以太网设备eth_type_trans()里被设置
2 如果是本地长生的数据包,其=NULL
2 如果是本地长生的数据包,其=NULL
TCP/IP分层结构
union {...} h -> L4层
union {...} nh -> L3层
union {...} mac -> L2层
union {...} h -> L4层
union {...} nh -> L3层
union {...} mac -> L2层
1 skb->mac 在处理L2以太层后,会记录下Ethernet头
2 skb->nh 在处理L3 IP层后,会记录数据包的IP层包头的位置
3 skb->h 在处理L4 TCP,UDP层后,会记录TCP或UDP层包头的位置
char cb[40] 保存私有信息存储空间
它在每1层之中,通过宏进行访问
举例: TCP_SKB_CB(skb) (struct tcp_skb_cb *)skb->cb[0]
举例: TCP_SKB_CB(skb) (struct tcp_skb_cb *)skb->cb[0]
struct dst_entry dst 该结构有路由子系统使用,
比较复杂需要在第7部分介绍
比较复杂需要在第7部分介绍
unsigned char cloned 表示结构是否被克隆过
管理函数
分配内存alloc_skb会分配2部分内存
1 分配sk_buff结构内存
2 分配数据包的内存
1 分配sk_buff结构内存
2 分配数据包的内存
dev_alloc_skb(int length) 分配skb
内部调用netdev_alloc_skb(dev=NULL,length, gfp_mask)
内部调用netdev_alloc_skb(dev=NULL,length, gfp_mask)
释放内存 kfree_skb(skb)
把2块内存,sk_buff和数据包都释放掉
把2块内存,sk_buff和数据包都释放掉
缓冲区sk_buff的克隆和拷贝-> P49
每clone一次,分配1份新的sk_buff内存,但数据包的内存只有1份,只是引用计数(dataref)增加
并且数据包(skb->head)的内容不能被修改
并且数据包(skb->head)的内容不能被修改
例外情况:
skb_copy()和pskb_copy的用途和区别
skb_copy()和pskb_copy的用途和区别
1 pskb_copy() 只重新拷贝skb_buff和部分数据包数据不包含分片结构
2 skb_copy() 完全拷贝,skb_buff, 所有数据包的数据包含分片结构
sk_buff列表管理函数,
(都需要获取锁保护之中,防止竞争)
(都需要获取锁保护之中,防止竞争)
把一个skb添加到队列的头或尾 skb_queue_head() , skb_queue_tail()
从队列头或尾拿走一个skb, skb_dequeue() , skb_dequeue_tail()
把队列清空 skb_queue_purge()
遍历队列中的每一个元素 skb_queue_walk
net_device 网络设备结构
所有网络设备的net_device结构,保存在全局链表dev_base
标识符:
1 int ifindex 设备唯一的ID, dev_new_index()时分配
1 int ifindex 设备唯一的ID, dev_new_index()时分配
配置相关
char name[] 网卡设备名
unsigned long mem_start, mem_end
该设备所使用的共享内存,用于设备和内核沟通
该设备所使用的共享内存,用于设备和内核沟通
unsigned long base_addr 设备自由内存映射到I/O内存起始地址
unsigned int rirq 设备的中断编号
request_irq() 申请中断, free_irq() 释放中断
unsigned short flags
代表网络设备的功能(IFF_MULTICAST), 或者状态改变(IFF_UP)
代表网络设备的功能(IFF_MULTICAST), 或者状态改变(IFF_UP)
unsigned char dev_addr[] 设备的链路层地址
举例: Ethernet类型是6字节
举例: Ethernet类型是6字节
int promiscuity 混杂模式
接受不仅限于目的是本机的包,所有包都会抓取
通用结构
refcnt 引用计数, 只有=0时,设备才能被注销unregister
int (*poll) -> 设备轮询,被NAPI功能使用
函数指针
struct ethtool_ops *ethtool_ops
用于设置和读取设备的配置
用于设置和读取设备的配置
用于开启或关闭一个网络设备
int (*open) , int (*stop)
int (*open) , int (*stop)
int (*do_ioctl) 用于向设备发出命令
5章 网络设备初始化
设备的注册初始化
硬件初始化:
有驱动程序和通用总线层(PCI, USB)合作完成
把设备的提供的功能配置成IRQ或I/O地址形式,使设备和内核能进行交互
有驱动程序和通用总线层(PCI, USB)合作完成
把设备的提供的功能配置成IRQ或I/O地址形式,使设备和内核能进行交互
NIC初始化的基本目标:
驱动程序如何分配建立设备/内核通讯所需的资源
驱动程序如何分配建立设备/内核通讯所需的资源
IRQ 中断
request_irq() 给1个中断设置回调处理函数
free_irq() 删除中断的回调处理函数
I/O端口和内存注册
驱动程序将其设备的1个内存区域(配置的寄存器)映射到系统内存,
通过request_region()和release_region()完成
通过request_region()和release_region()完成
设备处理层初始化 net_dev_init -> P104
dst_init() 路由结构的初始化
协议处理函数ptype_base的初始化
注册1个CPU热插拔事件的通知链, 回调函数dev_cu_callback
当CPU停止时,清空处理CPU入口队列包,交给netif_rx
当CPU停止时,清空处理CPU入口队列包,交给netif_rx
7部分路由
32章 Linux的实现
路由主要的数据结构
总体介绍:
路由用rt前缀, 转发信息数据库用fib, 功能用fn前缀表示
路由用rt前缀, 转发信息数据库用fib, 功能用fn前缀表示
struct fib_result 对路由表查找后返回该结构,它不止包含下一跳信息
struct fib_rule 表示策略路由在数据流路由时,选择的路由表规则
struct flowi 类似于访问控制列表(ACL) 从L3和L4包头中选择字段作为关键字查找路由
相当于五元组src,dst,sport,dport,proto
相当于五元组src,dst,sport,dport,proto
struct fib_node 1条路由表项
存储的是route add添加的1条路由信息
存储的是route add添加的1条路由信息
struct fn_zone 表示相同长度子网掩码的1组路由
举例: 24位的区(zone) 路由10.0.1.0/24和10.0.2.0/24
举例: 24位的区(zone) 路由10.0.1.0/24和10.0.2.0/24
struct fib_table 表示1张路由表
struct fib_info 不同路由表项之间可以共享的参数存储在这里
struct fib_nh 表示下一跳
举例: route add 10.81.0.0/24 nexthop via 10.80.2.1下一跳就是10.80.2.1
举例: route add 10.81.0.0/24 nexthop via 10.80.2.1下一跳就是10.80.2.1
struct dst_entry 协议无关的路由表缓存
struct dst_ops 使用的徐函数表
struct rtable 表示1条路由表缓存项的数据结构
struct rtengry 处理用户态的请求route add
当使用route add命令添加/删除路由表项请求时所使用的数据结构
当使用route add命令添加/删除路由表项请求时所使用的数据结构
35章路由查找
先查找路由缓存,缓存中没有时,才进行真正路由表的查询
ip_route_input_slow 从网卡收到的包,是转发包或者交给本机的包
ip_route_output_slow() 本机产生向外发出的包,进行的路由查找
无论那个方向的路由都通过 fib_lookup() 查找路由
33章路由的缓存DST
宏观介绍
路由缓存的用途是加速路由查找的速度
缓存的核心结构是协议无关的目的地址缓存DST(Protocol Indepent Destination Cache)
缓存的核心结构是协议无关的目的地址缓存DST(Protocol Indepent Destination Cache)
外部系统与DST交互通过dst_ops回调函数完成
核心数据结构
struct rtable 包含2部分
1 与协议相关的数据结构
2 与协议无关的数据结构dst_entry
1 与协议相关的数据结构
2 与协议无关的数据结构dst_entry
2 路由缓存中独立与协议的结构
struct dst_entry {
net_device *dev; 出口设备(将包报送到目的地的发送设备)
int (*input) (struct sk_buff *) ; 表示处理入口包的函数
int (*output) (struct sk_buff *); 表示处理出口包的函数
struct dst_ops *ops; 处理dst_entry结构的VFT
}
struct dst_entry {
net_device *dev; 出口设备(将包报送到目的地的发送设备)
int (*input) (struct sk_buff *) ; 表示处理入口包的函数
int (*output) (struct sk_buff *); 表示处理出口包的函数
struct dst_ops *ops; 处理dst_entry结构的VFT
}
路由缓存查找:
内核中查找路由时,先查找缓存,如果不存在再查找真正的路由表
内核中查找路由时,先查找缓存,如果不存在再查找真正的路由表
1 ip_route_outoput() 是网卡收到包,该包目的是本机或者转发包
2 ip_route_output_key() 由本机生成的向外发出的包
P885 IPsec变换时,对dst_entry的使用
1 无IPsec处理时,只有1个dst_entry,并且它的input,output用于对包的处理(转发或交给本地上层协议)
2 有IPsec处理时, 会有3个dst_entry连在一起,钱2个dst_entry的input,output负责加解密和添加/取出封包头ESP头
第3个dst_entry的input,output才负责转发或者交给本地上层协议
第3个dst_entry的input,output才负责转发或者交给本地上层协议
36章重要数据结构
flowi 结构用途: 用于路由查找的关键字
可以根据如入口/出口设备,L3和L4层包头的字段作为参数组合
对流量进行关键字的路由查找
可以根据如入口/出口设备,L3和L4层包头的字段作为参数组合
对流量进行关键字的路由查找
struct flowi
union {
struct flowi4
struct flowi6
union {
struct flowi4
struct flowi6
18章IPv4(因特网)
P413 大蓝图
IP内核协议战的关键函数
IP内核协议战的关键函数
举例: 转发包的处理流程
ip_rcv() -> NF_HOOK(NF_INET_PRE_ROUTING, ip_rcv_finish)
ip_rcv_finish() -> 查找路由ip_route_input_noref()
ip_forward() NF_HOOK(NF_INET_FORWARD, ip_forward_finish)
ip_output() -> NF_HOOK(NF_INET_POST_ROUTING,ip_output_finish)
ip_rcv() -> NF_HOOK(NF_INET_PRE_ROUTING, ip_rcv_finish)
ip_rcv_finish() -> 查找路由ip_route_input_noref()
ip_forward() NF_HOOK(NF_INET_FORWARD, ip_forward_finish)
ip_output() -> NF_HOOK(NF_INET_POST_ROUTING,ip_output_finish)
对本书的总结性的介绍
1 本书参照的linux内核代码是2.6.11
2 而我现在的分析是基于最新的linux内核4.15版本
我现在添加的许多标志性函数和结构都是
我现在添加的许多标志性函数和结构都是
0 条评论
下一页