linux0.11磁盘映像制作及其剩余程序阅读注释笔记

[ 1] linux0.11引导程序阅读注释。
[ 2] linux0.11由实模式进入保护模式程序阅读注释 。
[ 3] linux0.11护模式初始化程序阅读注释。
[ 4] linux0.11主存管理程序阅读注释。
[ 5] linux0.11中断/异常机制初始设置相关程序阅读注释。
[ 6] linux0.11缓冲区管理程序阅读注释。
[ 7] linux0.11文件系统管理程序阅读注释。
[ 8] linux0.11块设备驱动及访问请求管理程序阅读注释。
[ 9] linux0.11字符设备驱动及访问请求管理程序阅读注释。
[10] linux0.11多任务管理程序阅读注释。
[11] linux0.11信号处理程序阅读注释。

篇幅较长,可通过浏览器的搜索功能(Ctrl + f)搜索函数名了解相应函数的实现机制,如 do_execve。

[12] linux0.11磁盘映像制作及其剩余程序阅读注释

linux0.11粗略阅读完结篇,在此刻以及在阅读linux0.11过程中的某些时段,此文有过激动。

build.c
/*
 *  linux/tools/build.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * This file builds a disk-image from three different files:
 *
 * - bootsect: max 510 bytes of 8086 machine code, loads the rest
 * - setup: max 4 sectors of 8086 machine code, sets up system parm
 * - system: 80386 code for actual system
 *
 * It does some checking that all files are of the correct type, and
 * just writes the result to stdout, removing headers and padding to
 * the right amount. It also writes some system data to stderr.
 *
 * 本文件根据以下三个不同的文件构建linux0.11磁盘映像文件:
 * - bootsect:最多包含510字节8086机器码,用于加载后续两个文件内容
 * - setup:最多包含4个扇区8086机器码,用于设置系统启动所需参数
 * - system:80386机器码,真正的操作系统代码,前面两个文件是system的过度
 *
 * 根据以上三个文件制作磁盘映像文件时,本文件会检查这三个文件格式是否正
 * 确并将检查结果显示在标准输出上,然后将这三个文件中的编译链接头部信息
 * 移除并作适当填充(如setup不足4扇区时填充0补齐4扇区)。 若出错则将错误
 * 信息显示在标准输出上。
 */

/*
 * Changes by tytso to allow root device specification
 */

#include  /* fprintf */
#include 
#include  /* contains exit */
#include  /* unistd.h needs this */
#include 
#include 
#include  /* contains read/write */
#include 

/* bootsect.s,setup.s符合MINIX OS上可执行文件格式,头部为32字节;
 * system由gcc编译链接而来,头部有1024字节。*/
#define MINIX_HEADER 32
#define GCC_HEADER 1024

/* 0x20000为system最大尺寸 */
#define SYS_SIZE 0x2000

/* 默认根文件系统设备(第2个硬盘第1分区)的主次设备号 */
#define DEFAULT_MAJOR_ROOT 3
#define DEFAULT_MINOR_ROOT 6

/* max nr of sectors of setup: don't change unless you also change
 * bootsect etc 
 *
 * setup最多所占的扇区数,在没有修改跟加载setup相关代码时勿修改该宏值 */
#define SETUP_SECTS 4

#define STRINGIFY(x) #x

/* die,
 * 往错误输出写字符串str并终止当前程序。*/
void die(char * str)
{
    fprintf(stderr,"%s\n",str);
    exit(1);
}

/* usage,
 * 提示build程序的用法。*/
void usage(void)
{
    die("Usage: build bootsect setup system [rootdev] [> image]");
}

/* build工具主程序,
 * build工具将bootsect setup system
 * 三个文件的编译链接头部去除后依次
 * 写入image文件中得到磁盘映像文件。
 * 
 * 将该磁盘映像文件写入计算机的启动
 * 设备中,计算机在开机后,BIOS将读取
 * 并执行启动设备启动区内容即磁盘映
 * 像中的前512字节内容。*/
int main(int argc, char ** argv)
{
    int i,c,id;
    char buf[1024];
    char major_root, minor_root;
    struct stat sb;

    /* 在MINIX OS上运行build可执行程序制作linux0.11磁盘映像时,
     * 其用法为
     * build bootsect setup system [rootdev] [> image]
     * [rootdev]是可选参数,代表根文件系统设备名。
     * build往标准输出写入的内容将被被重定向到image文件中。*/
    if ((argc != 4) && (argc != 5))
        usage();

    /* 命令行参数有5个时,标识指定根文件系统设备 */
    if (argc == 5) {
        /* 若根文件系统设备不为软盘则通过stat
         * 获取根文件系统设备主次设备号。*/
        if (strcmp(argv[4], "FLOPPY")) {
            if (stat(argv[4], &sb)) {
                perror(argv[4]);
                die("Couldn't stat root device.");
            }
            major_root = MAJOR(sb.st_rdev);
            minor_root = MINOR(sb.st_rdev);
        } else {
            /* 标识根文件系统就是当前磁盘映像 */
            major_root = 0;
            minor_root = 0;
        }
    /* 命令行只有3个参数则使用默认设备作为根文件系统 */
    } else {
        major_root = DEFAULT_MAJOR_ROOT;
        minor_root = DEFAULT_MINOR_ROOT;
    }
    fprintf(stderr, "Root device is (%d, %d)\n", major_root, minor_root);
    /* 根文件主设备号不为软盘,硬盘,磁盘映像则出错退出 */
    if ((major_root != 2) && (major_root != 3) &&
        (major_root != 0)) {
        fprintf(stderr, "Illegal root device (major = %d)\n",
            major_root);
        die("Bad root device --- major #");
    }

    /* 初始化buf内存块 */
    for (i=0;i0 ; i+=c )
        if (write(1,buf,c)!=c)
            die("Write call failed");
    close (id);

    /* 若setup可执行文件内容超过4扇区则提示并终止程序 */
    if (i > SETUP_SECTS*512)
        die("Setup exceeds " STRINGIFY(SETUP_SECTS)
            " sectors - rewrite build/boot/setup");
    /* 若setup小于4扇区则用0补齐4扇区 */
    fprintf(stderr,"Setup is %d bytes.\n",i);
    for (c=0 ; c sizeof(buf))
            c = sizeof(buf);
        if (write(1,buf,c) != c)
            die("Write call failed");
        i += c;
    }

    /* 判断system可执行文件格式并除去头部内容后写到标准输出 */
    if ((id=open(argv[3],O_RDONLY,0))<0)
        die("Unable to open 'system'");
    if (read(id,buf,GCC_HEADER) != GCC_HEADER)
        die("Unable to read header of 'system'");
    if (((long *) buf)[5] != 0)
        die("Non-GCC header of 'system'");
    for (i=0 ; (c=read(id,buf,sizeof buf))>0 ; i+=c )
        if (write(1,buf,c)!=c)
            die("Write call failed");
    close(id);
        
    fprintf(stderr,"System is %d bytes.\n",i);
    if (i > SYS_SIZE*16)
        die("System is too big");
    return(0);

/* build将可执行文件bootsect,setup,system的头部信息去掉后, 将
 * 他们的内容依次写入标准输出,由于标准输出被重定向到image文件,
 * 所以image文件内容为
 * ---------------------------
 * |bootsect| setup | system |
 * ---------------------------
 * 0        0x200   0x800    0x20800
 * image就是所谓的磁盘映像文件了。
 * 可以跟包含文件系统的haribote磁盘映像做个简单对比,以进一步理
 * 解磁盘映像概念。*/
}

虽然Makefile有些显老,对理解磁盘映像制作、工程管理及编写风格有利。

Makefile
#
# if you want the ram-disk device, define this to be the
# size in blocks.
#
# 若欲使用虚拟硬盘,则用RAMDISK定义虚拟硬盘大小(单位Kb,见main)。
RAMDISK = #-DRAMDISK=512

# 8086指令 汇编编译器和链接器
# 没有度娘到as86官网,参考网上归结其编译选项如下
# as86
# -0 16bit指令
# -3 32bit指令
# -a 产生与GNU编译链接器兼容的机器码
# -b 产生二进制文件,其后可跟文件名
# -g 在目标文件中保存全局符号
# -j 所有跳转语句均为长跳转
# -m 在符号列表中扩展宏定义
# -o 产生目标文件,其后跟目标文件名
# -s 产生符号文件,其后跟符号文件名
# -u 产生未定义符号表
# -w 不显示编译警告信息
#
# ld86
# -0 产生16bit格式的头部信息
# -3 产生32bit格式的头部信息
# -M 在标准输出设备上显示已链接的符号
# -lx 将库/local/lib/subdir/libx.a加入链接表中
# -m 在标准输出设备上显示已链接的模块
# -o 指定输出文件名,其后跟输出文件名
# -r 产生适合进一步重定位的输出
# -s 删除目标文件中的符号
AS86    =as86 -0 -a
LD86    =ld86 -0

# GNU汇编编译器和链接器
AS  =gas
LD  =gld

# GNU链接器选项...
LDFLAGS =-s -x -M

# gcc及其编译选项,
# 可以在其官网上通过Manual页面查看(第3章)
# https://gcc.gnu.org/onlinedocs/
CC  =gcc $(RAMDISK)
CFLAGS  =-Wall -O -fstrength-reduce -fomit-frame-pointer \
-fcombine-regs -mstring-insns
# GNU CPP 预处理器
# https://gcc.gnu.org/onlinedocs/cpp/
# 要在官网文档找以下这两个特定选项也是比较难一点。
CPP =cpp -nostdinc -Iinclude

#
# ROOT_DEV specifies the default root-device when making the image.
# This can be either FLOPPY, /dev/xxxx or empty, in which case the
# default of /dev/hd6 is used by 'build'.
#
# ROOT_DEV用于指定 在用build工具制作磁盘映像时的 默认根文件设备。
ROOT_DEV=/dev/hd6

# 定义变量,作为后续规则的先决依赖条件或目标输出文件。
ARCHIVES=kernel/kernel.o mm/mm.o fs/fs.o
DRIVERS =kernel/blk_drv/blk_drv.a kernel/chr_drv/chr_drv.a
MATH    =kernel/math/math.a
LIBS    =lib/lib.a

# 隐式规则,用于匹配后续只含目标和先决依赖文件的规则 #
# 此处的自动变量$*和$<分别代表无后缀目标和第一个先决依赖文件 #
#
# 用于匹配目标为汇编源文件(.s),先决依赖文件为C源文件(.c)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用C编译器
# 及其编译选项将($<对应的)C源文件转换为($*.o指定的)汇编源文件。
.c.s:
    $(CC) $(CFLAGS) \
    -nostdinc -Iinclude -S -o $*.s $<
# 用于匹配目标为目标文件(.o),先决依赖文件为汇编源文件(.s)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用汇编器将
# ($<对应的)汇编源文件转换为($*.o指定的)目标文件。
.s.o:
    $(AS) -c -o $*.o $<
# 用于匹配目标为目标文件(.o),先决依赖文件为汇编源文件(.c)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用C编译器将
# ($<对应的)C源文件转换为($*.o指定的)目标文件。
.c.o:
    $(CC) $(CFLAGS) \
    -nostdinc -Iinclude -c -o $*.o $<
# 
# 为以上隐式规则举个简单例子。
# 当前目录有 Makefile main.c 文件
# 
# all: main.out
# 
# main.out : main.o
#     gcc main.o -o main.out
# 
# main.o : main.c
# 
# .c.o:
#     gcc -c -o $*.o $<
#
# 在当前目录执行 make 命令
# gcc -c -o main.o main.c
# gcc main.o -o main.out

# 最顶层目标all,其先决依赖文件为Image。
# 当执行"make all"时会先检查Image是否发生改变。
all:    Image

# 目标Image,其先决依赖文件为boot/bootset... tools/build等文件,
# 当先决依赖文件有改变时则会执行该规则下的命令以重新生成Image。
# 
# 该规则可由"make Image" "make disk"间接触发或由"make Image"直接触发。
Image: boot/bootsect boot/setup tools/system tools/build
    tools/build boot/bootsect boot/setup tools/system $(ROOT_DEV) > Image
    sync

# 目标disk的先决依赖文件也为Image, 当Image发生变化时该规则下的命令会执行。
# 该命令将Image以8192字节为单位写入/dev/PSO即第一个软驱设备上。将Image写
# 入到软驱设备软盘上后若BIOS将该软盘设置为启动设备,重启计算机就可以让计算
# 机启动并运行Linux0.11了。
#
# 由 make disk 触发该规则。
disk: Image
    dd bs=8192 if=Image of=/dev/PS0

# 目标tools/build的先决依赖文件为tools/build.c。
# 当依赖文件tools/build.c发生改变则执行规则中的命令及重新生成build。
#
# make Image; make Image; make disk都可触发该规则。
tools/build: tools/build.c
    $(CC) $(CFLAGS) \
    -o tools/build tools/build.c

# 目标boot/head.o的依赖文件文件boot/head.s
# 该规则与前文第2个隐式规则匹配,即若该规则
# 被触发则将boot/head.s转换为boot/head.o。
# 
# 该规则由下一条即tools/system目标所在规则触发。
boot/head.o: boot/head.s

# 目标tools/system的先决依赖文件为boot/head.o init/main.o... $(LIBS)等
# 当先决依赖文件有发生改变时,规则中的命令将被执行,即会生成system到tools
# 目录下。
#
# 该规则由make Image触发,凡能触发Image目标所在规则的规则都可触本规则。
tools/system:   boot/head.o init/main.o \
    $(ARCHIVES) $(DRIVERS) $(MATH) $(LIBS)
    $(LD) $(LDFLAGS) boot/head.o init/main.o \
    $(ARCHIVES) \
    $(DRIVERS) \
    $(MATH) \
    $(LIBS) \
    -o tools/system > System.map

# 目标kernel/math/math.a由命令
# (cd kernel/math; make)生成即进入kernel/math目录后执行make生成。
#
# 该规则直接由 目标tools/system所在规则直接触发。
kernel/math/math.a:
    (cd kernel/math; make)

# 目标kernel/blk_drv/blk_drv.a由命令
# (cd kernel/blk_drv; make)生成即进入kernel/blk_drv目录后执行make生成。
#
# 该规则直接由 目标tools/system所在规则直接触发。
kernel/blk_drv/blk_drv.a:
    (cd kernel/blk_drv; make)

# 目标kernel/chr_drv/chr_drv.a由命令
# (cd kernel/chr_drv; make)生成即进入kernel/chr_drv目录后执行make生成。
#
# 该规则直接由 目标tools/system所在规则直接触发。
kernel/chr_drv/chr_drv.a:
    (cd kernel/chr_drv; make)

# 目标kernel/kernel.o由命令
# (cd kernel; make)生成即进入kernel目录后执行make生成。
#
# 该规则直接由 目标tools/system所在规则直接触发。
kernel/kernel.o:
    (cd kernel; make)

# 目标mm/mm.o由命令
# (cd mm; make)生成即进入mm目录后执行make生成。
#
# 该规则直接由 目标tools/system所在规则直接触发。
mm/mm.o:
    (cd mm; make)

# 目标fs/fs.o由命令
# (cd fs; make)生成即进入fs目录后执行make生成。
#
# 该规则直接由 目标tools/system所在规则直接触发。
fs/fs.o:
    (cd fs; make)

# 目标lib/lib.a由命令
# (cd lib; make)生成即进入lib目录后执行make生成
# 
# 该规则直接由 目标tools/system所在规则直接触发。
lib/lib.a:
    (cd lib; make)

# 目标boot/setup的先决依赖文件为boot/setup.s。
# 当boot/setup.s发生改变时规则中命令将被执行
# 即生成setup可执行文件到boot目录下。
#
# 该规则直接由 目标Image所在规则直接触发。
boot/setup: boot/setup.s
    $(AS86) -o boot/setup.o boot/setup.s
    $(LD86) -s -o boot/setup boot/setup.o

# 目标boot/bootsect先决依赖文件为boot/bootsect.s
# 当依赖文件boot/bootsect.s发生改变时规则中命令将
# 会被执行即生成bootsect到boot目录下。
#
# 该规则直接由 目标Image所在规则直接触发。
boot/bootsect: boot/bootsect.s
    $(AS86) -o boot/bootsect.o boot/bootsect.s
    $(LD86) -s -o boot/bootsect boot/bootsect.o

# 忽略吧
tmp.s:  boot/bootsect.s tools/system
    (echo -n "SYSSIZE = (";ls -l tools/system | grep system \
        | cut -c25-31 | tr '\012' ' '; echo "+ 15 ) / 16") > tmp.s
    cat boot/bootsect.s >> tmp.s

# clean对应的命令将清除所有的结果文件
clean:
    rm -f Image System.map tmp_make core boot/bootsect boot/setup
    rm -f init/*.o tools/system tools/build boot/*.o
    (cd mm;make clean)
    (cd fs;make clean)
    (cd kernel;make clean)
    (cd lib;make clean)

backup: clean
    (cd .. ; tar cf - linux | compress - > backup.Z)
    sync

# init/main.o目标所在规则 所依赖头文件是通过手动指定的。
#
# make dep 将移除 ### Dependencies后续规则并将遍历本Makefile所在目录
# 下的源文件并确定其所依赖的头文件文件。原理是利用gcc预处理功能查看各
# C源文件中所包含的头文件,以将被包含头文件作为C源文件被编译的先决依赖文件。
dep:
    sed '/\#\#\# Dependencies/q' < Makefile > tmp_make
    (for i in init/*.c;do echo -n "init/";$(CPP) -M $$i;done) >> tmp_make
    cp tmp_make Makefile
    (cd fs; make dep)
    (cd kernel; make dep)
    (cd mm; make dep)

### Dependencies:
# 目标init/main.o所依赖的文件如下。
# 该规则由tools/system目标所在规则直接触发。
# 
# 该规则被触发后与本文件前面第3条隐式规则匹配,
# 即.c文件将被转换为init/main.o文件。
init/main.o : init/main.c include/unistd.h include/sys/stat.h \
  include/sys/types.h include/sys/times.h include/sys/utsname.h \
  include/utime.h include/time.h include/linux/tty.h include/termios.h \
  include/linux/sched.h include/linux/head.h include/linux/fs.h \
  include/linux/mm.h include/signal.h include/asm/system.h include/asm/io.h \
  include/stddef.h include/stdarg.h include/fcntl.h 


# 此文阅读Makefile主要是为了解下由工程文件生成映像文件的过程,所以只会重点关注与这个过程相关的规则。
# 欲了解映像文件生成,只阅读本Makefile应该就足够了,但也可以根据本Makefile提供的线索继续到各子目录看
# 各Makefile。
#
# kernel/Makefile
# kerne/math/Makefile
# kernel/blk_drv/Makefile
# kernel/chr_drv/Makefile
#
# mm/Makefile
# fs/Makefile
# lib/Makefile
kernel/Makefile
#
# Makefile for the FREAX-kernel.
#
# Note! Dependencies are done automagically by 'make dep', which also
# removes any old dependencies. DON'T put your own dependencies here
# unless it's something special (ie not a .c file).
#
# 注: make dep 命令将会移除手动指定的依赖并自动生成生成新的依赖。不要
# 手动放置依赖文件进入,除非该依赖文件比较特殊(如非.c文件)。

# 在制作映像文件过程中,kernel/Makefile由根目录Makefile(在本Makefile所在目录)执行make命令解析 #

# gar用于打包目标文件形成.a库文件,也能从库文件中抽取文件
# gas为GNU汇编器
# gld为GNU链接器
# gcc为GNU C编译器
# https://gcc.gnu.org/onlinedocs/
# https://gcc.gnu.org/onlinedocs/cpp/
AR  =gar
AS  =gas
LD  =gld
# 设置链接器链接参数
LDFLAGS =-s -x 
CC  =gcc
# 设置C编译器编译参数
CFLAGS  =-Wall -O -fstrength-reduce -fomit-frame-pointer -fcombine-regs \
    -finline-functions -mstring-insns -nostdinc -I../include
# C预处理器使用gcc -E选项对应的预处理器
CPP =gcc -E -nostdinc -I../include

# 隐式规则,用于匹配后续只含目标和先决依赖文件的规则 #
# 此处的自动变量$*和$<分别代表无后缀目标和第一个先决依赖文件 #
#
# 用于匹配目标为汇编源文件(.s),先决依赖文件为C源文件(.c)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用C编译器
# 及其编译选项将($<对应的)C源文件转换为($*.o指定的)汇编源文件。
.c.s:
    $(CC) $(CFLAGS) \
    -S -o $*.s $<
# 用于匹配目标为目标文件(.o),先决依赖文件为汇编源文件(.c)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用C编译器将
# ($<对应的)C源文件转换为($*.o指定的)目标文件。
.s.o:
    $(AS) -o $*.o $<
# 用于匹配目标为目标文件(.o),先决依赖文件为汇编源文件(.s)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用汇编器将
# ($<对应的)汇编源文件转换为($*.o指定的)目标文件。
.c.o:
    $(CC) $(CFLAGS) \
    -c -o $*.o $<
# 
# 为以上隐式规则举个简单例子。
# 当前目录有 Makefile main.c 文件
# 
# all: main.out
# 
# main.out : main.o
#     gcc main.o -o main.out
# 
# main.o : main.c
# 
# .c.o:
#     gcc -c -o $*.o $<
#
# 在当前目录执行 make 命令
# gcc -c -o main.o main.c
# gcc main.o -o main.out

# 手动指定目标文件集赋给OBJS变量
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

# kernel.o为本Makefile的顶层目标。当在本Makefile所在目录中执行
# make命令时,kernel.o将会作为make默认目标。
# 
# 目标kernel.o的先决依赖文件为OBJS变量中的目标文件集。当OBJS中
# 的目标文件有发生变化时,make将会(在命令行)运行该规则下将目标文
# 件链接成kerne.o目标文件的命令。
kernel.o: $(OBJS)
    $(LD) -r -o kernel.o $(OBJS)
    sync

# make clean 将删除由本Makefile生成的所有文件。
clean:
    rm -f core *.o *.a tmp_make keyboard.s
    for i in *.c;do rm -f `basename $$i .c`.s;done
    (cd chr_drv; make clean)
    (cd blk_drv; make clean)
    (cd math; make clean)

# make dep 将移除 ### Dependencies后续规则并将遍历本Makefile所在目录
# 下的源文件并确定其所依赖的头文件文件。原理是利用gcc预处理功能查看各
# C源文件中所包含的头文件,以将被包含头文件作为C源文件被编译的先决依赖文件。
dep:
    sed '/\#\#\# Dependencies/q' < Makefile > tmp_make
    (for i in *.c;do echo -n `echo $$i | sed 's,\.c,\.s,'`" "; \
        $(CPP) -M $$i;done) >> tmp_make
    cp tmp_make Makefile
    (cd chr_drv; make dep)
    (cd blk_drv; make dep)

### Dependencies:
# 多目标规则 t1 t2 : prerequisites 相当于
#   t1 : prerequisites
#   t2 :prerequisites
#
# 分别匹配前面第1条和第3条隐式规则,即由exit.c分别生成exit.s和exit.o。
exit.s exit.o : exit.c ../include/errno.h ../include/signal.h \
  ../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \
  ../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \
  ../include/asm/segment.h 
# 分别匹配前面第1条和第3条隐式规则,即由fork.c分别生成fork.s和fork.o。
fork.s fork.o : fork.c ../include/errno.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/sys/types.h \
  ../include/linux/mm.h ../include/signal.h ../include/linux/kernel.h \
  ../include/asm/segment.h ../include/asm/system.h 
# 分别匹配前面第1条和第3条隐式规则,即由mktime.c分别生成mktime.s和mktime.o。
mktime.s mktime.o : mktime.c ../include/time.h 
# 分别匹配前面第1条和第3条隐式规则,即由panic.c分别生成panic.s和panic.o。
panic.s panic.o : panic.c ../include/linux/kernel.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/sys/types.h \
  ../include/linux/mm.h ../include/signal.h 
# 分别匹配前面第1条和第3条隐式规则,即由printk.c分别生成printk.s和printk.o。
printk.s printk.o : printk.c ../include/stdarg.h ../include/stddef.h \
  ../include/linux/kernel.h 
# 分别匹配前面第1条和第3条隐式规则,即由sched.c分别生成sched.s和sched.o。
sched.s sched.o : sched.c ../include/linux/sched.h ../include/linux/head.h \
  ../include/linux/fs.h ../include/sys/types.h ../include/linux/mm.h \
  ../include/signal.h ../include/linux/kernel.h ../include/linux/sys.h \
  ../include/linux/fdreg.h ../include/asm/system.h ../include/asm/io.h \
  ../include/asm/segment.h 
# 分别匹配前面第1条和第3条隐式规则,即由signal.c分别生成signal.s和signal.o。
signal.s signal.o : signal.c ../include/linux/sched.h ../include/linux/head.h \
  ../include/linux/fs.h ../include/sys/types.h ../include/linux/mm.h \
  ../include/signal.h ../include/linux/kernel.h ../include/asm/segment.h 
# 分别匹配前面第1条和第3条隐式规则,即由sys.c分别生成sys.s和sys.o。
sys.s sys.o : sys.c ../include/errno.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/sys/types.h \
  ../include/linux/mm.h ../include/signal.h ../include/linux/tty.h \
  ../include/termios.h ../include/linux/kernel.h ../include/asm/segment.h \
  ../include/sys/times.h ../include/sys/utsname.h 
# 分别匹配前面第1条和第3条隐式规则,即由traps.c分别生成traps.s和traps.o。
traps.s traps.o : traps.c ../include/string.h ../include/linux/head.h \
  ../include/linux/sched.h ../include/linux/fs.h ../include/sys/types.h \
  ../include/linux/mm.h ../include/signal.h ../include/linux/kernel.h \
  ../include/asm/system.h ../include/asm/segment.h ../include/asm/io.h 
# 分别匹配前面第1条和第3条隐式规则,即由vsprintf.c分别生成vsprintf.s和vsprintf.o。
vsprintf.s vsprintf.o : vsprintf.c ../include/stdarg.h ../include/string.h 

# 这些匹配相应隐式规则而生成的目标文件将用于顶层目标kernel.o所在规则的先决依赖文件以生成kernel.o #
kerne/math/Makefile
#
# Makefile for the FREAX-kernel character device drivers.
#
# Note! Dependencies are done automagically by 'make dep', which also
# removes any old dependencies. DON'T put your own dependencies here
# unless it's something special (ie not a .c file).
#
# 注, make dep 会移除为源文件手动指定的依赖而自动为源文件生成新依赖。
# 执行该规则后,不要再手动放置C源文件作为依赖文件。

# 在制作映像文件过程中,kernel/math/Makefile由根目录Makefile(在本Makefile所在目录)执行make命令解析 #

# gar,gas,gld,gcc, gcc -E分别为
# GNU 库文件打包,汇编器,链接器,C编译器和C预处理器。
# https://gcc.gnu.org/onlinedocs/
# https://gcc.gnu.org/onlinedocs/cpp/
AR  =gar
AS  =gas
LD  =gld
LDFLAGS =-s -x
CC  =gcc
CFLAGS  =-Wall -O -fstrength-reduce -fomit-frame-pointer -fcombine-regs \
    -finline-functions -mstring-insns -nostdinc -I../../include
CPP =gcc -E -nostdinc -I../../include

# 隐式规则,用于匹配后续只含目标和先决依赖文件的规则 #
# 此处的自动变量$*和$<分别代表无后缀目标和第一个先决依赖文件 #
#
# 用于匹配目标为汇编源文件(.s),先决依赖文件为C源文件(.c)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用C编译器
# 及其编译选项将($<对应的)C源文件转换为($*.o指定的)汇编源文件。
.c.s:
    $(CC) $(CFLAGS) \
    -S -o $*.s $<
# 用于匹配目标为目标文件(.o),先决依赖文件为汇编源文件(.c)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用C编译器将
# ($<对应的)C源文件转换为($*.o指定的)目标文件。
.s.o:
    $(AS) -o $*.o $<
# 用于匹配目标为目标文件(.o),先决依赖文件为汇编源文件(.s)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用汇编器将
# ($<对应的)汇编源文件转换为($*.o指定的)目标文件。
.c.o:
    $(CC) $(CFLAGS) \
    -c -o $*.o $<
# 
# 为以上隐式规则举个简单例子。
# 当前目录有 Makefile main.c 文件
# 
# all: main.out
# 
# main.out : main.o
#     gcc main.o -o main.out
# 
# main.o : main.c
# 
# .c.o:
#     gcc -c -o $*.o $<
#
# 在当前目录执行 make 命令
# gcc -c -o main.o main.c
# gcc main.o -o main.out

# 目标文件(集)赋给变量OBJS
OBJS  = math_emulate.o

# math.a为本Makefile的顶层目标。当在本Makefile所在目录中执行
# make命令时,math.a将会作为make默认目标。
# 
# 目标math.a的先决依赖文件为OBJS变量中的目标文件(集)。当OBJS中
# 的目标文件有发生变化时,make将会(在命令行)运行该规则下将目标文
# 件打包成math.a库文件的命令。
math.a: $(OBJS)
    $(AR) rcs math.a $(OBJS)
    sync
    
# make clean 将清除由本Makefile生成的文件
clean:
    rm -f core *.o *.a tmp_make
    for i in *.c;do rm -f `basename $$i .c`.s;done

# make dep 将遍历本Makefile所在目录下的源文件并确定其所依赖的头文件文件。
# 原理是利用gcc预处理功能查看各C源文件中所包含的头文件,以将被包含头文
# 件作为C源文件被编译的先决依赖文件。
dep:
    sed '/\#\#\# Dependencies/q' < Makefile > tmp_make
    (for i in *.c;do echo -n `echo $$i | sed 's,\.c,\.s,'`" "; \
        $(CPP) -M $$i;done) >> tmp_make
    cp tmp_make Makefile

### Dependencies:
kernel/blk_drv/Makefile
#
# Makefile for the FREAX-kernel block device drivers.
#
# Note! Dependencies are done automagically by 'make dep', which also
# removes any old dependencies. DON'T put your own dependencies here
# unless it's something special (ie not a .c file).
#
# 注, make dep 会移除为源文件手动指定的依赖而自动为源文件生成新依赖。
# 执行该规则后,不要再手动放置C源文件作为依赖文件。

# 在制作映像文件过程中,kernel/blk_drv/Makefile由根目录Makefile(在本Makefile所在目录)执行make命令解析 #

# gar,gas,gld,gcc, gcc -E分别为
# GNU 库文件打包,汇编器,链接器,C编译器和C预处理器。
# https://gcc.gnu.org/onlinedocs/
# https://gcc.gnu.org/onlinedocs/cpp/
AR  =gar
AS  =gas
LD  =gld
LDFLAGS =-s -x
CC  =gcc
CFLAGS  =-Wall -O -fstrength-reduce -fomit-frame-pointer -fcombine-regs \
    -finline-functions -mstring-insns -nostdinc -I../../include
CPP =gcc -E -nostdinc -I../../include

# 隐式规则,用于匹配后续只含目标和先决依赖文件的规则 #
# 此处的自动变量$*和$<分别代表无后缀目标和第一个先决依赖文件 #
#
# 用于匹配目标为汇编源文件(.s),先决依赖文件为C源文件(.c)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用C编译器
# 及其编译选项将($<对应的)C源文件转换为($*.o指定的)汇编源文件。
.c.s:
    $(CC) $(CFLAGS) \
    -S -o $*.s $<
# 用于匹配目标为目标文件(.o),先决依赖文件为汇编源文件(.c)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用C编译器将
# ($<对应的)C源文件转换为($*.o指定的)目标文件。
.s.o:
    $(AS) -o $*.o $<
# 用于匹配目标为目标文件(.o),先决依赖文件为汇编源文件(.s)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用汇编器将
# ($<对应的)汇编源文件转换为($*.o指定的)目标文件。
.c.o:
    $(CC) $(CFLAGS) \
    -c -o $*.o $<
# 
# 为以上隐式规则举个简单例子。
# 当前目录有 Makefile main.c 文件
# 
# all: main.out
# 
# main.out : main.o
#     gcc main.o -o main.out
# 
# main.o : main.c
# 
# .c.o:
#     gcc -c -o $*.o $<
#
# 在当前目录执行 make 命令
# gcc -c -o main.o main.c
# gcc main.o -o main.out

# 将目标文件集赋给变量OBJS
OBJS  = ll_rw_blk.o floppy.o hd.o ramdisk.o

# blk_drv.a为本Makefile的顶层目标。当在本Makefile所在目录中执行
# make命令时,blk_drv.a将会作为make默认目标。
# 
# 目标blk_drv.a的先决依赖文件为OBJS变量中的目标文件集。当OBJS中
# 的目标文件有发生变化时,make将会(在命令行)运行该规则下将目标文
# 件打包成blk_drv.a库文件的命令。
blk_drv.a: $(OBJS)
    $(AR) rcs blk_drv.a $(OBJS)
    sync

# make clean 将删除由本Makefile生成的文件。
clean:
    rm -f core *.o *.a tmp_make
    for i in *.c;do rm -f `basename $$i .c`.s;done

# make dep 将移除 ### Dependencies后续规则并将遍历本Makefile所在目录
# 下的源文件并确定其所依赖的头文件文件。原理是利用gcc预处理功能查看各
# C源文件中所包含的头文件,以将被包含头文件作为C源文件被编译的先决依赖文件。
dep:
    sed '/\#\#\# Dependencies/q' < Makefile > tmp_make
    (for i in *.c;do echo -n `echo $$i | sed 's,\.c,\.s,'`" "; \
        $(CPP) -M $$i;done) >> tmp_make
    cp tmp_make Makefile

### Dependencies:
# 多目标规则 t1 t2 : prerequisites 相当于
#   t1 : prerequisites
#   t2 :prerequisites
#
#分别匹配前面第1条和第3条隐式规则,即由floppy.c分别生成floppy.s和floppy.o。
floppy.s floppy.o : floppy.c ../../include/linux/sched.h ../../include/linux/head.h \
  ../../include/linux/fs.h ../../include/sys/types.h ../../include/linux/mm.h \
  ../../include/signal.h ../../include/linux/kernel.h \
  ../../include/linux/fdreg.h ../../include/asm/system.h \
  ../../include/asm/io.h ../../include/asm/segment.h blk.h 
#分别匹配前面第1条和第3条隐式规则,即由hd.c分别生成hd.s和hd.o。
hd.s hd.o : hd.c ../../include/linux/config.h ../../include/linux/sched.h \
  ../../include/linux/head.h ../../include/linux/fs.h \
  ../../include/sys/types.h ../../include/linux/mm.h ../../include/signal.h \
  ../../include/linux/kernel.h ../../include/linux/hdreg.h \
  ../../include/asm/system.h ../../include/asm/io.h \
  ../../include/asm/segment.h blk.h 
#分别匹配前面第1条和第3条隐式规则,即由ll_rw_blk.c分别生成ll_rw_blk.s和ll_rw_blk.o。
ll_rw_blk.s ll_rw_blk.o : ll_rw_blk.c ../../include/errno.h ../../include/linux/sched.h \
  ../../include/linux/head.h ../../include/linux/fs.h \
  ../../include/sys/types.h ../../include/linux/mm.h ../../include/signal.h \
  ../../include/linux/kernel.h ../../include/asm/system.h blk.h 

# 这些匹配相应隐式规则而生成的目标文件目标文件将用于顶层目标blk_drv.a所在规则的先决依赖文件以生成blk_drv.a #
kernel/chr_drv/Makefile
#
# Makefile for the FREAX-kernel character device drivers.
#
# Note! Dependencies are done automagically by 'make dep', which also
# removes any old dependencies. DON'T put your own dependencies here
# unless it's something special (ie not a .c file).
#
# 注, make dep 会移除为源文件手动指定的依赖而自动为源文件生成新依赖。
# 执行该规则后,不要再手动放置C源文件作为依赖文件。

# 在制作映像文件过程中,kernel/chr_drv/Makefile由根目录Makefile(在本Makefile所在目录)执行make命令解析 #

# gar,gas,gld,gcc, gcc -E分别为
# GNU 库文件打包,汇编器,链接器,C编译器和C预处理器。
# https://gcc.gnu.org/onlinedocs/
# https://gcc.gnu.org/onlinedocs/cpp/
AR  =gar
AS  =gas
LD  =gld
LDFLAGS =-s -x
CC  =gcc
CFLAGS  =-Wall -O -fstrength-reduce -fomit-frame-pointer -fcombine-regs \
    -finline-functions -mstring-insns -nostdinc -I../../include
CPP =gcc -E -nostdinc -I../../include

# 隐式规则,用于匹配后续只含目标和先决依赖文件的规则 #
# 此处的自动变量$*和$<分别代表无后缀目标和第一个先决依赖文件 #
#
# 用于匹配目标为汇编源文件(.s),先决依赖文件为C源文件(.c)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用C编译器
# 及其编译选项将($<对应的)C源文件转换为($*.o指定的)汇编源文件。
.c.s:
    $(CC) $(CFLAGS) \
    -S -o $*.s $<
# 用于匹配目标为目标文件(.o),先决依赖文件为汇编源文件(.c)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用C编译器将
# ($<对应的)C源文件转换为($*.o指定的)目标文件。
.s.o:
    $(AS) -o $*.o $<
# 用于匹配目标为目标文件(.o),先决依赖文件为汇编源文件(.s)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用汇编器将
# ($<对应的)汇编源文件转换为($*.o指定的)目标文件。
.c.o:
    $(CC) $(CFLAGS) \
    -c -o $*.o $<
# 
# 为以上隐式规则举个简单例子。
# 当前目录有 Makefile main.c 文件
# 
# all: main.out
# 
# main.out : main.o
#     gcc main.o -o main.out
# 
# main.o : main.c
# 
# .c.o:
#     gcc -c -o $*.o $<
#
# 在当前目录执行 make 命令
# gcc -c -o main.o main.c
# gcc main.o -o main.out

# 将目标文件集赋给OBJS变量
OBJS  = tty_io.o console.o keyboard.o serial.o rs_io.o \
    tty_ioctl.o

# chr_drv.a为本Makefile的顶层目标。当在本Makefile所在目录中执行
# make命令时,chr_drv.a将会作为make默认目标。
# 
# 目标chr_drv.a的先决依赖文件为OBJS变量中的目标文件集。当OBJS中
# 的目标文件有发生变化时,make将会(在命令行)运行该规则下将目标文
# 件打包成chr_drv.a库文件的命令。
chr_drv.a: $(OBJS)
    $(AR) rcs chr_drv.a $(OBJS)
    sync

# 对keyboard.S进行预处理生成keyboard.s
# 疑惑: 没有生成keyboard.o的规则呢,如何生成的keyboard.o?
keyboard.s: keyboard.S ../../include/linux/config.h
    $(CPP) -traditional keyboard.S -o keyboard.s

# make clean 将清除由本Makefile生成的文件
clean:
    rm -f core *.o *.a tmp_make keyboard.s
    for i in *.c;do rm -f `basename $$i .c`.s;done
    
# make dep 将移除 ### Dependencies后续规则并将遍历本Makefile所在目录
# 下的源文件并确定其所依赖的头文件文件。原理是利用gcc预处理功能查看各
# C源文件中所包含的头文件,以将被包含头文件作为C源文件被编译的先决依赖文件。
dep:
    sed '/\#\#\# Dependencies/q' < Makefile > tmp_make
    (for i in *.c;do echo -n `echo $$i | sed 's,\.c,\.s,'`" "; \
        $(CPP) -M $$i;done) >> tmp_make
    cp tmp_make Makefile

### Dependencies:
# 多目标规则 t1 t2 : prerequisites 相当于
#   t1 : prerequisites
#   t2 :prerequisites
#
#分别匹配前面第1条和第3条隐式规则,即由console.c分别生成console.s和console.o。
console.s console.o : console.c ../../include/linux/sched.h \
  ../../include/linux/head.h ../../include/linux/fs.h \
  ../../include/sys/types.h ../../include/linux/mm.h ../../include/signal.h \
  ../../include/linux/tty.h ../../include/termios.h ../../include/asm/io.h \
  ../../include/asm/system.h 
#分别匹配前面第1条和第3条隐式规则,即由serial.c分别生成serial.s和serial.o。
serial.s serial.o : serial.c ../../include/linux/tty.h ../../include/termios.h \
  ../../include/linux/sched.h ../../include/linux/head.h \
  ../../include/linux/fs.h ../../include/sys/types.h ../../include/linux/mm.h \
  ../../include/signal.h ../../include/asm/system.h ../../include/asm/io.h 
#分别匹配前面第1条和第3条隐式规则,即由tty_io.c分别生成tty_io.s和tty_io.o。
tty_io.s tty_io.o : tty_io.c ../../include/ctype.h ../../include/errno.h \
  ../../include/signal.h ../../include/sys/types.h \
  ../../include/linux/sched.h ../../include/linux/head.h \
  ../../include/linux/fs.h ../../include/linux/mm.h ../../include/linux/tty.h \
  ../../include/termios.h ../../include/asm/segment.h \
  ../../include/asm/system.h 
#分别匹配前面第1条和第3条隐式规则,即由tty_ioctl.c分别生成tty_ioctl.s和tty_ioctl.o。
tty_ioctl.s tty_ioctl.o : tty_ioctl.c ../../include/errno.h ../../include/termios.h \
  ../../include/linux/sched.h ../../include/linux/head.h \
  ../../include/linux/fs.h ../../include/sys/types.h ../../include/linux/mm.h \
  ../../include/signal.h ../../include/linux/kernel.h \
  ../../include/linux/tty.h ../../include/asm/io.h \
  ../../include/asm/segment.h ../../include/asm/system.h 

# 这些匹配相应隐式规则而生成的目标文件目标文件将用于顶层目标chr_drv.a所在规则的先决依赖文件以生成chr_drv.a
# 不过没有看到生成keyboard.o和rs_io.o的规则呢,这两者是怎么被生成的?(也没看出来第二条隐式规则被使用) #
mm/Makefile
# 在制作映像文件过程中,mm/Makefile由根目录Makefile(在本Makefile所在目录)执行make命令解析 #

# gar,gas,gld,gcc, gcc -E分别为
# GNU 库文件打包,汇编器,链接器,C编译器和C预处理器。
# https://gcc.gnu.org/onlinedocs/
# https://gcc.gnu.org/onlinedocs/cpp/
CC  =gcc
CFLAGS  =-O -Wall -fstrength-reduce -fcombine-regs -fomit-frame-pointer \
    -finline-functions -nostdinc -I../include
AS  =gas
AR  =gar
LD  =gld
CPP =gcc -E -nostdinc -I../include

# 隐式规则,用于匹配后续只含目标和先决依赖文件的规则 #
# 此处的自动变量$*和$<分别代表无后缀目标和第一个先决依赖文件 #
#
# 用于匹配目标为目标文件(.o),先决依赖文件为汇编源文件(.s)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用汇编器将
# ($<对应的)汇编源文件转换为($*.o指定的)目标文件。
.c.o:
    $(CC) $(CFLAGS) \
    -c -o $*.o $<
# 用于匹配目标为目标文件(.o),先决依赖文件为汇编源文件(.c)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用C编译器将
# ($<对应的)C源文件转换为($*.o指定的)目标文件。
.s.o:
    $(AS) -o $*.o $<
# 用于匹配目标为汇编源文件(.s),先决依赖文件为C源文件(.c)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用C编译器
# 及其编译选项将($<对应的)C源文件转换为($*.o指定的)汇编源文件。
.c.s:
    $(CC) $(CFLAGS) \
    -S -o $*.s $<
# 
# 为以上隐式规则举个简单例子。
# 当前目录有 Makefile main.c 文件
# 
# all: main.out
# 
# main.out : main.o
#     gcc main.o -o main.out
# 
# main.o : main.c
# 
# .c.o:
#     gcc -c -o $*.o $<
#
# 在当前目录执行 make 命令
# gcc -c -o main.o main.c
# gcc main.o -o main.out

# 将目标文件集赋给OBJS变量
OBJS    = memory.o page.o

# all为本Makefile的顶层目标。当在本Makefile所在目录中执行
# make命令时,all将会作为make默认目标。该规则将会触发mm.o目
# 标所在规则被make解析。
all: mm.o

# 该规则由make all触发被make解析。
# 当先决依赖文件OBJS有改变时,该规则下的命令将会被make
# (丢到命令行)执行,从而将OBJS中的目标文件链接为mm.o目标文件。
mm.o: $(OBJS)
    $(LD) -r -o mm.o $(OBJS)

# make clean 将清除由本Makefile生成的文件
clean:
    rm -f core *.o *.a tmp_make
    for i in *.c;do rm -f `basename $$i .c`.s;done

# make dep 将移除 ### Dependencies后续规则并将遍历本Makefile所在目录
# 下的源文件并确定其所依赖的头文件文件。原理是利用gcc预处理功能查看各
# C源文件中所包含的头文件,以将被包含头文件作为C源文件被编译的先决依赖文件。
dep:
    sed '/\#\#\# Dependencies/q' < Makefile > tmp_make
    (for i in *.c;do $(CPP) -M $$i;done) >> tmp_make
    cp tmp_make Makefile

### Dependencies:
# 该规则将匹配本Makefile第2条隐式规则,即由memory.c生成memory.o供mm.o目标所在规则使用。
memory.o : memory.c ../include/signal.h ../include/sys/types.h \
  ../include/asm/system.h ../include/linux/sched.h ../include/linux/head.h \
  ../include/linux/fs.h ../include/linux/mm.h ../include/linux/kernel.h 

# 没有看到生成page.o的规则呢 #
fs/Makefile
# 在制作映像文件过程中,fs/Makefile由根目录Makefile(在本Makefile所在目录)执行make命令解析 #

# gar,gas,gld,gcc, gcc -E分别为
# GNU 库文件打包,汇编器,链接器,C编译器和C预处理器。
# https://gcc.gnu.org/onlinedocs/
# https://gcc.gnu.org/onlinedocs/cpp/
AR  =gar
AS  =gas
CC  =gcc
LD  =gld
CFLAGS  =-Wall -O -fstrength-reduce -fcombine-regs -fomit-frame-pointer \
    -mstring-insns -nostdinc -I../include
CPP =gcc -E -nostdinc -I../include

# 隐式规则,用于匹配后续只含目标和先决依赖文件的规则 #
# 此处的自动变量$*和$<分别代表无后缀目标和第一个先决依赖文件 #
#
# 用于匹配目标为汇编源文件(.s),先决依赖文件为C源文件(.c)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用C编译器
# 及其编译选项将($<对应的)C源文件转换为($*.o指定的)汇编源文件。
.c.s:
    $(CC) $(CFLAGS) \
    -S -o $*.s $<
# 用于匹配目标为目标文件(.o),先决依赖文件为汇编源文件(.s)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用汇编器将
# ($<对应的)汇编源文件转换为($*.o指定的)目标文件。
.c.o:
    $(CC) $(CFLAGS) \
    -c -o $*.o $<
# 用于匹配目标为目标文件(.o),先决依赖文件为汇编源文件(.c)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用C编译器将
# ($<对应的)C源文件转换为($*.o指定的)目标文件。
.s.o:
    $(AS) -o $*.o $<
# 
# 为以上隐式规则举个简单例子。
# 当前目录有 Makefile main.c 文件
# 
# all: main.out
# 
# main.out : main.o
#     gcc main.o -o main.out
# 
# main.o : main.c
# 
# .c.o:
#     gcc -c -o $*.o $<
#
# 在当前目录执行 make 命令
# gcc -c -o main.o main.c
# gcc main.o -o main.out

# 将目标文件集赋予变量OBJS
OBJS=   open.o read_write.o inode.o file_table.o buffer.o super.o \
    block_dev.o char_dev.o file_dev.o stat.o exec.o pipe.o namei.o \
    bitmap.o fcntl.o ioctl.o truncate.o

# fs.o为本Makefile的顶层目标。当在本Makefile所在目录中执行
# make命令时,fs.o将会作为make默认目标。
# 
# 目标fs.o的先决依赖文件为OBJS变量中的目标文件(集)。当OBJS中
# 的目标文件有发生变化时,make将会(在命令行)运行该规则下将目标文
# 件链接成fs.o目标文件的命令。
fs.o: $(OBJS)
    $(LD) -r -o fs.o $(OBJS)

# make clean 将清除由本Makefile生成的文件
clean:
    rm -f core *.o *.a tmp_make
    for i in *.c;do rm -f `basename $$i .c`.s;done

# make dep 将移除 ### Dependencies后续规则并将遍历本Makefile所在目录
# 下的源文件并确定其所依赖的头文件文件。原理是利用gcc预处理功能查看各
# C源文件中所包含的头文件,以将被包含头文件作为C源文件被编译的先决依赖文件。
dep:
    sed '/\#\#\# Dependencies/q' < Makefile > tmp_make
    (for i in *.c;do $(CPP) -M $$i;done) >> tmp_make
    cp tmp_make Makefile

### Dependencies:
# 匹配前面第1条隐式规则,即由*.c生成*.o。
bitmap.o : bitmap.c ../include/string.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/sys/types.h \
  ../include/linux/mm.h ../include/signal.h ../include/linux/kernel.h 
block_dev.o : block_dev.c ../include/errno.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/sys/types.h \
  ../include/linux/mm.h ../include/signal.h ../include/linux/kernel.h \
  ../include/asm/segment.h ../include/asm/system.h 
buffer.o : buffer.c ../include/stdarg.h ../include/linux/config.h \
  ../include/linux/sched.h ../include/linux/head.h ../include/linux/fs.h \
  ../include/sys/types.h ../include/linux/mm.h ../include/signal.h \
  ../include/linux/kernel.h ../include/asm/system.h ../include/asm/io.h 
char_dev.o : char_dev.c ../include/errno.h ../include/sys/types.h \
  ../include/linux/sched.h ../include/linux/head.h ../include/linux/fs.h \
  ../include/linux/mm.h ../include/signal.h ../include/linux/kernel.h \
  ../include/asm/segment.h ../include/asm/io.h 
exec.o : exec.c ../include/errno.h ../include/string.h \
  ../include/sys/stat.h ../include/sys/types.h ../include/a.out.h \
  ../include/linux/fs.h ../include/linux/sched.h ../include/linux/head.h \
  ../include/linux/mm.h ../include/signal.h ../include/linux/kernel.h \
  ../include/asm/segment.h 
fcntl.o : fcntl.c ../include/string.h ../include/errno.h \
  ../include/linux/sched.h ../include/linux/head.h ../include/linux/fs.h \
  ../include/sys/types.h ../include/linux/mm.h ../include/signal.h \
  ../include/linux/kernel.h ../include/asm/segment.h ../include/fcntl.h \
  ../include/sys/stat.h 
file_dev.o : file_dev.c ../include/errno.h ../include/fcntl.h \
  ../include/sys/types.h ../include/linux/sched.h ../include/linux/head.h \
  ../include/linux/fs.h ../include/linux/mm.h ../include/signal.h \
  ../include/linux/kernel.h ../include/asm/segment.h 
file_table.o : file_table.c ../include/linux/fs.h ../include/sys/types.h 
inode.o : inode.c ../include/string.h ../include/sys/stat.h \
  ../include/sys/types.h ../include/linux/sched.h ../include/linux/head.h \
  ../include/linux/fs.h ../include/linux/mm.h ../include/signal.h \
  ../include/linux/kernel.h ../include/asm/system.h 
ioctl.o : ioctl.c ../include/string.h ../include/errno.h \
  ../include/sys/stat.h ../include/sys/types.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \
  ../include/signal.h 
namei.o : namei.c ../include/linux/sched.h ../include/linux/head.h \
  ../include/linux/fs.h ../include/sys/types.h ../include/linux/mm.h \
  ../include/signal.h ../include/linux/kernel.h ../include/asm/segment.h \
  ../include/string.h ../include/fcntl.h ../include/errno.h \
  ../include/const.h ../include/sys/stat.h 
open.o : open.c ../include/string.h ../include/errno.h ../include/fcntl.h \
  ../include/sys/types.h ../include/utime.h ../include/sys/stat.h \
  ../include/linux/sched.h ../include/linux/head.h ../include/linux/fs.h \
  ../include/linux/mm.h ../include/signal.h ../include/linux/tty.h \
  ../include/termios.h ../include/linux/kernel.h ../include/asm/segment.h 
pipe.o : pipe.c ../include/signal.h ../include/sys/types.h \
  ../include/linux/sched.h ../include/linux/head.h ../include/linux/fs.h \
  ../include/linux/mm.h ../include/asm/segment.h 
read_write.o : read_write.c ../include/sys/stat.h ../include/sys/types.h \
  ../include/errno.h ../include/linux/kernel.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \
  ../include/signal.h ../include/asm/segment.h 
stat.o : stat.c ../include/errno.h ../include/sys/stat.h \
  ../include/sys/types.h ../include/linux/fs.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/mm.h ../include/signal.h \
  ../include/linux/kernel.h ../include/asm/segment.h 
super.o : super.c ../include/linux/config.h ../include/linux/sched.h \
  ../include/linux/head.h ../include/linux/fs.h ../include/sys/types.h \
  ../include/linux/mm.h ../include/signal.h ../include/linux/kernel.h \
  ../include/asm/system.h ../include/errno.h ../include/sys/stat.h 
truncate.o : truncate.c ../include/linux/sched.h ../include/linux/head.h \
  ../include/linux/fs.h ../include/sys/types.h ../include/linux/mm.h \
  ../include/signal.h ../include/sys/stat.h 
lib/Makefile
#
# Makefile for some libs needed in the kernel.
#
# Note! Dependencies are done automagically by 'make dep', which also
# removes any old dependencies. DON'T put your own dependencies here
# unless it's something special (ie not a .c file).
#
# 注, make dep 会移除为源文件手动指定的依赖而自动为源文件生成新依赖。
# 执行该规则后,不要再手动放置C源文件作为依赖文件。

# 在制作映像文件过程中,lib/Makefile由根目录Makefile(在本Makefile所在目录)执行make命令解析 #

# gar,gas,gld,gcc, gcc -E分别为
# GNU 库文件打包,汇编器,链接器,C编译器和C预处理器。
# https://gcc.gnu.org/onlinedocs/
# https://gcc.gnu.org/onlinedocs/cpp/
AR  =gar
AS  =gas
LD  =gld
LDFLAGS =-s -x
CC  =gcc
CFLAGS  =-Wall -O -fstrength-reduce -fomit-frame-pointer -fcombine-regs \
    -finline-functions -mstring-insns -nostdinc -I../include
CPP =gcc -E -nostdinc -I../include

# 隐式规则,用于匹配后续只含目标和先决依赖文件的规则 #
# 此处的自动变量$*和$<分别代表无后缀目标和第一个先决依赖文件 #
#
# 用于匹配目标为汇编源文件(.s),先决依赖文件为C源文件(.c)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用C编译器
# 及其编译选项将($<对应的)C源文件转换为($*.o指定的)汇编源文件。
.c.s:
    $(CC) $(CFLAGS) \
    -S -o $*.s $<
# 用于匹配目标为目标文件(.o),先决依赖文件为汇编源文件(.c)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用C编译器将
# ($<对应的)C源文件转换为($*.o指定的)目标文件。
.s.o:
    $(AS) -o $*.o $<
# 用于匹配目标为目标文件(.o),先决依赖文件为汇编源文件(.s)的规则。
# 若某显式规则在被解析时与此规则匹配,该规则下的命令使用汇编器将
# ($<对应的)汇编源文件转换为($*.o指定的)目标文件。
.c.o:
    $(CC) $(CFLAGS) \
    -c -o $*.o $<
# 
# 为以上隐式规则举个简单例子。
# 当前目录有 Makefile main.c 文件
# 
# all: main.out
# 
# main.out : main.o
#     gcc main.o -o main.out
# 
# main.o : main.c
# 
# .c.o:
#     gcc -c -o $*.o $<
#
# 在当前目录执行 make 命令
# gcc -c -o main.o main.c
# gcc main.o -o main.out

# 将目标文件集赋给OBJS变量
OBJS  = ctype.o _exit.o open.o close.o errno.o write.o dup.o setsid.o \
    execve.o wait.o string.o malloc.o

# lib.a为本Makefile的顶层目标。当在本Makefile所在目录中执行
# make命令时,lib.a将会作为make默认目标。
# 
# 目标lib.a的先决依赖文件为OBJS变量中的目标文件(集)。当OBJS中
# 的目标文件有发生变化时,make将会(在命令行)运行该规则下将目标文
# 件打包成lib.a库文件的命令。
lib.a: $(OBJS)
    $(AR) rcs lib.a $(OBJS)
    sync

# make clean 将清除由本Makefile生成的文件
clean:
    rm -f core *.o *.a tmp_make
    for i in *.c;do rm -f `basename $$i .c`.s;done

# make dep 将移除 ### Dependencies后续规则并将遍历本Makefile所在目录
# 下的源文件并确定其所依赖的头文件文件。原理是利用gcc预处理功能查看各
# C源文件中所包含的头文件,以将被包含头文件作为C源文件被编译的先决依赖文件。
dep:
    sed '/\#\#\# Dependencies/q' < Makefile > tmp_make
    (for i in *.c;do echo -n `echo $$i | sed 's,\.c,\.s,'`" "; \
        $(CPP) -M $$i;done) >> tmp_make
    cp tmp_make Makefile

### Dependencies:
# 多目标规则 t1 t2 : prerequisites 相当于
#   t1 : prerequisites
#   t2 :prerequisites
#
#分别匹配前面第1条和第3条隐式规则,即由*.c分别生成*.s和*.o。
_exit.s _exit.o : _exit.c ../include/unistd.h ../include/sys/stat.h \
  ../include/sys/types.h ../include/sys/times.h ../include/sys/utsname.h \
  ../include/utime.h 
close.s close.o : close.c ../include/unistd.h ../include/sys/stat.h \
  ../include/sys/types.h ../include/sys/times.h ../include/sys/utsname.h \
  ../include/utime.h 
ctype.s ctype.o : ctype.c ../include/ctype.h 
dup.s dup.o : dup.c ../include/unistd.h ../include/sys/stat.h \
  ../include/sys/types.h ../include/sys/times.h ../include/sys/utsname.h \
  ../include/utime.h 
errno.s errno.o : errno.c 
execve.s execve.o : execve.c ../include/unistd.h ../include/sys/stat.h \
  ../include/sys/types.h ../include/sys/times.h ../include/sys/utsname.h \
  ../include/utime.h 
malloc.s malloc.o : malloc.c ../include/linux/kernel.h ../include/linux/mm.h \
  ../include/asm/system.h 
open.s open.o : open.c ../include/unistd.h ../include/sys/stat.h \
  ../include/sys/types.h ../include/sys/times.h ../include/sys/utsname.h \
  ../include/utime.h ../include/stdarg.h 
setsid.s setsid.o : setsid.c ../include/unistd.h ../include/sys/stat.h \
  ../include/sys/types.h ../include/sys/times.h ../include/sys/utsname.h \
  ../include/utime.h 
string.s string.o : string.c ../include/string.h 
wait.s wait.o : wait.c ../include/unistd.h ../include/sys/stat.h \
  ../include/sys/types.h ../include/sys/times.h ../include/sys/utsname.h \
  ../include/utime.h ../include/sys/wait.h 
write.s write.o : write.c ../include/unistd.h ../include/sys/stat.h \
  ../include/sys/types.h ../include/sys/times.h ../include/sys/utsname.h \
  ../include/utime.h 
main.c
/*
 *  linux/init/main.c
 *
 *  (C) 1991  Linus Torvalds
 */

#define __LIBRARY__
#include 
#include 

/*
 * we need this inline - forking from kernel space will result
 * in NO COPY ON WRITE (!!!), until an execve is executed. This
 * is no problem, but for the stack. This is handled by not letting
 * main() use the stack at all after fork(). Thus, no function
 * calls - which means inline code for fork too, as otherwise we
 * would use the stack upon exit from 'fork()'.
 *
 * Actually only pause and fork are needed inline, so that there
 * won't be any messing with the stack from main(), but we define
 * some others too.
 *
 * 为了保证调用fork()处栈帧的正确性,此文使用内联函数机制即直接将fork()
 * 的机器码嵌套在其被调用处以避免函数调用对栈的操作。
 *
 * 实际上,只有pause()和fork()函数需要被定义为内联函数, 其他函数只是顺带
 * 这样定义。
 */
/* 宏_syscall*定义在unistd.h中 */
static inline _syscall0(int,fork)
static inline _syscall0(int,pause)
static inline _syscall1(int,setup,void *,BIOS)
static inline _syscall0(int,sync)

#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 

#include 

static char printbuf[1024];

extern int vsprintf();
extern void init(void);
extern void blk_dev_init(void);
extern void chr_dev_init(void);
extern void hd_init(void);
extern void floppy_init(void);
extern void mem_init(long start, long end);
extern long rd_init(long mem_start, int length);
extern long kernel_mktime(struct tm * tm);
extern long startup_time;

/*
 * This is set up by the setup-routine at boot-time
 */
/* setup.s通过BIOS调用将扩展内存大小信息存于
 * [0x90002, 0x90003]2字节内存中;
 * 将2个硬盘参数信息存于[0x90080, 0x900a0)32字节内存中。
 * bootsect.s将根文件系统的设备号存于[0x901FC, 0x901FD]2字节内存中。
 *
 * 以下几个宏定义分别从相应内存中读取出这几种信息。
 * 如(*(struct drive_info *)0x90080)
 * 将0x90080转换为指向(struct drive_info)类型的地址,
 * 然后从该地址中取出(struct drive_info)类型值。*/
#define EXT_MEM_K (*(unsigned short *)0x90002)
#define DRIVE_INFO (*(struct drive_info *)0x90080)
#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)

/*
 * Yeah, yeah, it's ugly, but I cannot find how to do this correctly
 * and this seems to work. I anybody has more info on the real-time
 * clock I'd be interested. Most of this was trial and error, and some
 * bios-listing reading. Urghh.
 *
 * 好吧,这个宏编写得有些勉强,因为我没找到CMOS RAM如何工作的资料,而这个宏
 * 似乎也恰能正确工作。如果有人有CMOS RAM的资料,  可以爱心式地分享我一份。
 * 这个宏是通过不断调试加阅读一些BIOS手册得来的,不容易~
 */
/* 70h端口用于接收CMOS RAM的内存地址,
 * 71h端口用于读写70h对应的内存单元。*/
#define CMOS_READ(addr) ({ \
outb_p(0x80|addr,0x70); \
inb_p(0x71); \
})

/* BCD以4位2进制表示一个十进制数,
 * 在一字节中, 高4位对应十进制数的十位,低4位对应个位。*/
#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)

/* 从RT/CMOS RAM接口芯片中读取时间,若读过程所花时间超过1s则重读。
 * 
 * 因为RT/CMOS RAM中的时间为BCD码,  所以需要将BCD码转换为十进制。
 *
 * CMOS RAM中初始时间为1970.01.01 00:00:00,将当前时间和初始时间
 * 差值转换为秒存入startup_time作为系统的开机时间。
 *
 * RT/CMOS RAM接口芯片由一个时钟和一块RAM组成,其有专门的电池供电,
 * 电脑关机后也能依靠电池继续工作。*/
static void time_init(void)
{
    struct tm time;

    do {
        time.tm_sec = CMOS_READ(0);
        time.tm_min = CMOS_READ(2);
        time.tm_hour = CMOS_READ(4);
        time.tm_mday = CMOS_READ(7);
        time.tm_mon = CMOS_READ(8);
        time.tm_year = CMOS_READ(9); /* 年份后两位 */
    } while (time.tm_sec != CMOS_READ(0));
    BCD_TO_BIN(time.tm_sec);
    BCD_TO_BIN(time.tm_min);
    BCD_TO_BIN(time.tm_hour);
    BCD_TO_BIN(time.tm_mday);
    BCD_TO_BIN(time.tm_mon);
    BCD_TO_BIN(time.tm_year);
    time.tm_mon--;
    startup_time = kernel_mktime(&time);
}
/* (Real Time)RT/CMOS RAM接口芯片可参考
 *《微型机(PC系列)接口控制教程》P123_128。
 * 70h接收CMOS RAM内存地址,71h用于读写70h内存地址对应内存单元。*/

static long memory_end = 0;
static long buffer_memory_end = 0;
static long main_memory_start = 0;

/* struct drive_info结构体类型用于描述在setup.s中获取的硬盘参数,
 * drive_info用于保存这些硬盘参数信息,在main开始处被初始化。*/
struct drive_info { char dummy[32]; } drive_info;

/* head.s完成保护模式的初始化工作后,便跳转到此处(见head.s)。*/
void main(void) /* This really IS void, no error here. */
{                  /* The startup routine assumes (well, ...) this */
/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 *
 * 此处main函数的返回类型和参数可以为void,启动程序代码(head.s)也是这样
 * "调用"main函数的。在未完全建立中断机制前,我们曾在setup.s中禁止了CPU
 * 处理中断,再做些必要设置后就可以重新使能CPU处理中断了。
 */

    /* 在bootsect.s偏移508处设置了根文件系统的逻辑设备分区号。此语句
     * 即获取bootsect.s所设置的根文件设备号存储到全局变量ROOT_DEV中。*/
    ROOT_DEV = ORIG_ROOT_DEV;

    /* 将setup.s通过BIOS所获取的硬盘参数信息存于全局变量drive_info中。*/
    drive_info = DRIVE_INFO;

    /* 计算内存总大小: 实模式内存(1Mb) + 扩展内存。
     * 在setup.s中开启页机制后, 内存以4Kb大小对齐,所以
     * 若内存总大小不为4Kb整数倍,则舍弃末尾不足4Kb部分。*/
    memory_end = (1<<20) + (EXT_MEM_K<<10);
    memory_end &= 0xfffff000;

    /* 用全局变量记录linux 0.11按用途所划分的内存段。
     *
     * MAIN_MEMORY-[main_memory_start, memory_end),
     * memory_end为linux 0.11所使用实际物理内存总大小,最大为16Mb。
     * 
     * BUFFER,操作系统内核程序将其用作外设(如硬盘)的缓冲区,
     * 这部分内存范围为[操作系统程序末尾处, buffer_memory_end)。
     *
     * RAM-DISK, 若定义了虚拟磁盘(用一段内存模拟磁盘),则操作系统内
     * 核程序将内存地址空间[buffer_memory_end, main_memory_start)用作虚拟磁盘。*/
    if (memory_end > 16*1024*1024)
        memory_end = 16*1024*1024;
    if (memory_end > 12*1024*1024) 
        buffer_memory_end = 4*1024*1024;
    else if (memory_end > 6*1024*1024)
        buffer_memory_end = 2*1024*1024;
    else
        buffer_memory_end = 1*1024*1024;
    main_memory_start = buffer_memory_end;
#ifdef RAMDISK /* 虚拟硬盘管理初始化 */
    main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif 
/* 经以上划分后, linux 0.11内存地址空间分布大体如下(16Mb为例)。
 * ---------------------------------------------------
 * | OS routines | BUFFER | [RAM-DISK] | MAIN_MEMORY |
 * |-------------|--------|------------|-------------|
 * 0x0           end      4Mb                        16Mb
 * BUFFER 用作硬盘、软盘等外设缓冲区;
 * RAM-DISK 用作虚拟磁盘(若定义);
 * MAIN_MEMORY 为剩余内存,将用作内核数据结构体的内存空间。*/

    /* 初始化主存(MAIN_MEMORY)的管理 */
    mem_init(main_memory_start,memory_end);

    trap_init();    /* 初始设置IDT和PIC */
    blk_dev_init(); /* 块设备请求管理初始化 */
    chr_dev_init();
    tty_init();     /* 字符设备及其请求管理的初始化 */
    time_init();    /* 设置系统开机时间 */
    sched_init();   /* 多任务管理初始化 */
    buffer_init(buffer_memory_end); /* 缓冲区管理初始化 */
    hd_init();      /* 块设备硬盘及其请求管理初始化 */
    floppy_init();  /* 块设备软盘及其请求管理初始化 */
    sti();          /* 允许CPU处理中断 */
    
    /* 从CPU内核模式转移到CPU用户模式以 init_task 所管理任务的名义执行。
     * move_to_user_mode执行完毕后,CPU将首次进入用户态和多任务运行模式。*/
    move_to_user_mode();

    /* 在初始进程中创建子进程(初始进程由结构体 init_task 描述和管理)。
     * 
     * fork定义在本文件的开头处: static inline _syscall0(int,fork),根
     * 据宏_syscall0看看fork的定义和执行轨迹,以理解fork()的两次返回即
     * 需被定义为内联函数的原理吧。*/
    if (!fork()) {  /* we count on this going ok */
        /* 子进程的执行流程开始处 */
        init();
    }
/*
 *   NOTE!!   For any other task 'pause()' would mean we have to get a
 * signal to awaken, but task0 is the sole exception (see 'schedule()')
 * as task 0 gets activated at every idle moment (when no other tasks
 * can run). For task0 'pause()' just means we go check if some other
 * task can run, and if not we return here.
 *
 * 注,由于task0所管理进程(初始进程)在系统无其他能运行进程时会默认运行,
 * 即会忽略pause()为其设置的就绪状态。所以, 在初始进程中调用pause()时,
 * 相当于仅是检查系统有没有其他可运行的进程, 若有则调度时间片最大的进
 * 程运行,若无则返回到初始进程中。—— 见schedule()。
 *
 * 而在其他进程中调用 pause() 后, 由于pause()会设置当前进程为就绪状态,
 * 所以需要用信号去唤醒该进程。
 */
/* pause()定义在本文件开头处,static inline _syscall0(int,pause),
 * 其对应的内核代码为sched.c/ sys_pause(). */
    for(;;) pause();
}

/* printf,
 * 写标准输出设备的可变参数函数。*/
static int printf(const char *fmt, ...)
{
    va_list args;
    int i;

    /* 让args指向fmt之后一个参数的栈地址 */
    va_start(args, fmt);
    
    /* 将printffmt之后的参数写往标准输出设备显示 */
    write(1,printbuf,i=vsprintf(printbuf, fmt, args));
    va_end(args);
    return i;
}

/* sh程序的命令行参数,环境变量参数 */
static char * argv_rc[] = { "/bin/sh", NULL };
static char * envp_rc[] = { "HOME=/", NULL };

static char * argv[] = { "-/bin/sh",NULL };
static char * envp[] = { "HOME=/usr/root", NULL };

/* init,
 * init进程,该进程创建子进程并加载可执行程序sh运行。
 * 
 * 若子进程sh被终止,则init进程会回收sh进程所创建子进
 * 程资源,并会立即再创建子进程并加载sh程序运行。*/
void init(void)
{
    int pid,i;

    /* setup,
     * 根据存储在drive_info中的硬盘参数,获取硬盘分区参数,
     * 并设置根文件系统和RAMDISK(若有)。
     * 
     * 系统调用setup定义在本文件开头处,
     * static inline _syscall1(int, setup, void *, BIOS),
     * 其对应的内核程序为hd.c/int sys_setup(void * BIOS)。*/
    setup((void *) &drive_info);

    /* 以读写属性打开文件/dev/tty0。由于此处是首次打开文件,
     * /dev/tty0的文件描述符将为0,即/dev/tty0即是与控制终端
     * 关联的文件哦。*/
    (void) open("/dev/tty0",O_RDWR,0);
/* 通过open打开或创建/dev/tty0时,根据底层函数实现,目录/dev
 * 应存在。/dev目录(甚至是/dev/tty0)可能是在格式化MINIX文件
 * 系统静态创建的。*/
 
    /* dup(0),
     * 让本进程中后续空闲描述符(1和2)指向文件描述符0所关联
     * 文件/dev/tty0。
     * 
     * dup定义在lib/dup.c中,其对应的内核函数为sys_dup()。*/
    (void) dup(0);
    (void) dup(0);
/* 此处创建文件/dev/tty0与控制终端关联,由文件描述符0,1,2关联,
 * 即系统层面所提到stdin,stdeout,stderr,即对应标准输入,标准输
 * 出,错误输出。*/

    /* 打印缓冲区和主存大小 */
    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);

    /* 在init进程中创建子进程 */
    if (!(pid=fork())) {
        /* 在init子进程中关闭标准输入。以用文件描述
         * 符0关联以只读方式打开的/etc/rc文件。close
         * 函数定义在lib/close.c文件中。*/
        close(0);
        if (open("/etc/rc",O_RDONLY,0))
            _exit(1);

        /* 加载/bin/sh"覆盖本进程"并执行sh程序,argv_rc
         * 和envp_rc分别充当sh程序的命令行和环境变量参数。
         *
         * execve函数定义在lib/execve.c中。*/
        execve("/bin/sh",argv_rc,envp_rc);
        
        /* 若execve加载可执行程序/bin/sh成功则该语句不会被执行,若
         * 失败则会执行_exit(2)退出本进程。另外,此处传给/bin/sh程
         * 序的参数使得/bin/sh为非交互模式运行,其运行完毕后会退出。*/
        _exit(2);
    }
/* 等待子进程退出返回,定义在lib/wait.c中。
 * 可以不用pid >0 的判断,子进程不会执行以下代码。*/
    if (pid>0)
        while (pid != wait(&i))
            /* nothing */;
    
    /* 非交互式/bin/sh运行退出时,重新创建子进程以交互式执行/bash/sh程序 */
    while (1) {
        if ((pid=fork())<0) {
            printf("Fork failed in init\r\n");
            continue;
        }
        /* 在子进程中关闭标准输入输出和错误输出;
         * 新建会话组;重新打开控制终端并将控制终
         * 端复制给标准输出和错误输出文件描述符;
         * 并重新启动/bin/sh程序以交互模式运行。*/
        if (!pid) {
            close(0);close(1);close(2);
            /* 创建会话组;
             * 定义在lib/setsid.c中,
             * 其内核函数为kernel/sys.c/sys_setsid() */
            setsid();
            (void) open("/dev/tty0",O_RDWR,0);
            (void) dup(0);
            (void) dup(0);
            _exit(execve("/bin/sh",argv,envp));
        }
        /* 等待子进程结束,子进程结束则提示并刷新缓冲区然后
         * 再回到循环开始处再次创建子进程运行/bin/sh程序。*/
        while (1)
            if (pid == wait(&i))
                break;
        printf("\n\rchild %d died with code %04x\n\r",pid,i);
        /* 同步缓冲区内容到设备上。
         * 
         * 其定义在本文件开始部分,
         * static inline _syscall0(int,sync),
         * 其内核函数为fs/buffer.c/sys_sync()。*/
        sync();
    }

    _exit(0); /* NOTE! _exit, not exit() */
}
malloc.c
/*
 * malloc.c --- a general purpose kernel memory allocator for Linux.
 * 
 * Written by Theodore Ts'o ([email protected]), 11/29/91
 *
 * This routine is written to be as fast as possible, so that it
 * can be called from the interrupt level.
 *
 * Limitations: maximum size of memory we can allocate using this routine
 *  is 4k, the size of a page in Linux.
 *
 * The general game plan is that each page (called a bucket) will only hold
 * objects of a given size.  When all of the object on a page are released,
 * the page can be returned to the general free pool.  When malloc() is
 * called, it looks for the smallest bucket size which will fulfill its
 * request, and allocate a piece of memory from that bucket pool.
 *
 * Each bucket has as its control block a bucket descriptor which keeps 
 * track of how many objects are in use on that page, and the free list
 * for that page.  Like the buckets themselves, bucket descriptors are
 * stored on pages requested from get_free_page().  However, unlike buckets,
 * pages devoted to bucket descriptor pages are never released back to the
 * system.  Fortunately, a system should probably only need 1 or 2 bucket
 * descriptor pages, since a page can hold 256 bucket descriptors (which
 * corresponds to 1 megabyte worth of bucket pages.)  If the kernel is using 
 * that much allocated memory, it's probably doing something wrong.  :-)
 *
 * Note: malloc() and free() both call get_free_page() and free_page()
 *  in sections of code where interrupts are turned off, to allow
 *  malloc() and free() to be safely called from an interrupt routine.
 *  (We will probably need this functionality when networking code,
 *  particularily things like NFS, is added to Linux.)  However, this
 *  presumes that get_free_page() and free_page() are interrupt-level
 *  safe, which they may not be once paging is added.  If this is the
 *  case, we will need to modify malloc() to keep a few unused pages
 *  "pre-allocated" so that it can safely draw upon those pages if
 *  it is called from an interrupt routine.
 *
 *  Another concern is that get_free_page() should not sleep; if it 
 *  does, the code is carefully ordered so as to avoid any race 
 *  conditions.  The catch is that if malloc() is called re-entrantly, 
 *  there is a chance that unecessary pages will be grabbed from the 
 *  system.  Except for the pages for the bucket descriptor page, the 
 *  extra pages will eventually get released back to the system, though,
 *  so it isn't all that bad.
 */
/*
 * malloc.c - Linux内核内存分配通用工具。
 * 由Theodore Ts'o于11/29/91编写。
 *
 * 本程序被尽可能地编写得能快速执行,这样就可以在中断层面使用本程序。
 *
 * 限制: 本程序可分配的最大内存空间为4Kb即Linux中一页内存大小。
 *
 * 在本程序中,将系统可用内存看作一个内存池,通过 get_free_page()从内
 * 存池中获取到的空闲内存页会被分隔成指定大小内存块-桶。即1内存页会
 * 被分割为特定数量的桶, 当内存页上的桶都空闲时则释放该内存页到系统
 * 内存池中。malloc()将会从桶目录数据结构体中寻找能满足所请求内存大
 * 小的最小桶并返回。
 *
 * 每个桶都有一个桶描述符与其对应,桶描述符同时将会记录内存页中桶的分
 * 配和释放情况。桶描述符也存储在由 get_free_page() 分配来的内存页中。
 * 与桶所在的内存页不同的是, 用作桶描述符的内存不会被释放。由于1页内
 * 存能够容纳256个桶描述符, 所以系统只会使用1到2个内存页用作桶描述符。
 * 但若在内核中过多使用malloc()来分配内存,有可能会出错。
 *
 * 注: 在 malloc() 和 free() 分别调用 get_free_page() 和 free_page() 时
 * 需禁止CPU对中断的响应,以使得 malloc() 和 free() 能在中断处理程序中被
 * 安全调用(在诸如网络编程中可能需要这个功能,尤其像NFS)。当没有加入分页
 * 机制时,get_free_page() 和 free_page() 似乎也是可在中断程序中被调用的。
 * 若加入了分页机制,则需修改 malloc() 以预分配几页内存以在中断程序中使用。
 *
 * 还有一点, 在调用 get_free_page()时不应进入睡眠,如果发生了睡眠,应仔细安
 * 排编码以防止竞争的发生。若 malloc() 能被重入就增加了能从系统获取内存页
 * 的情况。之前提到,除了桶描述符所占内存外,其余内存在用完之后都会被释放回
 * 系统,所以 malloc() 可重入特点也并非完全不可取。*/

#include 
#include 
#include 

/* struct bucket_desc,
 * 桶描述符结构体类型。*/
struct bucket_desc { /* 16 bytes */
    void                *page;  /* 桶描述符对应的内存页 */
    struct bucket_desc  *next;  /* 指向下一个桶描述符 */
    void                *freeptr;    /* 指向当前内存页空闲桶 */
    unsigned short      refcnt;      /* 计数内存页中被分配的桶数 */
    unsigned short      bucket_size; /* 本桶描述符对应桶大小 */
};

/* struct _bucket_dir,
 * 描述特定大小桶的结构体类型。*/
struct _bucket_dir { /* 8 bytes */
    int                 size;   /* 桶大小 */
    struct bucket_desc  *chain; /* 桶描述符(链表头)指针 */
};

/*
 * The following is the where we store a pointer to the first bucket
 * descriptor for a given size.  
 *
 * If it turns out that the Linux kernel allocates a lot of objects of a
 * specific size, then we may want to add that specific size to this list,
 * since that will allow the memory to be allocated more efficiently.
 * However, since an entire page must be dedicated to each specific size
 * on this list, some amount of temperance must be exercised here.
 *
 * Note that this list *must* be kept in order.
 */
/* 在以下结构体数组中设置指向特定大小桶的初始指针。
 * 
 * 如果程序发现某特定大小的桶将会被大量分配,本程序
 * 会将该大小桶的链表添加到以下数组中,在该数组中分
 * 配内存将会更加高效。然而,由于在链表中一页内存会
 * 被分割成指定大小的块-桶,所以需评估或调试下桶大小。
 *
 * 注,以下个元素 必须 以桶大小的升序排列,这样才能以
 * 保证调用 malloc() 申请内存时,以最小桶匹配申请。*/
struct _bucket_dir bucket_dir[] = {
    { 16,   (struct bucket_desc *) 0},
    { 32,   (struct bucket_desc *) 0},
    { 64,   (struct bucket_desc *) 0},
    { 128,  (struct bucket_desc *) 0},
    { 256,  (struct bucket_desc *) 0},
    { 512,  (struct bucket_desc *) 0},
    { 1024, (struct bucket_desc *) 0},
    { 2048, (struct bucket_desc *) 0},
    { 4096, (struct bucket_desc *) 0},
    { 0,    (struct bucket_desc *) 0}};   /* End of list marker */

/*
 * This contains a linked list of free bucket descriptor blocks
 */
/* 指向空闲桶描述符链表头部 */
struct bucket_desc *free_bucket_desc = (struct bucket_desc *) 0;

/*
 * This routine initializes a bucket description page.
 */
/* 该函数初始化一内存页用作桶描述符 */
/* init_bucket_desc,
 * 分配一页内存用作桶描述符链表。*/
static inline void init_bucket_desc()
{
    struct bucket_desc *bdesc, *first;
    int i;

    /* 分配一空闲内存页用作桶描述符链表 */
    first = bdesc = (struct bucket_desc *) get_free_page();
    if (!bdesc)
        panic("Out of memory in init_bucket_desc()");
    for (i = PAGE_SIZE/sizeof(struct bucket_desc); i > 1; i--) {
        bdesc->next = bdesc+1;
        bdesc++;
    }
    
    /*
     * This is done last, to avoid race conditions in case 
     * get_free_page() sleeps and this routine gets called again....
     */
    /* 该语句用于避免 get_free_page() 睡眠
     * 或本程序再次被调用时产生竞争条件。*/
    bdesc->next = free_bucket_desc;
    free_bucket_desc = first;
}

/* malloc,
 * 申请指定大小即len字节内存,通过该函
 * 数所申请到的内存大小将大于等于len。*/
void *malloc(unsigned int len)
{
    struct _bucket_dir  *bdir;
    struct bucket_desc  *bdesc;
    void    *retval;

    /*
    * First we search the bucket_dir to find the right bucket change
    * for this request.
    */
    /* 首先,从同目录 bucket_dir 数组中找到包含刚好能匹配len大小的桶的内存页 */
    for (bdir = bucket_dir; bdir->size; bdir++)
        if (bdir->size >= len)
            break;
    if (!bdir->size) {
        printk("malloc called with impossibly large argument (%d)\n",
            len);
        panic("malloc: bad arg");
    }
    
    /*
    * Now we search for a bucket descriptor which has free space
    */
    /* 在搜索到包含刚好能匹配len字节桶的内存页后,接下来在该内
     * 存页中搜索空闲桶(禁止CPU处理当前进程中断以避免竞争)。*/
    cli(); /* Avoid race conditions */
    for (bdesc = bdir->chain; bdesc; bdesc = bdesc->next) 
        if (bdesc->freeptr)
            break;
    /*
     * If we didn't find a bucket with free space, then we'll 
     * allocate a new one.
     */
    /* 若内存页中已无空闲桶,则新分配一页内存并分隔成指定大小的桶 */
    if (!bdesc) {
        char *cp;
        int  i;

        /* 若还未初始化桶描述符则初始化 */
        if (!free_bucket_desc)
            init_bucket_desc();

        /* 使用 free_bucket_desc 当前所指向空闲桶描述符管理内存页中的桶 */
        bdesc = free_bucket_desc;
        free_bucket_desc = bdesc->next;
        bdesc->refcnt = 0;
        bdesc->bucket_size = bdir->size;
        bdesc->page = bdesc->freeptr = (void *) cp = get_free_page();
        if (!cp)
            panic("Out of memory in kernel malloc()");
        
        /* Set up the chain of free objects */
        /* 将内存页分成指定大小的内存块(桶),
         * 每个桶的头部存储着下一个桶的地址。*/
        for (i=PAGE_SIZE/bdir->size; i > 1; i--) {
            *((char **) cp) = cp + bdir->size;
            cp += bdir->size;
        }
        /* 最后一个桶的头部置0即表示无下一个桶 */
        *((char **) cp) = 0;
        /* 将含指定大小桶的内存页附加到相应桶大小的桶目录元素中 */
        bdesc->next = bdir->chain; /* OK, link it in! */
        bdir->chain = bdesc;
    }
    /* retval值为当前空闲桶地址 */
    retval = (void *) bdesc->freeptr;
    /* 将桶描述符中空闲桶指针元素指向内存页中的下一个空闲桶 */
    bdesc->freeptr = *((void **) retval);
    bdesc->refcnt++;
    sti(); /* OK, we're safe again */
    return(retval);
}

/*
 * Here is the free routine.  If you know the size of the object that you
 * are freeing, then free_s() will use that information to speed up the
 * search for the bucket descriptor.
 * 
 * We will #define a macro so that "free(x)" is becomes "free_s(x, 0)"
 */
/* 以下是释放 malloc() 所申请内存的函数。
 * 若调用者知道所释放内存的大小, free_s() 将会根据该信息更快地搜索到该内存
 * 块(桶)的桶描述符。kernel.h 中定义了宏值为 free_s(x,0) 的宏 free(x)。当
 * size为0时,free_s将会依次遍历桶目录中各个桶大小的内存块,直到找到目标内存。*/

/* free_s,
 * 释放基址为obj的内存块。*/
void free_s(void *obj, int size)
{
    void *page;
    struct _bucket_dir *bdir;
    struct bucket_desc *bdesc, *prev;

    /* Calculate what page this object lives in */
    /* 首先计算obj内存块所属内存页 */
    page = (void *)  ((unsigned long) obj & 0xfffff000);

    /* Now search the buckets looking for that page */
    /* 然后在桶目录中搜索obj所属内存页 */
    for (bdir = bucket_dir; bdir->size; bdir++) {
        prev = 0;
        /* If size is zero then this conditional is always false */
        if (bdir->size < size)
            continue;
        /* 在桶大小刚好大于或等于size的内存页中寻找页机制为page的内存页 */
        for (bdesc = bdir->chain; bdesc; bdesc = bdesc->next) {
            if (bdesc->page == page) 
                goto found;
            prev = bdesc;
        }
    }
    panic("Bad address passed to kernel free_s()");
    
found:
    cli(); /* To avoid race conditions */
    /* 桶描述符桶空闲指针指向刚释放桶,释放桶头部指向原当前空闲桶,
     * 减少桶被分配计数,若内存页中无被分配桶则将该内存页释放。*/
    *((void **)obj) = bdesc->freeptr;
    bdesc->freeptr = obj;
    bdesc->refcnt--;
    if (bdesc->refcnt == 0) {
        /*
         * We need to make sure that prev is still accurate.  It
         * may not be, if someone rudely interrupted us....
         */
        /* 确保 prev 指向的下个桶描述符为刚释放桶的桶描述符 */
        if ((prev && (prev->next != bdesc)) ||
            (!prev && (bdir->chain != bdesc)))
                for (prev = bdir->chain; prev; prev = prev->next)
                    if (prev->next == bdesc)
                        break;
        /* 释放bdesc指向的桶分配数为0的内存页 */
        if (prev)
            prev->next = bdesc->next;
        else {
            if (bdir->chain != bdesc)
                panic("malloc bucket chains corrupted");
            bdir->chain = bdesc->next;
        }
        free_page((unsigned long) bdesc->page);

        /* 更新当前空闲桶描述符指针的值,让刚空闲下来
         * 的桶描述符指针充当空闲桶描述符表头元素。*/
        bdesc->next = free_bucket_desc;
        free_bucket_desc = bdesc;
    }
    sti();
    return;
}

/* 粗略理解内存页桶式分配过程。
 * 
 * 分配一页内存用作桶描述符(struct bucket_desc)
 * |-----------------|
 * | 0 | 1 | ... |255|
 * |-----------------|
 * bdesc
 *   ^
 *   |
 * free_bucket_desc
 * 
 * 设 malloc(2047) 被调用了3次,则 bucket_dir 数组情况大体如下
 * |=====|
 * | 16  |
 * |-----|  second page            first page
 * |  0  |  |---------|           |---------|
 * |=====|  |    |    |           |    |    |
 * |.....|  |---------|           |---------|
 * |=====|  m2<--|                m1<--|
 * |2048 |       |                     |
 * |-----|     |-------|           |-------|
 * |chain| --> | page  |           | page  |
 * |=====|     | next  | --------> | next  |--> NULL
 * |.....|     |freeptr|->m2+2048  |freeptr|--> NULL
 * |=====|     |.......|           |.......|
 * |0    |     bdesc[1]            bdesc[0]
 * |-----|
 * |0    |
 * |=====|
 * 
 * 分配两个桶描述符管理内存页中的桶之后
 * |---------------------|
 * | 0 | 1 | 2 | ... |255|
 * |---------------------|
 * bdesc    ^
 *          |
 *          |
 *   free_bucket_desc */
mktime.c
/*
 *  linux/kernel/mktime.c
 *
 *  (C) 1991  Linus Torvalds
 */

#include 

/*
 * This isn't the library routine, it is only used in the kernel.
 * as such, we don't care about years<1970 etc, but assume everything
 * is ok. Similarly, TZ etc is happily ignored. We just do everything
 * as easily as possible. Let's find something public for the library
 * routines (although I think minix times is public).
 *
 * 本文件中的代码不会充当库程序,仅会在内核中使用。实际上,本文件不会处理
 * 1970年以前的时间,不妨假设之前的这些时间一切正常吧。类似的,时间区域TZ
 * 等为题也被本文件嗨皮地忽略掉了,本文件打算尽可能简单的处理时间相关问题。
 * 可以使用公开的时间库程序(尽管我认为minix时间函数也属于公开的,你懂的)。
 */
/*
 * PS. I hate whoever though up the year 1970 - couldn't they have gotten
 * a leap-year instead? I also hate Gregorius, pope or no. I'm grumpy.
 *
 * 另外,1970年这个起始点时间设置得让我有些讨厌——就不能用一个闰年时间代替吗?
 * 我还讨厌Gregorius,pope...这件事情激发了讨厌链,我现在甚至可以一口气做20及
 * 以上的引体向上。
 */
/* 1分钟/小时/天/年对应的秒数 */
#define MINUTE 60
#define HOUR (60*MINUTE)
#define DAY (24*HOUR)
#define YEAR (365*DAY)

/* month[m]表示(m+1)月前所有月份对应的秒数,2月按照29年计算(闰年)。*/
/* interestingly, we assume leap-years */
static int month[12] = {
    0,
    DAY*(31),
    DAY*(31+29),
    DAY*(31+29+31),
    DAY*(31+29+31+30),
    DAY*(31+29+31+30+31),
    DAY*(31+29+31+30+31+30),
    DAY*(31+29+31+30+31+30+31),
    DAY*(31+29+31+30+31+30+31+31),
    DAY*(31+29+31+30+31+30+31+31+30),
    DAY*(31+29+31+30+31+30+31+31+30+31),
    DAY*(31+29+31+30+31+30+31+31+30+31+30)
};

/* kernel_mktime,
 * 计算并返回从1970.01.01 0:0:0开始到现在(2000年前有效)所经历的秒数并返回。*/
long kernel_mktime(struct tm * tm)
{
    long res;
    int year;

    year = tm->tm_year - 70; /* year=0, 1, 2, ... */
/* magic offsets (y+1) needed to get leapyears right.*/
    /* 计算[1970, 1970 + year)之间的秒数, 1970 + year年前几个月的秒数。
     * ((year + 1) / 4)用于计算[1970, 1970 + year)之间的闰年数。*/
    res = YEAR*year + DAY*((year+1)/4);
    res += month[tm->tm_mon];
/* and (y+2) here. If it wasn't a leap-year, we have to adjust */
    /* (year+2)%4)为0时表明1970 + year为闰年 */
    if (tm->tm_mon>1 && ((year+2)%4))
        res -= DAY;
    res += DAY*(tm->tm_mday-1); /* 不算今天一整天对应的秒数 */
    res += HOUR*tm->tm_hour;    /* 今天已经历小时数对应的秒数 */
    res += MINUTE*tm->tm_min;
    res += tm->tm_sec;
    return res;
}
stdarg.h
#ifndef _STDARG_H
#define _STDARG_H

typedef char *va_list;

/* Amount of space required in an argument list for an arg of type TYPE.
   TYPE may alternatively be an expression whose type is used.  */

/* __va_rounded_size(TYPE),
 * 以int类型大小对齐计算TYPE所占字节数。
 * 
 * 因为栈以int类型对齐存储数据,所以需将各类型数据所占字节数以int对齐。*/
#define __va_rounded_size(TYPE)  \
  (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))

/* va_start, 获取参数LASTARG上一个参数的栈地址。
 * 
 * 函数实参在父函数中从右至左依次入栈。
 * 
 * __builtin_saveregs ()将存储在寄存器中的参数压入栈中,可参考gcc手册。*/
#ifndef __sparc__
#define va_start(AP, LASTARG)                       \
 (AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
#else
#define va_start(AP, LASTARG)                       \
 (__builtin_saveregs (),                            \
  AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
#endif

/* 声明gnulib库只能怪的va_end,
 * 若要使用所声明的va_end则应将宏va_end(AP)注释掉。*/
void va_end (va_list);  /* Defined in gnulib */
#define va_end(AP)

/* va_arg(AP, TYPE),
 * 获取上一个(在函数参数列表中靠右)类型为TYPE的参数值,同时AP后移指向上一个参数的栈地址。*/
#define va_arg(AP, TYPE)                            \
 (AP += __va_rounded_size (TYPE),                   \
  *((TYPE *) (AP - __va_rounded_size (TYPE))))

#endif /* _STDARG_H */
vsprintf.c
/*
 *  linux/kernel/vsprintf.c
 *
 *  (C) 1991  Linus Torvalds
 */

/* vsprintf.c -- Lars Wirzenius & Linus Torvalds. */
/*
 * Wirzenius wrote this portably, Torvalds fucked it up :-)
 */

#include 
#include 

/* we use this so that we can do without the ctype library */
/* 咱自己定义个宏来判断字符c是否为数字字符,这样就可以避免使用ctype库了 */
#define is_digit(c) ((c) >= '0' && (c) <= '9')

/* skip_atoi,
 * 将(*s)所指内存中的数字字符转换为对应的数字并
 * 返回,同时修改(*s)跳过数字字符而指向后续字符。*/
static int skip_atoi(const char **s)
{
    int i=0;

    while (is_digit(**s))
        /* 取当前字节内容并将(*s)往后移1字节 */
        i = i*10 + *((*s)++) - '0';
    return i;
}

/* 补0标识;显示符号标识;显示正号标识;
 * 用空格代替正号标识;左对齐标识;16进
 * 制数标识;16进制小写模式标识。*/
#define ZEROPAD 1   /* pad with zero */
#define SIGN    2   /* unsigned/signed long */
#define PLUS    4   /* show plus */
#define SPACE   8   /* space if plus */
#define LEFT    16  /* left justified */
#define SPECIAL 32  /* 0x */
#define SMALL   64  /* use 'abcdef' instead of 'ABCDEF' */

/* to_div(n, baes),
 * 对十进制数n进行一次base进制数的转换,
 * 并返回十进制数n所对应base进制数低位,
 * 同时n被改变,其值为(n / base)的商。
 * 
 * 内联汇编输入。
 * "0" (n),eax=n;
 * "1" (0),edx=0;
 * "r" (base),任何通用空闲寄存器=base。
 *
 * divl %4, divl base 即(edx << 32) + eax / base --> eax=商,edx=余数。
 * 
 * 内联汇编输出。
 * "=a" (n),n=eax;
 * "=d" (__res),__res=edx。*/
#define do_div(n,base) ({ \
int __res; \
__asm__("divl %4":"=a" (n),"=d" (__res):"0" (n),"1" (0),"r" (base)); \
__res; })

/* number,
 * 将十进制数num根据转换精度precision,转换标志type 转换为base进制数存于str所指内存中。*/
static char * number(char * str, int num, int base, int size, int precision
    ,int type)
{
    char c,sign,tmp[36];
    const char *digits="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    int i;

    /* 小写模式; 左对齐模式判断 */
    if (type&SMALL) digits="0123456789abcdefghijklmnopqrstuvwxyz";
    if (type&LEFT) type &= ~ZEROPAD;

    /* 可转换为2进制-35进制数 */
    if (base<2 || base>36)
        return 0;
    /* 判断采用0还是空格补齐 */
    c = (type & ZEROPAD) ? '0' : ' ' ;

    /* 判断是否显示正负数符号 */
    if (type&SIGN && num<0) {
        sign='-';
        num = -num;
    } else
        sign=(type&PLUS) ? '+' : ((type&SPACE) ? ' ' : 0);
    if (sign) size--; /* 为符号腾空间 */

    /* 为16进制或8进制标识0x和o腾空间 */
    if (type&SPECIAL)
        if (base==16) size -= 2;
        else if (base==8) size--;

    /* 将数字num转换为base进制数(字符)保存在tmp中 */
    i=0;
    if (num==0)
        tmp[i++]='0';
    else while (num!=0)
        tmp[i++]=digits[do_div(num,base)];

    /* 计算还剩余的显示空间/宽度 */
    if (i>precision) precision=i;
    size -= precision;

    /* 无左对齐和0填充时用空格补齐剩余显示空间 */
    if (!(type&(ZEROPAD+LEFT)))
        while(size-->0)
            *str++ = ' ';
    /* 符号 */
    if (sign)
        *str++ = sign;
    /* 数制标识 */
    if (type&SPECIAL)
        if (base==8)
            *str++ = '0';
        else if (base==16) {
            *str++ = '0';
            *str++ = digits[33];
        }
    /* 数制标识与数值之间有填充的情况则用指定字符填充(0或空格) */
    if (!(type&LEFT))
        while(size-->0)
            *str++ = c;
    /* 数值长度没有精度值大时用0填充 */
    while(i0)
        *str++ = tmp[i];
    /* 左对齐的情况,数字右边用空格补齐空闲显示位 */
    while(size-->0)
        *str++ = ' ';
    return str;
}

/* vsprintf,
 * 将fmt中类型转换符(如%c %d等)对应的参数拷贝/转换到buf所指内存中。*/
int vsprintf(char *buf, const char *fmt, va_list args)
{
    int len;
    int i;
    char * str;
    char *s;
    int *ip;

    int flags;          /* flags to number() */

    int field_width;    /* width of output field */
    int precision;      /* min. # of digits for integers; max
                           number of chars for from string */
    int qualifier;      /* 'h', 'l', or 'L' for integer fields */

    for (str=buf ; *fmt ; ++fmt) {
        /* 将非类型转换符依次拷贝到buf所指内存中 */
        if (*fmt != '%') {
            *str++ = *fmt;
            continue;
        }

        /* process flags */
        flags = 0;
        repeat:
            ++fmt;  /* this also skips first '%',跳过字符'%' */
            switch (*fmt) {
                /* 置类型转换符对应参数的显示模式标志 */
                case '-': flags |= LEFT; goto repeat;
                case '+': flags |= PLUS; goto repeat;
                case ' ': flags |= SPACE; goto repeat;
                case '#': flags |= SPECIAL; goto repeat;
                case '0': flags |= ZEROPAD; goto repeat;
            }

        /* get field width */
        /* 获取显示参数的宽度,宽度可以直接写在类型转换符之前(如%3d);也
         * 可以用'*'表明用参数值来指定宽度,如vsprintf(buf, "%*d", 3,4),
         * 3将作为%d匹配到的整型数字4的显示宽度。*/
        field_width = -1;
        if (is_digit(*fmt))
            field_width = skip_atoi(&fmt);
        else if (*fmt == '*') {
            /* 应添加fmt++;语句跳过'*',否则进入switch中后会进入default */
            /* it's the next argument */
            field_width = va_arg(args, int);
            if (field_width < 0) { /* 若为负数则标识左对齐 */
                field_width = -field_width;
                flags |= LEFT;
            }
        }

        /* get the precision */
        /* 显示精度精度/位数可直接包含在类型转换
         * 符前,也有可能由占位符'*'匹配参数指定。*/
        precision = -1;
        if (*fmt == '.') {
            ++fmt;
            if (is_digit(*fmt))
                precision = skip_atoi(&fmt);
        else if (*fmt == '*') {
            /* it's the next argument */
            precision = va_arg(args, int);
        }
            if (precision < 0)
                precision = 0;
        }

        /* get the conversion qualifier */
        /* 获取类型转换限定符 */
        qualifier = -1;
        if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') {
            qualifier = *fmt;
            ++fmt;
        }

        /* 获取类型转换符 */
        switch (*fmt) {
        /* 以字符类型解析参数 */
        case 'c':
            /* 左对齐时在字符左边补相应数目空格 */
            if (!(flags & LEFT))
                while (--field_width > 0)
                    *str++ = ' ';
            /* 获取参数值并取低字节值即字符的值 */
            *str++ = (unsigned char) va_arg(args, int);
            /* 右对齐时在字符右边补相应数目空格 */
            while (--field_width > 0)
                *str++ = ' ';
            break;

        /* 以字符串解析参数 */
        case 's':
            /* 取字符串首地址和长度 */
            s = va_arg(args, char *);
            len = strlen(s);
        
            /* 决定字符串显示宽度 */
            if (precision < 0)
                precision = len;
            else if (len > precision)
                len = precision;

            /* 根据对齐标志决定是否要在字符串左边补齐相应数目空格 */
            if (!(flags & LEFT))
                while (len < field_width--)
                    *str++ = ' ';
            /* 从字符串所在内存拷贝字符到buf相应位置上 */
            for (i = 0; i < len; ++i)
                *str++ = *s++;
            /* 根据对齐标志决定是否要在字符串左边补齐相应数目空格 */
            while (len < field_width--)
                *str++ = ' ';
            break;

        /* 以无符号八进进制解析参数 */
        case 'o':
            str = number(str, va_arg(args, unsigned long), 8,
                field_width, precision, flags);
            break;

        /* 以16进制数解析参数,该参数占8个宽度,若参数不足8位则在左边补0 */
        case 'p':
            if (field_width == -1) {
                field_width = 8;
                flags |= ZEROPAD;
            }
            str = number(str,
                (unsigned long) va_arg(args, void *), 16,
                field_width, precision, flags);
            break;

        /* 以16进制解析参数,x和X分别标识16进制字母的小写和大写格式 */
        case 'x':
            flags |= SMALL;
        case 'X':
            str = number(str, va_arg(args, unsigned long), 16,
                field_width, precision, flags);
            break;

        /* 以有符号10进制解析参数,u表示以无符号10进制数解析参数 */
        case 'd':
        case 'i':
            flags |= SIGN;
        case 'u':
            str = number(str, va_arg(args, unsigned long), 10,
                field_width, precision, flags);
            break;

        /* 将当前解析的参数长度存在参数所指向的地址中 */
        case 'n':
            ip = va_arg(args, int *);
            *ip = (str - buf);
            break;

        /* 处理特殊情况或错误情况 */
        default:
            /* 在匹配到'%'后还能运行到这里,说明'%'之后紧跟了'%',
             * 若没有紧跟'%'则补写入'%'以方便错误提示。*/
            if (*fmt != '%')
                *str++ = '%';
            if (*fmt)
                *str++ = *fmt;
            else /* 若为NULL则回退一字节重新指向NULL以让for循环顺利退出 */
                --fmt;
            break;
        }
    }
    *str = '\0';
    
    /* 返回所解析参数的长度 */
    return str-buf;
}
_exit.c
/*
 *  linux/lib/_exit.c
 *
 *  (C) 1991  Linus Torvalds
 */

#define __LIBRARY__
#include 

/* _exit,
 * 用于退出当前进程。
 *
 * volatile用于修饰函数时告知编译器该函数不会
 * 返回,可以省略_exit函数返回相关的栈帧信息。
 *
 * volatile用于修饰内联汇编时则告知编译器不要优化内联汇编代码,如见inb_p。*/
volatile void _exit(int exit_code)
{
   /* eax=__NR_exit, ebx=exit_code; 
    * 即调用系统调用号为__NR_exit的系统调用,ebx携带了参数exi_code,
    * 其内核函数对应为 fs/sys_exit()。*/
    __asm__("int $0x80"::"a" (__NR_exit),"b" (exit_code));
}
exit.c
/*
 *  linux/kernel/exit.c
 *
 *  (C) 1991  Linus Torvalds
 */

#include 
#include 
#include 

#include 
#include 
#include 
#include 

int sys_pause(void);
int sys_close(int fd);

/* release,
 * 释放p所指向结构体所占内存。*/
void release(struct task_struct * p)
{
    int i;

    if (!p)
        return;
    /* 遍历地址为p的结构体,释放其内存并置空后进行任务调度 */
    for (i=1 ; i32)
        return -EINVAL;

    /* priv置位或当前进程与目标进程有效进程id相同或当前
     * 进程为超级进程则往p指向结构体所管理进程置sig信号。*/
    if (priv || (current->euid==p->euid) || suser())
        p->signal |= (1<<(sig-1));
    else
        return -EPERM;
    return 0;
}

/* kill_session,
 * 向与当前进程同会话的进程发送进程终止信号。*/
static void kill_session(void)
{
    struct task_struct **p = NR_TASKS + task;

    while (--p > &FIRST_TASK) {
        if (*p && (*p)->session == current->session)
            (*p)->signal |= 1<<(SIGHUP-1);
    }
}

/*
 * XXX need to check permissions needed to send signals to process
 * groups, etc. etc.  kill() permissions semantics are tricky!
 *
 * 
 */
int sys_kill(int pid,int sig)
{
    struct task_struct **p = NR_TASKS + task;
    int err, retval = 0;

    /* 当pid=0时,则向所有进程组id为当前进程id的进程发送sig信号 */
    if (!pid) while (--p > &FIRST_TASK) {
        if (*p && (*p)->pgrp == current->pid) 
            if (err=send_sig(sig,*p,1))
                retval = err;
    /* 若当前进程有足够权限(超级进程或与目标进程有效用户id相同)
     * 则向进程id为pid的进程发送sig信号 */
    } else if (pid>0) while (--p > &FIRST_TASK) {
        if (*p && (*p)->pid == pid) 
            if (err=send_sig(sig,*p,0))
                retval = err;
    /* 若pid为-1,则向所有进程尝试发送sig信号 */
    } else if (pid == -1) while (--p > &FIRST_TASK)
        if (err = send_sig(sig,*p,0))
            retval = err;
    /* 若pid小于-1,则将pid取反后当pid大于0情况处理 */
    else while (--p > &FIRST_TASK)
        if (*p && (*p)->pgrp == -pid)
            if (err = send_sig(sig,*p,0))
                retval = err;
    return retval;
}

/* tell_father,
 * 向父进程(pid)发送当前进程已停止的信号。*/
static void tell_father(int pid)
{
    int i;

    if (pid)
        for (i=0;ipid != pid)
            continue;
        task[i]->signal |= (1<<(SIGCHLD-1));
        return;
    }
/* if we don't find any fathers, we just release ourselves */
/* This is not really OK. Must change it to make father 1 */
/* 若当前进程没有父进程,则自我释放管理当前进程的结构体,不提倡自我释放管理当前
 * 进程的结构体,在没有找到父进程时应该do_exit中将其父进程id设置为1(init进程)。*/
    printk("BAD BAD - no father found\n\r");
    release(current);
}

/* do_exit,
 * 同步或释放当前进程所占资源,然后退出当前进程。*/
int do_exit(long code)
{
    int i;

    /* 释放当前进程数据段和代码段页表所占物理内存和页表所映射的物理内存页*/
    free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
    free_page_tables(get_base(current->ldt[2]),get_limit(0x17));

    /* 标记当前进程子进程的父进程id为1,若其子进程为僵尸进程则向init进程发送
     * SIGCHLD信号让init进程回收下管理当前进程子进程的结构体资源,见sys_waitpid。*/
    for (i=0 ; ifather == current->pid) {
            task[i]->father = 1;
            if (task[i]->state == TASK_ZOMBIE)
                /* assumption task[1] is always init */
                (void) send_sig(SIGCHLD, task[1], 1);
        }

    /* 关闭当前进程所打开的文件 */
    for (i=0 ; ifilp[i])
            sys_close(i);

    /* 同步当前目录,根目录,当前进程可执行文件i节点到设备并释放相应i节点 */
    iput(current->pwd);
    current->pwd=NULL;
    iput(current->root);
    current->root=NULL;
    iput(current->executable);
    current->executable=NULL;

    /* 若当前进程为会话首领且拥有终端则释放
     * 终端,清除当前进程使用协处理器的记录。*/
    if (current->leader && current->tty >= 0)
        tty_table[current->tty].pgrp = 0;
    if (last_task_used_math == current)
        last_task_used_math = NULL;

    /* 若当前进程为会话首领,则终止该会话下的所有进程 */
    if (current->leader)
        kill_session();

    /* 置当前进程为僵尸进程以标识管理该进程的结构体内存还未释放 */
    current->state = TASK_ZOMBIE;
    /* 置当前进程退出码以让回收该进程结构体资源的进程获取 */
    current->exit_code = code;

    /* 向父进程发送信号告知本进程已停止运行 */
    tell_father(current->father);
    
    /* 调度时间片最大的进程运行 */
    schedule();

    /* 已置当前进程为僵尸状态,本进程不会再被调度运行即
     * schedule()函数不会返回,此语句仅用于避免编译器的警告。*/
    return (-1); /* just to suppress warnings */
}

/* sys_exit,
 * 系统调用_exit内核入口函数。*/
int sys_exit(int error_code)
{
    return do_exit((error_code&0xff)<<8);
}

/* sys_waitpid,
 * 等待pid所指定子进程运行结束,并获取指定子进程运行结束退出码于stat_addr中。
 *
 * pid>0,等待进程id为pid的子进程结束;pid=0,等待与本进程组id相同的子进程结束;
 * pid=-1,等待任意子进程结束;pid<-1,等待组id为-pid的子进程结束。
 *
 * options=WNOHANG,不挂起等待即无指定子进程结束时返回0;
 * options=WUNTRACED,只等待处于僵尸状态的指定子进程。*/
int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)
{
    int flag, code;
    struct task_struct ** p;

    /* 写时拷贝当前进程数据段中的stat_addr */
    verify_area(stat_addr,4);
    
repeat:
    flag=0;
    for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) {
        /* 遍历系统当前进程管理结构体, */

        /* 跳过本进程及无关联进程的结构体元素 */
        if (!*p || *p == current)
            continue;
        /* 忽略父进程不为本进程的进程 */
        if ((*p)->father != current->pid)
            continue;
        /* pid>0时表等待指定的子进程id */
        if (pid>0) {
            if ((*p)->pid != pid)
                continue;
        /* pid=0时表等待与本进程组id相同的子进程 */
        } else if (!pid) {
            if ((*p)->pgrp != current->pgrp)
                continue;
        /* pid < -1时表等待组id为|pid|的子进程 */
        } else if (pid != -1) {
            if ((*p)->pgrp != -pid)
                continue;
        }
        /* pid=-1则表明等待任意的子进程结束 */
        switch ((*p)->state) {
            case TASK_STOPPED: /* 进程已停止 */
                /* 若不获取已停止进程状态则继续遍历 */
                if (!(options & WUNTRACED))
                    continue;
                /* 对于已停止进程,退出码为0x7f */
                put_fs_long(0x7f,stat_addr);
                return (*p)->pid; /* 返回子进程id */
            case TASK_ZOMBIE: /* 僵尸进程 */
                current->cutime += (*p)->utime;
                current->cstime += (*p)->stime;
                flag = (*p)->pid;
                code = (*p)->exit_code;
                release(*p); /* 回收僵尸进程资源 */
                /* 返回进程运行结束退出码和进程id */
                put_fs_long(code,stat_addr);
                return flag;
            default: /* 无子进程结束 */
                flag=1;
                continue;
        }
    }
    /* 若遍历完当前进程结构体仍无结束的子进程, */
    if (flag) {
        /* 若option为WNOHANG(不等待)则理解返回0, */
        if (options & WNOHANG)
            return 0;
        /* 否则将当前进程置为就绪状态并调度其他进程运行, */
        current->state=TASK_INTERRUPTIBLE;
        schedule();
        /* 本进程收到信号时会被重新置于可被调度状态从而从schedule()函数返回执行
         * 到此处。若本进程收到SIGCHLD信号则回到repeat处继续等待指定子进程结束。*/
        if (!(current->signal &= ~(1<<(SIGCHLD-1))))
            goto repeat;
        else
        /* 若当前进程被其它信号唤醒,则返回相应错误码。
         * 应用程序收到-EINTN返回值时应继续调用该函数。*/
        return -EINTR;
    }
    return -ECHILD;
}
exec.c
/*
 *  linux/fs/exec.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * #!-checking implemented by tytso.
 */

/*
 * Demand-loading implemented 01.12.91 - no need to read anything but
 * the header into memory. The inode of the executable is put into
 * "current->executable", and page faults do the actual loading. Clean.
 *
 * Once more I can proudly say that linux stood up to being changed: it
 * was less than 2 hours work to get demand-loading completely implemented.
 */

#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 

extern int sys_exit(int exit_code);
extern int sys_close(int fd);

/*
 * MAX_ARG_PAGES defines the number of pages allocated for arguments
 * and envelope for the new program. 32 should suffice, this gives
 * a maximum env+arg of 128kB !
 */
#define MAX_ARG_PAGES 32

/*
 * create_tables() parses the env- and arg-strings in new user
 * memory and creates the pointer tables from them, and puts their
 * addresses on the "stack", returning the new stack pointer value.
 */
/* create_tables,
 * 在进程内存段末端组织进程环境(变量)参数和命令行参数信息;
 * 该函数返回参数信息首地址。*/
static unsigned long * create_tables(char * p,int argc,int envc)
{
    unsigned long *argv,*envp;
    unsigned long * sp;

    /* 环境参数末端地址以4字节对齐 */
    sp = (unsigned long *) (0xfffffffc & (unsigned long) p);
    /* 空出用来存各环境参数和命令行参数的地址的空间 */
    sp -= envc+1;
    envp = sp;
    sp -= argc+1;
    argv = sp;
    /* 将存各环境参数的地址的内存段首地址,
     * 存各命令行参数的地址的内存段首地址,
     * 命令行参数个数依次写入内存中。*/
    put_fs_long((unsigned long)envp,--sp);
    put_fs_long((unsigned long)argv,--sp);
    put_fs_long((unsigned long)argc,--sp);
    
    /* 将各命令行参数的地址依次写入argv内存段 */
    while (argc-->0) {
        put_fs_long((unsigned long) p,argv++);
        /* 跳过命令行参数字符串串 */
        while (get_fs_byte(p++)) /* nothing */ ;
    }
    /* 存储命令行参数的地址的内存段结束标志位0 */
    put_fs_long(0,argv);

    /* 将各环境参数的地址依次写入envp内存段 */
    while (envc-->0) {
        put_fs_long((unsigned long) p,envp++);
        /* 跳过环境参数本身 */
        while (get_fs_byte(p++)) /* nothing */ ;
    }
    /* 存储环境参数的地址的内存段结束标志位0 */
    put_fs_long(0,envp);
    return sp;

}
/* 经create_tables后,
 * 进程跟参数相关的逻辑地址空间跟分布大体如下。
 *64Mb|      | 
 *    |======| 环
 *    |      | 境
 *    |      | 参
 *    |      | 数
 *    |      | 段
 *    |======|   
 *    |      | 命
 *    |      | 令
 *    |      | 行
 *    |      | 参
 *    |      | 数
 *    |      | 段
 *    |======|   
 *    |      | 各
 *    |      | 环
 *    |      | 境
 *    |      | 参
 *    |      | 数
 *    |      | 的
 *    |      | 地
 *    |      | 址
 *    |      | 的
 *    |      | 段
 *    |======|   
 *    |      | 各
 *    |      | 命
 *    |      | 令
 *    |      | 行
 *    |      | 参
 *    |      | 数
 *    |      | 的
 *    |      | 地
 *    |      | 址
 *    |      | 的
 *    |======| 段
 *    | envp |   
 *    |======|   
 *    | argv |   
 *    |======|   
 *    | argc |   
 *    |======| sp
 *    |  .   |   
 *    |  .   |   
 *   0|  .   | */

/*
 * count() counts the number of arguments/envelopes
 */
/* count,
 * 计算argv所指内存段中所包含的
 * (char *)类型指针元素, 指针元
 * 素以NULL结尾。*/
static int count(char ** argv)
{
    int i=0;
    char ** tmp;

    /* count函数应用场景(32位地址线)。
     * 
     * 指针数组存储字符串常量地址,以NULL结尾。
     * char *arg[] = {"1","2","3",NULL};
     *
     * 内存中的字符串常量。
     * -------------
     * |"1"|"2"|"3"|
     * -------------
     * x   y   z
     * 
     * 设arg所在内存首地址为w
     * ---------------------------
     * |x     |y     |z     |NULL|
     * ---------------------------
     * w      w+4    w+8    w+12
     * arg[0] arg[1] arg[2] arg[3]
     * arg+0  arg+1  arg+2  arg+3 
     *
     * -----
     * |w  |
     * -----
     * tmp=argv
     * 
     * tmp作为右值时为其内存中内容即w;
     * *((unsigned long *)tmp)即从内存地址w处读取sizeof(long)=4字节内容即x。
     * 
     * tmp++,作为指向(char *)类型的指针,tmp++对应w+4;
     * *( (unsigned long *)tmp)即从内存地址(w+4)中读取sizeof(long)=4字节内容即y。
     * ...
     * 
     * 待*( (unsigned long *)tmp)为NULL时则退出循环,
     * 便统计了指针数组arg中指针元素个数从而统计了指针数组所指字符串个数。*/

    if (tmp = argv)
        while (get_fs_long((unsigned long *) (tmp++)))
            i++;

    return i;
}

/*
 * 'copy_string()' copies argument/envelope strings from user
 * memory to free pages in kernel mem. These are in a format ready
 * to be put directly into the top of new user memory.
 *
 * Modified by TYT, 11/24/91 to add the from_kmem argument, which specifies
 * whether the string and the string array are from user or kernel segments:
 * 
 * from_kmem     argv *        argv **
 *    0          user space    user space
 *    1          kernel space  user space
 *    2          kernel space  kernel space
 * 
 * We do this by playing games with the fs segment register.  Since it
 * it is expensive to load a segment register, we try to avoid calling
 * set_fs() unless we absolutely have to.
 */
/* copy_strings,
 * 将argv中的argc个指针元素所指数据拷贝到内核中,拷贝后的目的内存由page中相应元素指向。
 * from_kmem用于标识 *argv 和 **argv 是内核还是用户空间地址。该函数返回参数内存块首地址。*/
static unsigned long copy_strings(int argc,char ** argv,unsigned long *page,
    unsigned long p, int from_kmem)
{
    char *tmp, *pag;
    int len, offset = 0;
    unsigned long old_fs, new_fs;

    if (!p)
        return 0;   /* bullet-proofing */
    new_fs = get_ds();
    old_fs = get_fs();
    /* 若参数在内核数据段则置fs加载内核数据段描述符 */
    if (from_kmem==2)
        set_fs(new_fs);

    /* 将argv中的argc个元素所指向字符串拷贝考内核内存中 */
    while (argc-- > 0) {
        /* 若 *argv 为内核空间地址则让fs加载内核数据段描述符 */
        if (from_kmem == 1)
            set_fs(new_fs);
        /* 将(argv+argc)地址中的地址取出 */
        if (!(tmp = (char *)get_fs_long(((unsigned long *)argv)+argc)))
            panic("argc is wrong");
        /* 若**argv为用户空间地址则恢复fs加载用户数据段描述符 */
        if (from_kmem == 1)
            set_fs(old_fs);
        
        /* 求取argv[argc]所指字符串长度 */
        len=0; /* remember zero-padding */
        do {
            len++;
        } while (get_fs_byte(tmp++));
        /* 判断字符串长度是否超过预留内存长度*/
        if (p-len < 0) { /* this shouldn't happen - 128kB */
            set_fs(old_fs);
            return 0;
        }
        
        /* 将argv[argc]即tmp所指字符串拷贝到空闲内核内存中 */
        while (len) {
            --p; --tmp; --len;
            if (--offset < 0) {
                /* offset=(PAGE_SIZE*32-5)%PAGE_SIZE=PAGE_SIZE-5 */
                offset = p % PAGE_SIZE;
                /* 若曾在本函数中间加载内核数据段描述
                 * 符于fs则先恢复以让内存分配函数使用。*/
                if (from_kmem==2)
                    set_fs(old_fs);
                /* 分配一页内存由page相应元素和pag指向 */
                if (!(pag = (char *) page[p/PAGE_SIZE]) &&
                    !(pag = (char *) page[p/PAGE_SIZE] =
                    (unsigned long *) get_free_page())) 
                    return 0;
                /* 恢复fs加载内核数据段描述符 */
                if (from_kmem==2)
                    set_fs(new_fs);
            }
            /* 将tmp所指字符串拷贝到内存页中(逆向拷贝) */
            *(pag + offset) = get_fs_byte(tmp);
        }
    }
    
    /* 恢复fs加载用户数据段描述符 */
    if (from_kmem==2)
        set_fs(old_fs);
    return p;
}

/* change_ldt,
 * 更改当前进程的LDT,使其代码段限长为text_size,数据段限长为64Mb;
 * 将进程数据段末端与page中保存环境变量和命令行等参数的内存页映射。*/
static unsigned long change_ldt(unsigned long text_size,unsigned long * page)
{
    unsigned long code_limit,data_limit,code_base,data_base;
    int i;

    /* 代码段以页对齐;数据段大小为64Mb */
    code_limit = text_size+PAGE_SIZE -1;
    code_limit &= 0xFFFFF000;
    data_limit = 0x4000000;

    /* 基于当前进程代码段和数据段基址和所计算的限长,设置新的LDT表 */
    code_base = get_base(current->ldt[1]);
    data_base = code_base;
    set_base(current->ldt[1],code_base);
    set_limit(current->ldt[1],code_limit);
    set_base(current->ldt[2],data_base);
    set_limit(current->ldt[2],data_limit);
    
/* make sure fs points to the NEW data segment */
    /* 确保fs寄存器加载用户数据段描述符 */
    __asm__("pushl $0x17\n\tpop %%fs"::);
    /* 将用户程序数据段末端与保存参数(环境、命令行等)的内存页映射 */
    data_base += data_limit;
    for (i=MAX_ARG_PAGES-1 ; i>=0 ; i--) {
        data_base -= PAGE_SIZE;
        if (page[i])
            put_page(page[i],data_base);
    }
    return data_limit;
}

/*
 * 'do_execve()' executes a new program.
 */
/* do_execve,
 *
 * 
 * eip,tmp在_sys_execve中被入栈,tmp为_sys_execve call do_execve
 * 时入栈的eip寄存器;
 * filename, argv, envp分别和_system_call中入栈的edx, ecx, ebx对应。*/
int do_execve(unsigned long * eip,long tmp,char * filename,
    char ** argv, char ** envp)
{
    struct m_inode * inode;
    struct buffer_head * bh;
    struct exec ex;
    unsigned long page[MAX_ARG_PAGES];
    int i,argc,envc;
    int e_uid, e_gid;
    int retval;
    int sh_bang = 0;
    unsigned long p=PAGE_SIZE*MAX_ARG_PAGES-4;

    /* eip[1]即*(eip + 1)即系统调用execve时cs寄存器的值,
     * 若cs段寄存器值不为0x0f则表明当时进程为内核程序。*/
    if ((0xffff & eip[1]) != 0x000f)
        panic("execve called from supervisor mode");
    
    for (i=0 ; ii_mode)) { /* must be regular file */
        retval = -EACCES;
        goto exec_error2;
    }
    i = inode->i_mode;
    /* 根据可执行文件属性标志,设置其用户id和组id,
     * 当前进程为即将执行可执行程序的父进程。*/
    e_uid = (i & S_ISUID) ? inode->i_uid : current->euid;
    e_gid = (i & S_ISGID) ? inode->i_gid : current->egid;

    /* 由于是有当前进程执行可执行文件,
     * 所以看看当前进程对于可执行文件
     * 的操作权限, 当当前进程是可执行
     * 文件组员时获取权限, 当当前进程
     * 是可执行文件宿主时获取其宿主权限。*/
    if (current->euid == inode->i_uid)
        i >>= 6;
    else if (current->egid == inode->i_gid)
        i >>= 3;
    /* 当当前进程作为组员或宿主身份对可执行文件没有执行权限且
     * 不满足可执行文件对所有进程都开放执行权限且当前进程为超
     * 级进程(初始进程的euid为0)的条件 时则出错返回。*/
    if (!(i & 1) &&
        !((inode->i_mode & 0111) && suser())) {
        retval = -ENOEXEC;
        goto exec_error2;
    }
    /* 读可执行程序文件前1Kb内容进入内存 */
    if (!(bh = bread(inode->i_dev,inode->i_zone[0]))) {
        retval = -EACCES;
        goto exec_error2;
    }
    /* 用作可执行文件头部的解析 */
    ex = *((struct exec *) bh->b_data); /* read exec-header */
    
    /* 若可执行文件为(shell)脚本文件 */
    if ((bh->b_data[0] == '#') && (bh->b_data[1] == '!') && (!sh_bang)) {
        /*
         * This section does the #! interpretation.
         * Sorta complicated, but hopefully it will work.  -TYT
         */

        char buf[1023], *cp, *interp, *i_name, *i_arg;
        unsigned long old_fs;

        /* 除去开头的#!字符,将脚本前1Kb内容拷贝到buf中并释放缓冲区块和i节点 */
        strncpy(buf, bh->b_data+2, 1022);
        brelse(bh);
        iput(inode);
        buf[1022] = '\0';
        
        /* 处理第1行内容(如#!/bash/sh),跳过#!后所有空格和制表符 */
        if (cp = strchr(buf, '\n')) {
            *cp = '\0';
            for (cp = buf; (*cp == ' ') || (*cp == '\t'); cp++);
        }
        /* 若没有回车符或已到行尾则表明脚本无内容需处理 */
        if (!cp || *cp == '\0') {
            retval = -ENOEXEC; /* No interpreter name found */
            goto exec_error1;
        }
        /* 如脚本首行内容为#!/bash/sh,i_name将等于"sh",interp="/bash/sh" */
        interp = i_name = cp;
        i_arg = 0;
        for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++) {
            if (*cp == '/')
            i_name = cp+1;
        }
        if (*cp) {
            *cp++ = '\0';
            i_arg = cp;
        }
/* 到此处,就解析出脚本首行中(#!/bash/sh)的不带参数的解释器(/bash/sh)了 */
        /*
         * OK, we've parsed out the interpreter name and
         * (optional) argument.
         */
         /* 将用户空间的环境变量参数和命令行参数拷贝到可执行程序用
          * 于存储参数的内存中, 保存环境变量参数和命令行参数内存的
          * 地址由page末尾元素指向, 即环境变量和命令行参数此处预分
          * 配128Kb内存末端。*/
        if (sh_bang++ == 0) {
            p = copy_strings(envc, envp, page, p, 0);
            p = copy_strings(--argc, argv+1, page, p, 0);
        }

        /*
         * Splice in (1) the interpreter's name for argv[0]
         *           (2) (optional) argument to interpreter
         *           (3) filename of shell script
         *
         * This is done in reverse order, because of how the
         * user environment and arguments are stored.
         */
        /* 将filename拷贝到可执行文件用于存储参数的内存段中&filename
         * 是内核内存空间地址,filename是用户内存空间地址。*/
        p = copy_strings(1, &filename, page, p, 1);
        argc++;
        /* 将脚本首行中的参数拷贝到可执行文件用于存储参数的内存段中 */
        if (i_arg) {
            p = copy_strings(1, &i_arg, page, p, 2);
            argc++;
        }
        /* 将解释器名拷贝到可执行文件用于存储参数的内存段中 */
        p = copy_strings(1, &i_name, page, p, 2);
        argc++;
        if (!p) {
            retval = -ENOMEM;
            goto exec_error1;
        }
/* -----------------------------------------------------------------------------------------------------------------
 * ... interpreter_name interpreter_arg|filename|argv[1] argv[2] ... argv[argc-1]|envp[0] envp[1] ... envp[envc-1] |
 * -----------------------------------------------------------------------------------------------------------------
 * 0                                                                                                              0x1ffff
 *
 * 为进程命令行参数和环境变量预留的128Kb内存与page[32]对应。*/

        /*
         * OK, now restart the process with the interpreter's inode.
         */
        /* 让fs加载内核数据段描述符 */
        old_fs = get_fs();
        set_fs(get_ds());
        /* 获取interp所指向脚本可执行程序名(如/bash/sh)的i节点 */
        if (!(inode=namei(interp))) { /* get executables inode */
            set_fs(old_fs);
            retval = -ENOENT;
            goto exec_error1;
        }
        /* 恢复fs指向用户空间数据段描述符 */
        set_fs(old_fs);
        /* 跳转restart_interp处以启动执行脚本可执行程序
         * (如/bash/sh),现在inode指向脚本程序(/bash/sh)。*/
        goto restart_interp;
    }
    
    /* 释放可执行程序前1Kb缓冲区块 */
    brelse(bh);
/* 解析可执行文件头部 */
    if (N_MAGIC(ex) != ZMAGIC || ex.a_trsize || ex.a_drsize ||
        ex.a_text+ex.a_data+ex.a_bss>0x3000000 ||
        inode->i_size < ex.a_text+ex.a_data+ex.a_syms+N_TXTOFF(ex)) {
        retval = -ENOEXEC;
        goto exec_error2;
    }
    if (N_TXTOFF(ex) != BLOCK_SIZE) {
        printk("%s: N_TXTOFF != BLOCK_SIZE. See a.out.h.", filename);
        retval = -ENOEXEC;
        goto exec_error2;
    }
    /* 若当前可执行程序文件不为脚本可执行文件则将环境变量
     * 和命令行参数拷贝到可执行文件用于存储参数内存段末端。*/
    if (!sh_bang) {
        p = copy_strings(envc,envp,page,p,0);
        p = copy_strings(argc,argv,page,p,0);
        if (!p) {
            retval = -ENOMEM;
            goto exec_error2;
        }
    }
/* 略看filename各参数的存储,假设参数未超过一页内存。
 * |<----------------------128Kb-------------------->|
 * ---------------------------------------------------
 * ...|........命令行参数+环境变量参数(+脚本程序参数)|
 * ---------------------------------------------------
 * 0           p                                     0x1ffff
 * 用于保存各参数内存页的物理地址存在page[31]中。*/

/* OK, This is the point of no return */
/* 使用当前进程管理结构体current管理execve所加载可执行文件的运行 */
    /* 覆盖可执行文件i节点 */
    if (current->executable)
        iput(current->executable);
    current->executable = inode;
    /* 复位信号处理函数指针 */
    for (i=0 ; i<32 ; i++)
        current->sigaction[i].sa_handler = NULL;
    /* 关闭所打开文件 */
    for (i=0 ; iclose_on_exec>>i)&1)
            sys_close(i);
    current->close_on_exec = 0;
    /* 释放当前进程代码段和数据段所占内存段 */
    free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
    free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
    /* 使用协处理标志复位 */
    if (last_task_used_math == current)
        last_task_used_math = NULL;
    current->used_math = 0;
    /* 将当前进程LDT更改以描述可执行程序filename代码段和数据段,
     * 并将可执行程序filename数据段末尾段内存空间地址与保存各参
     * 数的page内存页相映射。*/
    p += change_ldt(ex.a_text,page)-MAX_ARG_PAGES*PAGE_SIZE;
/* change_ldt执行完毕后,
 * 略看filename进程跟环境变量等参数相关内存地址空间。
 * |<---------------------64MB---------------->|
 * ---------------------------------------------
 * .............................|arguments.....|
 * ---------------------------------------------
 * 0                            p=64Mb-128Kb+p 0x3ffffff
 * 即将进程数据段末端映射到保存各参数的内存页。*/
    /* 在filename进程内存段末端组织环境变量和命令行参数 */
    p = (unsigned long) create_tables((char *)p,argc,envc);

    current->brk = ex.a_bss +
        (current->end_data = ex.a_data +
        (current->end_code = ex.a_text));
    current->start_stack = p & 0xfffff000; /* 栈顶地址 */
    /* 可执行文件属性不同时有效id不同(宿主id或继承当前进程id ) */
    current->euid = e_uid;
    current->egid = e_gid;
    i = ex.a_text+ex.a_data;

    /* 若进程末端内存地址不以4Kb对齐则往该内存地址对应内存页写0 */
    while (i&0xfff)
        put_fs_byte(0,(char *) (i++));
    /* 更改发生系统调用execve()时CPU往栈中备份的eip和esp寄存器的值,
     * eip=filename可执行文件指令入口地址处,
     * esp=p即进程命令行参数信息前4字节为进程栈顶。
     * 
     * 若对寄存器部分不太熟悉,则回system_call.s中看看栈中寄存器的备份布局吧。*/
    eip[0] = ex.a_entry; /* eip, magic happens :-) */
    eip[3] = p; /* stack pointer */
/* 由于用filename可执行文件代码入口地址修改了发生
 * 系统调用时CPU往栈中备份的eip寄存器,所以CPU会转
 * 而执行filename可执行文件(加上对诸如current->xx
 * 信息的修改,可执行文件的执行环境也是正确的啦)。
 *
 * 从调用系统调用execve开始到此处,就是execve加载
 * 可执行文件覆盖原进程执行的全部秘密啦。
 *
 * 能阅读明白的钥匙似乎是内存页机制,进程管理等相关知识和反复阅读。*/
    return 0;
exec_error2:
    iput(inode);
exec_error1:
    for (i=0 ; i
errno.c
/*
 *  linux/lib/errno.c
 *
 *  (C) 1991  Linus Torvalds
 */

/* 用于保存内核程序执行出错时的错误码 */
int errno;
errno.h
#ifndef _ERRNO_H
#define _ERRNO_H

/*
 * ok, as I hadn't got any other source of information about
 * possible error numbers, I was forced to use the same numbers
 * as minix.
 * Hopefully these are posix or something. I wouldn't know (and posix
 * isn't telling me - they want $$$ for their f***ing standard).
 *
 * We don't use the _SIGN cludge of minix, so kernel returns must
 * see to the sign by themselves.
 *
 * NOTE! Remember to change strerror() if you change this file!
 *
 * 由于寡人没有从其他源码中了解过关于错误码的定义,所以此处延用minix中
 * 定义的错误码。我不知道这是否能与posix或其他标准匹配,但孤希望如此。
 * (主要是posix也木有告知我——他们设计标准时总是向美刀看齐)
 *
 * 此文不用minix中定义的_SIGN,所以在内核中返回这些错误码时需手动添加负号。
 *
 * 注,若修改了本文件,则也需同步修改strerror()。
 */

/* 声明定义在errno.c中的全局变量errno */
extern int errno;

/* 当内核出错时则将相应错误码赋值给
 * errno变量,粗略领略下错误码含义吧。*/
#define ERROR   99  /* 普通错误 */
#define EPERM   1   /* 无操作权限 */
#define ENOENT  2   /* (文件或目录)项不存在 */
#define ESRCH   3   /* 进程不存在 */
#define EINTR   4   /* (阻塞型)系统调用被信号中断(唤醒)错误码 */
#define EIO     5   /* 输入/输出错误码 */
#define ENXIO   6   /* I/O不存在 */
#define E2BIG   7   /* 参数过多 */
#define ENOEXEC 8   /* 可执行文件格式错误 */
#define EBADF   9   /* 文件描述符错误 */
#define ECHILD  10  /* 子进程不存在 */
#define EAGAIN  11  /* 资源暂时不可用 */
#define ENOMEM  12  /* 内存暂无 */
#define EACCES  13  /* 无访问权限 */
#define EFAULT  14  /* 地址错误 */
#define ENOTBLK 15  /* 非块设备 */
#define EBUSY   16  /* 资源处于忙碌状态 */
#define EEXIST  17  /* 文件已存在 */
#define EXDEV   18  /* 跨设备链接 */
#define ENODEV  19  /* 无此设备 */
#define ENOTDIR 20  /* 非目录 */
#define EISDIR  21  /* 目录 */
#define EINVAL  22  /* 无效参数 */
#define ENFILE  23  /* 文件表溢出(无空闲) */
#define EMFILE  24  /* 达文件打开上限 */
#define ENOTTY  25  /* 无TTY终端 */
#define ETXTBSY 26  /* 文本文件忙,不可使用 */
#define EFBIG   27  /* 文件太大 */
#define ENOSPC  28  /* 设备无剩余空间 */
#define ESPIPE  29  /* 文件指针重定位非法 */
#define EROFS   30  /* 文件系统只读 */
#define EMLINK  31  /* 链接(link)过多 */
#define EPIPE   32  /* 管道错误 */
#define EDOM    33  /* 超函数域 */
#define ERANGE  34  /* 超最大结果 */
#define EDEADLK 35  /* 资源将发生死锁 */
#define ENAMETOOLONG 36  /* 文件名过长 */
#define ENOLCK  37  /* 无锁资源 */
#define ENOSYS  38  /* 未实现 */
#define ENOTEMPTY   39 /* 非空目录 */

#endif
time.h
#ifndef _TIME_H
#define _TIME_H

#ifndef _TIME_T
#define _TIME_T
typedef long time_t;
#endif

#ifndef _SIZE_T
#define _SIZE_T
typedef unsigned int size_t;
#endif

/* 100个时间片为1s */
#define CLOCKS_PER_SEC 100

typedef long clock_t;

/* struct tm,
 * 描述事件的结构体类型。*/
struct tm {
    int tm_sec;  /* 秒 */
    int tm_min;  /* 分 */
    int tm_hour; /* 时 */
    int tm_mday; /* 天 */
    int tm_mon;  /* 月 */
    int tm_year; /* 年 */
    int tm_wday; /* [0,6]-[周天,周六] */
    int tm_yday; /* 每年1月1日起的天数 */
    int tm_isdst; /* 大于0时表夏令时 */
};

clock_t clock(void);
time_t time(time_t * tp);
double difftime(time_t time2, time_t time1);
time_t mktime(struct tm * tp);

char * asctime(const struct tm * tp);
char * ctime(const time_t * tp);
struct tm * gmtime(const time_t *tp);
struct tm *localtime(const time_t * tp);
size_t strftime(char * s, size_t smax, const char * fmt, const struct tm * tp);
void tzset(void);

#endif
stddef.h
#ifndef _STDDEF_H
#define _STDDEF_H

#ifndef _PTRDIFF_T
#define _PTRDIFF_T
typedef long ptrdiff_t;
#endif

#ifndef _SIZE_T
#define _SIZE_T
typedef unsigned long size_t;
#endif

#undef NULL /* 取消在此之前的宏定义NULL */
#define NULL ((void *)0)

/* offsetof(TYPE, MEMBER),取MEMBER成员在TYPE类型中的偏移量。
 * 即取成员MEMBER的地址,因基址从0开始,所以此处所获取到的MEMBER
 * 地址即为其在TYPE类型中的偏移。*/
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

#endif
ctype.c
/*
 *  linux/lib/ctype.c
 *
 *  (C) 1991  Linus Torvalds
 */

#include 

char _ctmp;
/* 字符属性判断辅助数组,用于判定字符的属性(类型) */
unsigned char _ctype[] = {0x00,             /* EOF */
_C,_C,_C,_C,_C,_C,_C,_C,                    /* 0-7 */
_C,_C|_S,_C|_S,_C|_S,_C|_S,_C|_S,_C,_C,     /* 8-15 */
_C,_C,_C,_C,_C,_C,_C,_C,                    /* 16-23 */
_C,_C,_C,_C,_C,_C,_C,_C,                    /* 24-31 */
_S|_SP,_P,_P,_P,_P,_P,_P,_P,                /* 32-39 */
_P,_P,_P,_P,_P,_P,_P,_P,                    /* 40-47 */
_D,_D,_D,_D,_D,_D,_D,_D,                    /* 48-55 */
_D,_D,_P,_P,_P,_P,_P,_P,                    /* 56-63 */
_P,_U|_X,_U|_X,_U|_X,_U|_X,_U|_X,_U|_X,_U,  /* 64-71 */
_U,_U,_U,_U,_U,_U,_U,_U,                    /* 72-79 */
_U,_U,_U,_U,_U,_U,_U,_U,                    /* 80-87 */
_U,_U,_U,_P,_P,_P,_P,_P,                    /* 88-95 */
_P,_L|_X,_L|_X,_L|_X,_L|_X,_L|_X,_L|_X,_L,  /* 96-103 */
_L,_L,_L,_L,_L,_L,_L,_L,                    /* 104-111 */
_L,_L,_L,_L,_L,_L,_L,_L,                    /* 112-119 */
_L,_L,_L,_P,_P,_P,_P,_C,                    /* 120-127 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,            /* 128-143 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,            /* 144-159 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,            /* 160-175 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,            /* 176-191 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,            /* 192-207 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,            /* 208-223 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,            /* 224-239 */
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};           /* 240-255 */
ctype.h
#ifndef _CTYPE_H
#define _CTYPE_H

/* 字符类型标识 */
#define _U  0x01    /* upper,标识大写字母['A','Z'] */
#define _L  0x02    /* lower,标识小写字母['a','z'] */
#define _D  0x04    /* digit,标识数字字符['0','9'] */
#define _C  0x08    /* cntrl,标识所定义的控制字符 */
#define _P  0x10    /* punct,标识标点字符 */
#define _S  0x20    /* white space (space/lf/tab),标识空白字符 */
#define _X  0x40    /* hex digit,标识16进制数字 */
#define _SP 0x80    /* hard space (0x20),标识空格字符 */

/* 声明ctype.c中定义的字符属性数组和字符变量 */
extern unsigned char _ctype[];
extern char _ctmp;

/* 判断字符类型的宏,宏值为1时表明c为宏名所标识类型 */
#define isalnum(c) ((_ctype+1)[c]&(_U|_L|_D))   /* c为小整型(字符也是整型) */
#define isalpha(c) ((_ctype+1)[c]&(_U|_L))       /* c为字符 */
#define iscntrl(c) ((_ctype+1)[c]&(_C))          /* c为控制字符 */
#define isdigit(c) ((_ctype+1)[c]&(_D))          /* c为数字 */
#define isgraph(c) ((_ctype+1)[c]&(_P|_U|_L|_D)) /* c为可显示图形字符 */
#define islower(c) ((_ctype+1)[c]&(_L))           /* c为小写字符 */
#define isprint(c) ((_ctype+1)[c]&(_P|_U|_L|_D|_SP)) /* c为可打印字符 */
#define ispunct(c) ((_ctype+1)[c]&(_P)) /* c为标点字符 */
#define isspace(c) ((_ctype+1)[c]&(_S)) /* c为空格 */
#define isupper(c) ((_ctype+1)[c]&(_U)) /* c为大写字符 */
#define isxdigit(c) ((_ctype+1)[c]&(_D|_X)) /* c为十六进制数 */

/* c为ASCII,将c转换为ASCII */
#define isascii(c) (((unsigned) c)<=0x7f)
#define toascii(c) (((unsigned) c)&0x7f)

/* 将c转换为小/大写字母,该宏在多线程中使用会引起对_ctmp的冲突访问 */
#define tolower(c) (_ctmp=c,isupper(_ctmp)?_ctmp-('A'-'a'):_ctmp)
#define toupper(c) (_ctmp=c,islower(_ctmp)?_ctmp-('a'-'A'):_ctmp)

#endif
const.h
#ifndef _CONST_H
#define _CONST_H

#define BUFFER_END 0x200000

/* struct m_inode.i_mode字段属性标识 */
#define I_TYPE          0170000 /* 类型许可码 */
#define I_DIRECTORY     0040000 /* 目录 */
#define I_REGULAR       0100000 /* 常规文件 */
#define I_BLOCK_SPECIAL 0060000 /* 块设备特殊文件 */
#define I_CHAR_SPECIAL  0020000 /* 字符设备特殊文件 */
#define I_NAMED_PIPE    0010000 /* 命名管道 */
/* execve()对应的内核函数有涉及以下两个概念 */
#define I_SET_UID_BIT   0004000 /* 执行时设置有效用户ID */
#define I_SET_GID_BIT   0002000 /* 执行时设置有效组ID */

#endif
types.h
#ifndef _SYS_TYPES_H
#define _SYS_TYPES_H

/* 由typedef和数据类型定义一些具命名含义的类型,如若要定义
 * 跟时间相关的数据,则可统一使用time_t,统一使用time_t比统
 一使用long更好记一点,阅读性也可能会高一点。*/
#ifndef _SIZE_T
#define _SIZE_T
typedef unsigned int size_t;
#endif

#ifndef _TIME_T
#define _TIME_T
typedef long time_t;
#endif

#ifndef _PTRDIFF_T
#define _PTRDIFF_T
typedef long ptrdiff_t;
#endif

#ifndef NULL
#define NULL ((void *) 0)
#endif

typedef int pid_t;
typedef unsigned short uid_t;
typedef unsigned char gid_t;
typedef unsigned short dev_t;
typedef unsigned short ino_t;
typedef unsigned short mode_t;
typedef unsigned short umode_t;
typedef unsigned char nlink_t;
typedef int daddr_t;
typedef long off_t;
typedef unsigned char u_char;
typedef unsigned short ushort;

typedef struct { int quot,rem; } div_t;
typedef struct { long quot,rem; } ldiv_t;

/* 用于ustat()参数的结构体类型,ustat()暂未实现 */
struct ustat {
    daddr_t f_tfree;
    ino_t f_tinode;
    char f_fname[6];
    char f_fpack[6];
};

#endif
wait.c
/*
 *  linux/lib/wait.c
 *
 *  (C) 1991  Linus Torvalds
 */

#define __LIBRARY__
#include 
#include 

/* 该宏所定义的函数原型为
 * pid_t waitpid(pid_t pid, int * wait_stat, int options);
 * waitpid系统调用对应的内核函数为 sys_waitpid(...) */
_syscall3(pid_t,waitpid,pid_t,pid,int *,wait_stat,int,options)

pid_t wait(int * wait_stat)
{
    return waitpid(-1,wait_stat,0);
}
wait.h
#ifndef _SYS_WAIT_H
#define _SYS_WAIT_H

#include 

/* 取v低字节和高字节(0377为8进制) */
#define _LOW(v)     ( (v) & 0377)
#define _HIGH(v)    ( ((v) >> 8) & 0377)

/* options for waitpid, WUNTRACED not supported */
/* waitpid函数参数选项,见sys_waitpid。 */
#define WNOHANG     1
#define WUNTRACED   2

/* 用于判断所等待子进程的状态(内核未用,与sys_waitpid()返回值也不全匹配) */
#define WIFEXITED(s)    (!((s)&0xFF) /**/
#define WIFSTOPPED(s)   (((s)&0xFF)==0x7F)
#define WEXITSTATUS(s)  (((s)>>8)&0xFF)
#define WTERMSIG(s)     ((s)&0x7F)
#define WSTOPSIG(s)     (((s)>>8)&0xFF)
#define WIFSIGNALED(s)  (((unsigned int)(s)-1 & 0xFFFF) < 0xFF)

pid_t wait(int *stat_loc);
pid_t waitpid(pid_t pid, int *stat_loc, int options);

#endif
config.h
#ifndef _CONFIG_H
#define _CONFIG_H

/*
 * The root-device is no longer hard-coded. You can change the default
 * root-device by changing the line ROOT_DEV = XXX in boot/bootsect.s
 */
/* 根文件系统设备没有再写死在代码中。可以通过boot/bootsect.s中的ROOT_DEV设
 * 定根文件系统设备。*/

/*
 * define your keyboard here -
 * KBD_FINNISH for Finnish keyboards
 * KBD_US for US-type
 * KBD_GR for German keyboards
 * KBD_FR for Frech keyboard
 */
/* 该宏将会表征你使用什么类型的键盘
 * KBD_FINNISH - 芬兰键盘
 * KBD_US - 美式键盘
 * KBD_GR - 德式键盘
 * KBD_FR - 法式键盘。*/
/*#define KBD_US */
/*#define KBD_GR */
/*#define KBD_FR */
#define KBD_FINNISH

/*
 * Normally, Linux can get the drive parameters from the BIOS at
 * startup, but if this for some unfathomable reason fails, you'd
 * be left stranded. For this case, you can define HD_TYPE, which
 * contains all necessary info on your harddisk.
 *
 * 正常情况下,Linux在启动阶段(见setup.s)会通过BIOS获取硬盘参数,但
 * 若获取失败的话可在此处自定义满足你本地硬盘的参数。
 * 
 * The HD_TYPE macro should look like this:
 * 定义HD_TYPE宏的格式为(见hd.c),
 *
 * #define HD_TYPE { head, sect, cyl, wpcom, lzone, ctl}
 *
 * In case of two harddisks, the info should be sepatated by
 * commas:
 * 类似的,定义包含两个硬盘参数的宏为,
 * 
 * #define HD_TYPE { h,s,c,wpcom,lz,ctl },{ h,s,c,wpcom,lz,ctl }
 */
/*
 This is an example, two drives, first is type 2, second is type 3:

#define HD_TYPE { 4,17,615,300,615,8 }, { 6,17,615,300,615,0 }

 NOTE: ctl is 0 for all drives with heads<=8, and ctl=8 for drives
 with more than 8 heads.

 If you want the BIOS to tell what kind of drive you have, just
 leave HD_TYPE undefined. This is the normal thing to do.

 举一个定义两个硬盘参数的例子,第2个硬盘的类型为2,第2个为3

 #define HD_TYPE { 4,17,615,300,615,8 }, { 6,17,615,300,615,0 }

 注,磁头数小于8时ctl字段为0,磁头数大于8时ctl字段为8。

 若你想通过BIOS自动检测当前计算机所携带的硬盘,可以将这部分代码注释掉,
 用BIOS自动检测硬盘才是更合理的方法,这只是个备用方法。
*/

#endif
fdreg.h
/*
 * This file contains some defines for the floppy disk controller.
 * Various sources. Mostly "IBM Microcomputers: A Programmers
 * Handbook", Sanches and Canton.
 */
/* 本文件包含了关于软盘控制器的定义,此文参考了很多资料。
 * 参考最多的资料是 "IBM 迷你电脑:程序员手册", Sanches, Canton。*/
#ifndef _FDREG_H
#define _FDREG_H

extern int ticks_to_floppy_on(unsigned int nr);
extern void floppy_on(unsigned int nr);
extern void floppy_off(unsigned int nr);
extern void floppy_select(unsigned int nr);
extern void floppy_deselect(unsigned int nr);

/* Fd controller regs. S&C, about page 340 */
/* 软盘控制器寄存器,约在340页 */
#define FD_STATUS   0x3f4
#define FD_DATA     0x3f5
#define FD_DOR      0x3f2 /* Digital Output Register */
#define FD_DIR      0x3f7 /* Digital Input Register (read) */
#define FD_DCR      0x3f7 /* Diskette Control Register (write)*/

/* Bits of main status register */
#define STATUS_BUSYMASK 0x0F /* drive busy mask */
#define STATUS_BUSY  0x10    /* FDC busy */
#define STATUS_DMA   0x20    /* 0- DMA mode */
#define STATUS_DIR   0x40    /* 0- cpu->fdc */
#define STATUS_READY 0x80    /* Data reg ready */

/* Bits of FD_ST0 */
#define ST0_DS   0x03 /* drive select mask */
#define ST0_HA   0x04 /* Head (Address) */
#define ST0_NR   0x08 /* Not Ready */
#define ST0_ECE  0x10 /* Equipment chech error */
#define ST0_SE   0x20 /* Seek end */
#define ST0_INTR 0xC0 /* Interrupt code mask */

/* Bits of FD_ST1 */
#define ST1_MAM 0x01 /* Missing Address Mark */
#define ST1_WP  0x02 /* Write Protect */
#define ST1_ND  0x04 /* No Data - unreadable */
#define ST1_OR  0x10 /* OverRun */
#define ST1_CRC 0x20 /* CRC error in data or addr */
#define ST1_EOC 0x80 /* End Of Cylinder */

/* Bits of FD_ST2 */
#define ST2_MAM 0x01 /* Missing Addess Mark (again) */
#define ST2_BC  0x02 /* Bad Cylinder */
#define ST2_SNS 0x04 /* Scan Not Satisfied */
#define ST2_SEH 0x08 /* Scan Equal Hit */
#define ST2_WC  0x10 /* Wrong Cylinder */
#define ST2_CRC 0x20 /* CRC error in data field */
#define ST2_CM  0x40 /* Control Mark = deleted */

/* Bits of FD_ST3 */
#define ST3_HA 0x04 /* Head (Address) */
#define ST3_TZ 0x10 /* Track Zero signal (1=track 0) */
#define ST3_WP 0x40 /* Write Protect */

/* Values for FD_COMMAND */
#define FD_RECALIBRATE 0x07 /* move to track 0 */
#define FD_SEEK        0x0F /* seek track */
#define FD_READ        0xE6 /* read with MT, MFM, SKip deleted */
#define FD_WRITE       0xC5 /* write with MT, MFM */
#define FD_SENSEI      0x08 /* Sense Interrupt Status */
#define FD_SPECIFY     0x03 /* specify HUT etc */

/* DMA commands */
#define DMA_READ  0x46
#define DMA_WRITE 0x4A

#endif
kernel.h
/*
 * 'kernel.h' contains some often-used function prototypes etc
 */
/* kernel.h包含一些常用的函数原型等内容。*/
void verify_area(void * addr,int count);
volatile void panic(const char * str);
int printf(const char * fmt, ...);
int printk(const char * fmt, ...);
int tty_write(unsigned ch,char * buf,int count);
void * malloc(unsigned int size);
void free_s(void * obj, int size);

#define free(x) free_s((x), 0)

/*
 * This is defined as a macro, but at some point this might become a
 * real subroutine that sets a flag if it returns true (to do
 * BSD-style accounting where the process is flagged if it uses root
 * privs).  The implication of this is that you should do normal
 * permissions checks first, and check suser() last.
 */
/* 在使用该宏时,应该先进行一些普通权限检查最后再调用该宏,以防该宏返回
 * 真时导致一些设置而影响普通权限的检查。*/
#define suser() (current->euid == 0)
write.c
/*
 *  linux/lib/write.c
 *
 *  (C) 1991  Linus Torvalds
 */

#define __LIBRARY__
#include 

/* 系统调用write,其原型为
 * int write(int fd, const char *buf, off_t count),
 * 系统调用write对应的内核函数为 sys_write(...) */
_syscall3(int,write,int,fd,const char *,buf,off_t,count)

dup.c
/*
 *  linux/lib/dup.c
 *
 *  (C) 1991  Linus Torvalds
 */

#define __LIBRARY__
#include 

/* 系统调用dup原型
 * int dup(int fd),
 * 其对应内核函数为 sys_dup() */
_syscall1(int,dup,int,fd)

setsid.c
/*
 *  linux/lib/setsid.c
 *
 *  (C) 1991  Linus Torvalds
 */

#define __LIBRARY__
#include 

/* 系统调用setsid原型为
 * pid_t setsid(void);
 * 其对应的内核函数为 sys_setsid() */
_syscall0(pid_t,setsid)
printk.c
/*
 *  linux/kernel/printk.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * When in kernel-mode, we cannot use printf, as fs is liable to
 * point to 'interesting' things. Make a printf with fs-saving, and
 * all is well.
 */
/* 在内核模式中不能使用printf打印函数,由于fs寄存器可用来指向想要指向
 * 的内存段,所以此文将编写一个printk函数, 该函数除了让fs指向内核数据
 * 段以及所调用显示函数有所不同外,其余与printf并没有什么太大不同。*/
#include 
#include 

#include 

static char buf[1024];

extern int vsprintf(char * buf, const char * fmt, va_list args);

/* printk,
 * */
int printk(const char *fmt, ...)
{
    va_list args;
    int i;

    va_start(args, fmt);
    i=vsprintf(buf,fmt,args);
    va_end(args);

    /* 用户模式下的printf通过系统调用write将将指定字符串写往标准
     * 输出设备;内核模式中的printk调用tty_write显示buf中的内容。
     * write作为系统调用接口,最终会调用tty_write函数。
     *
     * 除此之外,在调用tty_write时fs寄存器已指向内核数据段,在调用
     * 完成后恢复。*/
    __asm__("push %%fs\n\t"
        "push %%ds\n\t"
        "pop %%fs\n\t"
        /* tty_write参数,
         * channel=0(对应终端),buf=buf,count=i.*/
        "pushl %0\n\t"
        "pushl $_buf\n\t"
        "pushl $0\n\t"
        "call _tty_write\n\t"
        "addl $8,%%esp\n\t"
        "popl %0\n\t"
        "pop %%fs"
        ::"r" (i):"ax","cx","dx");
    return i;
}
panic.c
/*
 *  linux/kernel/panic.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * This function is used through-out the kernel (includeinh mm and fs)
 * to indicate a major problem.
 */
/* panic()用于提示不可恢复的错误,其贯穿整个内核(包括内存管理模块mm和文件管理模块fs)。*/
#include 
#include 

/* sys_sync函数原型为 int sys_sync(void),
 * 由于这里不需要使用其返回值,所以将其声明为无返回值类型。*/
void sys_sync(void);    /* it's really int */

/* panic,
 * 往终端输出s所指的错误提示,同步当前进程资源如文件
 * 系统i节点,缓冲区块中的内容到外设,然后进入死循环。*/
volatile void panic(const char * s)
{
    printk("Kernel panic: %s\n\r",s);
    if (current == task[0])
        printk("In swapper task - not syncing\n\r");
    else
        sys_sync();
    for(;;);
}
math_emulate.c
/*
 * linux/kernel/math/math_emulate.c
 *
 * (C) 1991 Linus Torvalds
 */

/*
 * This directory should contain the math-emulation code.
 * Currently only results in a signal.
 */
/* 该目录(math)下应该包含模拟数学运算的代码,当前只有产生信号的代码。*/
#include 

#include 
#include 
#include 

/* math_emulate,
 * 数学协处理器模拟器程序。
 * 
 * 当协处理器不存在触发IDT[7]中断时由中断入口
 * 处理程序_device_not_available调用该函数。*/
void math_emulate(long edi, long esi, long ebp, long sys_call_ret,
    long eax,long ebx,long ecx,long edx,
    unsigned short fs,unsigned short es,unsigned short ds,
    unsigned long eip,unsigned short cs,unsigned long eflags,
    unsigned short ss, unsigned long esp)
{
    unsigned char first, second;

/* 0x0007 means user code space */
    /* 结合段选择符的位格式判断该语句含义
     * |15              3| 2|  0|
     * --------------------------
     * |      index      |TI|RPL|
     * --------------------------
     * 0xf表示选择用户进程ldt[1]即用户进程代码段
     * 若cs不为用户进程代码段选择符则表明cs指向内核代码段,
     * 如此则进行错误提示并死机。*/
    if (cs != 0x000F) {
        printk("math_emulate: %04x:%08x\n\r",cs,eip);
        panic("Math emulation needed in kernel");
    }

    /* 取用户进程触发IDT[7]中断的机器指令以提示触发该中
     * 断的指令,然后为当前进程设置协处理器出错的信号。*/
    first = get_fs_byte((char *)((*&eip)++));
    second = get_fs_byte((char *)((*&eip)++));
    printk("%04x:%08x %02x %02x\n\r",cs,eip-2,first,second);
    current->signal |= 1<<(SIGFPE-1);
}

/* math_error,
 * 协处理器出错中断C处理函数。
 * 该函数由_coprocessor_error调用。*/
void math_error(void)
{
    /* 清协处理器出错标志状态 */
    __asm__("fnclex");

    /* 向正使用协处理器的进程发送协处理器出错信号 */
    if (last_task_used_math)
        last_task_used_math->signal |= 1<<(SIGFPE-1);
}

你可能感兴趣的:(都市)