从Xen源代码中的compiler.h看GNU C编译器的扩展

来源:互联网 发布:亦鸥 知乎 编辑:程序博客网 时间:2024/06/06 00:18

__GNUC__ 

代表了该编译器,值代表了编译器版本。


#define barrier()     __asm__ __volatile__("": : :"memory")

内存屏障(memory barrier)
  #define set_mb(var, value) do { var = value; mb(); } while (0)
  #define mb() __asm__ __volatile__ ("" : : : "memory")

==============【转】=============================
1)set_mb(),mb(),barrier()函数追踪到底,就是__asm__ __volatile__("":::"memory"),而这行代码就是内存屏障。
2)__asm__用于指示编译器在此插入汇编语句
3)__volatile__用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的样子处理这这里的汇编。
4) memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。
5)"":::表示这是个空指令。barrier()不用在此插入一条串行化汇编指令。在后文将讨论什么叫串行化指令。
6)__asm__,__volatile__,memory在前面已经解释
7)Instruction List
  Instruction List 是汇编指令序列。它可以是空的,比如:__asm__ __volatile__(""); 或 __asm__ ("");都是完全合法的内联汇编表达式,只不过这两条语句没有什么意义。但并非所有Instruction List 为空的内联汇编表达式都是没有意义的,比如:__asm__ ("":::"memory");
  就非常有意义,它向GCC 声明:“内存作了改动”,GCC 在编译的时候,会将此因素考虑进去。 当在"Instruction List"中有多条指令的时候,可以在一对引号中列出全部指令,也可以将一条 或几条指令放在一对引号中,所有指令放在多对引号中。如果是前者,可以将每一条指令放在一行,如果要将多条指令放在一行,则必须用分号(;)或换行符(\n)将它们分开. 综上述:(1)每条指令都必须被双引号括起来 (2)两条指令必须用换行或分号分开。
  例如: 在ARM系统结构上关闭中断的操作
  int disable_interrupts (void)
  {
  unsigned long old,temp;
  __asm__ __volatile__("mrs %0, cpsr\n"
  "orr %1, %0, #0x80\n"
  "msr cpsr_c, %1"
  : "=r" (old), "=r" (temp)
  :
  : "memory");
  return (old & 0x80) == 0;
  }
  7.1.Output
  Output 用来指定当前内联汇编语句的输出
  例如:从arm协处理器p15中读出C1值
  static unsigned long read_p15_c1 (void)
  {
  unsigned long value;
  __asm__ __volatile__(
  "mrc p15, 0, %0, c1, c0, 0 @ read control reg\n"
  : "=r" (value) @编译器选择一个R*寄存器
  :
  : "memory");
  #ifdef MMU_DEBUG
  printf ("p15/c1 is = lx\n", value);
  #endif
  return value;
  }
  7.2. Input
  Input 域的内容用来指定当前内联汇编语句的输入Output和Input中,格式为形如“constraint”(variable)的列表(逗号分隔)
  例如:向arm协处理器p15中写入C1值
  static void write_p15_c1 (unsigned long value)
  {
  #ifdef MMU_DEBUG
  printf ("write lx to p15/c1\n", value);
  #endif
  __asm__ __volatile__(
  "mcr p15, 0, %0, c1, c0, 0 @ write it back\n"
  :
  : "r" (value) @编译器选择一个R*寄存器
  : "memory");
  read_p15_c1 ();
  }
  7.3.Clobber/Modify
  有时候,你想通知GCC当前内联汇编语句可能会对某些寄存器或内存进行修改,希望GCC在编译时能够将这一点考虑进去。那么你就可以在Clobber/Modify域声明这些寄存器或内存。这种情况一般发生在一个寄存器出现在"Instruction List",但却不是由Input/Output操作表达式所指定的,也不是在一些Input/Output操作表达式使用"r"约束时由GCC 为其选择的,同时此寄存器被"Instruction List"中的指令修改,而这个寄存器只是供当前内联汇编临时使用的情况。
  例如:
  __asm__ ("mov R0, #0x34" : : : "R0");
  寄存器R0出现在"Instruction List中",并且被mov指令修改,但却未被任何Input/Output操作表达式指定,所以你需要在Clobber/Modify域指定"R0",以让GCC知道这一点。
  因为你在Input/Output操作表达式所指定的寄存器,或当你为一些Input/Output操作表达式使用"r"约束,让GCC为你选择一个寄存器时,GCC对这些寄存器是非常清楚的——它知道这些寄存器是被修改的,你根本不需要在Clobber/Modify域再声明它们。但除此之外,GCC对剩下的寄存器中哪些会被当前的内联汇编修改一无所知。所以如果你真的在当前内联汇编指令中修改了它们,那么就最好在Clobber/Modify 中声明它们,让GCC针对这些寄存器做相应的处理。否则有可能会造成寄存器的不一致,从而造成程序执行错误。
  如果一个内联汇编语句的Clobber/Modify域存在"memory",那么GCC会保证在此内联汇编之前,如果某个内存的内容被装入了寄存器,那么在这个内联汇编之后,如果需要使用这个内存处的内容,就会直接到这个内存处重新读取,而不是使用被存放在寄存器中的拷贝。因为这个 时候寄存器中的拷贝已经很可能和内存处的内容不一致了。
  这只是使用"memory"时,GCC会保证做到的一点,但这并不是全部。因为使用"memory"是向GCC声明内存发生了变化,而内存发生变化带来的影响并不止这一点。
  例如:
  int main(int __argc, char* __argv[])
  {
  int* __p = (int*)__argc;
  (*__p) = 9999;
  __asm__("":::"memory");
  if((*__p) == 9999)
  return 5;
  return (*__p);
  }
  本例中,如果没有那条内联汇编语句,那个if语句的判断条件就完全是一句废话。GCC在优化时会意识到这一点,而直接只生成return 5的汇编代码,而不会再生成if语句的相关代码,而不会生成return (*__p)的相关代码。但你加上了这条内联汇编语句,它除了声明内存变化之外,什么都没有做。但GCC此时就不能简单的认为它不需要判断都知道 (*__p)一定与9999相等,它只有老老实实生成这条if语句的汇编代码,一起相关的两个return语句相关代码。
  另外在linux内核中内存屏障也是基于它实现的include/asm/system.h中
  # define barrier() _asm__volatile_("": : :"memory")
  主要是保证程序的执行遵循顺序一致性。有的时候你写代码的顺序,不一定是最终执行的顺序,这个是处理器有关的。 

=================【end转】======================


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

要理解宏G_LIKELY和G_UNLIKELY ,很明显必须理解__builtin_expect。__builtin_expect是GCC(version>=2.9)引进的宏,其作用就是帮助编译器判断条件跳转的预期值,避免跳转造成时间乱费。

if (G_LIKELY (acat == 1))     //表示大多数情况下if里面是真,程序大多数直接执行if里面的程序

if (G_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix)))//表示大多数情况if里面为假,程序大多数直接执行else里面的程序

两个函数编译生成的汇编语句所使用到的跳转指令不一样,仔细分析下会发现__builtin_expect实际上是为了满足在大多数情况不执行跳转指令,所以__builtin_expect仅仅是告诉编译器优化,并没有改变其对真值的判断


#define inline        __inline__
#define always_inline __inline__ __attribute__ ((always_inline))
#define noinline      __attribute__((noinline))

inine:使用__inline__代替关键字inline,是因为在GNU的-std和-ansi的编译条件下会禁用asm,type,inline,需要用‘__’的前缀后缀代替!

always_inline:inline是推荐编译器编译为内联函数,若要强制编译为内联,可以使用该关键字

noinline:不使用inline

关于__attribute__用法,可以参考:http://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/Attribute-Syntax.html


__attribute__((attr_1,attr_2,attr_3))

给函数指定属性

  1. always_inline 用此属性则命令编译器总是要内联我们的函数。
  2. deprecated 若其他程序员在他们的程序里用了这类的函数,则他们在编译的时候会得到警告。
  3. __used__ 通常如果你写了一个函数,但是并没有用它,那么编译器编译的时候会给出警告。我们用此属性告诉编译器一定不要发出这样的警告,即使我们真的在其他C程序里面没有调用这个函数。假如你只有在汇编代码里调用了此函数,那么这将会很有用。
  4. warn_unused_result 用此属性命令编译器去检查函数调用者是否已经检验函数返回值了。如果你写了一个函数,其本身要求调用者必须检查该函数的返回值,那就用上这个属性。因为倘若程序员忘记检查函数返回值了,编译器在编译的时候会给出警告。
  5. pure表明函数除返回值外,不会通过其它(如全局变量、指针)对函数外部产生任何影响。
  6. const属性只能用于带有数值类型参数的函数上。当重复调用带有数值参数的函数时,由于返回值是相同的,所以此时编译器可以进行优化处理。该属性主要适用于没有静态状态(static state)和副作用的一些函数(没有getchar()或time()),并且返回值仅仅依赖输入的参数。注意,不能用在带指针参数的函数上。

给变量指定属性

packed 告诉编译器必须按最小的对齐方式去对齐所指定的变量。这通常是按照一个字节去对齐,比如:

struct foo { char a;   int x[2] __attribute__ ((packed)); };

这样指定后,在结构体foo中,数组x[2]在内存中就紧跟在a后面了,并没有结构体的 padding 操作。


section ("section-name") 用来告诉编译器将指定的变量放在某一个特定的段(section)中去。:),您还记得ELF格式的可执行文件格式么?比如:

struct duart a __attribute__ ((section ("DUART_A"))) = { 0 };

struct duart b __attribute__ ((section ("DUART_B"))) = { 0 };

char stack[10000] __attribute__ ((section ("STACK"))) = { 0 };

int init_data __attribute__ ((section ("INITDATA"))) = 0;

给数据类型指定属性

aligned (alignment) 这个属性指定了用此类型定义变量的最小对齐方式,通常以字节为单位。比如:
struct S { short f[3]; } __attribute__ ((aligned (8))); 
typedef int more_aligned_int __attribute__ ((aligned (8)));
指定了任何用 struct S 或者 more_aligned_int 等类型定义的变量都必须最少以8字节来对齐。换句话讲,他们在连接和加载的时候,必须被分配到8的整数倍地址上。这里多说一句,关于结构体、联合体的对齐方式,您必须综合考虑cpu架构,连接器,程序指定的属性等多方面的因素来考虑,不可想当然的信手拈来。

#define offsetof(a,b) __builtin_offsetof(a,b)

即:#define offsetof(a,b) ((unsigned long)&(((a *)0)->b))

返回一个数据域在它所属的数据结构中的相对偏移,单位是ulong

原理:把地址0作为数据结构a的起始,获得b的地址,返回


define __must_be_array(a) \
  BUILD_BUG_ON_ZERO(__builtin_types_compatible_p(typeof(a), typeof(&a[0])))

编译时的检查:若不是数组(如指针),就会产生编译错误

其中:

BUILD_BUG_ON_ZERO的作用:e为0时返回0,e不为0时产生编译错误

#define BUILD_BUG_ON_ZERO(e) (sizeof(char[1 - 2 * !!(e)]) - 1)

__builtin_types_compatible_p:两个type compatible时返回1,再此typeof(a), typeof(&a[0])必须为不兼容,否则报错。


#pragma GCC visibility push(hidden)

隐藏可见的ELF符号,也可以使用__attribute__ ((visibility("hidden")))


#define RELOC_HIDE(ptr, off)                    \
  ({ unsigned long __ptr;                       \
    __asm__ ("" : "=r"(__ptr) : "0"(ptr));      \
    (typeof(ptr)) (__ptr + (off)); })

用于per_cpu机制,目的是为每个cpu建立其私有的数据域,该方法提供了从该域取数据的一个方法,具体详见:

http://www.cnblogs.com/M-book/articles/2152057.html



原创粉丝点击