我们在阅读Linux内核是,常见到这些宏 __init, __initdata, __initfunc(), asmlinkage, ENTRY(), FASTCALL()等等。它们定义在 /include/linux/init.h 和 /include/linux/linkage.h 以及其他一些.h 文件中。

  1. __init

位置:/include/linux/init.h

定义: #define __init   __attribute__ ((__section__ (".init.text")))

注释:这个标志符和函数声明放在一起,表示gcc编译器在编译时,需要把这个函数放在.text.init Section 中,而这个Section 在内核完成初始化之后,就会被释放掉。

  举例:asmlinkage void __init star_kerne(void) { ... }

  2. __initdata

  位置:/include/linux/init.h

  定义:#define __initdata __attribute__ ((__section__ (".init.data")))

  注释:这个标志符和变量声明放在一起,表示gcc编译器在编译时,需要把这个变量放在.data.init Section中,而这个Section 在内核完成初始化之后,会释放掉。

  举例:static struct kernel_param raw_params[] __initdata ={ ... }

  3. __initfunc()

  位置:/include/asm-i386/init.h

  定义:#define __initfunc(__arginit) \      __arginit __init; \      __arginit

  注释:__initfunc() 是一个自定义宏,用来定义一个 __init 函数,在Linux-2.4中已被__init宏所取代。

  举例:__initfunc (void mem_init(unsigned long start_mem, unsigned long end_mem)) { ... }

  4. asmlinkage

  位置:/include/linux/linkage.h

  定义:#define asmlinkage CPP_ASMLINKAGE  __attribute__((regparm(0)))

  注释:这个标志符和函数声明放在一起,带regparm(0)的属性声明告诉gcc编译器,该函数不需要通过任何寄存器来传递参数,参数只是通过堆栈来传递。gcc编译器在汇编过程中调用c语言函数时传递参数有两种方法:一种是通过堆栈,另一种是通过寄存器。缺省时采用寄存器,假如你要在你的汇编过程中调用c语言函数,并且想通过堆栈传递参数,你定义的 c 函数时要在函数前加上宏asmlinkage。

  举例:asmlinkage void __init start_kernel(void) { ... }

  5.ENTRY()

  位置:/include/linux/linkage.h

  定义:#define ENTRY(name) \                 .globl name; \                 ALIGN; \                 name:

  注释:将name 声明为全局,对齐,并定义为标号。

  举例: 

   ENTRY(swapper_pg_dir)
 
  .long 0x00102007
 
  .fill __USER_PGD_PTRS-1,4,0
 
 <span style="color: rgb(0, 0, 255);"> /* default: 767 entries */
</span>
  .long 0x00102007
 
<span style="color: rgb(0, 0, 255);">  /* default: 255 entries */
</span>
  .fill __KERNEL_PGD_PTRS-1,4,0
 
等价于
 
  .globl swapper_pg_dir
 
  .align 16,0x90
 
<span style="color: rgb(0, 0, 255);">  /* if i486 */
</span>
  swapper_pg_dir:
 
  .long 0x00102007
 
  .fill __USER_PGD_PTRS-1,4,0
 
<span style="color: rgb(0, 0, 255);">  /* default: 767 entries */
</span>
  .long 0x00102007
 
<span style="color: rgb(0, 0, 255);">  /* default: 255 entries */
</span>
  .fill __KERNEL_PGD_PTRS-1,4,0

  

  6. FASTCALL ()

  位置:/include/linux/kernel.h

  定义:#define FASTCALL(x)  x  __attribute__((regparm(3)))

  注释:这个标志符和函数声明放在一起,带regparm(3)的属性声明告诉gcc编译器,这个函数可以通过寄存器传递多达3个的参数,这3个寄存器依次为EAX、EDX 和 ECX。更多的参数才通过堆栈传递。这样可以减少一些入栈出栈操作,因此调用比较快。

  举例:extern void FASTCALL(__switch_to(struct task_struct *prev, struct task_struct *next))

  这个例子中,prev将通过eax,next通过edx传递。

  7. __sched

  位置:/include/linux/sched.h

  定义:/* Attach to any functions which should be ignored in wchan output. */      #define __sched  __attribute__((__section__(".sched.text")))

可执行文件的内存布局对程序性能的影响是非常巨大的,因为我最近一直在做性能优化,对这方面感触颇深。要搞明白可执行文件的内存布局,就必须得了解编译原理,当然编译原理实在是太过于高深了,我所知也是皮毛,所以我就从最实用的地方开始入手一点点的分析。

就从我前文里提到的__attribute__((section(“.sec_name”)))来说起吧,因为我使用这个东西确实给我们的性能带来了一定的提升。

关于attribute section这个东西,你要google的话,能够搜索出来不少前人的分析,不过实在都是大同小异,你抄我来我抄你,毫无营养。在他们的博客里,无非是说,“将作用的函数或者数据放入指定名为‘.sec_name’ 的输入段”,然后再巴拉巴拉一通什么是输入段,说的你云里雾里一头雾水分不清东西南北顿觉高大上。

那我们就来看下attribute section到底是什么。

要知道attribute section, 就要先理解链接脚本。链接脚本即链接器在把.o文件链接成最后的elf文件所遵循的规则,也就是,最终的可执行文件是什么样子的是由这个链接脚本决定的。链接脚本的语法和C语言很类似,我们能够很容易读明白,所以从链接脚本来入手分析这个东西会更清晰一些。对应于 __attribute__((section(“.sec_name”)))这句话,它在链接的时候采取的默认规则是:

1
2
3
4
.sec_name
{
*(.sec_name)
}

即把.sec_name指向的内容放在.sec_name这个段里面。我们再来稍微清晰化一些,下面举个例子。

1
2
void foo(void)  __attribute__((section(".in_name")));
void bar(void) __attribute__((section(".in_name")));

我们使用attribute section来声明了两个函数,然后我们在链接脚本里面做如下约束:

1
2
3
4
.out_name
{
*(.in_name)
}

这样就把foor(),bar()这两个函数给放在了最终elf文件里的.out_name这个section。而如果我们不再链接脚本里做这个约束,那么它在链接过程中就会采用默认规则,即输入段和输出段的名字是一样的:

1
2
3
4
.in_name
{
*(.in_name)
}

总结起来就是,__attribute__((section(“.in_name”)))的作用是把.in_name指向的符号给放在一起。

唔~ 仍然有点模糊是不? 那就好好读读《linkers and loaders》或者《程序员的自我修养》这两本书吧。

然后我们来看下linux内核对于attribute section的应用, 以linux kernel的链接脚本vmlinux.lds为例。先来看下linux kernel最终镜像的代码段是如何规划的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SECTIONS
{
. = VMLINUX_LOAD_ADDRESS;
/* read-only */
_text = .; /* Text and read-only data */
.text : {
TEXT_TEXT
SCHED_TEXT
LOCK_TEXT
KPROBES_TEXT
IRQENTRY_TEXT
*(.text.*)
*(.fixup)
*(.gnu.warning)
} :text = 0
}

如上就是一个典型的linux kenrel链接脚本的代码段部分。稍微解释下。 . = VMLINUX_LOAD_ADDRESS;的意思是说,此处的地址是VMLINUX_LOAD_ADDRESS,接着又把该值赋给了_text,也就是内核代码段的其实地址是VMLINUX_LOAD_ADDRESS,就这就开始了代码段。在代码段里面我们可以很明显的看到它划分了TEXT_TEXT、SCHED_TEXT、LOCK_TEXT、KPROBES_TEXT、IRQENTRY_TEXT,这样划分的目的,就是为了合理规划地址空间以提升性能。我们可以看下这几个宏到底表示什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#define TEXT_TEXT                                   \
ALIGN_FUNCTION(); \
*(.text.hot) \
*(.text) \
*(.ref.text) \
MEM_KEEP(init.text) \
MEM_KEEP(exit.text) \
*(.text.unlikely) #define SCHED_TEXT \
ALIGN_FUNCTION(); \
VMLINUX_SYMBOL(__sched_text_start) = .; \
*(.sched.text) \
VMLINUX_SYMBOL(__sched_text_end) = .; #define LOCK_TEXT \
ALIGN_FUNCTION(); \
VMLINUX_SYMBOL(__lock_text_start) = .; \
*(.spinlock.text) \
VMLINUX_SYMBOL(__lock_text_end) = .; #define KPROBES_TEXT \
ALIGN_FUNCTION(); \
VMLINUX_SYMBOL(__kprobes_text_start) = .; \
*(.kprobes.text) \
VMLINUX_SYMBOL(__kprobes_text_end) = .; #define IRQENTRY_TEXT \
ALIGN_FUNCTION(); \
VMLINUX_SYMBOL(__irqentry_text_start) = .; \
*(.irqentry.text) \
VMLINUX_SYMBOL(__irqentry_text_end) = .;

这些宏其实就是定义了一些input section, 比如.text.hot等。

接着以.sched.text为例来看看到底是怎么用的。

1
2
3
4
5
6
7
8
9
10
#define __sched          __attribute__((__section__(".sched.text")))

void __sched notrace preempt_schedule_context(void);
static void __sched __schedule(void);
asmlinkage void __sched schedule(void);
asmlinkage void __sched schedule_user(void);
void __sched schedule_preempt_disabled(void);
asmlinkage void __sched notrace preempt_schedule(void);
asmlinkage void __sched preempt_schedule_irq(void);
....

一目了然了吧? 就是前面我们说的attribute section这个东西,内核就是使用了这个东西来规划地址空间,将相互关联的代码给放在一起,以达到提升性能并保持稳定的作用。

因为松柏公司的性能受代码check-in影响波动较大,所以我就想到了使用linux kernel的这种做法来规划可执行文件的地址空间,按照不同模块来划分不同的section,这样来避免频繁code check-in对性能波动的影响。

1
2
3
4
5
6
7
.text
{
*(.module_a.text)
*(.module_b.text)
*(.module_c.text)
...
}

其实,再稍微的深入思考下,我们就能发现一个更细粒度的控制,那就是控制函数在可执行文件里的先后顺序。

1
2
3
4
5
6
7
8
9
void foo(void)  __attribute__((section(".in_name.1")));
void bar(void) __attribute__((section(".in_name.2"))); .text
{
*(.in_name.1)
*(.in_name.2)
...
}

这样做之后,在可执行文件里,foo就会在bar的前面,及foo和bar的地址紧挨着,bar紧跟在foo的后面。

我们在缩小一下我们的视角,从宏观上来看下这个链接脚本。

1
2
3
4
5
6
7
8
9
10
11
12
SECTIONS
{
.text : {
...
}
.data : {
...
}
.bss:{
...
}
}

这也是为什么可执行文件的内存布局先是代码段,接着数据段,再是bss段的原因,即链接脚本决定可执行文件的内存布局。在linux/freebsd机器上运行“ld —verbose”就可以获得ld使用的默认链接脚本。

Linux 内核常见宏定义的更多相关文章

  1. linux内核container_of宏定义分析

    看见一个哥们分析container_of很好,转来留给自己看 一.#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMB ...

  2. 转载 linux内核 asmlinkage宏

    转载http://blog.chinaunix.net/uid-7390305-id-2057287.html 看一下/usr/include/asm/linkage.h里面的定义:#define a ...

  3. 嵌入式C语言自我修养 04:Linux 内核第一宏:container_of

    4.1 typeof 关键字 ANSI C 定义了 sizeof 关键字,用来获取一个变量或数据类型在内存中所占的存储字节数.GNU C 扩展了一个关键字 typeof,用来获取一个变量或表达式的类型 ...

  4. Linux中THIS_MODULE宏定义详解

    一直都在耿耿于怀,这个THIS_MODULE到底是个什么玩意,linux内核中无处不在的东西.今天上网搜了一下,算是基本明白了.网上牛人写的已经比较详细,另外目前暂时没有时间往更深层次分析,所以直接贴 ...

  5. linux内核第一宏 container_of

    内核第一宏 list_entry()有着内核第一宏的美称,它被设计用来通过结构体成员的指针来返回结构体的指针.现在就让我们通过一步步的分析,来揭开它的神秘面纱,感受内核第一宏设计的精妙之处. 整理分析 ...

  6. 【编程基础】C语言常见宏定义

    我们在使用C语言编写程序的时候,常常会使用到宏定义以及宏编译指令,有的可能比较常用,有的可能并不是很常用,是不是所有的C语言宏定义以及宏指令你都清楚呢? 指令 用途详细介绍 # 空指令,无任何效果 # ...

  7. linux内核中宏likely和unlikely到底做了些什么?

    1. 先看看它们长啥样吧!(它们有两种定义,第一种是使能了程序trace功能的宏定义,第二种是普通的宏定义,咱们分析普通宏定义吧) # define likely(x) __builtin_expec ...

  8. linux内核源码中常见宏定义

    http://blog.csdn.net/yangdelong/article/details/5508057

  9. linux内核中宏container_of是干什么的?

    Linux Kernel Version 4.14 1. container_of是干什么的? 已知一个结构体中某个成员的首指针,那么就可以通过宏container_of来获得此结构体的首指针 2 先 ...

随机推荐

  1. stst

    静态代码块如果不要static 那么只要生成新的该对象,则会执行{}内的代码 super.onDestroy() 先后顺序问题 Ctrl + Alt + F refromat code目前看来不能对代 ...

  2. java的4种代码块

    一.普通代码块 直接在一个方法中出现的{}就称为普通代码块,例子程序如下: public class CodeDemo01{ public static void main(String[] args ...

  3. JS获取元素尺寸大小、鼠标位置

    //e.clientX|Y:表示鼠标相对浏览器可视窗口的当前坐标 //e.offsetX|Y:表示鼠标相对于事件源对象的坐标 //e.pageX|Y:表示鼠标相对于网页的坐标 /* element.o ...

  4. nginx.conf 解释

    http://snapshot.sogoucdn.com/websnapshot?ie=utf8&url=http%3A%2F%2Fwww.cszhi.com%2F20120513%2Fngi ...

  5. 解决Mac下MX4手机无法连接adb问题之解决方案

    一般的android连接mac 很方便不用安装驱动就可以啦,可是不知道为什么特殊情况下有的android手机(小米2,华为等)就是连接不上,下来就说说特殊情况下如何连接. 使用USB连接安卓手机后可以 ...

  6. AJAX第二发

    开始总结自己在项目上的使用AJAX的情况: 该图为向后台请求的数据,前台返回的结果,返回的数据类型为object,businessObj由许多key组成,我们项目前台需要调用的数据就藏在数组recor ...

  7. AR/VR技术交流

    本人建了一个QQ群(群号:331922761),欢迎对AR,VR技术感兴趣的同学加入一起学习讨论.

  8. css中vw,vh单位对于UC的兼容性问题

    vw,vh单位在移动端浏览器不兼容,在网上找半天也没找到什么官方的解决方法:我就试了一下在使用到vh的单位之前添加一个用px定义的样式: 如: 当浏览器不是别100vw单位的时候 就会赋给px单位的样 ...

  9. 【Delphi7】 解决“程序第一次可以正常编译,但再次编译的时候会报错,必须重新打开Delphi”的问题

    报错如下: Access violation at address 00495044 in module 'coreide70.bpl'. Read of address...Access viola ...

  10. 关于maven

    主要涉及的配置文件是setting.xml与pom.xml 其中setting配置文件主要负责加载jar包路径设置, pom.xml文件主要负责jar包配置. 包含jar包的版本. maven打包:之 ...