zoukankan      html  css  js  c++  java
  • [zz]GNU C 扩展之__attribute__ 机制简介 [2]

    __attribute__ 可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。
    __attribute__ 后面紧跟一对括号,里面是相应的__attribute__参数。
    __attribute__ 语法格式为:__attribute__ ((attribute-list))
                             位置为:放于函数等声明尾部的 ; 之前。

    函数属性(Function Attribute)
    函数属性帮助开发者把一些特性添加到函数声明中,可以使编译器在错误检查方面的功能更强大。
    __attribute__机制也很容易同非GNU应用程序做到兼容。
    GNU CC需要使用–Wall编译器来击活该功能。
    下面介绍几个常见的属性参数:
    regparm 
    告诉编译器使用几个通用寄存器传递函数参数。

    1. #define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
    2. #define fastcall __attribute__((regparm(3)))
    3. #define internal_function __attribute__ ((regparm (3), stdcall))

    regparm(0)表示不用寄存器传参 用栈传参。

    最后一行表示从寄存器传递3个参数,而stdcall表明由被调用函数来清栈,一般的函数是由调用者来负责清栈,用的是cdecl。
    format
    该属性给被声明的函数加上类似printf或者scanf的特征,它使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。该功能十分有用,尤其是处理一些很难发现的bug。
    format的语法格式为:
      format (archetype, string-index, first-to-check)
    format属性告诉编译器,按照printf, scanf, strftime或strfmon的参数表格式规则对该函数的参数进行检查。
    archetype指定是哪种风格;string-index指定传入函数的第几个参数是格式化字符串;first-to-check指定从函数的第几个参数开始按上述规则进行检查。
    具体使用格式如下:

    __attribute__((format(printf,m,n)))
    __attribute__((format(scanf,m,n)))

    其中参数m与n的含义为:
    m:第几个参数为格式化字符串(format string);
    n:参数集合中的第一个,即参数“…”里的第一个参数在函数参数总数排在第几,注意,有时函数参数里还有“隐身”的呢,后面会提到;
    在使用上,__attribute__((format(printf,m,n)))是常用的,而另一种却很少见到。
    下面举例说明,其中myprint为自己定义的一个带有可变参数的函数,其功能类似于printf:

    1. //m=1;n=2
    2. extern void myprint(const char *format,...) __attribute__((format(printf,1,2)));
    3. //m=2;n=3
    4. extern void myprint(int l,const char *format,...) __attribute__((format(printf,2,3)));

    需要特别注意的是,如果myprint是一个函数的成员函数,那么m和n的值可有点“悬乎”了,其原因是,类成员函数的第一个参数实际上一个“隐身”的“this”指针。
    这里给出测试用例:attribute.c,代码如下:

    1. extern void myprint(const char *format,...) __attribute__((format(printf,1,2)));
    2. void test()
    3. {
    4.     myprint("i=%d/n",6);
    5.     myprint("i=%s/n",6);
    6.     myprint("i=%s/n","abc");
    7.     myprint("%s,%d,%d/n",1,2);
    8. }

    运行$gcc –Wall –c attribute.c attribute后,输出结果为:
     
    attribute.c: In function `test':
    attribute.c:7: warning: format argument is not a pointer (arg 2)
    attribute.c:9: warning: format argument is not a pointer (arg 2)
    attribute.c:9: warning: too few arguments for format
     
    如果在attribute.c中的函数声明去掉__attribute__((format(printf,1,2))),再重新编译后,不会输出任何警告信息。
    注意,默认情况下,编译器是能识别类似printf的“标准”库函数。

    noreturn
    该属性通知编译器函数从不返回值,当遇到函数需要返回值却不可能运行到返回值处就已经退出来的情况,该属性可以避免出现错误信息。C库函数中的abort()和exit()的声明格式就采用了这种格式,如下所示:

    1. extern void exit(int) __attribute__((noreturn));
    2. extern void abort(void) __attribute__((noreturn));

     为方便理解,大家可以参考如下的例子:

    1. //name: noreturn.c 
    2. //测试__attribute__((noreturn))
    3. extern void myexit();
    4. int test(int n)
    5. {
    6.     if ( n > 0 ) {
    7.         myexit();
    8.               /* 程序不可能到达这里*/
    9.     } else
    10.         return 0;
    11. }

     编译显示的输出信息为:
     
    $gcc –Wall –c noreturn.c
    noreturn.c: In function `test':
    noreturn.c:12: warning: control reaches end of non-void function
     
    警告信息也很好理解,因为你定义了一个有返回值的函数test却有可能没有返回值,程序当然不知道怎么办了!
    加上__attribute__((noreturn))则可以很好的处理类似这种问题。
    extern void myexit();修改为:
    extern void myexit() __attribute__((noreturn));
    之后,编译不会再出现警告信息。

    const
    该属性只能用于带有数值类型参数的函数上。当重复调用带有数值参数的函数时,由于返回值是相同的,所以此时编译器可以进行优化处理,除第一次需要运算外,其它只需要返回第一次的结果就可以了,进而可以提高效率。
    该属性主要适用于没有静态状态static state和副作用的一些函数,并且返回值仅仅依赖输入的参数。
    为了说明问题,下面举个非常“糟糕”的例子,该例子将重复调用一个带有相同参数值的函数,具体如下:

    1. extern int square(int n) __attribute__((const));
    2. ... 
    3. for (i = 0; i < 100; i++) {
    4.     total += square(5) + i;  
    5. }

     通过添加__attribute__((const))声明,编译器只调用了函数一次,以后只是直接得到了相同的一个返回值。
    事实上,const参数不能用在带有指针类型参数的函数中,因为该属性不但影响函数的参数值,同样也影响到了参数指向的数据,它可能会对代码本身产生严重甚至是不可恢复的严重后果。
    并且,带有该属性的函数不能有任何副作用或者是静态的状态,所以,类似getchar()或time()的函数是不适合使用该属性的。

    -finstrument-functions
    该参数可以使程序在编译时,在函数的入口和出口处生成instrumentation调用。恰好在函数入口之后并恰好在函数出口之前,将使用当前函数的地址和调用地址来调用下面的 profiling 函数。
    (在一些平台上,__builtin_return_address不能在超过当前函数范围之外正常工作,所以调用地址信息可能对profiling函数是无效的。)

    1. void __cyg_profile_func_enter(void *this_fn, void *call_site);
    2. void __cyg_profile_func_exit(void *this_fn, void *call_site);

    其中,第一个参数this_fn是当前函数的起始地址,可在符号表中找到;第二个参数call_site是指调用处地址。
    instrumentation 也可用于在其它函数中展开的内联函数。从概念上来说,profiling调用将指出在哪里进入和退出内联函数。这就意味着这种函数必须具有可寻址形式。如果函数包含内联,而所有使用到该函数的程序都要把该内联展开,这会额外地增加代码长度。如果要在C 
    代码中使用extern inline声明,必须提供这种函数的可寻址形式。
    可对函数指定no_instrument_function属性,在这种情况下不会进行instrumentation操作。
    例如,可以在以下情况下使用no_instrument_function属性:上面列出的profiling函数、高优先级的中断例程以及任何不能保证profiling正常调用的函数。
    no_instrument_function
    如果使用了-finstrument-functions,将在绝大多数用户编译的函数的入口和出口点调用profiling函数。使用该属性,将不进行instrument操作。
    constructor/destructor
    若函数被设定为constructor属性,则该函数会在main()函数执行之前被自动的执行。类似的,若函数被设定为destructor属性,则该函数会在main()函数执行之后或者exit()被调用后被自动的执行。拥有此类属性的函数经常隐式的用在程序的初始化数据方面。
    这两个属性还没有在面向对象C中实现。

    同时使用多个属性
    可以在同一个函数声明里使用多个__attribute__,并且实际应用中这种情况是十分常见的。
    使用方式上,你可以选择两个单独的__attribute__,或者把它们写在一起,可以参考下面的例子:

    1. /* 把类似printf的消息传递给stderr 并退出 */
    2. extern void die(const char *format, ...) __attribute__((noreturn))  __attribute__((format(printf, 1, 2))); 

    或者写成

    1. extern void die(const char *format, ...)   __attribute__((noreturn, format(printf, 1, 2)));

    如果带有该属性的自定义函数追加到库的头文件里,那么所以调用该函数的程序都要做相应的检查。
     
    和非GNU编译器的兼容性
    庆幸的是,__attribute__设计的非常巧妙,很容易作到和其它编译器保持兼容,也就是说,如果工作在其它的非GNU编译器上,可以很容易的忽略该属性。即使__attribute__使用了多个参数,也可以很容易的使用一对圆括弧进行处理,例如:

    1. /* 如果使用的是非GNU C, 那么就忽略__attribute__ */
    2. #ifndef __GNUC__
    3. #define  __attribute__(x)  /*NOTHING*/
    4. #endif

    需要说明的是,__attribute__ 适用于函数的声明而不是函数的定义。所以,当需要使用该属性的函数时,必须在同一个文件里进行声明,例如:

    1. /* 函数声明 */
    2. void die(const char *format, ...) __attribute__((noreturn)) __attribute__((format(printf,1,2))); 
    3. void die(const char *format, ...){
    4.     /* 函数定义 */

    变量属性(Variable Attributes)
    关键字__attribute__也可以对变量(variable)或结构体成员(structure field)进行属性设置。
    这里给出几个常用的参数的解释,更多的参数可参考本文给出的连接。
    在使用__attribute__参数时,你也可以在参数的前后都加上“__”(两个下划线),例如,使用__aligned__而不是aligned,这样,你就可以在相应的头文件里使用它而不用关心头文件里是否有重名的宏定义。
    aligned (alignment)
    该属性规定变量或结构体成员的最小的对齐格式,以字节为单位。例如: 
    int x __attribute__ ((aligned (16))) = 0; 
    编译器将以16字节对齐的方式分配变量。
    也可以对结构体成员变量设置该属性,例如,创建一个双字对齐的int对,可以这么写:
    struct foo { int x[2] __attribute__ ((aligned (8))); }; 
    如上所述,你可以手动指定对齐的格式,同样,你也可以使用默认的对齐方式。
    如果aligned后面不紧跟一个指定的数字值,那么编译器将依据你的目标机器情况使用最大最有益的对齐方式。例如: 
    short array[3] __attribute__ ((aligned)); 
    选择针对目标机器最大的对齐方式,可以提高拷贝操作的效率。
    aligned属性使被设置的对象占用更多的空间,相反的,使用packed可以减小对象占用的空间。
    需要注意的是,attribute属性的效力与你的连接器也有关,如果你的连接器最大只支持16字节对齐,那么你此时定义32字节对齐也是无济于事的。


    packed
    使用该属性可以使得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐。
    下面的例子中,x成员变量使用了该属性,则其值将紧放置在a的后面:

    1. struct test{
    2.     char a;
    3.     int x[2] __attribute__ ((packed));          
    4. };

    其它可选的属性值还可以是:cleanup,common,nocommon,deprecated,mode,section,shared,tls_model,transparent_union,unused,vector_size,weak,dllimport,dlexport 等。

    类型属性(Type Attribute)
    关键字__attribute__也可以对结构体(struct)或共用体(union)进行属性设置。大致有六个参数值可以被设定,
    即:aligned, packed, transparent_union, unused, deprecated 和 may_alias。
    在使用__attribute__参数时,你也可以在参数的前后都加上“__”(两个下划线),例如,使用__aligned__而不是aligned,这样,你就可以在相应的头文件里使用它而不用关心头文件里是否有重名的宏定义。
    aligned (alignment)
    该属性设定一个指定大小的对齐格式(以字节为单位),例如:

    1. struct S { short f[3]; } __attribute__ ((aligned (8)));
    2. typedef int more_aligned_int __attribute__ ((aligned (8)));

     该声明将强制编译器确保(尽它所能)变量类型为struct S或者more-aligned-int的变量在分配空间时采用8字节对齐方式。
    如上所述,你可以手动指定对齐的格式,同样,你也可以使用默认的对齐方式。如果aligned后面不紧跟一个指定的数字值,那么编译器将依据你的目标机器情况使用最大最有益的对齐方式。例如:

    1. struct S { short f[3]; } __attribute__ ((aligned));

     这里,如果sizeof(short)的大小为2(byte),那么,S的大小就为6。取一个2的次方值,使得该值大于等于6,则该值为8,所以编译器将设置S类型的对齐方式为8字节。
    packed
    使用该属性对struct或者union类型进行定义,设定其类型的每一个变量的内存约束。当用在enum类型定义时,暗示了应该使用最小完整的类型(it indicates that the smallest integral type should be used)。
    下面的例子中,my-packed-struct类型的变量数组中的值将会紧紧的靠在一起,但内部的成员变量s不会被“pack”,如果希望内部的成员变量也被packed的话,my-unpacked-struct也需要使用packed进行相应的约束。

    1. struct my_unpacked_struct {
    2.       char c;
    3.       int i;
    4. };
    5.           
    6. struct my_packed_struct {
    7.     char c;
    8.     int  i;
    9.     struct my_unpacked_struct s;
    10. } __attribute__ ((__packed__));

    其它属性的含义见:http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Type-Attributes.html#Type-Attributes
    变量属性与类型属性举例
    下面的例子中使用__attribute__属性定义了一些结构体及其变量,并给出了输出结果和对结果的分析。
    程序代码为:

    1. struct p {
    2.     int a;
    3.     char b;
    4.     char c;
    5. }__attribute__((aligned(4))) pp;
    6. struct q {
    7.     int a;
    8.     char b;
    9.     struct n qn;
    10.     char c;
    11. }__attribute__((aligned(8))) qq;
    12. int main()
    13. {
    14.     printf("sizeof(int)=%d,sizeof(short)=%d.sizeof(char)=%d/n",sizeof(int),sizeof(short),sizeof(char));
    15.     printf("pp=%d,qq=%d /n", sizeof(pp),sizeof(qq));
    16.     return 0;
    17. }

    输出结果:
     
    sizeof(int)=4,sizeof(short)=2.sizeof(char)=1
    pp=8,qq=24
     
    分析: 
    sizeof(pp):
    sizeof(a)+ sizeof(b)+ sizeof(c)=4+1+1=6<23=8= sizeof(pp)
    sizeof(qq):
    sizeof(a)+ sizeof(b)=4+1=5
    sizeof(qn)=8;
    即qn是采用8字节对齐的,所以要在a,b后面添3个空余字节,然后才能存储qn,4+1+(3)+8+1=17
    因为qq采用的对齐是8字节对齐,所以qq的大小必定是8的整数倍,即qq的大小是一个比17大又是8的倍数的一个最小值,由此得到
    17<24+8=24= sizeof(qq)
     
    转自:http://blog.csdn.net/gates84/archive/2006/11/03/1365508.aspx

    Weak Alias
    Weak Alias 跟 Weak Reference 完全没有任何关系,不过是我在看到 Weak Reference 的时候想到的而已。
    Weak Alias 是 gcc 扩展里的东西,实际上是函数的属性。这个东西在库的实现里面可能会经常用到,比如 glibc 里面就用了不少。抄录一段 gcc 手册里面的话解释下函数属性是干啥的:
    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. 
    先上代码,看看 weak alias 怎么写。第一个文件 dummy.c 内容,

    1. #include 
    2. /* Do some thing. */
    3. int __foo() {
    4.     puts(“I do no thing.”);
    5. }
    6. int foo() __attribute__ ((weak, alias(“__foo”)));

    weak 和 alias 分别是两个属性。weak 使得 foo 这个符号在目标文件中作为 weak symbol 而不是 global symbol。用 nm 命令查看编译 dummy.c 生成的目标文件可用看到 foo 是一个 weak symbol,它前面的标记是 W。
    00000000 T __foo
    00000000 W foo
             U puts
    而 alias 则使 foo 是 __foo 的一个别名,__foo 和 foo 必须在同一个编译单元中定义,否则会编译出错。
    那么这个东西的用处是?看第二个文件,func.c,

    1. #include 
    2. int foo() {
    3.     puts(“I do something.”);
    4. }

    这里有一个函数名字是 foo。如果我们编译 func.c 和 dummy.c 得到两个目标文件,当我们同时使用 func.o 和 dummy.o 和其他目标文件进行链接时,如果其他目标文件里面引用符号 foo,最终使用到的是 func.c 中定义的函数,而不是 __foo,虽然它有一个别名 foo。也就是说,我们最终使用到的函数会是“实际做事”的那个函数。当然,单独使用 dummy.o 链接的话使用的是那个“不做事”的函数。如果 dummy.o 中的 foo 不是 weak symbol 的话,在链接时会产生冲突,这就是我们要使用 weak 的原因。
    glibc 的实现里面经常用 weak alias。比如它的 socket 函数,在 C 文件里面你会看到一个 __socket 函数,它几乎什么都没有做,只是设置了一些错误代码,返回些东西而已。在同一个 C 文件里面会再声明一个 __socket 的 weak alias 别名 socket。实际完成工作的代码通过汇编来实现,在另外的汇编文件里面会有设置系统调用号,执行 sysenter 或者 int 等动作去请求系统调用。以前看 glibc 里面系统调用的实现的时候郁闷过很久,就是那个时候才知道了 weak alias 这个东西。

    内核中的__attribute__用法

    所有的内核代码,基本都包含了linux/compile.h这个文件,所以它是基础,打算先分析这个文件里的代码看看,有空再分析分析其它的代码。

    首先印入眼帘的是对__ASSEMBLY__这个宏的判断,这个变量实际是在编译汇编代码的时候,由编译器使用-D这样的参数加进去的,AFLAGS这个变量也定义了这个变量,gcc会把这个宏定义为1。用在这里,是因为汇编代码里,不会用到类似于__user这样的属性(关于 __user这样的属性是怎么回子事,本文后面会提到),因为这样的属性是在定义函数的时候加的,这样避免不必要的在编译汇编代码时候的引用。
    接下来是一个对__CHECKER__这个宏的判断,这里需要讲的东西比较多。
    当编译内核代码的时候,使用make C=1或C=2的时候,会调用一个叫Sparse的工具,这个工具对内核代码进行检查,怎么检查呢,就是靠对那些声明过Sparse这个工具所能识别的特性的内核函数或是变量进行检查。在调用Sparse这个工具的同时,在Sparse代码里,会加上#define __CHECKER__ 1的字样。换句话说,就是,如果使用Sparse对代码进行检查,那么内核代码就会定义__CHECKER__宏,否则就不定义。
    所以这里就能看出来,类似于__attribute__((noderef, address_space(1)))这样的属性就是Sparse这个工具所能识别的了。
    那么这些个属性是干什么用的呢,我一个个做介绍。
    这样的属性说明,有一部分在gcc的文档里还没有加进去,至少我在gcc 4.3.2的特性里没有看到,网上有哥们问类似的问题,Greg对他进行了解答,然后他对Greg抱怨文档的事,Greg对他说,他有时间抱怨的话,还不如自己来更新文档。他不能对一个免费工具的文档有如此之高的要求,除非他付费。

    # define __user  __attribute__((noderef, address_space(1)))

    __user这个特性,即__attribute__((noderef, address_space(1))),是用来修饰一个变量的,这个变量必须是非解除参考(no dereference)的,即这个变量地址必须是有效的,而且变量所在的地址空间必须是1,即用户程序空间的。
    这里把程序空间分成了3个部分,0表示normal space,即普通地址空间,对内核代码来说,当然就是内核空间地址了。1表示用户地址空间,这个不用多讲,还有一个2,表示是设备地址映射空间,例如硬件设备的寄存器在内核里所映射的地址空间。

    所以在内核函数里,有一个copy_to_user的函数,函数的参数定义就使用了这种方式。当然,这种特性检查,只有当机器上安装了Sparse这个工具,而且进行了编译的时候调用,才能起作用的。

    # define __kernel /* default address space */

    根据定义,就是默认的地址空间,即0,我想定义成__attribute__((noderef, address_space(0)))也是没有问题的。

    # define __safe  __attribute__((safe))

    这个定义在sparse里也有,内核代码是在2.6.6-rc1版本变到2.6.6-rc2的时候被Linus加入的,经过我的艰苦的查找,终于查找到原因了,知道了为什么Linus要加入这个定义,原因是这样的:
    有人发现在代码编译的时候,编译器对变量的检查有些苛刻,导致代码在编译的时候老是出问题(我这里没有去检查是编译不通过还是有警告信息,因为现在的编译器已经不是当年的编译器了,代码也不是当年的代码)。比如说这样一个例子,
     int test( struct a * a, struct b * b, struct c * c ) {
      return a->a + b->b + c->c;
     }
    这个编译的时候会有问题,因为没有检查参数是否为空,就直接进行调用。但是呢,在内核里,有好多函数,当它们被调用的时候,这些个参数必定不为空,所以根本用不着去对这些个参数进行非空的检查,所以呢,就增加了一个__safe的属性,如果这样声明变量,
     int test( struct a * __safe a, struct b * __safe b, struct c * __safe c ) {
      return a->a + b->b + c->c;
     }
    编译就没有问题了。

    不过我在现在的代码里没有发现有使用__safe这个定义的地方,不知道是不是编译器现在已经支持这种特殊的情况了,所以就不用再加这样的代码了。

    # define __force __attribute__((force))

    表示所定义的变量类型是可以做强制类型转换的,在进行Sparse分析的时候,是不用报告警信息的。

    # define __nocast __attribute__((nocast))

    这里表示这个变量的参数类型与实际参数类型一定得对得上才行,要不就在Sparse的时候生产告警信息。

    # define __iomem __attribute__((noderef, address_space(2)))

    这个定义与__user, __user是一样的,只不过这里的变量地址是需要在设备地址映射空间的。

    # define __acquires(x) __attribute__((context(x,0,1)))
    # define __releases(x) __attribute__((context(x,1,0)))

    这是一对相互关联的函数定义,第一句表示参数x在执行之前,引用计数必须为0,执行后,引用计数必须为1,第二句则正好相反,这个定义是用在修饰函数定义的变量的。

    # define __acquire(x) __context__(x,1)
    # define __release(x) __context__(x,-1)

    这是一对相互关联的函数定义,第一句表示要增加变量x的计数,增加量为1,第二句则正好相反,这个是用来函数执行的过程中。

    以上四句如果在代码中出现了不平衡的状况,那么在Sparse的检测中就会报警。当然,Sparse的检测只是一个手段,而且是静态检查代码的手段,所以它的帮助有限,有可能把正确的认为是错误的而发出告警。要是对以上四句的意思还是不太了解的话,请在源代码里搜一下相关符号的用法就能知道了。这第一组与第二组,在本质上,是没什么区别的,只是使用的位置上,有所区别罢了。

    # define __cond_lock(x,c) ((c) ? ({ __acquire(x); 1; }) : 0)

    这句话的意思就是条件锁。当c这个值不为0时,则让计数值加1,并返回值为1。不过这里我有一个疑问,就是在这里,有一个__cond_lock定义,但没有定义相应的__cond_unlock,那么在变量的释放上,就没办法做到一致。而且我查了一下关于spin_trylock()这个函数的定义,它就用了__cond_lock,而且里面又用了_spin_trylock函数,在_spin_trylock函数里,再经过几次调用,就会使用到 __acquire函数,这样的话,相当于一个操作,就进行了两次计算,会导致Sparse的检测出现告警信息,经过我写代码进行实验,验证了我的判断,确实是会出现告警信息,如果我写两遍unlock指令,就没有告警信息了,但这是与程序的运行是不一致的。

    extern void __chk_user_ptr(const volatile void __user *);
    extern void __chk_io_ptr(const volatile void __iomem *);

    这两句比较有意思。这里只是定义了函数,但是代码里没有函数的实现。这样做的目的,就是在进行Sparse的时候,让Sparse给代码做必要的参数类型检查,在实际的编译过程中,并不需要这两个函数的实现。

    #define notrace __attribute__((no_instrument_function))

    这一句,是定义了一个属性,这个属性可以用来修饰一个函数,指定这个函数不被跟踪。那么这个属性到底是怎么回子事呢?原来,在gcc编译器里面,实现了一个非常强大的功能,如果在编译的时候把一个相应的选择项打开,那么就可以在执行完程序的时候,用一些工具来显示整个函数被调用的过程,这样就不需要让程序员手动在所有的函数里一点点添加能显示函数被调用过程的语句,这样耗时耗力,还容易出错。那么对应在应用程序方面,可以使用Graphviz这个工具来进行显示,至于使用说明与软件实现的原理可以自己在网上查一查,很容易查到。对应于内核,因为内核一直是在运行阶段,所以就不能使用这套东西了,内核是在自己的内部实现了一个ftrace的机制,编译内核的时候,如果打开这个选项,那么通过挂载一个debugfs的文件系统来进行相应内容的显示,具体的操作步骤,可以参看内核源码所带的文档。那上面说了这么多,与notrace这个属性有什么关系呢?因为在进行函数调用流程的显示过程中,是使用了两个特殊的函数的,当函数被调用与函数被执行完返回之前,都会分别调用这两个特别的函数。如果不把这两个函数的函数指定为不被跟踪的属性,那么整个跟踪的过程就会陷入一个无限循环当中。

    宏notrace的定义,这个宏用于修饰函数,说明该函数不被跟踪。这里所说的跟踪是gcc一个很重要的特性,只要在编译时打开相关的跟踪选择,编译器会 加入一些特性,使得程序在执行完后,可以通过工具(如Graphviz)来查看函数的调用过程。而对于内核来说,内部采用了ftrace机制,而不采用 trace的特性。

    #define likely(x) __builtin_expect(!!(x), 1)
    #define unlikely(x) __builtin_expect(!!(x), 0)

    这两句是一对对应关系。__builtin_expect(expr, c)这个函数是新版gcc支持的,它是用来作代码优化的,用来告诉编译器,expr的期,非常有可能是c,这样在gcc生成对应的汇编代码的时候,会把相应的可能执行的代码都放在一起,这样能少执行代码的跳转。为什么这样能提高CPU的执行效率呢?因为CPU在执行的时候,都是有预先取指令的机制的,把将要执行的指令取出一部分出来准备执行。CPU不知道程序的逻辑,所以都是从可程序程序里挨着取的,如果这个时候,能不做跳转,则CPU预先取出的指令都可以接着使用,反之,则预先取出来的指令都是没有用的。还有个问题是需要注意的,在__builtin_expect的定义中,以前的版本是没有!!这个符号的,这个符号的作用其实就是负负得正,为什么要这样做呢?就是为了保证非零的x的值,后来都为1,如果为零的0值,后来都为0,仅此而已。

    #ifndef barrier
    # define barrier() __memory_barrier()
    #endif

    这里表示如果没有定义barrier函数,则定义barrier()函数为__memory_barrier()。但在内核代码里,是会包含 compiler-gcc.h这个文件的,所以在这个文件里,定义barrier()为__asm__ __volatile__("": : :"memory")。barrier翻译成中文就是屏障的意思,在这里,为什么要一个屏障呢?这是因为CPU在执行的过程中,为了优化指令,可能会对部分指令以它自己认为最优的方式进行执行,这个执行的顺序并不一定是按照程序在源码内写的顺序。编译器也有可能在生成二进制指令的时候,也进行一些优化。这样就有可能在多CPU,多线程或是互斥锁的执行中遇到问题。那么这个内存屏障可以看作是一条线,内存屏障用在这里,就是为了保证屏障以上的操作,不会影响到屏障以下的操作。然后再看看这个屏障怎么实现的。__asm__表示后面的东西都是汇编指令,当然,这是一种在C语言中嵌入汇编的方法,语法有其特殊性,我在这里只讲跟这条指令有关的。__volatile__表示不对此处的汇编指令做优化,这样就会保证这里代码的正确性。""表示这里是个空指令,那么既然是空指令,则所对应的指令所需要的输入与输出都没有。在gcc中规定,如果以这种方式嵌入汇编,如果输出没有,则需要两个冒号来代替输出操作数的位置,所以需要加两个::,这时的指令就为"" : :。然后再加上为分隔输入而加入的冒号,再加上空的输入,即为"" : : :。后面的memory是gcc中的一个特殊的语法,加上它,gcc编译器则会产生一个动作,这个动作使gcc不保留在寄存器内内存的值,并且对相应的内存不会做存储与加载的优化处理,这个动作不产生额外的代码,这个行为是由gcc编译器来保证完成的。如果对这部分有更大的兴趣,可以考察gcc的帮助文档与内核中一篇名为memory-barriers.txt的文章。

    #ifndef RELOC_HIDE
    # define RELOC_HIDE(ptr, off)     /
      ({ unsigned long __ptr;     /
         __ptr = (unsigned long) (ptr);    /
        (typeof(ptr)) (__ptr + (off)); })
    #endif

    这个没有什么太多值得讲的,也能看明白,虽然不知道具体用在哪里,所以留做以后遇到了再说吧。

    接下来好多定义都没有实现,可以看一看注释就知道了,所以这里就不多说了。唉,不过再插一句,__deprecated属性的实现是为deprecated。

    #define noinline_for_stack noinline

    #ifndef __always_inline
    #define __always_inline inline
    #endif

    这里noinline与inline属性是两个对立的属性,从词面的意思就非常好理解了。

    #ifndef __cold
    #define __cold
    #endif

    从注释中就可以看出来,如果一个函数的属性为__cold,那么编译器就会认为这个函数几乎是不可能被调用的,在进行代码优化的时候,就会考虑到这一点。不过我没有看到在gcc里支持这个属性的说明。

    #ifndef __section
    # define __section(S) __attribute__ ((__section__(#S)))
    #endif

    这个比较容易理解了,用来修饰一个函数是放在哪个区域里的,不使用编译器默认的方式。这个区域的名字由定义者自己取,格式就是__section__加上用户输入的参数。

    #define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))

    这个函数的定义很有意思,它就是访问这个x参数所对应的东西一次,它是这样做的:先取得这个x的地址,然后把这个地址进行变换,转换成一个指向这个地址类型的指针,然后再取得这个指针所指向的内容。这样就达到了访问一次的目的,哈哈。

    真不容易,终于把这个东西写完了,仅仅几十行的代码,里面所包含的知识真的是异常丰富,通过分析这个头文件,我自己学得了不少东西,不敢独享,拿出来给与兴趣的朋友一同分享。

    syscall的实现方式

    #define SYSCALL_DEFINE3
    SYSCALL_DEFINE3(open, constchar__user *, filename, int, flags, int, mode)
    1050 {
    1051 longret;
    1052
    1053 if (force_o_largefile())
    1054 flags |= O_LARGEFILE;
    1055
    1056 ret = do_sys_open(AT_FDCWD, filename, flags, mode);
    1057 /* avoid REGPARM breakage on x86: */
    1058 asmlinkage_protect(3, ret, filename, flags, mode);
    1059 returnret;
    1060 }

    -----------------------------------
    #defineSYSCALL_DEFINE0(name) asmlinkagelongsys_##name(void)
    99 #defineSYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
    100 #defineSYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
    101 #defineSYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
    102 #defineSYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
    103 #defineSYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
    104 #defineSYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
    105



    #defineSYSCALL_DEFINEx(x, name, ...) \
    124 asmlinkagelongsys##name(__SC_DECL##x(__VA_ARGS__)); \
    125 staticinlinelongSYSC##name(__SC_DECL##x(__VA_ARGS__)); \
    126 asmlinkagelongSyS##name(__SC_LONG##x(__VA_ARGS__)) \
    127 { \
    128 __SC_TEST##x(__VA_ARGS__); \
    129 return (long) SYSC##name(__SC_CAST##x(__VA_ARGS__)); \
    130 } \
    131 SYSCALL_ALIAS(sys##name, SyS##name); \
    132 staticinlinelongSYSC##name(__SC_DECL##x(__VA_ARGS__))
  • 相关阅读:
    前端开发网址
    Iconfot阿里妈妈-css高级应用
    手机端的META你知道多少?
    24个 HTML5 & CSS3 下拉菜单效果及制作教程
    css :clip rect 正确的使用方法
    layui :iframe 与 layer 的位置问题
    时间戳转现实时间的方法
    关于 iframe 的小问题若干
    使用 forever 启动 vue 需要注意的问题
    var 的一个坑,以及 let
  • 原文地址:https://www.cnblogs.com/zhangzhang/p/2844682.html
Copyright © 2011-2022 走看看