5.13 calibrate_delay()
近似计算BogoMIPS数字的内核函数。作为第一次估算,calibrate_delay计算出在每一秒内执行多少次__delay循环,也就是每个定时器滴答(timer tick)―百分之一秒内延时循环可以执行多少次。这种计算只是一种估算,结果并不能精确到纳秒,但这个数字供内核使用已经足够精确了。
BogoMIPS的数字由内核计算并在系统初始化的时候打印。它近似的给出了每秒钟CPU可以执行一个短延迟循环的次数。在内核中,这个结果主要用于需要等待非常短周期的设备驱动程序――例如,等待几微秒并查看设备的某些信息是否已经可用。
计算一个定时器滴答内可以执行多少次循环需要在滴答开始时就开始计数,或者应该尽可能与它接近。全局变量jiffies中存储了从内核开始保持跟踪时间开始到现在已经经过的定时器滴答数, jiffies保持异步更新,在一个中断内——每秒一百次,内核暂时挂起正在处理的内容,更新变量,然后继续刚才的工作。
5.14 mem_init()
内存初始化。本函数通过内存碎片的重组等方法标记当前剩余内存, 设置内存上下界和页表项初始值。
5.15 kmem_cache_sizes_init()
内核内存管理器的初始化,也就是初始化cache和SLAB分配机制。
5.16 pgtable_cache_init()
页表cache初始化。
5.17 fork_init()
这里根据硬件的内存情况,如果计算出的max_threads数量太大,可以自行定义。
5.18 proc_caches_init();
为proc文件系统创建高速缓冲
5.19 vfs_caches_init(num_physpages);
为VFS创建SLAB高速缓冲
5.20 buffer_init(num_physpages);
初始化buffer
5.21 page_cache_init(num_physpages);
页缓冲初始化
5.22 signals_init();
创建信号队列高速缓冲
5.23 proc_root_init();
在内存中创建包括根结点在内的所有节点
5.24 check_bugs();
检查与处理器相关的bug
5.25 smp_init();
5.26 rest_init(); 此函数调用kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL)函数。
5.26.1 kernel_thread()函数分析
这里调用了arch/armnommu/kernel/process.c中的函数kernel_thread,kernel_thread函数中通过 __syscall(clone) 创建新线程。__syscall(clone)函数参见armnommu/kernel目录下的entry-common.S文件。
5.26.2 init()完成下列功能:
Init()函数通过kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL)的回调函数执行,完成下列功能。
do_basic_setup()
在该函数里,sock_init()函数进行网络相关的初始化,占用相当多的内存,如果所开发系统不支持网络功能,可以把该函数的执行注释掉。
do_initcalls()实现驱动的初始化, 这里需要与vmlinux.lds联系起来看才能明白其中奥妙。
static void __init do_initcalls(void)
{
initcall_t *call;
call = &__initcall_start;
do {
(*call)();
call++;
} while (call < &__initcall_end);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_tasks();
}
查看 /arch/i386/vmlinux.lds,其中有一段代码
__initcall_start = .;
.initcall.init : { *(.initcall.init) }
__initcall_end = .;
其含义是__initcall_start指向代码节.initcall.init的节首,而__initcall_end指向.initcall.init的节尾。
do_initcalls所作的是系统中有关驱动部分的初始化工作,那么这些函数指针数据是怎样放到了.initcall.init节呢?在include/linux/init.h文件中有如下3个定义:
1. #define __init_call __attribute__ ((unused,__section__ (".initcall.init")))
__attribute__的含义就是构建一个在.initcall.init节的指向初始函数的指针。
2. #define __initcall(fn) static initcall_t __initcall_##fn __init_call = fn
##意思就是在可变参数使用宏定义的时候构建一个变量名称为所指向的函数的名称,并且在前面加上__initcall_
3. #define module_init(x) __initcall(x);
很多驱动中都有类似module_init(usb_init)的代码,通过该宏定义逐层解释存放到.initcall.int节中。
blkmem相关的修改(do_initcalls()初始化驱动时执行此代码)
在blkmem_init()函数中,调用了blk_init_queue()函数,blk_init_queue()函数调用了 blk_init_free_list()函数,blk_init_free_list()函数又调用了blk_grow_request_list() 函数,在这个函数中会kmem_cache_alloc出nr_requests个request结构体。
这里如果nr_requests的值太大,则将占用过多的内存,将造成硬件内存不够,因此可以根据实际情况将其替换成了较小的值,比如32、16等。
free_initmem
这个函数在arch/armnommu/mm/init.c文件中,其作用就是对init节的释放,也可以通过修改代码指定为不释放。
5.26.3 init执行过程
在内核引导结束并启动init之后,系统就转入用户态的运行,在这之后创建的一切进程,都是在用户态进行。这里先要清楚一个概念:就是init进程虽然是从内核开始的,即在前面所讲的init/main.c中的init()函数在启动后就已经是一个核心线程,但在转到执行init程序(如 /sbin/init)之后,内核中的init()就变成了/sbin/init程序,状态也转变成了用户态,也就是说核心线程变成了一个普通的进程。这样一来,内核中的init函数实际上只是用户态init进程的入口,它在执行execve("/sbin/init",argv_init, envp_init)时改变成为一个普通的用户进程。这也就是exec函数的乾坤大挪移法,在exec函数调用其他程序时,当前进程被其他进程“灵魂附体”。
除此之外,它们的代码来源也有差别,内核中的init()函数的源代码在/init/main.c中,是内核的一部分。而/sbin/init程序的源代码是应用程序。
init程序启动之后,要完成以下任务:检查文件系统,启动各种后台服务进程,最后为每个终端和虚拟控制台启动一个getty进程供用户登录。由于所有其它用户进程都是由init派生的,因此它又是其它一切用户进程的父进程。
init进程启动后,按照/etc/inittab的内容进程系统设置。很多嵌入式系统用的是BusyBox的init,它与一般所使用的init不一样,会先执行/etc/init.d/rcS而非/etc/rc.d/rc.sysinit。
小结:
本想多整理一些相关资料,无奈又要开始新项目的奔波,start_kernel()函数也刚好差不多讲完了,分析的不是很深入,希望对嵌入式Linux移植的网友们有一些帮助。最后列举下面几处未整理的知识点,有兴趣的网友可作进一步探讨。
text.init和data.init说明
__init标示符在gcc编译器中指定将该函数置于内核的特定区域。在内核完成自身初始化之后,就试图释放这个特定区域。实际上,内核中存在两个这样的区域,.text.init和.data.init――第一个是代码初始化使用的,另外一个是数据初始化使用的。另外也可以看到 __initfunc和__initdata标志,前者和__init类似,标志初始化专用代码,后者则标志初始化专用数据。
System.map内核符号表
irq的处理过程
Linux内核调度过程
阅读(1085) | 评论(0) | 转发(0) |