程序在使用一个函数之前
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第14章头文件
程序在使用一个函数之前,应该首先声明该函数。
为了便于使用,通常的做法是把同一类函数或数据结构以及常数的声明放在一个头文件(header file)中。
头文件中也可以包括任何相关的类型定义和宏(macros)。
在程序源代码文件中则使用预处理指令“#include”来引用相关的头文件。
程序中如下形式的一条控制行语句将会使得该行被文件filename的内容替换掉:
# include <filename>
当然,文件名filename中不能包含> 和换行字符以及"、'、\、或/* 字符。
编译系统会在定义的一系列地方搜索这个文件。
类似地,下面形式的控制行会让编译器首先在源程序所在目录中搜索filename文件:
# include "filename"
如果没有找到,编译器再执行同上面一样的搜索过程。
在这种形式中,文件名filename中不能包含换行字符和"、'、\、或/* 字符,但允许使用> 字符。
在一般应用程序源代码中,头文件与开发环境中的库文件有着不可分割的紧密联系,库中的每个函数都需要在头文件中加以声明。
应用程序开发环境中的头文件(通常放置在系统/usr/include/目录中)可以看作是其所提供函数库(例如libc.a)中函数的一个组成部分,是库函数的使用说明或接口声明。
在编译器把源代码程序转换成目标模块后,链接程序(linker)会把程序所有的目标模块组合在一起,包括用到的任何库文件中的模块。
从而构成一个可执行的程序。
对于标准C函数库来讲,其最基本的头文件有15个。
每个头文件都表示出一类特定函数的功能说明或结构定义,例如I/O操作函数、字符处理函数等。
有关标准函数库的详细说明及其实现可参照Plauger编著的《The Standard C Library》一书。
而对于本书所描述的内核源代码,其中涉及的头文件则可以看作是对内核及其函数库所提供服务的一个概要说明,是内核及其相关程序专用的头文件。
在这些头文件中主要描述了内核所用到的所有数据结构、初始化数据、常数和宏定义,也包括少量的程序代码。
除了几个专用的头文件以外(例如块设备头文件blk.h),Linux 0.12内核中所用到的头文件都放在内核代码树的include/目录中。
因此编译Linux 0.12内核无需使用开发环境提供的位于/usr/include/目录下的任何头文件。
当然,tools/build.c程序除外。
因为这个程序虽然被包含在内核源代码树中,但它只是一个用于组合创建内核映像文件的工具程序或应用程序,不会被链接到内核代码中。
从0.95版开始,内核代码树中的头文件需要复制到/usr/include/linux目录下才能顺利地编译内核。
即从该版内核开始头文件已经与开发环境使用的头文件合二为一。
14.1 include/目录下的文件
内核所用到的头文件都保存在include/目录下。
该目录下的文件如表11-1所示。
这里需要说明一点:为了方便使用和兼容性,Linus在编制内核程序头文件时所使用的命名方式与标准C
776
Linux内核完全剖析—基于0.12内核
库头文件的命名方式相似,许多头文件的名称甚至其中的一些内容都与标准C库的头文件基本相同,但这些内核头文件仍然是内核源代码或与内核有紧密联系的程序专用的。
在一个Linux 系统中,它们与标准库的头文件并存。
通常的做法是将这些头文件放置在标准库头文件目录中的子目录下,以让需要用到内核数据结构或常数的程序使用。
另外,也由于版权问题,Linus试图重新编制一些头文件以取代具有版权限制的标准C库的头文件。
因此这些内核源代码中的头文件与开发环境中的头文件有一些重叠的地方。
在Linux 系统中,列表14-1中的asm/、linux/和sys/三个子目录下的内核头文件通常需要复制到标准C 库头文件所在的目录(/usr/include)中,而其他一些文件若与标准库的头文件没有冲突则可以直接放到标准库头文件目录下,或者改放到这里的三个子目录中。
asm/目录下主要用于存放与计算机体系结构密切相关的函数声明或数据结构的头文件。
例如Intel CPU 端口IO汇编宏文件io.h、中断描述符设置汇编宏头文件system.h等。
linux/目录下是Linux内核程序使用的一些头文件。
其中包括调度程序使用的头文件sched.h、内存管理头文件mm.h和终端管理数据结构文件tty.h等。
而sys/目录下存放着几个与内核资源相关头文件。
不过从0.98版开始,内核目录树下sys/目录中的头文件被全部移到了linux/目录下。
Linux 0.12版内核中共有32个头文件(*.h),其中asm/子目录中含有4个,linux/子目录中含有10个,sys/子目录中含有5个。
从下一节开始我们首先描述include/目录下的13个头文件,然后依次说明每个子目录中的文件。
说明顺序按照文件名称排序进行。
表14-1 linux/include/目录下的文件
14.2 a.out.h文件
14.2.1 功能描述
在Linux 内核中,a.out.h文件用于定义被加载的可执行文件结构。
主要用于加载程序fs/exec.c中。
该文件不属于标准C库,它是内核专用的头文件。
但由于与标准库的头文件名没有冲突,因此在Linux系统中一般可以放/usr/include/目录下,以供涉及相关内容的程序使用。
该头文件中定义了目标文件的一种a.out(Assembly out)格式。
Linux 0.12系统中使用的.o文件和可执行文件就采用了这种目标文件格式。
第14章头文件777
a.out.h文件包括三个数据结构定义和一些相关的宏定义,因此文件可被相应地分成三个部分:
●第1~108行给出并描述了目标文件执行头结构和相关的宏定义。
●第109~185行对符号表项结构的定义和说明。
●第186~217行对重定位表项结构进行定义和说明。
由于该文件内容比较多,因此对其中三个数据结构以及相关宏定义的详细说明放在程序列表后。
从0.96版内核开始,Linux系统直接采用了GNU的同名头文件a.out.h。
因此造成在Linux 0.9x下编译的程序不能在Linux 0.1x系统上运行。
下面对两个a.out头文件的不同之处进行分析,并说明如何让0.9x下编译的一些不是用动态链接库的执行文件也能在0.1x下运行。
Linux 0.12使用的a.out.h文件与GNU同名文件的主要区别在于exec结构的第一个字段a_magic。
GNU的该文件字段名称是a_info,并且把该字段又分成3个子域:标志域(Flags)、机器类型域(Machine Type)和魔数域(Magic Number)。
同时为机器类型域定义了相应的宏N_MACHTYPE和N_FLAGS,如图14-1所示。
图14-1 执行文件头结构exec中的第一个字段a_magic(a_info)
在Linux 0.9x系统中,对于采用静态库连接的执行文件,图中各域注释中括号内的值是该字段的默认值。
这种二进制执行文件开始处的4个字节是:
0x0b, 0x01, 0x64, 0x00
而这里的头文件仅定义了魔数域。
因此,在Linux 0.1x系统中一个a.out格式的二进制执行文件开始的4个字节是:
0x0b, 0x01, 0x00, 0x00
可以看出,采用GNU的a.out格式的执行文件与Linux 0.1x系统上编译出的执行文件的区别仅在机器类型域。
因此我们可以把Linux 0.9x上的a.out格式执行文件的机器类型域(第3个字节)清零,让其运行在0.1x系统中。
只要被移植的执行文件所调用的系统调用都已经在0.1x系统中实现即可。
在开始重新组建Linux 0.1x根文件系统中的很多命令时,作者就采用了这种方法。
在其他方面,GNU的a.out.h头文件与这里的a.out.h没有什么区别。
14.2.2 代码注释
程序14-1 linux/include/a.out.h
1 #ifndef _A_OUT_H
2 #define _A_OUT_H
778
Linux内核完全剖析—基于0.12内核
3
4 #define __GNU_EXEC_MACROS__
5
// 第6--108行是该文件第1部分。
定义目标文件执行结构以及相关操作的宏定义。
// 目标文件头结构。
参见程序后的详细说明。
// =============================
// unsigned long a_magic // 执行文件魔数。
使用N_MAGIC等宏访问。
// unsigned a_text // 代码长度,字节数。
// unsigned a_data // 数据长度,字节数。
// unsigned a_bss // 文件中的未初始化数据区长度,字节数。
// unsigned a_syms // 文件中的符号表长度,字节数。
// unsigned a_entry // 执行开始地址。
// unsigned a_trsize // 代码重定位信息长度,字节数。
// unsigned a_drsize // 数据重定位信息长度,字节数。
// -----------------------------
6 struct exec {
7 unsigned long a_magic; /* Use macros N_MAGIC, etc for access */
8 unsigned a_text; /* length of text, in bytes */
9 unsigned a_data; /* length of data, in bytes */
10 unsigned a_bss; /* length of uninitialized data area for file,in bytes*/
11 unsigned a_syms; /* length of symbol table data in file, in bytes */
12 unsigned a_entry; /* start address */
13 unsigned a_trsize; /* length of relocation info for text, in bytes */
14 unsigned a_drsize; /* length of relocation info for data, in bytes */
15 };
16
// 用于取上述exec结构中的魔数。
17 #ifndef N_MAGIC
18 #define N_MAGIC(exec) ((exec).a_magic)
19 #endif
20
21 #ifndef OMAGIC
22 /* Code indicating object file or impure executable. */
/* 指明为目标文件或者不纯的可执行文件的代号 */
// 历史上最早在PDP-11计算机上,魔数(幻数)是八进制数0407(0x107)。
它位于执行程序 // 头结构的开始处。
原本是PDP-11的一条跳转指令,表示跳转到随后7个字后的代码开始处。
// 这样加载程序(loader)就可以在把执行文件放入内存后直接跳转到指令开始处运行。
现在 // 已没有程序使用这种方法,但这个八进制数却作为识别文件类型的标志(魔数)保留了下来。
// OMAGIC可以认为是Old Magic 的意思。
23 #define OMAGIC 0407
24 /* Code indicating pure executable. */
/* 指明为纯可执行文件的代号 */ // New Magic,1975年以后开始使用。
涉及虚存机制。
25 #define NMAGIC 0410 // 0410 == 0x108
26 /* Code indicating demand-paged executable. */
/* 指明为需求分页处理的可执行文件 */ // 其头结构占用文件开始处1K空间。
27 #define ZMAGIC 0413 // 0413 == 0x10b
28 #endif /* not OMAGIC */
29 // 另外还有一个QMAGIC,是为了节约磁盘容量,把盘上执行文件的头结构与代码紧凑存放。
// 下面宏用于判断魔数字段的正确性。
如果魔数不能被识别,则返回真。
30 #ifndef N_BADMAG
31 #define N_BADMAG(x) \
32 (N_MAGIC(x) != OMAGIC && N_MAGIC(x) != NMAGIC \
33 && N_MAGIC(x) != ZMAGIC)
34 #endif
35
36 #define _N_BADMAG(x) \
第14章头文件779
37 (N_MAGIC(x) != OMAGIC && N_MAGIC(x) != NMAGIC \
38 && N_MAGIC(x) != ZMAGIC)
39
// 目标文件头结构末端到1024字节之间的长度。
40 #define _N_HDROFF(x) (SEGMENT_SIZE - sizeof (struct exec))
41
// 下面宏用于操作目标文件的内容,包括.o模块文件和可执行文件。
// 代码部分起始偏移值。
// 如果文件是 ZMAGIC类型的,即是执行文件,那么代码部分是从执行文件的1024字节偏移处 // 开始;否则执行代码部分紧随执行头结构末端(32字节)开始,即文件是模块文件(OMAGIC类型)。
42 #ifndef N_TXTOFF
43 #define N_TXTOFF(x) \
44 (N_MAGIC(x) == ZMAGIC?_N_HDROFF((x)) + sizeof (struct exec): sizeof (struct exec))
45 #endif
46
// 数据部分起始偏移值。
从代码部分末端开始。
47 #ifndef N_DATOFF
48 #define N_DATOFF(x) (N_TXTOFF(x) + (x).a_text)
49 #endif
50
// 代码重定位信息偏移值。
从数据部分末端开始。
51 #ifndef N_TRELOFF
52 #define N_TRELOFF(x) (N_DATOFF(x) + (x).a_data)
53 #endif
54
// 数据重定位信息偏移值。
从代码重定位信息末端开始。
55 #ifndef N_DRELOFF
56 #define N_DRELOFF(x) (N_TRELOFF(x) + (x).a_trsize)
57 #endif
58
// 符号表偏移值。
从上面数据段重定位表末端开始。
59 #ifndef N_SYMOFF
60 #define N_SYMOFF(x) (N_DRELOFF(x) + (x).a_drsize)
61 #endif
62
// 字符串信息偏移值。
在符号表之后。
63 #ifndef N_STROFF
64 #define N_STROFF(x) (N_SYMOFF(x) + (x).a_syms)
65 #endif
66
// 下面对可执行文件被加载到内存(逻辑空间)中的位置情况进行操作。
67 /* Address of text segment in memory after it is loaded. */
/* 代码段加载后在内存中的地址 */
68 #ifndef N_TXTADDR
69 #define N_TXTADDR(x) 0 // 可见,代码段从地址0开始执行。
70 #endif
71
72 /* Address of data segment in memory after it is loaded.
73 Note that it is up to you to define SEGMENT_SIZE
74 on machines not listed here. */
/* 数据段加载后在内存中的地址。
注意,对于下面没有列出名称的机器,需要你自己来定义
对应的SEGMENT_SIZE */
75 #if defined(vax) || defined(hp300) || defined(pyr)
76 #define SEGMENT_SIZE PAGE_SIZE
780
Linux内核完全剖析—基于0.12内核
77 #endif
78 #ifdef hp300
79 #define PAGE_SIZE 4096
80 #endif
81 #ifdef sony
82 #define SEGMENT_SIZE 0x2000
83 #endif /* Sony. */
84 #ifdef is68k
85 #define SEGMENT_SIZE 0x20000
86 #endif
87 #if defined(m68k) && defined(PORTAR)
88 #define PAGE_SIZE 0x400
89 #define SEGMENT_SIZE PAGE_SIZE
90 #endif
91
// 这里,Linux 0.12内核把内存页定义为4KB,段大小定义为1KB。
因此没有使用上面的定义。
92 #define PAGE_SIZE 4096
93 #define SEGMENT_SIZE 1024
94
// 以段为界的大小(进位方式)。
95 #define _N_SEGMENT_ROUND(x) (((x) + SEGMENT_SIZE - 1) & ~(SEGMENT_SIZE - 1))
96
// 代码段尾地址。
97 #define _N_TXTENDADDR(x) (N_TXTADDR(x)+(x).a_text)
98
// 数据段开始地址。
// 如果文件是OMAGIC类型的,那么数据段就直接紧随代码段后面。
否则的话数据段地址从代码 // 段后面段边界开始(1KB边界对齐)。
例如ZMAGIC类型的文件。
99 #ifndef N_DATADDR
100 #define N_DATADDR(x) \
101 (N_MAGIC(x)==OMAGIC? (_N_TXTENDADDR(x)) \
102 : (_N_SEGMENT_ROUND (_N_TXTENDADDR(x))))
103 #endif
104
105 /* Address of bss segment in memory after it is loaded. */
/* bss段加载到内存以后的地址 */
// 未初始化数据段bbs位于数据段后面,紧跟数据段。
106 #ifndef N_BSSADDR
107 #define N_BSSADDR(x) (N_DATADDR(x) + (x).a_data)
108 #endif
109
// 第110—185行是第2部分。
对目标文件中的符号表项和相关操作宏进行定义和说明。
a.out目标文 // 件中符号表项结构(符号表记录结构)。
参见程序后的详细说明。
110 #ifndef N_NLIST_DECLARED
111 struct nlist {
112 union {
113 char *n_name;
114 struct nlist *n_next;
115 long n_strx;
116 } n_un;
117 unsigned char n_type; // 该字节分成3个字段,146--154行是相应字段的屏蔽码。
118 char n_other;
119 short n_desc;
120 unsigned long n_value;
121 };
122 #endif
第14章头文件781 123
// 下面定义nlist结构中n_type字段值的常量符号。
124 #ifndef N_UNDF
125 #define N_UNDF 0
126 #endif
127 #ifndef N_ABS
128 #define N_ABS 2
129 #endif
130 #ifndef N_TEXT
131 #define N_TEXT 4
132 #endif
133 #ifndef N_DATA
134 #define N_DATA 6
135 #endif
136 #ifndef N_BSS
137 #define N_BSS 8
138 #endif
139 #ifndef N_COMM
140 #define N_COMM 18
141 #endif
142 #ifndef N_FN
143 #define N_FN 15
144 #endif
145
// 以下3个常量定义是nlist结构中n_type字段的屏蔽码(八进制表示)。
146 #ifndef N_EXT
147 #define N_EXT 1 // 0x01(0b0000,0001)符号是否是外部的(全局的)。
148 #endif
149 #ifndef N_TYPE
150 #define N_TYPE 036 // 0x1e(0b0001,1110)符号的类型位。
151 #endif
152 #ifndef N_STAB // STAB -- 符号表类型(Symbol table types)。
153 #define N_STAB 0340 // 0xe0(0b1110,0000)这几个比特用于符号调试器。
154 #endif
155
156 /* The following type indicates the definition of a symbol as being
157 an indirect reference to another symbol. The other symbol
158 appears as an undefined reference, immediately following this symbol.
159
160 Indirection is asymmetrical. The other symbol's value will be used
161 to satisfy requests for the indirect symbol, but not vice versa.
162 If the other symbol does not have a definition, libraries will
163 be searched to find a definition. */
/* 下面的类型指明对一个符号的定义是作为对另一个符号的间接引用。
紧接该
* 符号的其他的符号呈现为未定义的引用。
*
* 这种间接引用是不对称的。
另一个符号的值将被用于满足间接符号的要求,
* 但反之则不然。
如果另一个符号没有定义,则将搜索库来寻找一个定义 */
164 #define N_INDR 0xa
165
166 /* The following symbols refer to set elements.
167 All the N_SET[ATDB] symbols with the same name form one set.
168 Space is allocated for the set in the text section, and each set
169 element's value is stored into one word of the space.
170 The first word of the space is the length of the set (number of elements). 171
782
Linux内核完全剖析—基于0.12内核
172 The address of the set is made into an N_SETV symbol
173 whose name is the same as the name of the set.
174 This symbol acts like a N_DATA global symbol
175 in that it can satisfy undefined external references. */
/* 下面的符号与集合元素有关。
所有具有相同名称N_SET[ATDB]的符号
形成一个集合。
在代码部分中已为集合分配了空间,并且每个集合元素
的值存放在一个字(word)的空间中。
空间的第一个字存有集合的长度(集合元素数目)。
集合的地址被放入一个N_SETV符号中,它的名称与集合同名。
在满足未定义的外部引用方面,该符号的行为像一个N_DATA全局符号。
*/
176
177 /* These appear as input to LD, in a .o file. */
/* 以下这些符号在 .o 文件中是作为链接程序LD的输入。
*/
178 #define N_SETA 0x14 /* Absolute set element symbol */ /* 绝对集合元素符号 */ 179 #define N_SETT 0x16 /* Text set element symbol */ /* 代码集合元素符号 */ 180 #define N_SETD 0x18 /* Data set element symbol */ /* 数据集合元素符号 */ 181 #define N_SETB 0x1A /* Bss set element symbol */ /* Bss集合元素符号 */ 182
183 /* This is output from LD. */
/* 下面是LD的输出。
*/
184 #define N_SETV 0x1C /* Pointer to set vector in data area. */
/* 指向数据区中集合向量。
*/
185
186 #ifndef N_RELOCATION_INFO_DECLARED
187
188 /* This structure describes a single relocation to be performed.
189 The text-relocation section of the file is a vector of these structures, 190 all of which apply to the text section.
191 Likewise, the data-relocation section applies to the data section. */
/* 下面结构描述单个重定位操作的执行。
文件的代码重定位部分是这些结构的一个数组,所有这些适用于代码部分。
类似地,数据重定位部分用于数据部分。
*/
192
// a.out目标文件中代码和数据重定位信息结构。
193 struct relocation_info
194 {
195 /* Address (within segment) to be relocated. */
/* 段内需要重定位的地址。
*/
196 int r_address;
197 /* The meaning of r_symbolnum depends on r_extern. */
/* r_symbolnum的含义与r_extern有关。
*/
198 unsigned int r_symbolnum:24;
199 /* Nonzero means value is a pc-relative offset
200 and it should be relocated for changes in its own address
201 as well as for changes in the symbol or section specified. */
/* 非零意味着值是一个pc相关的偏移值,因而在其自己地址空间
以及符号或指定的节改变时,需要被重定位 */
202 unsigned int r_pcrel:1;
203 /* Length (as exponent of 2) of the field to be relocated.
204 Thus, a value of 2 indicates 1<<2 bytes. */
/* 需要被重定位的字段长度(是2的次方)。
因此,若值是2则表示1<<2字节数。
*/
205 unsigned int r_length:2;
206 /* 1 => relocate with value of symbol.
207 r_symbolnum is the index of the symbol
208 in file's the symbol table.
第14章头文件783 209 0 => relocate with the address of a segment.
210 r_symbolnum is N_TEXT, N_DATA, N_BSS or N_ABS
211 (the N_EXT bit may be set also, but signifies nothing). */
/* 1 => 以符号的值重定位。
r_symbolnum是文件符号表中符号的索引。
0 => 以段的地址进行重定位。
r_symbolnum是N_TEXT、N_DATA、N_BSS或N_ABS
(N_EXT比特位也可以被设置,但是毫无意义)。
*/
212 unsigned int r_extern:1;
213 /* Four bits that aren't used, but when writing an object file
214 it is desirable to clear them. */
/* 没有使用的4个比特位,但是当进行写一个目标文件时
最好将它们复位掉。
*/
215 unsigned int r_pad:4;
216 };
217 #endif /* no N_RELOCATION_INFO_DECLARED. */
218
219
220 #endif /* __A_OUT_GNU_H__ */
221
14.2.3 a.out执行文件格式
Linux内核0.12版仅支持a.out(Assembly out)执行文件和目标文件的格式,虽然这种格式目前已经渐渐不用,而使用功能更为齐全的ELF(Executable and Link Format)格式,但是由于其简单性,作为入门的学习材料比较适用。
下面全面介绍一下a.out格式。
在头文件a.out.h中声明了三个数据结构以及一些宏。
这些数据结构描述了系统上目标文件的结构。
在Linux 0.12系统中,编译产生的目标模块文件(简称模块文件)和链接生成的二进制可执行文件均采用a.out格式。
这里统称为目标文件。
一个目标文件由7部分(7节)组成。
它们依次为:(1)执行头部分(exec header)。
该部分中含有一些参数(exec结构),内核使用这些参数把执行文件加载到内存中并执行,而链接程序(ld)使用这些参数将一些模块文件组合成一个可执行文件。
这是目标文件唯一必要的组成部分。
(2)代码段部分(text segment)。
含有程序执行时被加载到内存中的指令代码和相关数据。
可以以只读形式被加载。
(3)数据段部分(data segment)。
这部分含有已经初始化过的数据,总是被加载到可读写的内存中。
(4)代码重定位部分(text relocations)。
这部分含有供链接程序使用的记录数据。
在组合目标模块文件时用于定位代码段中的指针或地址。
(5)数据重定位部分(data relocations)。
类似于代码重定位部分的作用,但是用于数据段中指针的重定位。
(6)符号表部分(symbol table)。
这部分同样含有供链接程序使用的记录数据,用于在二进制目标模块文件之间对命名的变量和函数(符号)进行交叉引用。
(7)字符串表部分(string table)。
该部分含有与符号名对应的字符串。
每个目标文件均以一个执行数据结构(exec structure)开始。
该数据结构的形式如下:struct exec {
unsigned long a_magic // 目标文件魔数。
使用N_MAGIC等宏访问。
784
Linux内核完全剖析—基于0.12内核
unsigned a_text // 代码长度,字节数。
unsigned a_data // 数据长度,字节数。
unsigned a_bss // 文件中的未初始化数据区长度,字节数。
unsigned a_syms // 文件中的符号表长度,字节数。
unsigned a_entry // 执行开始地址。
unsigned a_trsize // 代码重定位信息长度,字节数。
unsigned a_drsize // 数据重定位信息长度,字节数。
};
各个字段的功能如下:
1)a_magic——该字段含有三个子字段,分别是标志字段、机器类型标识字段和魔数字段,参见图11-1。
不过对于Linux 0.12系统其目标文件只使用了其中的魔数子字段,并使用宏N_MAGIC()来访问,它唯一地确定了二进制执行文件与其他加载的文件之间的区别。
该子字段中必须包含以下值之一:
● OMAGIC。
表示代码和数据段紧随在执行头后面并且是连续存放的。
内核将代码和数据
段都加载到可读写内存中。
编译器编译出的目标文件的魔数是OMAGIC(八进制0407)。
●NMAGIC。
同OMAGIC一样,代码和数据段紧随在执行头后面并且是连续存放的。
然
而内核将代码加载到了只读内存中,并把数据段加载到代码段后下一页可读写内存边界开始。
● ZMAGIC。
内核在必要时从二进制执行文件中加载独立的页面。
执行头部、代码段和数
据段都被链接程序处理成多个页面大小的块。
内核加载的代码页面是只读的,而数据段的页面是可写的。
链接生成的可执行文件的魔数即是ZMAGIC(0413,即0x10b)。
2)a_text——该字段含有代码段的长度值,字节数。
3)a_data——该字段含有数据段的长度值,字节数。
4)a_bss——含有bss段的长度,内核用其设置在数据段后初始的break(brk)。
内核在加载程序时,这段可写内存显现出处于数据段后面,并且初始时为全零。
5)a_syms——含有符号表部分的字节长度值。
6)a_entry——含有内核将执行文件加载到内存中以后,程序执行起始点的内存地址。
7)a_trsize——该字段含有代码重定位表的大小,是字节数。
8)a_drsize——该字段含有数据重定位表的大小,是字节数。
在a.out.h头文件中定义了几个宏,这些宏使用exec结构来测试一致性或者定位执行文件中各个部分(节)的位置偏移值。
这些宏有:
● N_BADMAG(exec)。
如果a_magic字段不能被识别,则返回非零值。
● N_TXTOFF(exec)。
代码段的起始位置字节偏移值。
● N_DATOFF(exec)。
数据段的起始位置字节偏移值。
● N_DRELOFF(exec)。
数据重定位表的起始位置字节偏移值。
● N_TRELOFF(exec)。
代码重定位表的起始位置字节偏移值。
● N_SYMOFF(exec)。
符号表的起始位置字节偏移值。
● N_STROFF(exec)。
字符串表的起始位置字节偏移值。
重定位记录具有标准的格式,它使用重定位信息(relocation_info)结构来描述,如下所示:
struct relocation_info
{
int r_address; // 段内需要重定位的地址。
第14章头文件785
unsigned int r_symbolnum:24; // 含义与r_extern有关。
指定符号表中一个符号或者一个段。
unsigned int r_pcrel:1; // PC相关标志。
unsigned int r_length:2; // 要被重定位字段长度(2的次方)。
若值是2则1<<2字节数。
unsigned int r_extern:1; // 1 => 以符号的值重定位。
0 => 以段的地址进行重定位。
unsigned int r_pad:4; // 没有使用的4个位,但最好将它们复位掉。
};
该结构中各字段的含义如下:
1)r_address——该字段含有需要链接程序处理(编辑)的指针的字节偏移值。
代码重定位的偏移值是从代码段开始处计数的,数据重定位的偏移值是从数据段开始处计算的。
链接程序会将已经存储在该偏移处的值与使用重定位记录计算出的新值相加。
2)r_symbolnum——该字段含有符号表中一个符号结构的序号值(不是字节偏移值)。
链接程序在算出符号的绝对地址以后,就将该地址加到正在进行重定位的指针上。
(如果r_extern 比特位是0,那么情况就不同,见下面。
)
3)r_pcrel——如果设置了该位,链接程序就认为正在更新一个指针,该指针使用pc相关寻址方式,是属于机器码指令部分。
当运行程序使用这个被重定位的指针时,该指针的地址被隐式地加到该指针上。
4)r_length——该字段含有指针长度的2的次方值:0表示1字节长,1表示2字节长,2表示4字节长。
5)r_extern——如果被置位,表示该重定位需要一个外部引用;此时链接程序必须使用一个符号地址来更新相应指针。
当该位是0时,则重定位是“局部”的。
链接程序更新指针以反映各个段加载地址中的变化,而不是反映一个符号值的变化。
在这种情况下,r_symbolnum字段的内容是一个n_type值;这类字段告诉链接程序被重定位的指针指向那个段。
6)r_pad——Linux系统中没有使用的4个比特位。
在写一个目标文件时最好全置0。
符号将名称映射为地址(或者更通俗地讲是字符串映射到值)。
由于链接程序对地址的调整,一个符号的名称必须用来表示其地址,直到已被赋予一个绝对地址值。
符号是由符号表中固定长度的记录以及字符串表中的可变长度名称组成。
符号表是nlist结构的一个数组,如下所示:
struct nlist {
union {
char *n_name;
struct nlist *n_next;
long n_strx;
} n_un;
unsigned char n_type; // 该字节分成3个字段,146-154行是相应字段的屏蔽码。
char n_other;
short n_desc;
unsigned long n_value;
};
其中各字段的含义为:
1)n_un.n_strx——含有本符号的名称在字符串表中的字节偏移值。
当程序使用nlist()函数访问一个符号表时,该字段被替换为n_un.n_name字段,这是内存中字符串的指针。
2)n_type——用于链接程序确定如何更新符号的值。
使用第146~154行开始的位屏蔽(bitmasks)码可以将8比特宽度的n_type字段分割成三个子字段,如图14-2所示。
对于N_EXT类型位置位的符号,链接程序将它们看作是“外部的”符号,并且允许其他二进制目标文件对它们的引用。
N_TYPE
786
Linux内核完全剖析—基于0.12内核
屏蔽码用于链接程序感兴趣的比特位:Array
●N_UNDF。
一个未定义的符号。
链接程
序必须在其他二进制目标文件中定位
一个具有相同名称的外部符号,以确定
图14-2 符号类型属性n_type字段该符号的绝对数据值。
特殊情况下,如
果n_type字段是非零值,并且没有二进制文件定义了这个符号,则链接程序在BSS段中将该符号解析为一个地址,保留长度等于n_value的字节。
如果符号在多于一个二进制目标文件中都没有定义并且这些二进制目标文件对其长度值不一致,则链接程序将选择所有二进制目标文件中最大的长度。
● N_ABS。
一个绝对符号。
链接程序不会更新一个绝对符号。
●N_TEXT。
一个代码符号。
该符号的值是代码地址,链接程序在合并二进制目标文件时
会更新其值。
● N_DA TA。
一个数据符号。
与N_TEXT类似,但是用于数据地址。
对应代码和数据符号
的值不是文件的偏移值而是地址;为了找出文件的偏移,就有必要确定相关部分开始加载的地址并减去它,然后加上该部分的偏移。
● N_BSS。
一个BSS符号。
与代码或数据符号类似,但在二进制目标文件中没有对应的偏移。
● N_FN。
一个文件名符号。
在合并二进制目标文件时,链接程序会将该符号插入在二进制
文件中的符号之前。
符号的名称就是给予链接程序的文件名,而其值是二进制文件中首个代码段地址。
链接和加载时不需要文件名符号,但对于调式程序非常有用。
● N_STAB。
屏蔽码用于选择符号调式程序(例如gdb)感兴趣的位。
其值在stab()中说明。
3)n_other——该字段按照n_type确定的段,提供有关符号重定位操作的符号独立性信息。
目前,n_other字段的最低4位含有两个值之一:AUX_FUNC和AUX_OBJECT(有关定义参见<link.h>)。
AUX_FUNC将符号与可调用的函数相关,AUX_OBJECT将符号与数据相关,无论它们是位于代码段还是数据段。
该字段主要用于链接程序ld,用于动态可执行程序的创建。
4)n_desc——保留给调式程序使用;链接程序不对其进行处理。
不同的调试程序将该字段用作不同的用途。
5)n_value——含有符号的值。
对于代码、数据和BSS符号,这是一个地址;对于其他符号(例如调式程序符号),值可以是任意的。
字符串表由长度为unsigned long后跟一null结尾的符号字符串组成。
长度代表整个表的字节大小,所以在32位的机器上其最小值(即第1个字符串的偏移)总是4。
14.3 const.h文件
14.3.1 功能描述
该文件定义了i节点中文件属性和类型i_mode字段所用到的一些标志位常量符号。
14.3.2 代码注释
程序14-2 linux/include/const.h
1 #ifndef _CONST_H。