花了三天时间根据我们产品的现行软件代码把linux的启动过程大概捋了一遍,现在大概明白一些了,系统初始化的时候不是像我原来想的那样,一上来就是什么设备驱动啊,各个功能模块或者是用户进程之类的初始化工作,如果看到系统已经初始化到这里了,那证明基本的系统初始化早就结束了。
一般嵌入式linux的启动过程大致可以分为3个部分吧,首先是bootloader做的工作,这应该是每一个嵌入式系统的启动都要最先执行的东西吧,bootloader就是一个加载器,设备加电的时候最先被启动脚本载入内存的就是这个东西,我之前转过一篇关于bootloader的文章,对于bootloader的工作原理和怎么写bootloader应该算是说的很清楚了,不过我还没有非常细致的看过那篇文章,O(∩_∩)O~。这个不是我记录这篇文章的重点,可以用简单的一句话来总结bootloader的作用,它就是用来把系统内核镜像文件加载到系统内存的某个位置,然后设置好硬件参数,准备好这些最基本的必要的东西以后,设置寄存器内容好让系统能够正确的进入系统的启动流程。
重要的是第二部分的工作,也是我这三天关注的内容,第一部分bootloader会把环境准备好,然后把一个函数的地址设置到EIP中,这个函数就是start_kernel(),系统的初始化也就是从这里开始。当程序执行到这个函数的时候,整个系统中的内存还处于未初始化状态,内核的内存管理系统还不能使用,系统中甚至还没有进程的概念,这时候的系统就像一张白纸,我在这里纠正了我的一个错误观点,那就是系统中的资源并不是一开始就属于内核的,像内存这种资源,就算是内核需要对内存进行管理,也要进行申请,因为刚启动的内存是bootloader加载器进行管理的,它提供一个allocator来分配和回收系统中的所有物理内存。系统初始化的第一件事就是上大内核锁,然后对物理页面地址映射表进行初始化,听起来很复杂的样子,但实际上很简单,就是初始化了三个表,page_address_pool,page_address_maps和page_address_htable,后面内核申请的物理内存页面就会对应的放在这三个表中,这时候内核仍然不能管理任何物理内存,这个阶段需要用到的内存都要找bootloader申请才行。
执行完这个以后开始体系结构的初始化,也就是一个重要的函数setup_arch(),这个函数主要完成体系结构相关的设置和初始化。在有些体系结构中,可能需要用到不同于内核本身的内存管理机制,例如我们现行系统中就使用到共享内存这种自己的管理机制,这个初始化就需要在这个阶段来完成,因为过了这个阶段,内核会向bootloader申请所有的内存来进行管理,所以为了我们自己的需要,就抢在内核之前把我们要自己管理的内存先申请了,一般情况下系统启动都会对CPU进行一个自检,这个过程也是在这里完成,而且是在内存初始化之前。这个过程中最重要的是初始化内存的页面映射机制(paging_init()),这些东东都是在这个过程中进行初始化的,顺序大概是:硬件自检-->自定义内存管理初始化-->页面映射机制的建立。我想因为自定义的内存管理所管理的内存是不需要内核再来进行管理的,也就是因为这个原因吧,页面映射机制的建立放在其后可以避免对自定义的内存管理机制所管理的内存建立映射机制。执行完这个函数以后,内核中才有了内存的页面映射机制,但是并不意味着内核可以使用内存了,目前内核的内存管理仍然还没有管理任何内存页面。
页面映射机制建立了以后,内核会对调度器进行初始化,它为每一个cpu建立运行队列,并进行初始化,为每个cpu设置idle线程。
调度器初始化以后,内核开始建立内存管理区的控制结构,这个过程会把内核内存管理的基础架构全部建立起来,主要是对zone管理区的划分,对需要用到的控制数据结构进行分配和初始化。到这里,内核内存管理机制仍然不能用,物理内存仍然在bootloader手里。zone划分好了以后,要对gfp进行初始化,因为后面内核要从bootloader申请内存,这个过程中需要用到gfp的分配函数。
这些完了以后要对中断进行初始化,分配中断向量,建立rcu锁机制。这些完成以后,内核才开始对内核定时器的基础结构进行初始化,时钟的软中断处理函数同时也要进行设置,然后设置系统时钟。
在上述的所有结束以后,内核才正式从bootloader申请内存来完成内核内存管理机制的建立。系统调用函数回收bootloader持有的所有可用内存,然后把这些内存放到已经建立的zone管理区中,最终物理页面会被放到不同的管理区中,变成一个个不同大小的块,分为1个单独页面大小的块,2个连续页面大小的块,4个连续页面大小的块,8个连续页面大小的块……;这时候内核内存管理才真正拥有可以用来进行分配的内存。接下来内核就开始建立slab的管理结构,到这一步结束,内核内存管理机制才算是完全建立起来,内存管理算法才开始起作用。剩下的部分就是各个模块建立自己需要的常用数据结构缓冲区,这么做的目的是提高系统的性能。文件系统的主要数据结构比如inode节点啊,mnt需要的数据结构啊,也是在这个过程中被建立起来并且初始化完成的。这一步走完以后文件系统也初始化的差不多了。这里开始,kmalloc这一套内存管理机制才可以使用,同时系统初始化的第二部分也进入尾声。
最后,内核调用rest_init()来创建init内核线程,这时调度器,内存,文件系统已经准备好了,内核创建了cpu_idle进程以后,就会把控制权交给init线程,系统初始化的第二部分也就结束了。
系统初始化的第三部分就是系统中的设备驱动初始化,各个模块初始化,用户进程启动和初始化,这些工作都由init线程一个一个的执行。然后系统中的东西就越来越多,越来越丰满了。
看了三天,实际上总共完成的工作最主要就是三个:调度器初始化,内存初始化,中断、时钟和定时器、文件系统等的初始化。其中内存的初始化是重中之重,尤其是paging_init(),它要是出了问题什么都完蛋,而我们想要怎么样子修改内核的内存管理或者其他东东,也都从这里开始了。看完这个我终于可以看着控制台就知道系统运行到哪里了,基本问题会出在什么地方,不错,有点点收获,O(∩_∩)O~