linux内核网络揭秘《二》

一 引用计数

当一段代码试图访问一个已经被释放的数据结构时,内核并不欢迎,而大多用户对于内核的这种反应也会感到不愉快,为了避免这类讨厌的问题,同时让垃圾回收机制更为容易而且有效率,多数数据结构都会保存引用技术reference count,对每一个数据结构的每一次存储和释放,良好的内核组建都会分别递增和递减该数据结构的引用计数,人和数据结构若需要引用计数,拥有该数据结构的内核组建通常会提供两个函数,用以递增和递减引用计数值。这类函数通常分别称为xxx_hold和xxx_release,有时候,释放函数被称为xxx_put(例如,net_device结构的释放函数就是dev_put)

虽然我们希望内核中么有不良组建,但是开发人员也是人,因此不可能永远写出无缺陷的代码,。引用计数的运用是一种简单有效的机制,可以避免释放在被引用的数据结构,然而,因其忽略了平衡递增量和递减量。

如果你是放了一个对数据结构的引用,但是却忘记了调用xxx_release函数,内核将永远不会让该数据结构背释放掉,这样会导致内存逐渐耗尽。

如果你引用了一个数据结构,但是忘记了调用xxx_hold,后来碰巧又成为唯一的引用持有者,结果该结构就会提早备释放,这种情况肯定比前一种更具有灾难性,下次你试图访问结构时,可能会破坏其他数据,或者引起内核恐慌panic。

当某个数据结构因为某种原因被删除时,可以明确引用持有该数据结构即将消失,使其能按规则释放其引用,而这是通过通知链实现的。

数据结构的引用计数通常在下列情况下可做递增。

两种数据结构类型之间又很密切的关系,在这种情况下,其中的一个结构通常会维持一个被初始化为第二个结构的地址的指针。

一个定时器启动,而该定时器的处理程序将访问改数据结构,当定时器启动时,该结构的引用计数值就会递增,因为你最不愿意看到的是,在定时器到期前该数据结构就被释放了。

对列表的hash表的成功查询会反悔一个指向匹配的元素的指针,多数情况下,返回的结果背调用者用来执行某种任务,因此,常见的做法是,查询函数递增匹配元素引用计数值,然后在必要时再让调用者予以释放。

当一个数据结构的最后一个引用也被释放时,该数据结构就可以被释放。因为再也用不到该数据结构了,不过不一定非这么做不可。

新引入的sysfs文件系统有助于产生内核的一部分良好代码,运用它可以更关注引用计数和一致性。

二 垃圾回收

内存是有限的共享资源,不应该浪费,特别是在内核中,因为内核不适用虚拟内存,多数内核子系统会实现某种垃圾收集,以回收未使用或者无效的数据结构实例所持有的内存,根据特定功能所需而定,你会发现有两种主要的垃圾收集。

异步

这种垃圾收集类型和特点的事件无关,一个定时器会定期启用一个函数,以扫描一组数据结构,然后把那些适合删除的数据结构释放掉,数据结构符合删除的条件依赖于该系统的功能和逻辑。但是一个常见的准则为是否存在null引用计数。

同步

在内存不足时的i请你赶快下会立即触发垃圾收集,而不能等待定时器触发的异步垃圾手机,用于选取符合删除的数据结构的准则,不一定与异步清理机制所用的准则相同。

函数指针和虚拟函数表(VFT)

函数指针是一种方便的方式,可以写出简洁的C代码,又能利用面向对象语言的某些优点,在数据结构类型的定义中,你可以包含一组函数指针,于是,该结构的某些全部的操作都可以通过嵌入的函数完成。C语言的函数指针的数据结构中类似如下所示。

struct sock {

void (*sk_state_change)(strict sock *sk);

void (*sk_data_ready)(struct sock *sk, int bytes);

};

使用函数指针最主要的优点就是,可以根据不同准则以及该对象所扮演的角色进行初始化,因此,调用sk_state_change时,实际上可能会为不同的sock套接字而调用不同的函数。

函数指针在网络代码中广为使用,下面只是很少的实例。

当入口数据封包或者出口数据封包由路由子系统处理时,会对缓冲区数据结构中的两个函数做初始化,在第三十五章就会看到,参考第二章所列sk_buff数据结构中的完整函数指针列表。

当数据封包已准备好在网络硬件上传输时,就会交给net_device数据结构的hard_start_xmit 函数指针,该函数由设备所关联的设备驱动程序进行初始化,

当L3协议想传输数据封包时,会调用一组函数指针中的一个,这些函数指针由该L3协议相关联的地址解析协议(address reolution protocol)

负责初始化位一组函数,根据函数指针初始化史记汉书,可以产生透明化的L3层到L2曾的地址解析,当不需要地址解析时,就会使用另一个函数。

通过上述几个例子可知,函数指针可作为内核组件之间的接口,或者 作为一种通用的机制,根据不同的子系统所做的某事的结果,在适当的时机调用适当的函数处理程序,在其他情况下,函数指针也可以作为一种简单的方式,使协议,设备驱动程序或者任何其他功能得以让某种动作个别化。

看一个例子,当设备驱动程序在内核注册网络设备,无论是任何类型的设备,都需要经过一系列步骤,到了某一时刻,就会对net_device数据结构调用一个函数指针,使设备驱动程序在必要时做些其他事情,设备驱动程序即可以把函数指针初始化成其拥有的一个函数,也可以让该指针保持位NULL,因为内核默认的执行步骤已经足够了。

执行一个函数指针前,必须始终检查其值,以避免提取NULL指针所指向单元的值的事情发生,例如 register_netdevice 中的片段。

if (dev->init && dev->init(dev) != 0)

函数指针有一个主要的缺点,代码难以理解。

当选择函数分配给函数指针是基于特定数据片段时(如处理数据的协议,或者接收的给定数据封包来自的设备驱动程序)们就比较容易推导出该函数。例如,如果给定设备是由drivers/net/3c59x.c设备驱动程序管理,就可以阅读该设备驱动程序提供的设备初始化函数,从而导出分配给net_device数据结构的特定函数指针的函数。

当函数的选择是基于更为复杂的逻辑之时,如L3层倒L2层地址映射解析的状态,人和时刻所用的函数都会依赖于无法预测的外在事件。

指向一个数据结构的一组函数指针通常就称为虚拟函数表,virtual function table, 当VFT作为两个主要子系统之间的接口时,数据结构中函数指针树木可能会膨胀,以包含许多不同的指针,适应各种各样的协议或者功能。这种用过头,VFT变得很笨重。

相关推荐
会又不会5 分钟前
Jenkins-Publish HTML reports插件
运维·jenkins
烟雨书信17 分钟前
Docker文件操作、数据卷、挂载
运维·docker·容器
IT成长日记20 分钟前
【Docker基础】Docker数据卷管理:docker volume prune及其参数详解
运维·docker·容器·volume·prune
这儿有一堆花26 分钟前
Docker编译环境搭建与开发实战指南
运维·docker·容器
LuckyLay26 分钟前
Compose 高级用法详解——AI教你学Docker
运维·docker·容器
Uluoyu35 分钟前
redisSearch docker安装
运维·redis·docker·容器
靡樊44 分钟前
NAT、代理服务、内网穿透
网络·内网穿透·nat·代理服务·内网打洞
IT成长日记5 小时前
【Docker基础】Docker数据持久化与卷(Volume)介绍
运维·docker·容器·数据持久化·volume·
一只栖枝6 小时前
网络安全 vs 信息安全的本质解析:数据盾牌与网络防线的辩证关系关系
网络·网络安全·信息安全·it·信息安全认证
物联网老王7 小时前
Ubuntu Linux Cursor 安装与使用一
linux·运维·ubuntu