该文件是内核最先执行的一个文件,包括内核入口ENTRY(stext)到start_kernel间的初始化代码,主要作用是检查CPU ID, Architecture Type,初始化BSS等操作,并跳到start_kernel函数。在执行前,处理器应满足以下状态:
if(!++current-
kernel_flag 是一个内核大自旋锁,所有进程都通过这个大锁来实现向内核态的迁移。只有获得这个大自旋
锁的处理器可以进入内核,如中断处理程序等。在任何一对 lock_kernel/unlock_kernel函数里至多可以有一个程序占用CPU。 进程的lock_depth成员初始化为-1,在 kerenl/fork.c文件中设置。在它小于0时
bank指定每块内存的范围,PHYS _OFFSET指定某块内存块的开始地址,MEM_SIZE指定某块内存块长度。
PHYS _OFFSET是内存的开始地址,MEM_SIZE就是内存的结束地址。这个结构在接下来内存的初始化代码中
每一个任务都有一个mm_struct结构管理其内存空间,init_mm 是内核的mm_struct。其中设置成员变量
swapper_pg_dir是内核的页目录,ARM体系结构的内核页目录大小定义为16k。init_mm定义了整个内核的
1.2.6 paging_init(&meminfo, mdesc)创建内核页表,映射所有物理内存和IO空间,对于不同的处理器,该函数差别比较大。下面简单描述一下ARM体系结构的存储系统及MMU相关的概念。
在ARM存储系统中,使用内存管理单元(MMU)实现虚拟地址到实际物理地址的映射。利用MMU,可把SDRAM的
地址完全映射到0x0起始的一片连续地址空间,而把原来占据这片空间的FLASH或者ROM映射到其他不相冲突
映射到0x9000 0000~0x90FFFFFF(此处地址空间为空闲,未被占用)。映射完成后,如果处理器发生异常,
假设依然为IRQ中断,PC指针指向0xl8处的地址,而这个时候PC实际上是从位于物理地址的0x3000 0018处
读取指令。通过MMU的映射,则可实现程序完全运行在SDRAM之中。在实际的应用中.可能会把两片不连续的
物理地址空间分配给SDRAM。而在操作系统中,习惯于把SDRAM的空间连续起来,方便内存管理,且应用程序
申请大块的内存时,操作系统内核也可方便地分配。通过MMU可实现不连续的物理地址空间映射为连续的虚拟
地址空间。操作系统内核或者一些比较关键的代码,一般是不希望被用户应用程序访问。通过MMU可以控制地
MMU的实现过程,实际上就是一个查表映射的过程。建立页表是实现MMU功能不可缺少的一步。页表位于系统的
内存中,页表的每一项对应于一个虚拟地址到物理地址的映射。每一项的长度即是一个字的长度(在ARM中,
一个字的长度被定义为4Bytes)。页表项除完成虚拟地址到物理地址的映射功能之外,还定义了访问权限和缓
MMU的映射分为两种,一级页表的变换和二级页表变换。两者的不同之处就是实现的变换地址空间大小不同。
一级页表变换支持1 M大小的存储空间的映射,而二级可以支持64 kB,4 kB和1 kB大小地址空间的映射。
动态表(页表)的大小=表项数*每个表项所需的位数,即为整个内存空间建立索引表时,需要多大空间存放索
1.3 parse_options()分析由内核引导程序发送给内核的启动选项,在初始化过程中按照某些选项运行,并将剩余部分传送给init进程。这些选项可能已经存储在配置文件中,也可能是由用户在系统启动时敲入的。但内核并不关心这些,这些
件中,对于ARM处理器,共有复位、未定义指令、SWI、预取终止、数据终止、IRQ和FIQ 几种方式。SWI主要
用来实现系统调用,而产生了IRQ之后,通过exception vector进入中断处理过程,执行do_IRQ函数。
来取消阻塞;mask_ack的作用是阻塞中断,同时还回应ack给硬件表示这个中断已经被处理了,否则硬件将再
次发生同一个中断。这里,不是所有硬件需要这个ack回应,所以很多时候mask_ack与mask用的是同一个函
1.6 sched_init()初始化系统调度进程,主要对定时器机制和时钟中断的Bottom Half的初始化函数进行设置。与时间相关的初始化过程主要有两步:(1)调用 init_timervecs()函数初始化内核定时器机制;(2)调用init_bh()函
1.9 console_init()控制台初始化。控制台也是一种驱动程序,由于其特殊性,提前到该处完成初始化,主要是为了提前看到输出信息,据此判断内核运行情况。很多
1.10 init_modules()模块初始化。如果编译内核时使能该选项,则内核支持模块化加载/卸载功能
1.13 calibrate_delay()近似计算BogoMIPS数字的内核函数。作为第一次估算,calibrate_delay计算出在每一秒内执行多少次__delay循环,也就是每个定时器滴答(timer tick)―百分之一秒内延时循环可以执行多少次。这种计算只
BogoMIPS的数字由内核计算并在系统初始化的时候打印。它近似的给出了每秒钟CPU可以执行一个短延迟循环
的次数。在内核中,这个结果主要用于需要等待非常短周期的设备驱动程序――例如,等待几微秒并查看设备的
计算一个定时器滴答内可以执行多少次循环需要在滴答开始时就开始计数,或者应该尽可能与它接近。全局变
量jiffies中存储了从内核开始保持跟踪时间开始到现在已经经过的定时器滴答数, jiffies保持异步更
新,在一个中断内每秒一百次,内核暂时挂起正在处理的内容,更新变量,然后继续刚才的工作。
1.14 mem_init()内存初始化。本函数通过内存碎片的重组等方法标记当前剩余内存, 设置内存上下界和页表项初始值。
1.17 fork_init()这里根据硬件的内存情况,如果计算出的max_threads数量太大,可以自行定义。
在该函数里,sock_init()函数进行网络相关的初始化,占用相当多的内存,如果所开发系统不支持网络功
do_initcalls所作的是系统中有关驱动部分的初始化工作,那么这些函数指针数据是怎样放到了.initcall.init节呢?在include/linux/init.h文件中有如下3个定义:
##意思就是在可变参数使用宏定义的时候构建一个变量名称为所指向的函数的名称,并且在前面加上__initcall_
这里如果nr_requests的值太大,则将占用过多的内存,将造成硬件内存不够,因此可以根据实际情况将其替
这个函数在arch/armnommu/mm/init.c文件中,其作用就是对init节的释放,也可以通过修改代码指定为
1.26.3 init执行过程在内核引导结束并启动init之后,系统就转入用户态的运行,在这之后创建的一切进程,都是在用户态进行。这里先要清楚一个概念:就是init进程虽然是从内核开始的,即在前面所讲的init/main.c中的init()函数
在启动后就已经是一个核心线程,但在转到执行init程序(如 /sbin/init)之后,内核中的init()就变成
了/sbin/init程序,状态也转变成了用户态,也就是说核心线程变成了一个普通的进程。这样一来,内核中
envp_init)时改变成为一个普通的用户进程。这也就是exec函数的乾坤大挪移法,在exec函数调用其他程
除此之外,它们的代码来源也有差别,内核中的init()函数的源代码在/init/main.c中,是内核的一部
init程序启动之后,要完成以下任务:检查文件系统,启动各种后台服务进程,最后为每个终端和虚拟控制台
启动一个getty进程供用户登录。由于所有其它用户进程都是由init派生的,因此它又是其它一切用户进程的
init进程启动后,按照/etc/inittab的内容进程系统设置。很多嵌入式系统用的是BusyBox的init,

