linux0.12内核剖析 - 引导启动(二)

 

在用户能登录shell前,操作系统的初始化函数main做了什么?

  • 相关文件:init/main.c

首先在main函数中,内核进行了各方面的硬件初始化工作,包括陷阱门、块设备、字符设备和tty,还包括人工设置第一个任务(task 0)。待所有初始化工作完成后,程序就设置中断允许标志以开启中断,并切换到任务0中运行。到此时,可以说内核已基本完成所有设置工作。接下来内核会通过任务0创建几个最初的任务,运行shell程序并显示命令行提示符,从而Linux系统进入正常运行阶段。

kernel_init

init进程主要完成4件事:

  1. 安装根文件系统

    代码首先调用系统调用setup(),用来收集硬盘设备分区表信息并安装根文件系统

  2. 显示系统信息

    打开一个终端设备tty0并复制其文件描述符以产生标准输入stdin、标准输出stdout和错误输出stderr设备,随后利用这些描述符在终端上显示一些系统信息。

  3. 执行资源配置文件rc

    接着init()又新建了一个进程(进程2),内核以非交互形式执行/bin/sh程序,执行配置文件etc/rc中设置的命令

  4. 执行登录shell程序

    在一个无限循环中,为用户建立一个新的会话,并运行用户登录shell程序/bin/sh。

细节

move_to_user_mode的分析

  • 相关文件:include/asm/system.h

函数move_to_user_mode()是用于内核在初始化结束时人工切换(移动)到初始进程(任务0)去执行,即从特权级0代码转移到特权级3的代码中去运行。

move_to_user_mode

通过调用门、中断门或陷阱门,CPU允许低级别(特权级3)的代码来调用或转移到高级别(特权级0)的代码中运行,但反之则不允许。因此,所使用的方法是模拟中断调用返回过程,即利用IRET指令来实现特权级的变更和堆栈的切换,从而把CPU执行控制流移动到初始任务0的环境中运行。

/* 利用iret指令实现从内核模式移到用户模式去执行初始任务0 */
#define move_to_user_mode()						\
__asm__ (										\
    "movl %%esp,%%eax\n\t"                      \
    "pushl $0x17\n\t"      /* 将堆栈段选择符SS入栈 */       \
    "pushl %%eax\n\t"      /* 将堆栈指针esp入栈 */	        \
    "pushfl\n\t"           /* 将标志寄存器eflags入栈 */     \
    "pushl $0x0f\n\t"      /* 将Task0代码段选择符cs入栈 */	\
    "pushl $1f\n\t"        /* 将下标1地址入栈 */			\
    "iret\n"			   /* 执行中断返回指令,跳转到下标1处 */	\
"1:\tmovl $0x17,%%eax\n\t" /* 初始化段寄存器指向本局部表的数据段 */	 \
    "mov %%ax,%%ds\n\t"	    	                \
    "mov %%ax,%%es\n\t"							\
    "mov %%ax,%%fs\n\t"							\
    "mov %%ax,%%gs"								\
    :::"ax")