一 常见编码模式
与其他内核功能类似,每种网络功能只是内核各种功能之一,因此,也必须适当公平的使用内存,CPU以及其他各种共享资源,多数功能在内核中并非代码片段,而是根据该功能而定,或多或少会和其他内核组建交互。因此,类似的功能都会尽可能采用类似的机制实现(已经发明的东西没有必要再重新发明一次)
有些内核组件有一些共同的需求,如必须分配同一种数据结构类型的几个实例,必须记录某种数据结构实例的引用,以避免不安全内存回收,诸如此类。下面几个小节中,我们将说明linux处理这类需求的常见做法。
二 内存缓存
内核分别使用kmalloc和kfree函数分配和释放一个内存块,和malloc free类似。内核组建为同一种数据结构类型分配几个实例instance 是很常见的事情,当分配和回收经常发生时,相关的内核组件初始化函数,fib_hash_init 通常会分配一块特殊的内存缓存,以作分配之用,当一个内存块被映射释放时,实际上是返回到当初被分配的同一个缓存区中。
内核维护的其专属内存缓存的一些网络数据结构的例子包括。
套接字缓冲区描述符,
这个缓存是由net/core/sk_buff.c的skb_init分配的,用于分配sk_buff缓冲区描述符,sk_buff结构可能是网络子系统中分配和回收注册次数最高的。
邻居协议映射
每个邻居协议都使用一个缓冲区,以分配存储L3层到L2层地址映射的数据结构
参见第二十七章
路由表
路由代码使用两块缓存,用于定义路径的两个数据结构。以下是用于处理内存缓存的关键内核函数。
kmem_cache_create
kmem_cache_destroy
创建和销毁一个缓存
kmem_cache_alloc
kmem_cache_free
为缓存分配以及回收一个缓冲,通常由调用包裹函数wrapper 实现分配和回收一个缓存缓冲区,包裹函数在较高层中管理用于处理分配和回收的请求,例如,用kfree_skb 要求释放一个sk_buff 缓冲区的是一个实例时,只有当所有对该缓冲区的引用都已经释放,而且相关的子系统也罢所有必要的清理工作都做完之后,才回去调用kmem_cache_free.
给定缓冲区所能分配的实例树木的限制,通常由内含kmem_cache_alloc 的包裹函数决定,但是有些时候可以通过/proc 里的参数进行配置。
有关内存缓存的实现以及其和厚片分配器,slab_allocator 之间的接口细节,参考《深入理解linux内核》一书。
三 缓存和hash表
使用缓存以提高性能是很常见的事情,在网络代码中,L3层到L2层之间的映射,以及路由表等都使用缓存。
缓存查询函数通常有一个输入参数,指出发生缓存不命中cache miss 时,是否应该在缓冲中创建并添加一个新的元素,而没有这个参数时,查询函数则岁时会把不命中的元素新增进来。
缓存通常用三列表(即hash表)实现,内核提供一组数据类型,如单向和双向列表,用来构建简单的hash表,有些输入经过hash后得到相同的值,其标准处理方式就是将其放入一个列表中,查询这个列表所花的时间,比采用hash关键字(key)查询所花的时间长得多。因此,把hash值相同的输入值树木缩到最小一直都是很重要的事情。
当hash表的查询时间(无论是否使用缓存)是拥有者子系统的关键参数时,可以实现一种机制,以增加hash表的大小,使得冲突列表(collision list)的平均长度得以下降,而平均查询时间得以改善,参见第三十四章,基于子网掩码的hash表容量的动态调整,一节的实例。
你也会发现有些子系统,在关键字key中添加一个随机分子,使元素分散到缓存bucket中呢,这是为了减少拒绝服务攻击的损害,这种攻击把hash表的元素都集中到一个bucket中。