【Linux0.11代码分析】06 之 kernel 初始化 init 进程代码分析
【Linux0.11代码分析】06 之 kernel 初始化 init 进程代码分析
系列文章如下:
系列文章汇总:《【Linux0.11代码分析】之 系列文章链接汇总(全)》
.
1.《【Linux0.11代码分析】01 之 代码目录分析》
2.《【Linux0.11代码分析】02 之 bootsect.s 启动流程》
3.《【Linux0.11代码分析】03 之 setup.s 启动流程》
4.《【Linux0.11代码分析】04 之 head.s 启动流程》
5.《【Linux0.11代码分析】05 之 kernel 初始化 init\main.c 代码分析》
6.《【Linux0.11代码分析】06 之 kernel 初始化 init 进程代码分析》
在前文《【Linux0.11代码分析】05 之 kernel 初始化 init\main.c 代码分析》中,
我们分析到,进程0
在main()
中 会调用 if( !fork() ){ init() ; }
来创建一个任务1
子进程,
由任务1
子进程来运行 init()
函数,任务0
父进程则作为空闲进程运行 for(;;) pause();
本文,我们重点分析下,进程1
运行 init()
的详细流程:
- 读取硬盘参数包括分区表信息,建立
RamDisk
虚拟盘,安装RootFS
根文件系统设备 - 打开 tty0 字符设备 终端控制台, 问题来了: /dev/tty0 何时创建的?
代码如下:
// init\main.c
static char printbuf[1024]; // 用于内核显示信息的缓存
// 打印格式化后的字符串信息到输出设备 stdout(1)屏幕上显示,
// 该程序使用vsprintf()将格式化的字符串放入printbuf 缓冲区,然后用write()将缓冲区的内容输出到标准设备(1--stdout)
static int printf (const char *fmt, ...)
{
va_list args; // char * args;
int i;
va_start (args, fmt); // args=(char *) &(fmt) + ( (sizeof(fmt)+sizeof(int)-1) / sizeof (int) ) * sizeof(int)
write (1, printbuf, i = vsprintf (printbuf, fmt, args));
va_end (args);
return i;
}
// 读取并执行/etc/rc文件时所使用的命令行参数和环境参数
static char *argv_rc[] ={"/bin/sh", NULL}; // 调用执行程序时参数的字符串数组。
static char *envp_rc[] ={"HOME=/", NULL}; // 调用执行程序时的环境字符串数组。
// 运行登录shell时所使用的命令行参数和环境参数
// 下面 argv[0]中的字符“-”是传递给shell程序sh的一个标志。
// 通过识别该标志,sh程序会作为登录shell执行。其执行过程与在shell提示符下执行sh不一样。
static char *argv[] ={"-/bin/sh", NULL}; // 同上。
static char *envp[] ={"HOME=/usr/root", NULL};
void init (void)
{
int pid, i;
// 1. 读取硬盘参数包括分区表信息,建立 RamDisk 虚拟盘,安装 RootFS 根文件系统设备
setup ((void *) &drive_info); // linux-0.11\kernel\blk_drv\hd.c
===================>
+// 读取CMOS 和硬盘参数表信息(位于0x90080),用于设置硬盘分区结构hd,并加载RAM 虚拟盘和 根文件系统,
+ int sys_setup(void * BIOS)
+ {
+ struct partition *p;
+ struct buffer_head * bh;
+
+ // 如果没有在config.h 中定义硬盘参数,就从0x90080 处读入
+ #ifndef HD_TYPE
+ for (drive=0 ; drive<2 ; drive++) {
+ hd_info[drive].cyl = *(unsigned short *) BIOS; // 柱面数
+ hd_info[drive].head = *(unsigned char *) (2+BIOS); // 磁头数
+ hd_info[drive].wpcom = *(unsigned short *) (5+BIOS); // 写前预补偿柱面号
+ hd_info[drive].ctl = *(unsigned char *) (8+BIOS); // 控制字节
+ hd_info[drive].lzone = *(unsigned short *) (12+BIOS); // 磁头着陆区柱面号
+ hd_info[drive].sect = *(unsigned char *) (14+BIOS); // 每磁道扇区数
+ BIOS += 16; // 每个硬盘的参数表长16 字节,这里BIOS 指向下一个表
+ }
+ // setup.s 程序在取BIOS 中的硬盘参数表信息时,如果只有1 个硬盘,就会将对应第2 个硬盘的16 字节全部清零。
+ // 因此这里只要判断第2 个硬盘柱面数是否为0 就可以知道有没有第2 个硬盘了
+ if (hd_info[1].cyl) NR_HD=2; // 硬盘数置为2
+ else NR_HD=1;
+ #endif
+ // 设置每个硬盘的起始扇区号和扇区总数
+ for (i=0 ; i<NR_HD ; i++) {
+ hd[i*5].start_sect = 0; // 起始扇区号
+ hd[i*5].nr_sects = hd_info[i].head*hd_info[i].sect*hd_info[i].cyl; // 扇区总数
+ }
+ // 可能会出现这样的情况,我们有一块SCSI/ESDI/等的控制器,它是以ST-506 方式与BIOS 兼容的,
+ // 因而会出现在我们的BIOS 参数表中,但却又不是寄存器兼容的,因此这些参数在CMOS 中又不存在,
+ // 我们假设ST-506 驱动器(如果有的话)是系统中的基本驱动器,也即以驱动器1 或2出现的驱动器
+ // 第1 个驱动器参数存放在CMOS 字节0x12 的高半字节中,第2 个存放在低半字节中。
+ // 该4 位字节息可以是驱动器类型,也可能仅是0xf
+ // 0xf 表示使用CMOS 中0x19 字节作为驱动器1 的8 位类型字节,使用CMOS 中0x1A 字节作为驱动器2 的类型字节。
+ // 总之,一个非零值意味着我们有一个AT 控制器硬盘兼容的驱动器
+ if ((cmos_disks = CMOS_READ(0x12)) & 0xf0)
+ if (cmos_disks & 0x0f) NR_HD = 2;
+ else NR_HD = 1;
+ else NR_HD = 0;
+
+ // 若NR_HD=0,则两个硬盘都不是AT 控制器兼容的,硬盘数据结构清零。
+ // 若NR_HD=1,则将第2 个硬盘的参数清零。
+ for (i = NR_HD ; i < 2 ; i++) {
+ hd[i*5].start_sect = 0; // 起始扇区号
+ hd[i*5].nr_sects = 0; // 扇区总数
+ }
+ for (drive=0 ; drive<NR_HD ; drive++) {
+ // 读取每一个硬盘上第1 块数据(第1 个扇区有用),获取其中的分区表信息, 参数中的0x300 是硬盘的主设备号
+ if (!(bh = bread(0x300 + drive*5,0))) {
+ printk("Unable to read partition table of drive %d\n\r",drive);
+ panic("");
+ }
+ //然后根据硬盘头1个扇区位置0x1fe处的两个字节是否为'55AA'来判断该扇区中位于0x1BE开始的分区表是否有效
+ if (bh->b_data[510] != 0x55 || (unsigned char) bh->b_data[511] != 0xAA) {
+ printk("Bad partition table on drive %d\n\r",drive);
+ panic("");
+ }
+ // 最后将分区表信息放入硬盘分区数据结构hd 中
+ p = 0x1BE + (void *)bh->b_data;
+ for (i=1;i<5;i++,p++) {
+ hd[i+5*drive].start_sect = p->start_sect; // 起始扇区号
+ hd[i+5*drive].nr_sects = p->nr_sects; // 扇区总数
+ }
+ brelse(bh); // 释放为存放硬盘块而申请的内存缓冲区页
+ }
+ if (NR_HD) // 如果有硬盘存在并且已读入分区表,则打印分区表正常信息
+ printk("Partition table%s ok.\n\r",(NR_HD>1)?"s":"");
+ // 加载(创建)RAMDISK(kernel/blk_drv/ramdisk.c,71)。
+ rd_load();
+ // 安装根文件系统(fs/super.c,242)
+ mount_root();
+ return (0);
+ }
<===================
// 2. 打开 tty0 字符设备 终端控制台, 问题来了: /dev/tty0 何时创建的?
(void) open ("/dev/tty0", O_RDWR, 0); // 用读写访问方式打开设备“/dev/tty0”, 返回的句柄号0 -- stdin 标准输入设备。
// 复制0号句柄,产生句柄1号,用作stdout标准输出设备
// 一个程序支持句柄最大为20个,sys_dup会使用一个当前可用的最小文件句柄,
// 如 /dev/tty0已经打开为0句柄,下面可用的最小句柄为1,此处复制后的句柄为
(void) dup (0);
====================>
+ // 复制指定文件句柄oldfd,新句柄的值是当前最小的未用句柄。
+ int sys_dup (unsigned int fildes){
+ return dupfd (fildes, 0);
+ }
<===================
// 复制0句柄,产生句柄2,用作stderr 标准出错输出设备。
(void) dup (0);
// 打印缓冲区块数和总字节数,每块1024 字节
printf ("%d buffers = %d bytes buffer space\n\r", NR_BUFFERS, NR_BUFFERS * BLOCK_SIZE);
// 空闲内存字节数
printf ("Free mem: %d bytes\n\r", memory_end - main_memory_start);
// 使用fork()创建一个子进程,
// 下面fork()用于创建一个子进程(子任务)。对于被创建的子进程,fork()将返回0 值,
// 对于原(父进程)将返回子进程的进程号。所以180-184 句是子进程执行的内容。该子进程
// 关闭了句柄0(stdin),以只读方式打开/etc/rc 文件,并执行/bin/sh 程序,所带参数和
// 环境变量分别由argv_rc 和envp_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,“文件或目录不存在”)。
}
// 下面是父进程执行的语句。wait()是等待子进程停止或终止,其返回值应是子进程的进程号(pid)。
// 这三句的作用是父进程等待子进程的结束。&i 是存放返回状态信息的位置。如果wait()返回值不
// 等于子进程号,则继续等待。
if (pid > 0)
while (pid != wait (&i))
/* nothing */ ;
// 如果执行到这里,说明刚创建的子进程的执行已停止或终止了。下面循环中首先再创建一个子进程,
// 如果出错,则显示“初始化程序创建子进程失败”的信息并继续执行。对于所创建的子进程关闭所有
// 以前还遗留的句柄(stdin, stdout, stderr),新创建一个会话并设置进程组号,然后重新打开
// /dev/tty0 作为stdin,并复制成stdout 和stderr。再次执行系统解释程序/bin/sh。但这次执行所
// 选用的参数和环境数组另选了一套(见上面165-167 行)。然后父进程再次运行wait()等待。如果
// 子进程又停止了执行,则在标准输出上显示出错信息“子进程pid 停止了运行,返回码是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));
}
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() */
}
待研究:
- rd_load () // 加载(创建)RAMDISK
- mount_root (); // 安装根文件系统
- /dev/tty0 终端设备(tty)驱动架构
《linux终端设备:tty子系统相关的初始化》
《Linux的tty设备介绍》