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