内核进行所有方面的硬件初始化工作,包括陷阱门、块设备、字符设备和tty,包括手工设置第一个任务。待所有初始化工作完成后程序就设置中断允许标志以开启中断,mai()也切换到了任务0中运行。
在整个内核完成初始化后,内核将执行权切换到了用户模式,此时main.c的主程序就工作在任务0中。然后系统第一次调用进程创建函数fork(),创建出一个用于运行init()的子进程。在该函数中程序将继续进行应用环境的初始化并执行shell登陆程序。而原进程0则会在系统空闲时被调度执行,因此进程0通常也被称为idle进程。此时进程0仅执行pause()系统调用,并又会调用调度函数。
init()函数的功能可氛围4个部分:(1)安装根文件系统(2)显示系统消息(3)运行系统初始资源配置文件rc中的命令(4)执行用户登陆shell程序。
代码首先调用setup(),用来收集硬盘设备分区表信息并安装根文件系统。在安装根文件系统之前系统会先判断是否需要先建立虚拟盘。若编译内核时设置了虚拟盘的大小,并在前面内核中已经开辟了一块内存用作虚拟盘,则内核就会首先尝试把根文件系统加载到内存的虚拟盘区中。
然后init()打开一个中断设备tty0,并复制其文件描述符以产生标准输入stdin、标准输出stdout和错误输出stderr设备。内核随后利用这些描述符在终端上显示一些系统信息。接着init()又新建一个进程(进程2),并在其中为建立用户交互使用环境而执行一些初始配置操作,即在用户可以使用shell命令行环境之前,内核调用/bin/sh程序运行了配置文件etc/rc中设置的命令。rc文件的作用与DOS系统根目录上的AUTOEXEC.BAT文件类似。这段代码首先通过关闭文件描述符0,并立刻打开文件/etc/rc,从而把标准输入stdin定向到etc/rc文件。这样,所有的 标准输入数据都将从该文件中读取。然后内核以非交互式执行/bin/sh,从而实现执行/etc/rc文件中的命令。当该文件中的命令执行完毕后,/bin/sh就会立刻退出。因此进程2也就随之结束。
inti()函数的最后一部分在新建进程中为用户建立一个新的会话,并运行用户登陆shell程序/bin/sh。在系统执行进程2中的程序时,父进程(init进程)一直等待着它的结束。随着进程2的退出,父进程就进入到一个无限循环中。在该循环中,父进程会再次生成一个新进程,然后在该进程中创建一个新的会话,并以登陆shell方式再次执行程序/bin/sh,以创建用户交互shell环境。然后父进程继续等待该子进程。登陆shell虽然与前面的非交互方式shell是同一个程序/bin/sh,但是所使用的命令行参数不同。从这时开始,用户就可以正常使用linux命令行环境了,而父进程随之又进入等待状态。此后若用户在命令行上执行了exit或logout命令,那么在显示一条当前登陆shell退出的信息后,系统就会在这个无限循环中再次重复以上创建登陆shell进程的过程。
由于创建新进程的过程是通过完全复制父进程代码段和数据段的方式实现,因此在首次使用fork()创建新进程init时,为了确保新进程用户态栈中没有进程0的多余信息,要求进程0在创建第一个新进程(进程1)之前不要使用其用户态栈,即要求任务0不要调用函数。因此在main.c主程序移动到任务0执行后,任务0中的代码fork()不能以函数形式进行调用。程序中的实现方法时采用gcc函数内嵌(内联)形式来执行这个系统调用。