引导程序
有过电脑系统装机经历的人都知道 BIOS 的概念,为 Android 手机刷机过的小伙伴都听过Bootloader ,那这些概念和手机启动有什么关系呢?
首先,无论是电脑还是手机,操作系统都不是开机就直接启动的,都是通过引导程序来帮助启动操作系统的,对于电脑来说 BIOS 起到了这个功能,对于手机则是 Bootloader 来担此重任。可能有些人会有疑问,电脑和手机的开机为什么不直接启动操作系统,而是需要通过引导程序来协助呢。这主要是由目前的硬件结构决定的。众所周知,不管是电脑还是手机操作系统都是运行在RAM 中的,而 RAM 在仅上电未进行一定初始化之前,是无法运行程序的。所以在RAM 运行操作系统之前,需要有一个程序来完成相应的初始化工作,这一艰巨的任务就交给了引导程序。
那引导程序是如何工作的呢?在系统一上电的时候,CPU 会根据配置(比如配置从EMMC/SDCARD等启动方式)选择从某个固定位置来读取运行的第一行代码,这个地方存储的就是引导程序。引导程序运行后,进行必要的(最小)初始化(如:时钟、MMU等)后,将存储在 ROM 中的操作系统搬移到 RAM 中的某个位置(可能还会牵涉到解压的操作),接着引导程序会将控制权转移给RAM中的操作系统,启动操作系统。
操作系统的首指令
从上面的分析可知,只要找到操作系统的首条指令(语句),就可以顺着这条线索,理清启动流程。鸿蒙镜像编译的链接文件(关键语句)如下:
// ~/kernel/liteos_a/tools/build/liteos_llvm.ld
ENTRY(reset_vector)
即鸿蒙镜像的入口函数为 reset_vector
。而该函数的定义如下所示:
// ~/kernel/liteos_a/arch/arm/arm/src/startup/reset_vector_up.S
reset_vector:
//必要的初始化
......
bl main
.....
其中(下同):
- “…”代表省略掉的干扰主体逻辑的代码
- 首行//后的内容代表该代码段的来源文件
C 语言的天地
从 main
函数开始就进入了我们熟悉的C语言环境了。其主要的工作,可从下面的代码一探究竟。
# ~/kernel/liteos_a/platform/main.c
main()
OsSetMainTask(); // 一
OsCurrTaskSet(OsGetMainTask()); //将当前task的信息写入寄存器
......
OsSystemInfo(); //打印系统信息
......
uwRet = OsMain(); // 二
......
OsStart(); // 三
while (1) {
__asm volatile("wfi");
}
从上述的代码段,我们可以看到main
函数主要做了三方面的工作。接下来我们就从这个函数开始启动流程的分析。
第一步:OsSetMainTask()
// ~/kernel/liteos_a/kernel/base/core/los_task.c
LITE_OS_SEC_BSS STATIC LosTaskCB g_mainTask[LOSCFG_KERNEL_CORE_NUM];
OsSetMainTask()
...
for (i = 0; i < LOSCFG_KERNEL_CORE_NUM; i++) {
g_mainTask[i].taskStatus = OS_TASK_STATUS_UNUSED;
g_mainTask[i].taskID = LOSCFG_BASE_CORE_TSK_LIMIT;
g_mainTask[i].priority = OS_TASK_PRIORITY_LOWEST;
#if (LOSCFG_KERNEL_SMP_LOCKDEP == YES)
g_mainTask[i].lockDep.lockDepth = 0;
g_mainTask[i].lockDep.waitLock = NULL;
#endif
ret = memcpy_s(g_mainTask[i].taskName, OS_TCB_NAME_LEN, name, strlen(name));
if (ret != EOK) {
g_mainTask[i].taskName[0] = '\0';
}
LOS_ListInit(&g_mainTask[i].lockList);
}
...
从OsSetMainTask()
的代码,可以清晰地看到,在这个函数中,初始化了LOSCFG_KERNEL_CORE_NUM
个 LosTaskCB
,这个LosTaskCB
数组用于记载当前系统中Task
的控制信息(Control Block),包括优先级、状态、标志ID、锁的状态等等信息,可以根据命名略知一二。LOSCFG_KERNEL_CORE_NUM
变量一般是根据主控芯片,来选择和是数值。至于修改可参考相关文档,不是启动流程的关键信息。
第二步:OsMain()
OsMain()
函数的主要代码如下所示:
// ~/kernel/liteos_a/kernel/common/los_config.c
OsMain()
osRegister();
......
OsExcInit();
OsTickInit();
......
ret = OsTaskInit();
......
ret = OsIpcInit();
......
OsSysMemInit();
......
SyscallHandleInit();
.......
ret = OsKernelInitProcess(); //1
......
ret = OsSystemInit(); //2
......
ret = OsFutexInit();
......
ret = OomTaskInit();
&esmp;从OsMain()
函数的流程可以看到,启动过程中顺序地做了以下工作:
- 寄存器
- 异常
- 时钟
- OsTaskInit
- 进程间通信
- 内存
- OsKernelInitProcess
- 系统初始化
- 等等
对于这些初始化工作,我们不都做分析,重点分析系统初始化函数OsSystemInit
,因为后面好多重要的工作都是由这个函数引出的。
系统初始化OsSystemInit
函数
// ~/kernel/liteos_a/kernel/common/los_config.c
UINT32 OsSystemInit(VOID)
{
UINT32 ret;
#ifdef LOSCFG_FS_VFS
los_vfs_init();
#endif
#ifdef LOSCFG_COMPAT_LINUXKPI
g_pstSystemWq = create_workqueue("system_wq");
#endif
ret = OsSystemInitTaskCreate(); // (1)
if (ret != LOS_OK) {
return ret;
}
#ifdef LOSCFG_MEM_RECORDINFO
ret = OsMemShowTaskCreate();
if (ret != LOS_OK) {
PRINTK("create memshow_Task error %u\n", ret);
return ret;
}
PRINTK("create memshow_Task ok\n");
#endif
#ifdef LOSCFG_KERNEL_TICKLESS
LOS_TicklessEnable();
#endif
return 0;
}
在系统初始化函数OsSystemInit
中,除了OsSystemInitTaskCreate()
函数外,其他函数都有宏定义的开关,也即这些宏定义包含下的函数是系统的一些“扩展”特性,非必须特性。所以我们的重点工作可以放在OsSystemInitTaskCreate()
函数上。接下来,就进入OsSystemInitTaskCreate()
函数的分析。
// ~/kernel/liteos_a/kernel/common/los_config.c
STATIC UINT32 OsSystemInitTaskCreate(VOID)
{
UINT32 taskID;
TSK_INIT_PARAM_S sysTask;
(VOID)memset_s(&sysTask, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));
sysTask.pfnTaskEntry = (TSK_ENTRY_FUNC)SystemInit; // ①
sysTask.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
sysTask.pcName = "SystemInit";
sysTask.usTaskPrio = LOSCFG_BASE_CORE_TSK_DEFAULT_PRIO;
sysTask.uwResved = LOS_TASK_STATUS_DETACHED;
#if (LOSCFG_KERNEL_SMP == YES)
sysTask.usCpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());
#endif
return LOS_TaskCreate(&taskID, &sysTask);
}
在OsSystemInitTaskCreate()
函数中创建了一个名为SystemInit
的 task
,这个 task
的入口函数通过pfnTaskEntry
属性来指定,指向函数 SystemInit()
的入口地址。这个函数的具体实现在vendor
目录下,即对每个不同的产品(product),系统初始化需要的操作步骤是不同的。
接下来,看看函数SystemInit()
具体执行的操作,这个函数的执行的代码如下所示:
// ~/vendor/st/stm32mp157/board/board.c
SystemInit()
ProcFsInit();
mem_dev_register();
imx6ull_driver_init();
imx6ull_mount_rootfs();
DeviceManagerStart(); //HDF,加载驱动,使外射可以正常工作。
uart_dev_init();
......
OsUserInitProcess();
在这个函数中有两个对后续流程的分析有关键作用的函数,DeviceManagerStart()
和OsUserInitProcess()
,他们的作用分别为:
DeviceManagerStart()
:用于启动鸿蒙驱动框架(HDF
)相关代码。OsUserInitProcess()
:启动initt
进程,其作用类似于linux
中的init
进程。
第三步:OsStart()
// ~/kernel/liteos_a/kernel/common/los_config.c
LITE_OS_SEC_TEXT_INIT VOID OsStart(VOID)
{
LosProcessCB *runProcess = NULL;
LosTaskCB *taskCB = NULL;
UINT32 cpuid = ArchCurrCpuid();
OsTickStart();
LOS_SpinLock(&g_taskSpin);
taskCB = OsGetTopTask(); // 获取最上面的Task
runProcess = OS_PCB_FROM_PID(taskCB->processID);
runProcess->processStatus |= OS_PROCESS_STATUS_RUNNING;
#if (LOSCFG_KERNEL_SMP == YES)
/*
* attention: current cpu needs to be set, in case first task deletion
* may fail because this flag mismatch with the real current cpu.
*/
taskCB->currCpu = cpuid;
runProcess->processStatus = OS_PROCESS_RUNTASK_COUNT_ADD(runProcess->processStatus);
#endif
OS_SCHEDULER_SET(cpuid);
PRINTK("cpu %d entering scheduler\n", cpuid);
OsStartToRun(taskCB);
}
OsStart()
函数首先获取最上面的那个task
,然后运行这个task
。函数OsStartToRun()
s是通过汇语言实现的,具体位置./kernel/liteos_a/arch/arm/arm/src/los_dispatch.S
。
至此,鸿蒙系统已经从引导程序中完全接过了控制权,启动了驱动程序的服务框架,并启动了用户空间的第一个进程init
,通过init
进程就可以“孵化”出用户需要的其他进程。