新闻资讯
Group news
青岛广盛源肥业有限公司    您的位置: 首页  >  新闻资讯  >  正文

你了解过Linux--start_kernel()函数?

2019年10月12日 文章来源:网络整理 热度:167℃ 作者:刘英

你了解过Linux--start_kernel()函数?

这个入口的函数是start_kernel函数,它主要更进一步地初始化系统相关的内容,以便系统进入一种服务状态,提供一种虚拟机的服务,提供各种API调用的服务。
在start_kernel函数里,需要非常注意的是里面初始化函数的顺序,这些初始化函数不能随便调换初始化顺序,否则就会导致系统运行出错。
由于这个函数的内容非常多,涉及的内容也非常广泛,每个函数都有一个比较大的概念,一种原理,一种想法。
因此,对于这个函数的学习需要很多时间,需要有漫长学习的心理准备。
由于本书基于ARM体系的一种结构学习,其它与此体系结构无关的代码,就不再分析介绍。好了,现在就来开始学习第一节的内容,代码如下:

asmlinkage void __init start_kernel(void){ char * command_line; extern const struct kernel_param __start___param[], __stop___param[];

看了这段代码,首先发现asmlinkage和__init与一般开发C语言的应用程序有着明显的差别,导致看不懂这两个宏到底是用来做干什么用的。
其实这两个宏是写内核代码的一种特定表示,一种尽可能快的思想表达,一种尽可能占用空间少的思路。
asmlinkage是一个宏定义,它的作用主要有两个,一个是让传送给函数的参数全部使用栈式传送,不用寄存器来传送。
因为寄存器的个数有限,使用栈可以传送更多的参数,比如在X86的CPU里只能使用6个寄存器传送,只能传送4个参数,而使用栈就没有这种限制;另外一个用处是声明这个函数是给汇编代码调用的。
不过在ARM体系里,并没有使用栈传送参数的特性,原因何在?由于ARM体系的寄存器个数比较多,多达13个,这样绝大多数的函数参数都可以通过寄存器来传送,达到高效的目标。
因此,看到文件./include/linux/linkage.h里的asmlinkage宏定义如下:

#include #include #ifdef __cplusplus#define CPP_ASMLINKAGE extern "C"#else#define CPP_ASMLINKAGE#endif#ifndef asmlinkage#define asmlinkage CPP_ASMLINKAGE#endif#ifndef asmregparm# define asmregparm#endif

在这里可以看到asmlinkage,其实没有定义,所以ARM体系里还是通过寄存器来传送参数的。如果看一下X86下的代码,就会定义如下:

#ifdef CONFIG_X86_32#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

这里定义asmlinkage为通过栈传送参数。

接着来看另外一个宏定义__init,这个宏定义主要用来标志这个函数编译出来的目标代码放在那一段里。
对于应用程序的编译和连接,不需要作这样的考虑,但是对于内核代码来说,就需要了,因为不同的段代码有着不同的作用,比如初始化段的代码,当系统运行正常以后,这段代码就没有什么用了,聪明的做法就是回收这段代码占用的内存,让内核的代码占最少的内存。

还有另外一个作用,比如同一段的代码都是编译在一起,让相关联的代码尽可能同在一片内存里,这样当CPU加载代码到缓存时,就可以一起命中,提高缓存的命中率,这样就大大提高代码的执行速度。
宏__init定义在文件./include/linux/init.h里,代码如下:

/* These are for everybody (although not all archs will actually discard it in modules) */#define __init __section(.init.text) __cold notrace

使用这个宏声明的函数,编译时就会把目标代码放到段.init.text里,这段都是放置初始化的代码。
最后看到声明一个字符的指针command_line,这个指针是指向命令行参数的指针,主要用来指向引导程序传送给内核的命令行参数,在后面的函数setup_arch和函数setup_command_line就会对它进行处理。

smp_setup_processor_id();

紧跟参数后面的,就是调用函数smp_setup_processor_id()了,这个函数主要作用是获取当前正在执行初始化的处理器ID。
如果仔细地阅读完初始化函数start_kernel,就会发现里面还有调用smp_processor_id()函数,这两个函数都是获取多处理器的ID,为什么会需要两个函数呢?
其实这里有一个差别的,smp_setup_processor_id()函数可以不调用setup_arch()初始化函数就可以使用,而smp_processor_id()函数是一定要调用setup_arch()初始化函数后,才能使用。
smp_setup_processor_id()函数是直接获取对称多处理器的ID,而smp_processor_id()函数是获取变量保存的处理器ID,因此一定要调用初始化函数。
由于smp_setup_processor_id()函数不用调用初始化函数,可以放在内核初始化start_kernel函数的最前面使用,而函数smp_processor_id()只能放到setup_arch()函数调用的后面使用了。
smp_setup_processor_id()函数每次都要中断CPU去获取ID,这样效率比较低。在这个函数里,还需要懂得另外一个概念,就是对称多处理器(SymmetricalMulTI-Processing)。
由于单处理器的频率已经慢慢变得不能再高了,那么处理器的计算速度还要提高,还有别的办法吗?这样自然就想到多个处理器的技术。
这就好比物流公司,有很多货只让一辆卡车在高速公路上来回运货,这样车的速度已经最快了,运的货就一定了,不可能再多得去。
那么物流公司想提高运货量,那只能多顾用几台卡车了,这样运货量就比以前提高了。处理器的制造厂家自然也想到这样的办法,就是几个处理器放到一起,这样就可以提高处理速度。
接着下来的问题又来,那么这几个处理器怎么样放在一起,成本最低,性能最高。考虑到这样的一种情况,处理器只有共享主内存、总线、外围设备,其它每个处理器是独立的,这样可以省掉很多外围硬件成本。
当然所有这些处理器还共享一个操作系统,这样的结构就叫做对称多处理器(SymmetricalMulTI-Processing)。在对称多处理器系统里,所有处理器只有在初始化阶段处理有主从之分,到系统初始化完成之后,大家是平等的关系,没有主从处理器之分了。
在内核里所有以smp开头的函数都是处理对称多处理器相关内容的,对称多处理器与单处理器在操作系统里,主要区别是引导处理器与应用处理器,每个处理器不同的缓存,中断协作,锁的同步。因此,在内核初始化阶段需要区分,在与缓存同步数据需要处理,在中断方面需要多个处理协作执行,在多个进程之间要做同步和通讯。如果内核只是有单处理器系统,smp_setup_processor_id()函数是是空的,不必要做任保的处理。

/* * Need to run as early as possible, to iniTIalize the * lockdep hash: */ lockdep_init();

上一篇:从misc子系统到3+2+1设备识别驱动框架


下一篇:关于Linux你可能不是非常了解的七件事

友情链接
Links