Linux内核启动.docx
《Linux内核启动.docx》由会员分享,可在线阅读,更多相关《Linux内核启动.docx(28页珍藏版)》请在冰豆网上搜索。
![Linux内核启动.docx](https://file1.bdocx.com/fileroot1/2023-2/3/488d8f44-1dfc-4f7d-b497-80bf227dcb09/488d8f44-1dfc-4f7d-b497-80bf227dcb091.gif)
Linux内核启动
linuxsubsys_initcall
分类:
LINUX2011-09-0515:
33310人阅读评论(0)收藏举报
宏定义__define_initcall(level,fn)对于内核的初始化很重要,他指示编译器在编译的时候,将一系列初始化函数的起始地址值按照一定的顺序放在一个section中。
在内核初始化段,do_initcalls()将按顺序从该section中以函数指针的形式取出这些函数的起始地址,来依次完成相应的初始化。
于内核某些部分的初始化需要依赖于其他某些部分的初始化的完成,因此这个顺序排列常常很重要。
下面将从__define_initcall(level,fn)宏定义的代码分析入手,依次分析名称为initcall.init的section的结构,最后分析内核初始化函数do_initcalls()是怎样利用宏定义__define_initcall(level,fn)及其相关的衍生的7个宏宏定义,来实现内核某些部分的顺序初始化的。
1、分析__define_initcall(level,fn)宏定义
1)这个宏的定义位于inlclude\linux\init.h中:
#define__define_initcall(level,fn) \
staticinitcall_t__initcall_##fn \
__attribute__((__section__(".initcall"level".init")))\
=fn
其中initcall_t是个函数指针类型:
typedefint(*initcall_t)(void);
而属性__attribute__((__section__()))则表示把对象放在一个这个由括号中的名称所指代的section中。
所以这个宏定义的的含义是:
1)声明一个名称为__initcall_##fn的函数指针(其中##表示替换连接,);2)将这个函数指针初始化为fn;3)编译的时候需要把这个函数指针变量放置到名称为".initcall"level".init"的section中(比如level="1",代表这个section的名称是".initcall1.init")。
2)举例:
__define_initcall(6,pci_init)
上述宏调用的含义是:
1)声明一个函数指针__initcall_pic_init=pci_init;且2)这个指针变量__initcall_pic_init需要放置到名称为.initcall6.init的section中(其实质就是将这个函数pic_init的首地址放置到了这个section中)。
3)这个宏一般并不直接使用,而是被定义成下述其他更简单的7个衍生宏
这些衍生宏宏的定义也位于inlclude\linux\Init.h中:
#definecore_initcall(fn) __define_initcall("1",fn)
#definepostcore_initcall(fn) __define_initcall("2",fn)
#definearch_initcall(fn) __define_initcall("3",fn)
#definesubsys_initcall(fn) __define_initcall("4",fn)
#definefs_initcall(fn) __define_initcall("5",fn)
#definedevice_initcall(fn) __define_initcall("6",fn)
#definelate_initcall(fn) __define_initcall("7",fn)
因此通过宏core_initcall()来声明的函数指针,将放置到名称为.initcall1.init的section中,而通过宏postcore_initcall()来声明的函数指针,将放置到名称为.initcall2.init的section中,依次类推。
4)举例:
device_initcall(pci_init)
解释同上1-2)。
2、和初始化调用有关section--initcall.init被分成了7个子section
1)他们依次是.initcall1.init、.initcall2.init、...、.initcall7.init
2)按照先后顺序依次排列
3)他们的定义在文档vmlinux.lds.S中
例如对于i386+,在i386\kernel\vmlinux.lds.S中有:
__initcall_start=.;
.initcall.init:
{
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
}
__initcall_end=.;
而在makefile中有
LDFLAGS_vmlinux+=-Tarch/$(ARCH)/kernel/vmlinux.lds.s
4)在这7个section总的开始位置被标识为__initcall_start,而在结尾被标识为__initcall_end。
3、内核初始化函数do_basic_setup():
do_initcalls()将从.initcall.init中,也就是这7个section中依次取出任何的函数指针,并调用这些函数指针所指向的函数,来完成内核的一些相关的初始化。
这个函数的定义位于init\main.c中:
externinitcall_t__initcall_start,__initcall_end;
staticvoid__initdo_initcalls(void)
{
initcall_t*call;
....
for(call=&__initcall_start;call
***********************************************************************
假如您希望某个初始化函数在内核初始化阶段就被调用,那么您就应该使用宏__define_initcall(level,fn)或其7个衍生宏来把这个初始化函数fn的起始地址按照初始化的顺序放置到相关的section中。
内核初始化时的do_initcalls()将从这个section中按顺序找到这些函数来执行。
GNU汇编器手册之attribute
2010年01月21日星期四17:
05
说一下GCC编译器相关__attribute__机制。
__attribute__可以设置函数属性(FunctionAttribute)、变量属性(VariableAttribute)和类型属性(TypeAttribute)。
__attribute__书写特征是:
__attribute__前后都有两个下划线,并切后面会紧跟一对原括弧,括弧里面是相应的__attribute__参数。
__attribute__语法格式为:
__attribute__((attribute-list))
其位置约束为:
放于声明的尾部“;”之前。
函数属性(FunctionAttribute)
函数属性可以帮助开发者把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大。
__attribute__机制也很容易同非GNU应用程序做到兼容之功效。
GNUCC需要使用–Wall编译器来击活该功能,这是控制警告信息的一个很好的方式。
下面介绍几个常见的属性参数。
__attribute__format
该__attribute__属性可以给被声明的函数加上类似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:
第几个参数为格式化字符串(formatstring);
n:
参数集合中的第一个,即参数“…”里的第一个参数在函数参数总数排在第几,注意,有时函数参数里还有“隐身”的呢,后面会提到;
在使用上,__attribute__((format(printf,m,n)))是常用的,而另一种却很少见到。
下面举例说明,其中myprint为自己定义的一个带有可变参数的函数,其功能类似于printf:
//m=1;n=2
externvoidmyprint(constchar*format,...)__attribute__((format(printf,1,2)));
//m=2;n=3
externvoidmyprint(intl,constchar*format,...)__attribute__((format(printf,2,3)));
需要特别注意的是,如果myprint是一个函数的成员函数,那么m和n的值可有点“悬乎”了,例如:
//m=3;n=4
externvoidmyprint(intl,constchar*format,...)__attribute__((format(printf,3,4)));
其原因是,类成员函数的第一个参数实际上一个“隐身”的“this”指针。
(有点C++基础的都知道点this指针,不知道你在这里还知道吗?
)
这里给出测试用例:
attribute.c,代码如下:
1:
2:
externvoidmyprint(constchar*format,...)__attribute__((format(printf,1,2)));
3:
4:
voidtest()
5:
{
6:
myprint("i=%d",6);
7:
myprint("i=%s",6);
8:
myprint("i=%s","abc");
9:
myprint("%s,%d,%d",1,2);
10:
}
运行$gcc–Wall–cattribute.cattribute后,输出结果为:
attribute.c:
Infunction`test':
attribute.c:
7:
warning:
formatargumentisnotapointer(arg2)
attribute.c:
9:
warning:
formatargumentisnotapointer(arg2)
attribute.c:
9:
warning:
toofewargumentsforformat
如果在attribute.c中的函数声明去掉__attribute__((format(printf,1,2))),再重新编译,既运行$gcc–Wall–cattribute.cattribute后,则并不会输出任何警告信息。
注意,默认情况下,编译器是能识别类似printf的“标准”库函数。
__attribute__noreturn
该属性通知编译器函数从不返回值,当遇到类似函数需要返回值而却不可能运行到返回值处就已经退出来的情况,该属性可以避免出现错误信息。
C库函数中的abort()和exit()的声明格式就采用了这种格式,如下所示:
externvoidexit(int) __attribute__((noreturn));
externvoidabort(void)__attribute__((noreturn));
为了方便理解,大家可以参考如下的例子:
//name:
noreturn.c;测试__attribute__((noreturn))
externvoidmyexit();
inttest(intn)
{
if(n>0)
{
myexit();
/*程序不可能到达这里*/
}
else
return0;
}
编译显示的输出信息为:
$gcc–Wall–cnoreturn.c
noreturn.c:
Infunction`test':
noreturn.c:
12:
warning:
controlreachesendofnon-voidfunction
警告信息也很好理解,因为你定义了一个有返回值的函数test却有可能没有返回值,程序当然不知道怎么办了!
加上__attribute__((noreturn))则可以很好的处理类似这种问题。
把
externvoidmyexit();
修改为:
externvoidmyexit()__attribute__((noreturn));
之后,编译不会再出现警告信息。
__attribute__const
该属性只能用于带有数值类型参数的函数上。
当重复调用带有数值参数的函数时,由于返回值是相同的,所以此时编译器可以进行优化处理,除第一次需要运算外,其它只需要返回第一次的结果就可以了,进而可以提高效率。
该属性主要适用于没有静态状态(staticstate)和副作用的一些函数,并且返回值仅仅依赖输入的参数。
为了说明问题,下面举个非常“糟糕”的例子,该例子将重复调用一个带有相同参数值的函数,具体如下:
externintsquare(intn)__attribute__((const));
...
for(i=0;i<100;i++)
{
total+=square(5)+i;
}
通过添加__attribute__((const))声明,编译器只调用了函数一次,以后只是直接得到了相同的一个返回值。
事实上,const参数不能用在带有指针类型参数的函数中,因为该属性不但影响函数的参数值,同样也影响到了参数指向的数据,它可能会对代码本身产生严重甚至是不可恢复的严重后果。
并且,带有该属性的函数不能有任何副作用或者是静态的状态,所以,类似getchar()或time()的函数是不适合使用该属性的。
no_instrument_function
如果使用了-finstrument-functions,将在绝大多数用户编译的函数的入口和出口点调用profiling函数。
使用该属性,将不进行instrument操作。
constructor/destructor
若函数被设定为constructor属性,则该函数会在main()函数执行之前被自动的执行。
类似的,若函数被设定为destructor属性,则该函数会在main()函数执行之后或者exit()被调用后被自动的执行。
拥有此类属性的函数经常隐式的用在程序的初始化数据方面。
这两个属性还没有在面向对象C中实现。
变量属性(VariableAttributes)
关键字__attribute__也可以对变量(variable)或结构体成员(structurefield)进行属性设置。
这里给出几个常用的参数的解释,更多的参数可参考本文给出的连接。
在使用__attribute__参数时,你也可以在参数的前后都加上“__”(两个下划线),例如,使用__aligned__而不是aligned,这样,你就可以在相应的头文件里使用它而不用关心头文件里是否有重名的宏定义。
aligned(alignment)
该属性规定变量或结构体成员的最小的对齐格式,以字节为单位。
例如:
intx__attribute__((aligned(16)))=0;
编译器将以16字节(注意是字节byte不是位bit)对齐的方式分配一个变量。
也可以对结构体成员变量设置该属性,例如,创建一个双字对齐的int对,可以这么写:
structfoo{intx[2]__attribute__((aligned(8)));};
如上所述,你可以手动指定对齐的格式,同样,你也可以使用默认的对齐方式。
如果aligned后面不紧跟一个指定的数字值,那么编译器将依据你的目标机器情况使用最大最有益的对齐方式。
例如:
shortarray[3]__attribute__((aligned));
选择针对目标机器最大的对齐方式,可以提高拷贝操作的效率。
aligned属性使被设置的对象占用更多的空间,相反的,使用packed可以减小对象占用的空间。
需要注意的是,attribute属性的效力与你的连接器也有关,如果你的连接器最大只支持16字节对齐,那么你此时定义32字节对齐也是无济于事的。
packed
使用该属性可以使得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐。
下面的例子中,x成员变量使用了该属性,则其值将紧放置在a的后面:
structtest
{
chara;
intx[2]__attribute__((packed));
};
其它可选的属性值还可以是:
cleanup,common,nocommon,deprecated,mode,section,shared,tls_model,transparent_union,unused,vector_size,weak,dllimport,dlexport等,
类型属性(TypeAttribute)
关键字__attribute__也可以对结构体(struct)或共用体(union)进行属性设置。
大致有六个参数值可以被设定,即:
aligned,packed,transparent_union,unused,deprecated和may_alias。
在使用__attribute__参数时,你也可以在参数的前后都加上“__”(两个下划线),例如,使用__aligned__而不是aligned,这样,你就可以在相应的头文件里使用它而不用关心头文件里是否有重名的宏定义。
aligned(alignment)
该属性设定一个指定大小的对齐格式(以字节为单位),例如:
structS{shortf[3];}__attribute__((aligned(8)));
typedefintmore_aligned_int__attribute__((aligned(8)));
该声明将强制编译器确保(尽它所能)变量类型为structS或者more-aligned-int的变量在分配空间时采用8字节对齐方式。
如上所述,你可以手动指定对齐的格式,同样,你也可以使用默认的对齐方式。
如果aligned后面不紧跟一个指定的数字值,那么编译器将依据你的目标机器情况使用最大最有益的对齐方式。
例如:
structS{shortf[3];}__attribute__((aligned));
这里,如果sizeof(short)的大小为2(byte),那么,S的大小就为6。
取一个2的次方值,使得该值大于等于6,则该值为8,所以编译器将设置S类型的对齐方式为8字节。
aligned属性使被设置的对象占用更多的空间,相反的,使用packed可以减小对象占用的空间。
需要注意的是,attribute属性的效力与你的连接器也有关,如果你的连接器最大只支持16字节对齐,那么你此时定义32字节对齐也是无济于事的。
packed
使用该属性对struct或者union类型进行定义,设定其类型的每一个变量的内存约束。
当用在enum类型定义时,暗示了应该使用最小完整的类型(itindicatesthatthesmallestintegraltypeshouldbeused)。
下面的例子中,my-packed-struct类型的变量数组中的值将会紧紧的靠在一起,但内部的成员变量s不会被“pack”,如果希望内部的成员变量也被packed的话,my-unpacked-struct也需要使用packed进行相应的约束。
structmy_unpacked_struct
{
charc;
inti;
};
structmy_packed_struct
{
charc;
inti;
structmy_unpacked_structs;
}__attribute__((__packed__));
2010-01-1118:
27:
01
gcc的__attribute__编译属性
要了解LinuxKernel代码的分段信息,需要了解一下gcc的__attribute__的编绎属性,__attribute__主要用于改变所声明或定义的函数或数据的特性,它有很多子项,用于改变作用对象的特性。
比如对函数,noline将禁止进行内联扩展、noreturn表示没有返回值、pure表明函数除返回值外,不会通过其它(如全局变量、指针)对函数外部产生任何影响。
但这里我们比较感兴趣的是对代码段起作用子项section。
__attribute__的section子项的使用格式为:
__attribute__((section("section_name")))
其作用是将作用的函数或数据放入指定名为"section_name"输入段。
这里还要注意一下两个概念:
输入段和输出段
输入段和输出段是相对于要生成最终的elf或binary时的Link过程说的,Link过程的输入大都是由源代码编绎生成的目标文件.o,那么这些.o文件中包含的段相对link过程来说就是输入段,而Link的输出一般是可执行文件elf或库等,这些输出文件中也包含有段,这些输出文件中的段就叫做输出段。
输入段和输出段本来没有什么必然的联系,是互相独立,只是在Link过程中,Link程序会根据一定的规则(这些规则其实来源于LinkScript),将不同的输入段重新组合到不同的输出段中,即使是段的名字,输入段和输出段可以完全不同。
其用法举例如下:
intvar__attribute__((section(".xdata")))=