0%

Linux网络内核源码分析|重要数据结构

概述

内核版本:linux-2.6.8.1

linux目录解析

网络内核布局

套接字

struct sk_buff, struct sk_buff_head

skbuff.h

sk_buff是套接字缓冲区类型,用来管理网络数据包,为发/收的网络数据提供存储区域和操作方法。

sk_buff_head管理一个双向链表,来组织sk_buff。

sk_buff与数据存储

1
2
3
4
5
6
{//struct sk_buff
unsigned char *head,//缓冲区头指针
*data,//数据块头指针
*tail,//数据块尾指针
*end;//缓冲区尾指针
}

sk_buff用union定义的三个协议头部指针:h, nh, mac。每个union有一个用于初始化的raw指针。采用tcp协议的数据包从第三层递交到第四层时指针的变化情况。

sk_buff->end指针指向数据报分片信息,数据包分片由struct skb_shared_info管理。

alloc_skb()

skbuff.c

创建套接字缓冲区并初始化,建立了sk_buff和struct skb_shared_info的关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{//alloc_skb
//分配一个struct sk_buff结构体
skb = kmem_cache_alloc(skbuff_head_cache,
gfp_mask & ~__GFP_DMA);
//...
// 初始化数据区指针
skb->head = data;
skb->data = data;
skb->tail = data;
skb->end = data + size;
// 初始化struct skb_shared_info结构内容
// #define skb_shinfo(SKB) ((struct skb_shared_info *)((SKB)->end))
atomic_set(&(skb_shinfo(skb)->dataref), 1);
skb_shinfo(skb)->nr_frags = 0;
skb_shinfo(skb)->tso_size = 0;
skb_shinfo(skb)->tso_segs = 0;
skb_shinfo(skb)->frag_list = NULL;
}

skb_put(), skb_trim(), skb_push(), skb_pull(), skb_reserve()

skbuff.h

skb_put()用来在数据区后加某协议尾部;调整tail,增加len。

skb_trim()用来去掉数据包的协议尾部;调整tail, 减少len。

skb_push()用来在数据区前加某协议头部;调整data,增加len。

skb_pull()用来去掉数据包的协议头部;调整data,减少len。

skb_reserve()用来在数据区创建存储协议头部的空间。应用:方便skb_push添加头部;调整数据区大小。

skb_queue_head(), skb_queue_tail()

skbuff.h

skb_queue_head() –> _skb_queue_head(),在套接字缓冲区链表头部添加一个缓冲区。

skb_dequeue() –> _skb_dequeue(),把排在头部的缓冲区从套接字缓冲区链表中移走,返回该缓冲区。

skb_queue_tail() –> _skb_queue_tail(),在套接字缓冲区链表尾部添加一个缓冲区。

skb_dequeue_tail() –> _skb_dequeue_tail(),从套接字缓冲区链表尾部移走一个缓冲区。

其中,套接字缓冲区链表即struct sk_buff_head *list,涉及增加操作时参数有待增加缓冲区struct sk_buff *newsk。

网络设备

struct net_device

管理网络设备的数据结构。含:通用字段、硬件配置字段、网络层数据字段、物理层数据字段、设备驱动程序中的函数。

include/linux/netdevice.h

通用字段:设备名称、next指针指向下一个net_device、设备状态state、网络设备索引值ifindex(用来标志网络设备以便快速定位,设备被创建后由dev_get_index函数分配)、refcnt表示网络设备的引用次数。

硬件配置字段:内存共享字段(描述网络适配器与内核共享的内存空间,指定发送包和接受包所在的区域)、I/O基地址(用于驱动程序搜索设备)、设备使用的中断号irq、分配给设备的DMA通道号、多端口设备使用的不同端口if_port(网络介质类型决定)。

物理层数据字段:指定2层协议头部长度、最大传输单元mtu(以太网1500byte)、网络设备输出队列的最大长度、网络设备类型type、地址字段(广播地址、多播地址表等)。

网络层数据字段:协议信息字段(xxx_ptr,指向xxx协议信息)、网络地址信息字段(协议地址族family例如AF_INET、协议地址长度、网络设备地址pa_addr)等。

设备驱动程序中的函数指针

  • int (*init)指向设备初始化函数,注册设备时调用,初始化struct net_device;
  • void (*uninit)指向注销设备函数,删除设备时调用;
  • int (*open)指向打开设备接口的函数,注册设备需要的系统资源(I/O端口、irq、dma);
  • int (*stop)指向停止设备的函数;

网络设备的创建和注册过程

一个网络设备被使用前,需要先被创建成为一个struct net_device并注册。下面描述注册过程:

设备驱动被编译成模块,内核在加载模块时进行初始化,调用init_module。init_module依次调用alloc_etherdev、do_netcard_probe、register_netdev。

alloc_etherdev创建一个以太网设备,返回管理该设备的struct net_device,其中alloc_netdev函数为新创建的网络设备分配struct net_device结构空间,ether_setup初始化与以太网相关的设备信息并将协议相关的函数指针添加到struct net_device结构中。

driver/net/net_init.c

do_netcard_probe设置struct net_device_device主要字段。

register_netdev函数注册该设备,最后调用register_netdevice向dev_base链表添加设备。不需要网络设备时需要unregister_netdev函数注销设备,关闭活动状态的设备并从dev_base链表中删除设备。

net/core/dev.c

网络设备的开启与关闭

net/core/dev.c

*开启网络设备函数dev_open(struct net_device dev)。如果网络设备已经激活或者它尚未被注册,函数返回错误信息。

  • 判断设备是否激活;
  • 使用set_bit函数修改设备状态为__LINK_STATE_START,调用net_device中的open指向函数设置该设备;
  • 激活网络设备的队列和调度器;
  • 将事件(NETDEV_UP)登记到通知链:notifier_call_chain(&netdev_chain,事件,dev)。

*关闭网络设备函数dev_close(struct net_device dev)

  • 如果网络设备未被激活,则不需要关闭;
  • 将事件(NETDEV_GOING_DOWN)登记到通知链:notifier_call_chain(&netdev_chain,事件名,dev);
  • 删除包调度器中的相应信息:dev_deactivate;
  • 清除设备的活动状态:clear_bit;
  • 调用net_device中的stop指向函数,执行停止操作。

通知链

kernel/sys.c

struct notifier_block类型定义了通知链中每个元素的结构。

1
2
3
4
5
6
struct notifier_block{
//表示这个元素所记录的函数
int (*notifier_call)(struct notifier_block *self,unsigned long,void *);
struct notifier_block *next;//指向下一个元素
int priority;//描述优先级
};

通知链注册事件函数notifier_chain_register(**notifier_block列表,notifier_block待登记块)**。可以向通知链中等级一个事件,事件发生时可以从notifier_block链表中找到该事件对应的元素,执行记录的函数。

通知链注销事件函数notifier_chain_unregister(**notifier_block列表,notifier_block待注销块)**。可以向通知链中注销一个事件。

事件发生时,使用*notifier_call_chain(通知链名,事件名,void)。向某个通知链表发送消息,按顺序调用链表块中记录的函数**。