在用户能登录shell前,操作系统的初始化函数main做了什么?
- 相关文件:
init/main.c
首先在main函数中,内核进行了各方面的硬件初始化工作,包括陷阱门、块设备、字符设备和tty,还包括人工设置第一个任务(task 0)。待所有初始化工作完成后,程序就设置中断允许标志以开启中断,并切换到任务0中运行。到此时,可以说内核已基本完成所有设置工作。接下来内核会通过任务0创建几个最初的任务,运行shell程序并显示命令行提示符,从而Linux系统进入正常运行阶段。
init进程主要完成4件事:
-
安装根文件系统
代码首先调用系统调用setup(),用来收集硬盘设备分区表信息并安装根文件系统。
-
显示系统信息
打开一个终端设备tty0并复制其文件描述符以产生标准输入stdin、标准输出stdout和错误输出stderr设备,随后利用这些描述符在终端上显示一些系统信息。
-
执行资源配置文件rc
接着init()又新建了一个进程(进程2),内核以非交互形式执行/bin/sh程序,执行配置文件etc/rc中设置的命令。
-
执行登录shell程序
在一个无限循环中,为用户建立一个新的会话,并运行用户登录shell程序/bin/sh。
细节
move_to_user_mode的分析
- 相关文件:
include/asm/system.h
函数move_to_user_mode()
是用于内核在初始化结束时人工切换(移动)到初始进程(任务0)去执行,即从特权级0代码转移到特权级3的代码中去运行。
通过调用门、中断门或陷阱门,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")