From 88167feec1f2c68a7aeb6472c43c2da6c10de28f Mon Sep 17 00:00:00 2001 From: DChen Date: Tue, 10 Jun 2014 16:15:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=AC=E6=AC=A1=E6=B3=A8=E9=87=8A=E7=9A=84?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=BA=EF=BC=9A=20=20=20=20=20init/main.c?= =?UTF-8?q?=20=20=20=20=20kernel/Makefile=20=E4=B8=8A=E9=9D=A2=E4=B8=A4?= =?UTF-8?q?=E4=B8=AA=E6=96=87=E4=BB=B6=E9=83=BD=E5=B7=B2=E5=85=A8=E9=83=A8?= =?UTF-8?q?=E5=AE=8C=E6=88=90=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- init/main.c | 65 ++++++++++++++++++++++++++++++++++++++++--------- kernel/Makefile | 35 +++++++++++++++++++++++--- 2 files changed, 86 insertions(+), 14 deletions(-) diff --git a/init/main.c b/init/main.c index a1b8eae..33552a4 100644 --- a/init/main.c +++ b/init/main.c @@ -202,6 +202,10 @@ void main(void) /* This really IS void, no error here. */ for(;;) pause(); } +// 下面函数产生格式化信息并输出到标准输出设备stdout(1),这里是指屏幕上显示。参数'*fmt' +// 指定输出将采用的格式,具体可以看标准C语言书籍。该子程序正好是vsprintf如何使用一个 +// 简单例子。该程序使用vsprintf()将格式化的字符串放入printfbuf缓冲区,然后用write()将 +// 缓冲区的内容输出到标准设备(1--stdout).vsprintf()函数实现在kernel/vsprintf.c中。 static int printf(const char *fmt, ...) { va_list args; @@ -213,41 +217,77 @@ static int printf(const char *fmt, ...) return i; } -static char * argv_rc[] = { "/bin/sh", NULL }; -static char * envp_rc[] = { "HOME=/", NULL }; +// 读取并执行/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 }; +// 在main()中已经进行了系统初始化,包括内存管理、各种硬件设备和驱动程序。init()函数 +// 运行在任务0第1次创建的子进程(任务1)中。它首先对第一个将要执行的程序(shell)的环境 +// 进行初始化,然后以登录shell方式加载该程序并执行。 void init(void) { int pid,i; - setup((void *) &drive_info); + // setup()是一个系统调用。用于读取硬盘参数包括分区表信息并加载虚拟盘(若存在的话) + // 和安装根文件系统设备。该函数用25行上的宏定义,对应函数是sys_setup(),在块设备 + // 子目录kernel/blk_drv/hd.c中。 + setup((void *) &drive_info); // drive_info结构是2个硬盘参数表 + // 下面以读写访问方式打开设备"/dev/tty0",它对应终端控制台。由于这是第一次打开文件 + // 操作,因此产生的文件句柄号(文件描述符)肯定是0。该句柄是UNIX类操作系统默认的 + // 控制台标准输入句柄stdin。这里再把它以读和写的方式别人打开是为了复制产生标准输出(写) + // 句柄stdout和标准出错输出句柄stderr。函数前面的"(void)"前缀用于表示强制函数无需返回值。 (void) open("/dev/tty0",O_RDWR,0); - (void) dup(0); - (void) dup(0); + (void) dup(0); // 复制句柄,产生句柄1号——stdout标准输出设备 + (void) dup(0); // 复制句柄,产生句柄2号——stderr标准出错输出设备 + // 打印缓冲区块数和总字节数,每块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()用于创建一个子进程(任务2)。对于被创建的子进程,fork()将返回0值,对于 + // 原进程(父进程)则返回子进程的进程号pid。该子进程关闭了句柄0(stdin)、以只读方式打开 + // /etc/rc文件,并使用execve()函数将进程自身替换成/bin/sh程序(即shell程序),然后 + // 执行/bin/sh程序。然后执行/bin/sh程序。所携带的参数和环境变量分别由argv_rc和envp_rc + // 数组给出。关闭句柄0并立即打开/etc/rc文件的作用是把标准输入stdin重定向到/etc/rc文件。 + // 这样shell程序/bin/sh就可以运行rc文件中的命令。由于这里的sh的运行方式是非交互的, + // 因此在执行完rc命令后就会立刻退出,进程2也随之结束。 + // _exit()退出时出错码1 - 操作未许可;2 - 文件或目录不存在。 if (!(pid=fork())) { close(0); if (open("/etc/rc",O_RDONLY,0)) - _exit(1); - execve("/bin/sh",argv_rc,envp_rc); - _exit(2); + _exit(1); // 如果打开文件失败,则退出(lib/_exit.c) + execve("/bin/sh",argv_rc,envp_rc); // 替换成/bin/sh程序并执行 + _exit(2); // 若execve()执行失败则退出。 } + // 下面还是父进程(1)执行语句。wait()等待子进程停止或终止,返回值应是子进程的进程号(pid). + // 这三句的作用是父进程等待子进程的结束。&i是存放返回状态信息的位置。如果wait()返回值 + // 不等于子进程号,则继续等待。 if (pid>0) while (pid != wait(&i)) /* nothing */; + // 如果执行到这里,说明刚创建的子进程的执行已停止或终止了。下面循环中首先再创建 + // 一个子进程,如果出错,则显示“初始化程序创建子进程失败”信息并继续执行。对于所 + // 创建的子进程将关闭所有以前还遗留的句柄(stdin, stdout, stderr),新创建一个会话 + // 并设置进程组号,然后重新打开/dev/tty0作为stdin,并复制成stdout和sdterr.再次 + // 执行系统解释程序/bin/sh。但这次执行所选用的参数和环境数组另选了一套。然后父 + // 进程再次运行wait()等待。如果子进程又停止了执行,则在标准输出上显示出错信息 + // “子进程pid挺直了运行,返回码是i”,然后继续重试下去....,形成一个“大”循环。 + // 此外,wait()的另外一个功能是处理孤儿进程。如果一个进程的父进程先终止了,那么 + // 这个进程的父进程就会被设置为这里的init进程(进程1),并由init进程负责释放一个 + // 已终止进程的任务数据结构等资源。 while (1) { if ((pid=fork())<0) { printf("Fork failed in init\r\n"); continue; } - if (!pid) { + if (!pid) { // 新的子进程 close(0);close(1);close(2); - setsid(); + setsid(); // 创建一新的会话期 (void) open("/dev/tty0",O_RDWR,0); (void) dup(0); (void) dup(0); @@ -257,7 +297,10 @@ void init(void) if (pid == wait(&i)) break; printf("\n\rchild %d died with code %04x\n\r",pid,i); - sync(); + sync(); // 同步操作,刷新缓冲区。 } + // _exit()和exit()都用于正常终止一个函数。但_exit()直接是一个sys_exit系统调用, + // 而exit()则通常是普通函数库中的一个函数。它会先执行一些清除操作,例如调用 + // 执行各终止处理程序、关闭所有标准IO等,然后调用sys_exit。 _exit(0); /* NOTE! _exit, not exit() */ } diff --git a/kernel/Makefile b/kernel/Makefile index fbd0d43..bc08c81 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -6,15 +6,33 @@ # unless it's something special (ie not a .c file). # +# GNU的二进制文件处理程序,用于创建、修改以及从归档文件中抽取文件 AR =ar -AS =as -LD =ld +AS =as # GNU的汇编程序 +LD =ld # GNU的链接程序 +# 链接程序所有的参数,-s 输出文件中省略所有符号信息。-x 删除所有局部符号。 LDFLAGS =-s -x -CC =gcc -mcpu=i386 +CC =gcc -mcpu=i386 # GNU C语言编译器 +# C 编译程序选项。-Wall 显示所有的警告信息:-0 优化选项,优化代码长度和执行时间; +# -fstrength-reduce 优化循环代码,排除重复变量;-fomit-frame-pointer省略保存不 +# 必要的框架指针;-fcombine-regs合并寄存器,减少寄存器类的使用;-finline-function +# 将所有简单短小的函数代码嵌入调用程序中;-mstring-insns Linus自己添加的优化选项, +# 以后不再使用;-nostdinc -L../include不使用默认路径中的包含文件,而使用这里指定 +# 目录中的。 CFLAGS =-Wall -O -fstrength-reduce -fomit-frame-pointer \ -finline-functions -nostdinc -I../include +# C前处理选项。-E只运行C前处理,对所有指定的C程序进行预处理并将处理结果输出到标准 +# 输出设备或指定的输出文件中. CPP =gcc -E -nostdinc -I../include +# 下面的规则指示make利用下面的命令将所有的.c文件编译生成.s汇编程序。该规则的命令 +# 指使gcc采用CFLAGS所指定的选项对C代码编译后不进行汇编就停止(-S),从而产生与输入的 +# 各个C文件对应的汇编代码文件。默认情况下所产生的汇编程序文件名是原C文件名去掉.c而 +# 加上.s的后缀。-o表示其后是输出文件的名称。其中$*.s(或$@)是自动目标变量,$<代表第 +# 一个先决条件,这里即是符合条件*.c的文件。 +# 下面这3个不同规则分别用于不同的操作要求。若目标是.s文件,而源文件是.c文件则会使 +# 用第一个规则;若目录是.o,而原文件是.s,则使用第2个规则;若目录是.o文件而原文件 +# 是c文件,则可直接使用第3个规则。 .c.s: $(CC) $(CFLAGS) \ -S -o $*.s $< @@ -28,10 +46,14 @@ OBJS = sched.o system_call.o traps.o asm.o fork.o \ panic.o printk.o vsprintf.o sys.o exit.o \ signal.o mktime.o +# 在有了先决条件OBJS后使用下面的命令连接成目标kernel.o +# 选项'-r' 用于指示生成可重定位的输出,即产生可以作为链接器ld输入的目标文件。 kernel.o: $(OBJS) $(LD) -r -o kernel.o $(OBJS) sync +# 下面规则用于清理工作。当执行'make clean'时,就会执行上面的命令,去除所有编译 +# 链接生成的文件。'rm'是文件删除命令,选项-f含义是忽略不存在的文件并且不显示删除信息。 clean: rm -f core *.o *.a tmp_make keyboard.s for i in *.c;do rm -f `basename $$i .c`.s;done @@ -39,6 +61,13 @@ clean: (cd blk_drv; make clean) (cd math; make clean) +# 下面的目标或规则用于检查各文件之间的依赖关系。方法如下: +# 使用字符串编辑程序sed对makefile文件(这里即是自己)进行处理,输出为删除Makefile +# 文件中'### Dependencies'行后面的所有航,并生成tmp_make临时文件。然后对kernel/目录下 +# 的每一个C文件执行gcc预处理操作,-M标志告诉预处理程序输出描述符每个目标文件相关性的 +# 规则,并且这些规则符合make语法。对于每一个源文件,预处理程序输出一个make规则,其结果 +# 形式是相应源程序文件的目标文件名加上其依赖关系——该源文件中包含的所有头文件列表。把 +# 预处理结果都添加到临时文件tmp_make中,然后将该临时文件复制成新的Makefile文件。 dep: sed '/\#\#\# Dependencies/q' < Makefile > tmp_make (for i in *.c;do echo -n `echo $$i | sed 's,\.c,\.s,'`" "; \