[Linux内核完全剖析]第七章 初始化程序(init)总结

系统初始化程序init/main.c主要功能是对系统进行初始化,并切换到用户模式下执行登录程序。
主要步骤如下:
1、系统初始化部分:

// init/main.c void main(void) // 前面代码略,以下是内核进行所有方面的初始化工作。 mem_init (main_memory_start, memory_end); trap_init (); // 陷阱门(硬件中断向量)初始化。(kernel/traps.c,181 行) blk_dev_init (); // 块设备初始化。 (kernel/blk_dev/ll_rw_blk.c,157 行) chr_dev_init (); // 字符设备初始化。 (kernel/chr_dev/tty_io.c,347 行) tty_init (); // tty 初始化。 (kernel/chr_dev/tty_io.c,105 行) time_init (); // 通过读取CMOS设置开机启动时间 startup_time(见76 行)。 sched_init (); // 调度程序初始化(加载了任务0 的tr, ldtr) (kernel/sched.c,385) buffer_init (buffer_memory_end); // 缓冲管理初始化,建内存链表等。(fs/buffer.c,348) hd_init (); // 硬盘初始化。 (kernel/blk_dev/hd.c,343 行) floppy_init (); // 软驱初始化。 (kernel/blk_dev/floppy.c,457 行) sti (); // 所有初始化工作都做完了,开启中断。

2、然后是切换到用户层(即idle进程),并使用fork系统调用来生成init进程,idle则循环使用pause()进入进程调度。

// include/asm/system.h // 切换到用户模式运行。 // 该函数利用iret 指令实现从内核模式切换到用户模式(初始任务0)。 #define move_to_user_mode() / __asm__ ( "movl %%esp,%%eax/n/t" / // 保存堆栈指针esp 到eax 寄存器中。 "pushl $0x17/n/t" / // 首先将堆栈段选择符(SS)入栈。 "pushl %%eax/n/t" / // 然后将保存的堆栈指针值(esp)入栈。 "pushfl/n/t" / // 将标志寄存器(eflags)内容入栈。 "pushl $0x0f/n/t" / // 将内核代码段选择符(cs)入栈。 "pushl $1f/n/t" / // 将下面标号1 的偏移地址(eip)入栈。 "iret/n" / // 执行中断返回指令,则会跳转到下面标号1 处。 "1:/tmovl $0x17,%%eax/n/t" / // 此时开始执行任务0, "movw %%ax,%%ds/n/t" / // 初始化段寄存器指向本局部表的数据段。 "movw %%ax,%%es/n/t" "movw %%ax,%%fs/n/t" "movw %%ax,%%gs":::"ax") // init/main.c void main(void) // 下面过程通过在堆栈中设置的参数,利用中断返回指令切换到任务0。 move_to_user_mode (); // 移到用户模式。 (include/asm/system.h,第1 行) if (!fork ()) { /* we count on this going ok */ init (); } for(;;) pause();

3、fork出来的新进程调用init()函数执行shell以及登录程序

//init/main.c static char *argv_rc[] = {"/bin/sh", NULL}; // 调用执行程序时参数的字符串数组。 static char *envp_rc[] = {"HOME=/", NULL}; // 调用执行程序时的环境字符串数组。 static char *argv[] = {"-/bin/sh", NULL}; // 作为登录shell的argv static char *envp[] = {"HOME=/usr/root", NULL}; void init (void) { int pid, i; // 读取硬盘参数包括分区表信息并建立虚拟盘和安装根文件系统设备。 // 对应函数是sys_setup(),在kernel/blk_drv/hd.c,71 行。 setup ((void *) &drive_info); (void) open ("/dev/tty0", O_RDWR, 0); // 返回的句柄号0 -- stdin 标准输入设备。 (void) dup (0); // 复制句柄,产生句柄1 号 -- stdout 标准输出设备。 (void) dup (0); // 复制句柄,产生句柄2 号 -- stderr 标准出错输出设备。 printf ("%d buffers = %d bytes buffer space/n/r", NR_BUFFERS, NR_BUFFERS * BLOCK_SIZE);// 打印缓冲区块数和总字节数,每块1024 字节。 printf ("Free mem: %d bytes/n/r", memory_end - main_memory_start); //空闲内存字节数。 //下面的子程序执行/etc/rc脚本来初始化用户态环境 if (!(pid = fork ())) { close (0); if (open ("/etc/rc", O_RDONLY, 0)) _exit (1); // 如果打开文件失败,则退出(/lib/_exit.c,10)。 execve ("/bin/sh", argv_rc, envp_rc); // 装入/bin/sh 程序并执行。 _exit (2); // 若execve()执行失败则退出(出错码2,“文件或目录不存在”)。 } // 父进程执行,循环等待等待子进程运行完毕。 if (pid > 0) while (pid != wait (&i)); // 循环执行并等待登录进程。 while (1) { if ((pid = fork ()) < 0) { printf ("Fork failed in init/r/n"); continue; } //子进程,登录进程 if (!pid) { close (0); close (1); close (2);// 关闭文件描述符 setsid ();// 设置会话 (void) open ("/dev/tty0", O_RDWR, 0); (void) dup (0); (void) dup (0);//设置文件描述符和中断关联 _exit (execve ("/bin/sh", argv, envp));//执行登录属性的shell } //父进程,循环等待子进程退出 while (1) if (pid == wait (&i)) break; printf ("/n/rchild %d died with code %04x/n/r", pid, i); sync (); } _exit (0); /* NOTE! _exit, not exit() */ }

4、系统完成了初始化过程,进入shell程序继续执行。
注意:init进程(进程1)是从idle进程(进程0)中fork出来,init继承了idle的代码、数据空间、堆栈、文件描述符等等。但由于直接从内核切换出来的idle进程没有写时复制技术,所以这两个进程共享了上述内容。为了确保数据不发生混乱,idle进程对堆栈不进行任何操作,即没有函数调用。所以在move_to_user()宏调用之后进入idle进程开始,整个idle进程使用了宏的fork(),pause()作为替换。

你可能感兴趣的:(操作系统)