Linux TCP/IP协议栈的通用编码模式解析
9. 条件检查的编译优化
大多数时候,内核会把一个变量与一个外部值比较来看某一条件是否已满足,这种比较的结果很大程度上是可以预测的。这样的例子很常见,例如,检查合法性的代码。内核使用likely和
unlikely两个宏来分别表示返回的结果是true(1)或false(0)。这两个宏使用gcc可根据它的返回值来优化编译结果的特性来提升代码的性能。
这里有个例子。我们假设调用do_something函数,在出错的情况下,我们调用handle_error来处理错误:
err = do_something(x,y,z);
if (err)
handle_error(err);
如果do_something很少出错,我们可以将代码重写为:
err = do_something(x,y,z);
if (unlikely(err))
handle_error(err);
一个能够使用likely和unlikely宏来优化代码的例子是处理ip option。由于ip option只在特定的情况下出现,内核可以假设大多数的ip包都没有ip option.当内核转发一个ip包时,它不需要关心
Ip option选项。在ip_forward_finish里面,处理转发包的最后阶段,这个函数使用unlikely宏来检查是否有ip选项需要处理。
10. 互斥
锁在网络代码中大量使用,你会发现本书的每个话题都会涉及到它。互斥,锁机制和同步是编程领域中的一个很有意思,但也很复杂的话题,尤其是在内核编程领域。经过多年的发展和优化,linux内核中已经包含了多种途径来完成代码的互斥。这里我们重点描述网络代码中用的锁。
每种互斥机制都有它适用的环境。下面描述的是网络代码中常见的互斥机制:
自旋锁
这个锁,同一时刻只能由一个线程占有。其他试图得到锁的线程会重复尝试得到锁直到这个锁被释放。由于循环会有一定的消耗,所以,这个锁一般用在多处理器系统里面,而且开发者都希望线程占有锁的时间不会太长。由于其他尝试得到锁的线程也有一定的消耗,所以,占有锁的线程,其执行过程不能睡眠。
读写自旋锁
如果一个锁的用户可以明确分为只读和读写两类,这种情况下,推荐使用读写自旋锁。自旋锁和读写自旋锁的区别在于后者可以由多个读者同时占有,但是,如果写者获得了锁,读者就不能获取这个锁。由于读者的优先级比写者高,所以这个锁适用于读者多(或者只读的锁定请求多),而写者少(或者写的锁定请求少)的情况。
如果锁被读者获得,写者就不能获得这个锁;只有读者释放了这个锁,写者才能获得这个锁。
读-拷贝-更新Read-Read-Update(RCU)
RCU是linux最新提供的一种互斥机制。它在以下情况下表现良好:
读请求多而读写请求很少
获得锁的代码自动执行,而且不会睡眠.
被锁保护的数据结构通过指针访问
第一条规则与性能有关,后面两条规则是使用RCU时的限制条件。
值得注意的是,按照第一条规则的条件,一般都使用读写自旋锁来做为RCU的替代。为了理解为什么在某些情况下RCU的性能要高于读写自旋锁,你需要考虑其他一些因素,比如SMP系统中的处理器缓存的影响等。
网络代码中使用RCU的例子是路由子系统。路由查找的次数比路由更新的次数多,而且路由查找代码不会在中途被阻塞。
内核提供了信号量,但是本书描述的网络代码中很少用到它。
11.主机字节顺序和网络字节顺序序的转换
多于一个字节的数据结构在内存中的存储有两种不同的格式:Little Endian和 Big Endian。Little Endian格式把最低字节存储在最低的地址中,而Big Endian刚好与此相反。linux操作系统使用的存储格式与具体的处理器有关。例如,intel处理器使用Little Endian模型,而Motorola处理器使用Big Endian模型。
假设我们的linux主机从远端的主机上收到了一个IP包。由于不知道这个包在远端的主机上是以何种方式发送的,我们就不知道如何去读取这个包的包头。因为这个原因,每个协议簇都必须定义它的网络包的存储格式。比如,TCP/IP协议栈就使用Big Endian模型。
但是内核开发人员依然面临一个问题:必须编写可以在不同处理器,不同存储模式下使用的代码。有些处理器的存储模式可能与网络包的存储模式一致,这种情况下,就不需要转换网络包的存储格式。


















文章评论
共有 位CH网友发表了评论 查看完整内容