前言
从学习各类单片机,到PC端的C/C++编程,我越发感到焦躁难安,其原因是两个字:鸿沟。
向上,我会使用操作系统,甚至会写一些算法,编一些软件,向下,我也会直接编程访问操作硬件,甚至绘制一些原理图,讲出一些模块的工作原理。但我却不清楚操作系统如何存储在存储设备上,如何在硬件上实现启动,不知道如何从简简单单的中断构建出进程调度。这中间存在着最后一道鸿沟:操作系统。如今,自以为自己的软硬件水平已经可以达到学习操作系统底层原理的地步,终于找到机会学习Linux系统,打算消除这个鸿沟。
值得说明的是,写博客不可能囊括所有内容,本篇希望达到的效果是“概览”,是系统启动的“骨架”,希望自己和读者在看完之后能有宏观的轮廓式认知,尤其是如何从上电到运行裸机程序,裸机怎么跳转到uboot(虽然事实上uboot也是一种裸机的代码,不过姑且这么称呼),uboot如何再启动内核,内核如何再加载根文件系统,从而开始能够和用户交互。也就是说,重点在这些模块彼此之间的衔接细节,而不纠结每个模块里究竟都做了些什么。
关于课程:我推荐正点原子的课程,原子哥讲的简洁扼要,句句都在回答我想了解的问题。
裸机->uboot
简单点说:
SOC厂商会有一个写在芯片里的bootloader,例如ARM的起始地址均为0,SOC厂商一般会把一块iROM的地址设置为0,其运行结束后会根据配置好的boot方式(如emmc启动,nand启动),操作设备,到固定的地址取指令,把uboot的入口地址放在这里,就能启动uboot了。
uboot->linux
在uboot交互中,使用printenv
打印uboot的环境变量,查看其中的bootcmd
启动命令,比如,如下的bootcmd表示从mmc 0直接读取一个系统镜像,将它放到内存中的0x80600000处,然后从跳转到这个内存地址运行,亦即启动linux。
bootcmd=fatload mmc 0 0x80600000 uimage; bootm 0x80600000
另有一个环境变量,叫做bootargs
,如下(现在手上没板子,去网上随便找了个,之后替换成自己用的)。这里的参数会传递给linux内核,也就是Linux启动的时候就拿到了这些信息,自然明白该到哪里去找根文件系统,明白把自己的输入输出放到哪里。
这里用到/dev/ram
感觉涉及到设备树的问题了,也有把根文件系统设置成/dev/nfs
的,之后再研究。
bootargs=console=ttyPS0,115200n8 root=/dev/ram rw initrd=0x800000,8M earlyprintk rootfstype=ext4 rootwait devtmpfs.mount=0
评价:
- 可以看出,系统镜像其实是只读的,系统运行过程中,内核是常驻内存的,关机时也并无写回动作,我们平时在系统中所做的修改,其实是在文件系统中做的修改,而“操作系统”这条二进制代码并不在这个根文件系统中存储。而根文件系统的加载和uboot并无关系,仅由linux完成。
- 不管系统镜像是来自什么存储介质,uboot只需要将其读出即可。sd、emmc、nand…各种地方都可以放系统镜像,不得不说,这很神奇,uboot作为一个裸机软件,却可以跨各类平台,各类架构来支持可能出现的存储介质,甚至还支持网络传输,它的模块和接口设计一定十分合理,之后会继续听uboot移植的课程,了解其面向SOC厂商设计的接口。
linux启动和根文件系统
Linux启动的时候,会根据bootargs
指定的存储介质加载文件系统,并把这个文件系统作为根文件系统。之后的运行什么什么init,什么什么threadd,便都顺理成章可以从根文件系统里读出来运行了。
总结
哈哈,最开始写下来一篇,打了好多个问号,(虽然目前基本已经全部解决了),linux学习的繁杂程度可想而知。但是不论如何,我绝不可含糊了事,总有一天要把本篇中存在的疑点解决。现在uboot->linux这部分我自认为已经清晰了,这真的很爽,期待自己linux学习大成,豁然开朗的那天。