C语言运行时数据
1 可执行文件的格式
在UNIX传统的操作系统中,所有编译生成的输出文件都缺省地使用同一个名字a.out,在现代操作系统中,a.out格式的可执行文件是链接器的输出,而不是汇编程序的输出(在计算机的远古时代,a.out是汇编器的输出,那个时候还没有链接器)。
目标文件和可执行文件有几种不同的格式,大多数都采用了一种ELF的格式,更多的格式可以使用下面的命令来查看(有的系统中可能找不到手册):
$ man a.out
你可以在linux系统中,对编译链接生成的可执行文件使用:
$ file 可执行文件名
可以看到他的输出,说明这是一个ELF格式的可执行文件:
yuanlu@bear-labpc:~/workspace/wifi/wifi_hello$ file hello.ko
hello.ko: ELF 32-bit LSB relocatable, ARM, version 1 (SYSV), not stripped
2 UNIX中段的概念
在NUIX系统中,有很多的不同格式,但他们都有一个共同的概念—段。
所谓的段是目标文件的概念,一个目标文件有多个段,他们是二进制文件中简单的的区域,里面保存了和某种特定类型相关的所有信息,如:符号表条目。这里不要把UNIX和Intel X86中的段概念混淆,后者中的段表示一种内存模型的设计结果,在这种设计中,地址空寂并非一个整体,而是分成一些固定大小的区域,称之为段。
对于一个目标文件,运行size命令可以告诉你这个文件的三个段的大小。这三个段分别是:代码段(文本段),数据段和bss段:
yuanlu@bear-labpc:~/workspace/wifi/wifi_hello$ size hello.ko
text data bss dec hex filename
224 300 0 524 20c hello.ko
这里对三个段中存放的内容做一个说明:
(1) 文本段:即代码段,存放要执行的指令代码
(2) 数据段:存放全局或静态的已经初始化的数据变量
(3) bss段:存放全局或静态的尚未初始化的数据变量。由于BSS段只保存没有值得变量,所以事实上他并不需要保存这些变量的映像,而只是将BSS段在运行时需要的大小记录在目标文件中,因此BSS段并不占据目标文件的任何空间。
需要注意的是一个a.out可执行文件的组成是下面这个样子的:
a. a.out神奇数字
b. a.out的其他内容
c. BSS数据段所需大小
d. 数据段(初始化后的全局和静态变量)
e. 文本段(可执行文件的指令)
其中文件的头部有一个a.out的神奇数字,它是一种能够确认一组随机的二进制位集合的什么数字,我们暂且不用理会;对于局部变量而言,它不存在a.out中而是在运行时创建。下面我们借用号称最简单的hello world驱动来证明上面的内容的正确性。
1 #include <linux/init.h>
2 #include <linux/module.h>
3
4 MODULE_LICENSE("GPL");
5
6 /* the init function*/
7 static __init int hello_init(void)
8
9 {
10
11 printk(KERN_WARNING "Hello world !/n");
12
13 return 0;
14 }
15
16 /* the distory function*/
17 static __exit void hello_exit(void)
18 {
19 printk(KERN_WARNING "Goodbye!/n");
20 }
21
22 module_init(hello_init);
23 module_exit(hello_exit);
编译生成hello.ko可执行文件后,使用size hello.ko命令查看每个段的使用情况:
yuanlu@bear-labpc:~/workspace/wifi/wifi_hello$ size hello.ko
text data bss dec hex filename
224 300 0 524 20c hello.ko
现在的bss段的内容为空,这里的0表示bss段的大小,data段的大小为300(单位都是字节)。我们增加分别两个全局和静态已经初始化的变量和未初始化的变量,在函数中也增加一个大数组的声明:
1 #include <linux/init.h>
2 #include <linux/module.h>
3
4 MODULE_LICENSE("GPL");
5
6 int i;
7 int m = 2;
8
9 static int j;
10 static int n = 1;
11
12 /* the init function*/
13 static __init int hello_init(void)
14
15 {
16 printk(KERN_WARNING "Hello world !/n");
17
18 return 0;
19 }
20
21 /* the distory function*/
22 static __exit void hello_exit(void)
23 {
24 printk(KERN_WARNING "Goodbye!/n");
25 }
26
27 module_init(hello_init);
28 module_exit(hello_exit);
编译后使用size工具:
yuanlu@bear-labpc:~/workspace/wifi/wifi_hello$ size hello.ko
text data bss dec hex filename
224 300 8 532 214 hello.ko
这里发现bss段的大小变成了8,data段却没有增加,通过后面的继续求证发现这样一个事实:
(1)对于函数内部的局部变量没有存放在可执行文件里
(2)对于全局变量而言,未初始化的变量存放在bss段,初始化的变量分两种:第一,如果被初始化为0,仍然被放在bss段,否则放在数据段(这一点有点不同哦)
(3)对于静态全局变量而言,不管有没有初始化,都不存放在可执行文件中
如果说前面两点很好理解的话,俺么对于第三点的实际表现与理论上的比较大的出入,有待后面继续求证,这里只能猜测是编译器的差异(我的环境是交叉编译环境)。
3 a.out的内存布局
段可以被方便地映射到连接器在运行时可以直接载入的对象中,载入器只是去可执行文件的每一个段的一个映像,本质上段就是正在执行的程序中的一块内存区域。连接器把每个段从文件拷贝到内存中,一般使用mmap()系统调用。
下面是可执行文件中的段在内存中的布局图:
堆栈段
&nbs
补充:软件开发 , C语言 ,