原子操作的内核源代码剖析
作者: HUFAN标题: 原子操作的内核源代码剖析 时间: Mon Aug 31 11:44:12 2009点击: 26分析人:余旭 分析版本:Linux Kernel 2.6.14 来自于:www.kernel.org 分析文件: 1./include/asm-i386/atomic.h 2./include/linux/compiler-
·
作 者: HUFAN 标 题: 原子操作的内核源代码剖析 时 间: Mon Aug 31 11:44:12 2009 点 击: 26 分析人:余旭 分析版本:Linux Kernel 2.6.14 来自于:www.kernel.org 分析文件: 1./include/asm-i386/atomic.h 2./include/linux/compiler-gcc4.h 3./include/linux/compiler.h 4./linux/bitops.h 5./asm-x86_64/bitops.h 6./asm-i386/bitops.h 7./include/asm-x86_64/atomic.h 8./include/asm-i386/cache.h 分析开始时间:2005-11-22-8:20:34 分析结束时间:2005-11-28-20:24:25 编号:2-3 类别:进程管理-准备工作-原子操作 Email:yuxu9710108@163.com 版权声明:版权保留。本文用作其他用途当经作者本人同意,转载请注明作者姓名 All Rights Reserved. If for other use,must Agreed By the writer.Citing this text,please claim the writer's name. Copyright (C) 2005 YuXu **************************************************************** 原子操作是指某些操作的不可中断。分为bitops和atomic_t两类。bitops用于标志的设置 ;而atomic_t用于原子性的加减之类的运算。 在原子操作中运用了很多的volatile声明。这样系统会阻止编译器对volatile声明的变量 进行优化,确保变量使用用户定义的精确地址,而不是使用有着同一信息的别名。 **************************************************************** 原子操作的第一个方面: 算术运算。32位平台和64位平台 **************************************************************** 32位平台的原子操作(算术运算)的头文件 **************************************************************** -----------------/include/asm-i386/atomic.h--------------------- 以下的源代码分析除非作特殊说明,全来自于/include/asm-i386/atomic.h头文件: ---------------------------------------------------------------- #ifndef __ARCH_I386_ATOMIC__ #define __ARCH_I386_ATOMIC__ 自己分析: 这是用于防止重复包含头文件的Linux Kernel中的常用手法。 如果尚未包含此atomic.h,则会定义__ARCH_I386_ATOMIC__宏,然后再将此atomic.h文件 包含进来。下次再遇到要包含此头文件的地方,由于已经定义__ARCH_I386_ATOMIC__宏, 所以#ifndef里面的__ARCH_I386_ATOMIC__宏的定义和此头文件将不会再包含进来。这就防 止了重复包含同一个头文件。 ---------------------------------------------------------------- #include <linux/config.h> #include <linux/compiler.h> #include <asm/processor.h> /* * Atomic operations that C can't guarantee us. Useful for * resource counting etc.. */ 分析:C语言无法保证我们原子操作。但原子操作在资源计数中很有用。 --------------------------------------------------------------- LOCK宏 ---------------------------------------------------------------- #ifdef CONFIG_SMP #define LOCK "lock ; " #else #define LOCK "" #endif 分析: 1.原子操作的来历: 某些汇编指令的“读-修改-写”的性质。即:第一次访存,读取原值,在CPU中修改后,第 二次访存,写回新值。如果在多CPU上各自执行各自的具有“读-修改-写”的性质的汇编指 令,而这些汇编指令访问同一个内存地址的话,则如果不是原子操作的话,这些汇编指令 交错执行,将会导致错误。 例如:两个CPU均执行“读-修改-写”的性质的汇编指令。这两个CPU向同一内存地址同时 发出读请求,这时存储器仲裁器通过决策只让其中一个CPU(设为A)访问内存读取了这个 地址的旧值。再让另一个CPU(设为B)随后又读取这个旧值。CPU_A将旧值修改为2,CPU_ B将旧值修改为3,两者同时写回这一地址的内存。这时,存储器仲裁器让CPU_A写回2,再 让CPU_B写回3。这时该内存地址单元中的数值就出错了,CPU_A运算的结果被CPU_B的结果 覆盖了。如果CPU_A后继指令还需要用到这个2结果的话,则被3覆盖了,CPU_A后继结果将 出错。交错的“读-修改-写”的性质的汇编指令的执行是导致错误的根源。 2.LOCK宏: 当为多处理器环境时候,LOCK宏展开为lock;汇编指令前缀。使得后继的操作在芯片级上为 原子的。任何一个这样的操作都必须为单个指令执行,中间不能被中断。当CPU中的控制器 检测到这个前缀时候,就会锁定内存总线,一直到该条指令执行完毕,在此期间其它的CP U不能访问这条指令所访问的内存单元。 从源代码看出在单CPU环境上,LOCK前缀定义为空,即在单CPU上不会有lock汇编指令前缀 ,不会锁定内存总线,因为不存在多CPU的交错的“读-修改-写”的性质的汇编指令的执行 。 我将其理解为:原子操作只用于多CPU环境,而在单CPU上无原子操作。 --------------------------------------------------------------- atomic_t{} --------------------------------------------------------------- /* * Make sure gcc doesn't try to be clever and move things around * on us. We need to use _exactly_ the address the user gave us, * not some alias that contains the same information. */ 分析: 1.这段英文的内核注释是解释atomic_t{}为什么使用volatile关键字。为了使用精确的地 址。 2.结构体不长的话,将其写成一行。(学习内核编程风格) ---------------------------------------------------------------- typedef struct { volatile int counter; } atomic_t; ---------------------------------------------------------------- ATOMIC_INIT() ---------------------------------------------------------------- #define ATOMIC_INIT(i) { (i) } 注意:结构体的初始化宏的写法 ---------------------------------------------------------------- atomic_read() ---------------------------------------------------------------- /** * atomic_read - read atomic variable * @v: pointer of type atomic_t * Atomically reads the value of @v. */ #define atomic_read(v) ((v)->counter) 功能:读取atomic_t{}型变量v的值。 ---------------------------------------------------------------- atomic_set() ---------------------------------------------------------------- /** * atomic_set - set atomic variable * @v: pointer of type atomic_t * @i: required value * Atomically sets the value of @v to @i. */ #define atomic_set(v,i) (((v)->counter) = (i)) 功能:将atomic_t{}型变量v的值设置为i。 小经验: 1.v,i宏中的参数均用括号括起来:(v),(i)((v)->counter) 2.'='前后均空一个空格 --------------------------------------------------------------- atomic_add() --------------------------------------------------------------- static __inline__ void atomic_add(int i, atomic_t *v) { __asm__ __volatile__( LOCK "addl %1,%0" :"=m" (v->counter) :"ir" (i), "m" (v->counter)); } 1.原子操作的成因: 这里用的都是单个汇编指令,只用一条汇编指令来实现之,再在LOCK(只对多处理器有用 ,单处理器上LOCK为空),这样就实现了原子操作。 我想,在单处理器上LOCK是空的,则原子操作来源于整个函数为单个汇编指令就完成了目 标。 2.%1:i,%0:v->counter,'='表示输出。 AT&T格式汇编:汇编指令部:输出部:输入部:损坏部(这里没有) *************************************************************** 吃掉gcc手册中的attribute中的相关知识 **************************************************************** /include/linux/compiler-gcc4.h **************************************************************** gcc不同版本对c有着不同的扩充,而Linux内核又是使用了不同的gnu c的扩充。所以不同 的Linux内核当用不同的版本的gcc来编译。Linux内核与gcc是平行发展的。当Linux中的内 核使用了新版本的gcc的c扩充后,则该版本的Linux内核将只能用此版本或更新版本的gcc 来编译。这样对Linux内核的移植性有着一定的损害,因为要移植Linux内核,同时也要移 植gcc或要gcc支持所要移植的平台。但gcc所支持的cpu比Linux内核所支持的cpu更多,而 且gcc还支持各种cpu的交叉编译,所以这个问题并非很严重。再者,Linux是本质,而gcc 是个编译的工具,是为Linux内核服务的,Linux内核追求的是高水平的内核,自然对可移 植性目标要求没要求内核高质量那么重视。gcc这个工具也就只好服从于Linux内核的需要 。Linux内核追求质量第一,而内核的可移植性问题由gcc去发展去支持多平台的编译来解 决。 ---------------------------------------------------------------- inline __inline__ __inline ---------------------------------------------------------------- #define inline inline __attribute__((always_inline)) #define __inline__ __inline__ __attribute__((always_inline)) #define __inline __inline __attribute__((always_inline)) #define noinline __attribute__((noinline)) 再进一步将/include/linux/compiler-gcc4.h 文件中的其他的宏一并吃掉: #define __attribute_used__ __attribute__((__used__)) #define __attribute_pure__ __attribute__((pure)) #define __attribute_const__ __attribute__((__const__)) #define __must_check __attribute__((warn_unused_result)) 分析: 1.代码分析: 1)使用inline,__inline__,__inline均是使函数总是内嵌入到代码中去。 2)noinline则无论编译器开启优化选项与否,函数均不内嵌入代码中。 ---------------------------------------------------------------- 2.参考gcc手册:(Download from www.gnu.org) ---------------------------------------------------------------- 1)In GNU C, you declare certain things about functions called in your program which help the compiler optimize function calls and check your code more carefully. 利用attribute来help编译器优化函数调用和更详细的检查代码。 ---------------------------------------------------------------- 2)You may also specify attributes with ‘__’ preceding and following each keyword. This allows you to use them in header files without being concerned about a possible macro of the same name. For example, you may use __noreturn__ instead of noreturn. 对每个关键字用上双下划线来防止可能出现的同名宏。__inline__ instead of inline. ---------------------------------------------------------------- 3)对几个重要的关键字的解释: (1)always_inline: Generally, functions are not inlined unless optimization is specified. For functions declared inline, this attribute inlines the function even if no optimization level was specified. 编译时候,如果不指定优化选项,函数就不会被内联嵌入;当指定了alway_inline这个关键 字时候,就算编译时候不指定优化选项,函数也会被内联嵌入。 ---------------------------------------------------------------- (2)noinline: This function attribute prevents a function from being considered for inlining . noinline指明函数属性禁止内联。 ---------------------------------------------------------------- (3)noreturn: A few standard library functions, such as abort and exit, cannot return. GCC knows this automatically. Some programs define their own functions that never return. You can declare them noreturn to tell the compiler this fact. For example, void fatal () __attribute__ ((noreturn)); void fatal (/* . . . */) { /* . . . */ /* Print error message. */ /* . . . */ exit (1); } The noreturn keyword tells the compiler to assume that fatal cannot return.It can then optimize without regard to what would happen if fatal ever did return . This makes slightly better code. More importantly, it helps avoid spurious warnings of uninitialized variables. 指明函数无返回值。这样编译器会对函数代码更进一步优化。同时也防止了编译器对无初 始化的变量的警告。 ----------------------------------------------------------------(4)__ attribute__((__used__)): used This attribute, attached to a function, means that code must be emitted for the function even if it appears that the function is not referenced. This is useful,for example, when the function is referenced only in inline assembly . 用于未被引用的函数。这样编译器将忽略其函数的代码。 ---------------------------------------------------------------- (5)__attribute__((pure)): pure Many functions have no effects except the return value and their return value depends only on the parameters and/or global variables. pure属性用于函数的影响只有返回值,而且返回值只依赖于函数参数和/或全局变量。 For example, int square (int) __attribute__ ((pure)); says that the hypothetical function square is safe to call fewer times than the program says. Some of common examples of pure functions are strlen or memcmp. Interesting non-pure functions are functions with infinite loops or those depending on volatile memory or other system resource, that may change between two consecutive calls (such as feof in a multithreading environment). strlen()和memcmp()函数为pure function()。而non-pure函数为那些函数中有infinite循 环或函数依赖于volatile memory,依赖于系统资源的函数。 ---------------------------------------------------------------- (6)__attribute__((__const__)): ---------------------------------------------------------------- (7)__attribute__((warn_unused_result)): The warn_unused_result attribute causes a warning to be emitted if a caller of the function with this attribute does not use its return value. This is useful for functions where not checking the result is either a security problem or always a bug, such as realloc. int fn () __attribute__ ((warn_unused_result)); int foo () { if (fn () < 0) return -1; fn (); return 0; } 如果一个函数调用了有__attribute__((warn_unused_result))属性的函数的话,而又没有 使用其的函数返回值,编译器将报错。 ---------------------------------------------------------------- 4)lucian_yao 01-03-23 23:14 在www.linuxforum.net上发的贴子。 下面举几例内核中常见的: ---------------------------------------------------------------- __attribute__((regparm(0))) int printk(const char * fmt, ...) __attribute__ (( format (printf, 1, 2))); 禁止printk使用寄存器传递调用参数,format用于指定该函数使用的格式与printf,scanf ,strftime相似风格的参数。而使用这类格式的函数,最易犯的错误是格式串与参数不符。 使用format后,编译器将对格式匹配进行检查。 这里表示printk的参数1为格式串,从参数2开始检查其类型是否与格式相匹配,-------- -------------------------------------------------------- void __switch_to(struct task_struct *prev, struct task_struct *next) __ attribute__((regparm(3))) ; __switch_to保留3个寄存器用作传递参数; ---------------------------------------------------------------- void __attribute__ ((__section__ (".text.init"))) mem_init(); 将mem_init编译到.text.init段; ---------------------------------------------------------------- struct tasklet_head tasklet_vec[32] __attribute__((__aligned__((32)),__section __(".data.cacheline_aligned"))) ; 将tasklet_vec[32]编绎到.data.cacheline_aligned段,并将它在32字节边界上对齐; ---------------------------------------------------------------- void do_exit(long error_code)__attribute__((noreturn)); do_exit不会返回; ---------------------------------------------------------------- struct Xgt_desc_struct { unsigned short size; unsigned long address __ attribute__((packed));}; 将address在结构中紧凑排列。 ---------------------------------------------------------------- 我想定义一个全局数组g_Array,使它的起始地址能和页面大小对齐,即和4096对齐(for x86)。 int g_Array[1024] __attribute__ ((aligned(4096))); ---------------------------------------------------------------- **************************************************************** *************************************************************** ---------------------------------------------------------------- atomic_sub() ---------------------------------------------------------------- static __inline__ void atomic_sub(int i, atomic_t *v) { __asm__ __volatile__( LOCK "subl %1,%0" :"=m" (v->counter) :"ir" (i), "m" (v->counter)); } 解释类似atomic_add(int i ,atomic_t *v) 功能:v->counter中原子减去i ---------------------------------------------------------------- atomic_sub_and_test() ---------------------------------------------------------------- static __inline__ int atomic_sub_and_test(int i, atomic_t *v) { unsigned char c; __asm__ __volatile__( LOCK "subl %2,%0; sete %1" :"=m" (v->counter), "=qm" (c) :"ir" (i), "m" (v->counter) : "memory"); return c; } 功能:从v->counter中减去1,若v->counter为0了,则置c为1为返回值;否则c为0为返回值 。 1.sete/setz当ZF=1,即:等于0或相等时,置OPRD为1。 sete/setz OPRD OPRD: 只能是8位寄存器或存储单元,用于存放测试的结果。故这里用unsigned char c;来 作为OPRD。 2.%0:v->counter %1: c %2: i 从输出部开始,依次往后%0, %1, %2... ... 3.q:表示为EAX,EBX,ECX,EDX之一 4.subl %2,%0; =======> subl i,v->counter; sete %1; =======> sete c; 5.memory :强制cpu认为内存单元都被修改过,若某寄存器的内容来自于某内存单元,这时 该内存单元可能已经被修改了,两者可能不一致了。这里的memory为损坏部。 (先是指令部:输出部:输入部:损坏部) 6.LOCK宏中带';',而"sete %1"没有带';'因为在')'外面带了一个';' ---------------------------------------------------------------- atomic_inc() ---------------------------------------------------------------- static __inline__ void atomic_inc(atomic_t *v) { __asm__ __volatile__( LOCK "incl %0" :"=m" (v->counter) :"m" (v->counter)); } 功能:从v->counter中原子的加1 为什么不用v->counter++呢? 答:因为不知道这句C语言汇编后会变成什么样子,会被汇编成几条什么样的指令,多条汇 编指令被编译器优化重组后,CPU乱序执行后,这就没法保证其的原子性。 ---------------------------------------------------------------- atomic_dec() ---------------------------------------------------------------- /** * atomic_dec - decrement atomic variable * @v: pointer of type atomic_t * * Atomically decrements @v by 1. */ static __inline__ void atomic_dec(atomic_t *v) { __asm__ __volatile__( LOCK "decl %0" :"=m" (v->counter) :"m" (v->counter)); } 功能:atomic_dec - decrement atomic variable v->counter--,v->counter减1。 ---------------------------------------------------------------- atomic_dec_and_test() ---------------------------------------------------------------- /** * atomic_dec_and_test - decrement and test * @v: pointer of type atomic_t * * Atomically decrements @v by 1 and * returns true if the result is 0, or false for all other * cases. */ static __inline__ int atomic_dec_and_test(atomic_t *v) { unsigned char c; __asm__ __volatile__( LOCK "decl %0; sete %1" :"=m" (v->counter), "=qm" (c) :"m" (v->counter) : "memory"); return c != 0; } 源代码解释类似上面同类函数。 如果v->counter减一后为0,c=1,return 1 如果v->counter减一后非0,c=0,return 0 ---------------------------------------------------------------- atomic_inc_and_test() ---------------------------------------------------------------- /** * atomic_inc_and_test - increment and test * @v: pointer of type atomic_t * * Atomically increments @v by 1 * and returns true if the result is zero, or false for all * other cases. */ static __inline__ int atomic_inc_and_test(atomic_t *v) { unsigned char c; __asm__ __volatile__( LOCK "incl %0; sete %1" :"=m" (v->counter), "=qm" (c) :"m" (v->counter) : "memory"); return c != 0; } 源代码解释类似上面同类函数。 如果v->counter加一后为0,c=1,return 1 如果v->counter加一后非0,c=0,return 0 ---------------------------------------------------------------- atomic_add_negative() ---------------------------------------------------------------- /** * atomic_add_negative - add and test if negative * @v: pointer of type atomic_t * @i: integer value to add * * Atomically adds @i to @v */ static __inline__ int atomic_add_negative(int i, atomic_t *v) { unsigned char c; __asm__ __volatile__( LOCK "addl %2,%0; sets %1" :"=m" (v->counter), "=qm" (c) :"ir" (i), "m" (v->counter) : "memory"); return c; } sets OPRD; sets: 如果为负(zf=1)则置OPRD为1 返回值:returns true if the result <0 returns false if the result >=0 ---------------------------------------------------------------- atomic_add_return() ---------------------------------------------------------------- 在atomic_add_return()函数的解读中,要遇到unlikely(),xaddl指令,local_irq_ disable(), local_irq_enable(),boot_cpu_data等新东东,所以前面对此先作好各类准备 工作,为后面啃下atomic_add_return()源代码作好准备。 **************************************************************** /include/linux/compiler.h 准备工作--atomic_add_return() ---------------------------------------------------------------- 内核源代码: #if __GNUC__ > 4 #error no compiler-gcc.h file for this gcc version #elif __GNUC__ == 4 # include <linux/compiler-gcc4.h> #elif __GNUC__ == 3 # include <linux/compiler-gcc3.h> #elif __GNUC__ == 2 # include <linux/compiler-gcc2.h> #else # error Sorry, your compiler is too old/not recognized. #endif /* * Generic compiler-dependent macros required for kernel * build go below this comment. Actual compiler/compiler version * specific implementations come from the above header files */ #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) 分析: 1.__GNUC__用来表示gcc编译器的版本,gcc有gcc2,gcc3,gcc4。gcc不同版本对c有着不同 的扩充,而Linux内核又是使用了不同的gnu c的扩充。所以不同的Linux内核当用不同的版 本的gcc来编译。Linux内核与gcc是平行发展的。当Linux中的内核使用了新版本的gcc的c 扩充后,则该版本的Linux内核将只能用此版本或更新版本的gcc来编译。 2.在其它的编译器的头文件(compiler-gcc4.h,compiler-gcc3.h,compiler-gcc2.h)中有 这么一段注释: /* Never include this file directly. Include <linux/compiler.h> instead.*/从上 面的compiler.h文件中的源代码中看出原来编译器要先通过宏:__GNUC__来确定gcc编译器 的版本,再来决定选用包含哪个头文件。 3.likely(),unlikely(): 由于likely(),unlikely()很多,故专门来讲解: **************************************************************** likely(),unlikely()专题 --------------------------------------------------------------- 以下参考引用了:www.linuxforum.net论坛上的众人贴子,人多,且作者对其作了修改调 整,以适合本文,特此向这此论坛上的贴子的作者们致谢。 --------------------------------------------------------------- 1.预取指令: CPU的调度里面不是有预测技术的吗?这个功能叫pre-load,或叫预取指令。 一旦预测出错CPU有自己的机制恢复流水线吧,但是恢复是要花时间的. likely和unlikely不是指令,它只是给编译器看的, 编译器看到后可以合理地安排指令的先 后顺序. ---------------------------------------------------------------- 2.branch prediction的分类: there are 2 kind of "branch prediction": 1,动态预测,cpu内部硬件做掉的.基本思路是,如果对某一条指令你这次跳转了,那么我就猜 下次执行到你的话你还要跳转. 2,静态预测,compiler根据cpu的特性来优化代码.比如下面: test expression(X) if(TRUE) goto A else goto B 实现上指令里A和B可以这样排列: test X beq A B: ...... A: ..... 如果在一般情况下都会执行B,那么这个排列就会比较高效,因为执行到beq A这里B的代码已 经在cache里了.对compiler而言并不知道一般情况下会执行A还是B,所以它很难决定A/B的 次序,但是程序员往往知道,所以用likely()来告诉compiler.有了likely()以后compiler应 该还有其它的方面可以优化,但是反正大概意思就是这样了. ---------------------------------------------------------------- 3.内核源代码分析: #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) 分析: 1)likely(x)==__builtin_expect((x),1)==x 一方面:该__builtin_expect((x),1)即:likely(x),返回x本身的值,不做任何更改。 所以:if( likely(x) ) == if( x ) == if( __builtin_expect( (x),1 ) ---------------------------------------------------------------- 2)另一方面:编译器认为 x == 1 很可能发生,是最可能的情况。当按照这种情况来优化 代码。 ---------------------------------------------------------------- 3)gcc手册: `__builtin_expect(EXP, C)' (1)You may use `__builtin_expect' to provide the compiler with branch prediction information. 指明__builtin_expect()用于编译器的分支预测。 ---------------------------------------------------------------- (2)The return value is the value of EXP, which should be an integral expression. The value of C must be a compile-time constant. The semantics of the builtin are that it is expected that EXP == C. __builtin_expect(EXP,C)的函数返回值就等于EXP,而这个函数的意思就是期望EXP==C.请 注意:C必须是在编译时候就可以确定的常数;而EXP当为整数表达式。 if(__builtin_expect(EXP,C))等价于if(EXP)。但EXP==C很可能成立。所以,一方面__ builtin_expect(EXP,C)原封不动的返回EXP的值,另一方面,认为EXP==C很可能成立,从 而编译器按照EXP==C成立的这种情况来优化代码。 ---------------------------------------------------------------- (3)For example: if (__builtin_expect (x, 0)) foo (); would indicate that we do not expect to call `foo', since we expect `x' to be zero. Since you are limited to integral expressions for EXP, you should use constructions such as 分析: if(__builtin_expect (x, 0)) == if( x ) 但编译器认为x==0 十有八九要发生,按这种情况来优化代码。这样的话,foo()函数就十 有八九不会被调用。 此处的x取值受限,只能为整数。(long)下面一个例子将x改为一个表达式ptr != NULL,则 就大大扩展了其的应用范围。 if (__builtin_expect (ptr != NULL, 1)) error (); when testing pointer or floating-point values. 分析: if (__builtin_expect (ptr != NULL, 1)) == if( ptr != NULL ) 但编译器认为( ptr != NULL ) == 1 很可能经常发生,按照这种情况来优化代码。 ---------------------------------------------------------------- 4.反汇编验证: 一段代码的时间局部性和空间局部性对性能的影响很大,因为这影响CPU的pre-load和 caching,likely实际就是间接的完成这个任务,如果还不是很清楚,那可以写一小段代码 ,编译,看汇编代码在使用__builtin_expect()和不使用时的区别,一看便知。 1)举个例子,在sch_htb.c中有个地方用到了likely,先把它反汇编一下,看看怎么实现的, 因为正常情况下cpu会把下面的指令预读进来,提高效率, 下面这段代码中,我们希望的主要 流程是skb不为空,这是较多的情况,所以源码用了likely,这样我们可以看到,当执行if( skb!=NULL)时,cpu会顺序把下面的指令 1dad读进来,这样确实提高了效率 if (likely (skb != NULL)) 1da9: 85 db test %ebx,%ebx 1dab: 74 41 顺序下一条指令 ================================================================ 2)这样已经很明显了,gcc通过优化代码使likely的代码直接放在判断后面,使效率提高,而 unlikely 的放在其它地方,以使cpu正常预测,只是简单地帮助了一下cpu. if (unlikely (skb != NULL)) 1da9: 85 db test %ebx,%ebx 1dab: 75 0a jne 1db7 <htb_dequeue_tree+0x97> 分析: 1.if( unlikely (skb != NULL)) == if( skb != NULL ) == if(__builtin_expect(!!( skb != NULL), 0)) == if(__builtin_expect((skb != NULL), 0)) 2.编译器认为(skb != NULL) == 0,即if条件不满足的情况是十有八九要发生的。就按照 这种情况来优化代码。也就是skb == NULL的情况。既然十有八九要发生的是skb == NULL 的情况,是if条件不满足的情况,则编译器就没有将if语句的下一条语句预读入预取指令 cache中,而是认为常发生的应当是if条件不满足后,当跳转到if条件不满足后当跳到的代 码段。 3.jne 1db7 <htb_dequeue_tree+0x97>:说明编译器没有预取if语句的下一条语句,即:编 译器认为((skb != NULL) == 0)是经常发生的,skb == NULL是最可能的。故就预取了当 跳转的指令到指令cache中去。 --------------------------------------------------------------- likely(),unlikely()专题--结束 **************************************************************** **************************************************************** /include/asm-i386/processor.h boot_cpu_data准备工作 --------------------------------------------------------------- boot_cpu_data准备工作 --------------------------------------------------------------- L1_CACHE_BYTES --------------------------------------------------------------- /include/asm-i386/cache.h: /* L1 cache line size */ #define L1_CACHE_SHIFT (CONFIG_X86_L1_CACHE_SHIFT) #define L1_CACHE_BYTES (1 << L1_CACHE_SHIFT) #define L1_CACHE_SHIFT_MAX 7 /* largest L1 which this arch supports */ -------------------------------------------------------------- SMP_CACHE_BYTES -------------------------------------------------------------- /include/linux/cache.h #ifndef SMP_CACHE_BYTES #define SMP_CACHE_BYTES L1_CACHE_BYTES #endif -------------------------------------------------------------- /include/asm-i386/processor.h /* *CPU type and hardware bug flags. Kept separately for each CPU. *Members of this structure are referenced in head.S, so think *twice before touching them. [mj] */ -------------------------------------------------------------- typedef unsigned char __u8; -------------------------------------------------------------- cpuinfo_x86{} -------------------------------------------------------------- struct cpuinfo_x86 { __u8 x86; /* CPU family */ __u8 x86_vendor; /* CPU vendor */ __u8 x86_model; __u8 x86_mask; char wp_works_ok; /* It doesn't on 386's */ char hlt_works_ok; /* Problems on some 486Dx4's and old 386's */ char hard_math; char rfu; int cpuid_level; /* Maximum supported CPUID level, -1=no CPUID */ unsigned long x86_capability[NCAPINTS]; char x86_vendor_id[16]; char x86_model_id[64]; int x86_cache_size; /* in KB - valid for CPUS which support this call */ int x86_cache_alignment; /* In bytes */ int fdiv_bug; int f00f_bug; int coma_bug; unsigned long loops_per_jiffy; unsigned char x86_num_cores; } __attribute__((__aligned__(SMP_CACHE_BYTES))); 分析: 1.#define NCAPINTS 7 /* N 32-bit words worth of info */ 2.解释内核注释中的486Dx: 80486是80X86家族中继80386之后的又一种32位微处理器。80486分为80486DX和80486SX两 款。80486DX是在80386的基础上集成了浮点处理部件和超高速缓存。而80486SX则不包含浮 点处理部件。 3.CPU vendor 宏: #define X86_VENDOR_INTEL 0 #define X86_VENDOR_CYRIX 1 #define X86_VENDOR_AMD 2 #define X86_VENDOR_UMC 3 #define X86_VENDOR_NEXGEN 4 #define X86_VENDOR_CENTAUR 5 #define X86_VENDOR_RISE 6 #define X86_VENDOR_TRANSMETA 7 #define X86_VENDOR_NSC 8 #define X86_VENDOR_NUM 9 #define X86_VENDOR_UNKNOWN 0xff --------------------调查以上Linux内核所支持的CPU厂家------------- 1)知名CPU厂家:INTEL (Pentium),AMD都支持。 2)台湾联华(UMC)---来自于 MSN-狼窝岭 大陆八位机的CPU基本是台湾联华(UMC)提供的。据联华方面私下提供的消息,共卖给大 陆约3000万个CPU,除去少量出口、库存,大陆游戏机拥有量在2500万台以上应无问题。以 每台游戏机平均利润120元,2500万台为30亿人民币;以每台游戏机拥有两盘卡,大陆共出 售5000万盘为50亿人民币。两项合计,利润总额应不低于80亿人民币。 3)在1995年,一个名叫NexGen的公司(后被AMD收购)认识到X86指令集是提高CPU性能的 一个很大的障碍,因为X86指令的长度和格式都各不相同,会造成解码困难。于是他们开发 出了NX586(AMD在NX586的基础上开发出了K6核心),NX586在相同时钟频率下的运行速度比 Pentium快,秘密就在于它把那些长度不定的X86指令集翻译成了短小且长度固定的类RISC 指令。 4)Centaur:威盛的子公司。C5X可说是Centaur加深了对处理器体系认知的重要证据。C5 X将是Centaur首枚具备乱序执行(Out-of-Order,简称OoO)风格的CPU,也是Centaur首枚 真正超标量(superscalar)内核的处理器。 5)Cyrix本是家CPU设计公司,早在486时代就同IDT/Rise公司一起,以生产低价CPU而著称 ,在INTEL降价营销策略的打击下,IDT和Rise一个个被INTEL逼走,Cyrix也在97年被美国 国家半导体公司收购,99年6月又被威盛公司兼并,同时被收购的还有IDT公司的半人马个 人电脑CPU设计部门,威盛准备用这两套人马杀进CPU市场,开发代号为Joshua(约书亚) 的Cyrix III。 6)IDT的CPU名叫WINCHIP,可惜在芯片(CHIP)领域,IDT难以取胜(WIN) 7)Transmeta公司是制造低能耗Crusoe笔记本电脑及服务器专用CPU的厂家,它已把它的两 项技术授权给AMD生产64位“Hammer”芯片。 --------------------------------------------------------------- 4.cpuinfo_x86{}要按SMP_CACHE_BYTES字节来对齐,以便能一次性读入高速cache中。 --------------------------------------------------------------- 全局变量: boot_cpu_data new_cpu_data --------------------------------------------------------------- extern struct cpuinfo_x86 boot_cpu_data; extern struct cpuinfo_x86 new_cpu_data; --------------------------------------------------------------- 小段代码分析: if(unlikely(boot_cpu_data.x86==3)) 1.( boot_cpu_data.x86 == 3 )可用来判断CPU FAMILY是否为386CPU 2. if(unlikely(boot_cpu_data.x86==3)) if(unlikely(boot_cpu_data.x86 == 3)) <==> if(boot_cpu_data.x86 == 3) 而编译器认为十有八九boot_cpu_data.x86 != 3,即:CPU非386CPU,而是现代的486+ 以 上的CPU。该if()条件常常不成立。gcc编译器将按照boot_cpu_data.x86 != 3的情况来优 化源代码。 ---------------------------------------------------------------- boot_cpu_data准备工作--结束 **************************************************************** xadd 80486指令准备工作 --------------------------------------------------------------- 参考:《80X86汇编语言程序设计教程》杨季文编著,清华大学出版社 P528: xadd 交换加指令,为80486新增加的指令 xadd OPRD1,OPRD2 :交换OPRD1,OPRD2的内容,并将(OPRD1+OPRD2)相加的结果存入OPRD1 中。 OPRD1可为8位,16位,32位的寄存器或存储单元。 OPRD2可为8位,16位,32位的通用寄存器。 例如: xadd al,ah ; 设:开始时:ax = 1122H,即: al = 22H,ah = 11H 执行时: 1)先交换ah和al,ah = 22H , al = 11H 2)再把(al+ah)即:(22+11)H == 33H 存入al中:33H 执行后结果为:ax = 2233H 再如一个例子: __asm__ __volatile__( LOCK "xaddl %0, %1;" :"=r"(i) :"m"(v->counter), "r"(i)); 分析: xaddl %0,%1; <==> xaddl i,v->counter; 1)%0,%1交换: <==> i,v->counter交换 交换后:i == v->counter v->counter == i 2)%1 =(%0 + %1) <==> v->counter = (i + v->counter) 原因:这里是AT&T格式的汇编指令,与上一个INTEL汇编指令的源操作数,目的操作数的顺 序恰好相反。 3)执行完后的结果: i == v->counter的初始值 v->counter == i + v->counter开始的自己的值 4)i的初始值从寄存器中输入,而v->counter从存储器中输入。 注意:这里是"r"(i)与下面内核中的源代码中的"0"(i)不同。这里是更一般的情况。 ---------------------------------------------------------------- 准备工作完毕--atomic_add_return() **************************************************************** 准备工作---local_irq_disable(),local_irq_enable() --------------------------------------------------------------- #define local_irq_disable() __asm__ __volatile__("cli": : :"memory") #define local_irq_enable() __asm__ __volatile__("sti": : :"memory") 分析: local_irq_disable():用于关中断 local_irq_enable(): 用于开中断 --------------------------------------------------------------- 准备工作结束---local_irq_disable(),local_irq_enable() **************************************************************** atomic_add_return() --------------------------------------------------------------- /** * atomic_add_return - add and return * @v: pointer of type atomic_t * @i: integer value to add * Atomically adds @i to @v and returns @i + @v */ static __inline__ int atomic_add_return(int i, atomic_t *v) { int __i; #ifdef CONFIG_M386 if(unlikely(boot_cpu_data.x86==3)) goto no_xadd; #endif /* Modern 486+ processor */ __i = i; __asm__ __volatile__( LOCK "xaddl %0, %1;" :"=r"(i) :"m"(v->counter), "0"(i)); return i + __i; #ifdef CONFIG_M386 no_xadd: /* Legacy 386 processor */ local_irq_disable(); __i = atomic_read(v); atomic_set(v, i + __i); local_irq_enable(); return i + __i; #endif } 代码分析: 1.__i = i; __asm__ __volatile__( LOCK "xaddl %0, %1;" :"=r"(i) :"m"(v->counter), "0"(i)); return i + __i; __i为临时变量,保存i的初始值。 然后"0"(i),将i设置为%0,%1为v->counter的初始值。 通过xaddl i,v->counter指令,i,v->counter相互交换各自的值。v->counter = i的初始 值,而i = v->counter的初始值; 然后v->counter 又被( i + v->counter)所覆盖。 最后: return i + __i为(v->counter的初始值 + i的初始值) v->counter为 (v->counter的初始值 + i的初始值) 2.no_xadd:表示该计算机上为386处理器,而386处理器上无xadd交换加指令。 3.有了上述的准备工作,这段代码很易理解。不再详述了。 ---------------------------------------------------------------- ============================================================== 茶余饭后一点小资料 ============================================================== Linux源码分析方法谈 著者 华中科技大学 喻锋荣 ( hustyucc@163.net ) Linux的最大的好处之一就是它的源码公开。同时,公开的核心源码也吸引着无 数的电脑爱好者和程序员;他们把解读和分析Linux的核心源码作为自己的最大兴趣,把修改 Linux源码和改造Linux系统作为自己对计算机技术追求的最大目标。Linux内核源码是很具 吸引力的,特别是当你弄懂了一个分析了好久都没搞懂的问题;或者是被你修改过了的内核 ,顺利通过编译,一切运行正常的时候。那种成就感真是油然而生!而且,对内核的分析,除了 出自对技术的狂热追求之外,这种令人生畏的劳动所带来的回报也是非常令人着迷的,这也 正是它拥有众多追随者的主要原因: 首先,你可以从中学到很多的计算机的底层知识,如后面将讲到的系统的引导和硬件提供的 中断机制等;其它,象虚拟存储的实现机制,多任务机制,系统保护机制等等,这些都是非都源 码不能体会的。同时,你还将从操作系统的整体结构中,体会整体设计在软件设计中的份量 和作用,以及一些宏观设计的方法和技巧:Linux的内核为上层应用提供一个与具体硬件不相 关的平台;同时在内核内部,它又把代码分为与体系结构和硬件相关的部分,和可移植的部分 ;再例如,Linux虽然不是微内核的,但他把大部分的设备驱动处理成相对独立的内核模块,这 样减小了内核运行的开销,增强了内核代码的模块独立性 。。。 而且你还能从对内核源码 的分析中,体会到它在解决某个具体细节问题时,方法的巧妙:如后面将分析到了的Linux通 过Botoom_half机制来加快系统对中断的处理。 最重要的是:在源码的分析过程中,你将会被一点一点地,潜移默化地专业化。一个专业的程 序员,总是把代码的清晰性,兼容性,可移植性放在很重要的位置。他们总是通过定义大量的 宏,来增强代码的清晰度和可读性,而又不增加编译后的代码长度和代码的运行效率;他们总 是在编码的同时,就考虑到了以后的代码维护和升级。。。 甚至,只要分析百分之一的代码 后,你就会深刻地体会到,什么样的代码才是一个专业的程序员写的,什么样的代码是一个业 余爱好者写的。而这一点是任何没有真正分析过标准代码的人都无法体会到的。 然而,由于内核代码的冗长,和内核体系结构的庞杂,所以分析内核也是一个很艰难,很需要 毅力的事;在缺乏指导和交流的情况下,尤其如此。只有方法正确,才能事半功倍。 ============================================================== ---------------------------------------------------------------- atomic_sub_return() ---------------------------------------------------------------- static __inline__ int atomic_sub_return(int i, atomic_t *v) { return atomic_add_return(-i,v); } 分析:设计思想就是充分利用已有加法函数atomic_add_return(),将参数i改成-i而矣。代 码重用。 ---------------------------------------------------------------- atomic_inc_return() atomic_dec_return() ---------------------------------------------------------------- #define atomic_inc_return(v) (atomic_add_return(1,v)) #define atomic_dec_return(v) (atomic_sub_return(1,v)) 设计思想:自增一,自减一的运算,充分重用加法,减法函数,只不过将所减的数改为1而 矣。这就是减法函数的设计依赖于加法函数,自增一,自减一的运算函数依赖于加法函数 ,减法函数。真正认真设计的函数就只有加法函数。 ---------------------------------------------------------------- atomic_clear_mask() ---------------------------------------------------------------- /* These are x86-specific, used by some header files */ #define atomic_clear_mask(mask, addr) / __asm__ __volatile__(LOCK "andl %0,%1" / : : "r" (~(mask)),"m" (*addr) : "memory") 分析: 1.取出当时在atomic_set_mask()中所设置的掩码mask,将其取反,就将当时其位为1的位 置为了0,而当时为0的位为1了。 2.再通过与运算就将原来在atomic_set_mask()中由mask所人为设置的1位通通变成0了,而 无关位不变。 思路巧妙!!! 3.用同一个mask,atomic_set_mask()使用该mask将其置位,然后用同一个mask在atomic_ clear_mask()将其位清除。 ---------------------------------------------------------------- atomic_set_mask() ---------------------------------------------------------------- #define atomic_set_mask(mask, addr) / __asm__ __volatile__(LOCK "orl %0,%1" / : : "r" (mask),"m" (*(addr)) : "memory") 分析: 1.%0:mask %1:(*(addr)) 2.将mask作为掩码,将要置的位置为1,无关的位置为0。这样再通过或运算与目标操作数 相或,就将目标操作数的相应的要置的位置了1,无关的位依然不动。 ---------------------------------------------------------------- smp_mb__before_atomic_dec() smp_mb__after_atomic_dec() smp_mb__before_atomic_inc() smp_mb__after_atomic_inc() ---------------------------------------------------------------- #define barrier() __asm__ __volatile__("": : :"memory") /* Atomic operations are already serializing on x86 */ #define smp_mb__before_atomic_dec() barrier() #define smp_mb__after_atomic_dec() barrier() #define smp_mb__before_atomic_inc() barrier() #define smp_mb__after_atomic_inc() barrier() 提醒: 上面的宏定义注意到没有,都是左对齐的,尽管函数名长短不一,但barrier()函数都是左 对齐的。美观! ---------------------------------------------------------------- **************************************************************** 32位平台的原子操作(算术运算)的头文件--至此全部完成 **************************************************************** **************************************************************** 64位平台的原子操作(算术运算)的头文件 -----------------/include/asm-x86_64/atomic.h------------------- ---------------------------------------------------------------- __ARCH_X86_64_ATOMIC__ CONFIG_SMP LOCK ---------------------------------------------------------------- #ifndef __ARCH_X86_64_ATOMIC__ #define __ARCH_X86_64_ATOMIC__ #include <linux/config.h> #ifdef CONFIG_SMP #define LOCK "lock ; " #else #define LOCK "" #endif 其代码与32位平台的源代码一样,没什么区别。请参考前面的32位平台的解释。 ---------------------------------------------------------------- atomic_t{} ---------------------------------------------------------------- typedef struct { volatile int counter; } atomic_t; ---------------------------------------------------------------- ATOMIC_INIT() ---------------------------------------------------------------- #define ATOMIC_INIT(i) { (i) } ---------------------------------------------------------------- atomic_read() ---------------------------------------------------------------- /** * atomic_read - read atomic variable * @v: pointer of type atomic_t * Atomically reads the value of @v. */ #define atomic_read(v) ((v)->counter) ---------------------------------------------------------------- atomic_set() ---------------------------------------------------------------- /** * atomic_set - set atomic variable * @v: pointer of type atomic_t * @i: required value * Atomically sets the value of @v to @i. */ #define atomic_set(v,i) (((v)->counter) = (i)) ---------------------------------------------------------------- atomic_add() ---------------------------------------------------------------- /** * atomic_add - add integer to atomic variable * @i: integer value to add * @v: pointer of type atomic_t * Atomically adds @i to @v. */ static __inline__ void atomic_add(int i, atomic_t *v) { __asm__ __volatile__( LOCK "addl %1,%0" :"=m" (v->counter) :"ir" (i), "m" (v->counter)); } ---------------------------------------------------------------- atomic_sub() ---------------------------------------------------------------- /** * atomic_sub - subtract the atomic variable * @i: integer value to subtract * @v: pointer of type atomic_t * Atomically subtracts @i from @v. */ static __inline__ void atomic_sub(int i, atomic_t *v) { __asm__ __volatile__( LOCK "subl %1,%0" :"=m" (v->counter) :"ir" (i), "m" (v->counter)); } ---------------------------------------------------------------- atomic_sub_and_test() ---------------------------------------------------------------- /** * atomic_sub_and_test - subtract value from variable and test result * @i: integer value to subtract * @v: pointer of type atomic_t * Atomically subtracts @i from @v and returns * true if the result is zero, or false for all * other cases. */ static __inline__ int atomic_sub_and_test(int i, atomic_t *v) { unsigned char c; __asm__ __volatile__( LOCK "subl %2,%0; sete %1" :"=m" (v->counter), "=qm" (c) :"ir" (i), "m" (v->counter) : "memory"); return c; } ---------------------------------------------------------------- atomic_inc() ---------------------------------------------------------------- /** * atomic_inc - increment atomic variable * @v: pointer of type atomic_t * Atomically increments @v by 1. */ static __inline__ void atomic_inc(atomic_t *v) { __asm__ __volatile__( LOCK "incl %0" :"=m" (v->counter) :"m" (v->counter)); } ---------------------------------------------------------------- atomic_dec() ---------------------------------------------------------------- /** * atomic_dec - decrement atomic variable * @v: pointer of type atomic_t * Atomically decrements @v by 1. */ static __inline__ void atomic_dec(atomic_t *v) { __asm__ __volatile__( LOCK "decl %0" :"=m" (v->counter) :"m" (v->counter)); } ---------------------------------------------------------------- atomic_dec_and_test() ---------------------------------------------------------------- /* * atomic_dec_and_test - decrement and test * @v: pointer of type atomic_t * Atomically decrements @v by 1 and * returns true if the result is 0, or false for all other * cases. */ static __inline__ int atomic_dec_and_test(atomic_t *v) { unsigned char c; __asm__ __volatile__( LOCK "decl %0; sete %1" :"=m" (v->counter), "=qm" (c) :"m" (v->counter) : "memory"); return c != 0; } 若v->counter为0,则 c = 1 ,于是: c != 0 成立为1。return 1 若v->counter非0,则 c = 0 ,于是:c != 0 不成立,为0,return 0 ---------------------------------------------------------------- atomic_inc_and_test() ---------------------------------------------------------------- /** * atomic_inc_and_test - increment and test * @v: pointer of type atomic_t * Atomically increments @v by 1 * and returns true if the result is zero, or false for all * other cases. */ static __inline__ int atomic_inc_and_test(atomic_t *v) { unsigned char c; __asm__ __volatile__( LOCK "incl %0; sete %1" :"=m" (v->counter), "=qm" (c) :"m" (v->counter) : "memory"); return c != 0; } 若v->counter为0,则 c = 1 ,于是: c != 0 成立为1。return 1 若v->counter非0,则 c = 0 ,于是:c != 0 不成立,为0,return 0 ---------------------------------------------------------------- atomic_add_negative() ---------------------------------------------------------------- /** * atomic_add_negative - add and test if negative * @v: pointer of type atomic_t * @i: integer value to add * Atomically adds @i to @v and returns true * if the result is negative, or false when * result is greater than or equal to zero. */ static __inline__ int atomic_add_negative(int i, atomic_t *v) { unsigned char c; __asm__ __volatile__( LOCK "addl %2,%0; sets %1" :"=m" (v->counter), "=qm" (c) :"ir" (i), "m" (v->counter) : "memory"); return c; } addl %2,%0 <=====> addl i,v->counter sets c <=====> sets c 结果: v->counter = v->counter + i 如果(v->counter + i)后为负,则c = 1 如果(v->counter + i)后非负, 则c = 0 ---------------------------------------------------------------- atomic64_t{} ---------------------------------------------------------------- /* An 64bit atomic type */ typedef struct { volatile long counter; } atomic64_t; 注意: 前面的atomic_t{}中为volatile int 是在32位平台。此处的64位平台为long 在现有的i386平台上,int 是4B,而long为8B,只有long long才是8B 自己认为: typedef struct {volatile long long counter; } atomic64_t;才在32位操作系统平台上 为8B。 ---------------------------------------------------------------- ATOMIC64_INIT() ---------------------------------------------------------------- #define ATOMIC64_INIT(i) { (i) } ---------------------------------------------------------------- atomic64_read() ---------------------------------------------------------------- /** * atomic64_read - read atomic64 variable * @v: pointer of type atomic64_t * Atomically reads the value of @v. * Doesn't imply a read memory barrier. */ #define atomic64_read(v) ((v)->counter)
更多推荐
已为社区贡献3条内容
所有评论(0)