linux内核初始化及启动之start_kernel

Linux的 源代码 可以从www.kernel.org得到,或者你可以查看linux代码交叉引用网站:http://lxr.linux.no/ 进行在线的代码查看,这是一个很好的工具网站。

  在start_kernel中将调用到大量的init函数,来完成内核的各种初始化。如:

page_address_init();
sched_init();
page_alloc_init();
init_IRQ();
softirq_init();
console_init();
calibrate_delay();
vfs_caches_init(num_physpages);
rest_init();

  具体内容可以参考[http://lxr.linux.no/source/init/main. c ]

Linux version 2.4.22-uc0 (root@local) (gcc version 2.95.3 20010315 (release)) #33 .?1.. 20 12:09:106

  上面的代码输出信息,是跟踪linux代码分析后得到的,进入init目录下的main.c的start_kernel启动函数.

  嵌入式linux使用的是linux内核版本为2.4.22

  linux source code代码中start_kernel中输出的linux_banner信息。这个信息是每个 linux kernel 都会打印一下的信息,如果你没有把这句去掉的话。 

Found bootloader memory map at 0x10000fc0.

  bootloader经过 内存 映射后的地址为:0x10000fc0, 按上面的地址换算方法,1后面有7个0,那么虚拟地址256M左右处。

Processor: ARM pt110 revision 0

  pT110是ARM微处理器arm核的一种,另一种为pT100。此处为显示ARM的类型。

On node 0 totalpages: 20480
zone(0): 20480 pages.
zone(0): Set minimum memory threshold to 12288KB
Warning: wrong zone alignment (0x90080000, 0x0000000c, 0x00001000)
zone(1): 0 pages.
zone(2): 0 pages.

  预留内存大小,在节点0上总共20页, zone(0) 设置最小内存为12MB, zone(1)和zone(2)为0页。警告:对齐不正确

Kernel command line: root=/dev/mtdblock3

  Kernel 启动命令设为:/dev/mtdblock3(在后面的说明中会看到mtdblock3是指的 flash 上的romfs分区。),用来指定根文件系统所在的位置,kernel会将块设备mtdblock3当作文件系统来处理。

  也就是说,内核会根据上面的kernel命令行,知道只读文件系统romfs将是根文件系统rootfs。

  start_kernel(void)中输出的上面的这句信息。

  这行命令是在linux内核启动过程中都会输出的一句。

Console: colour dummy device 80x30

  代码中console_init()的输出信息, 显示控制台属性:一般使用VGA text console,标准是80 X 25行列的文本控制台,这里是对属性进行了设置。

serial_xx: setup_console @ 115

  串口设置值为115200,此为波特率输出信息。对串口设置的信息做一个打印的动作,在调试时会非常有用。

Calibrating delay loop... 82.94 BogoMIPS

  Calibrate:校准, 进入时延校准循环。检查 CPU 的MIPS(每秒百万条指令),Bogo是Bogus(伪)的意思。这里是对CPU进行一个实时 测试 ,来得到一个大体的MIPS数值

  Bogomips,是由linus Torvalds写的, 是Linux 操作系统 中衡量计算机处理器运行速度的一种尺度。提供这种度量的程序被称为BogoMips,当启动计算机时,BogoMips能显示系统选项是否处于最佳性能。

  linux内核中有一个函数calibrate_delay(),它可以计算出cpu在一秒钟内执行了多少次一个极短的循环,计算出来的值经过处理后得到BogoMIPS值

  你可以将计算机的bogomips与计算机处理器的bogomips进行比较。Torvalds称这个程序为BogoMips来暗示两台计算机间的性能度量是错误的,因为并非所有起作用因素都能被显示出来或被认可。尽管计算机基准中经常用到MIPS,但环境的变化容易导致度量的错误。Bogomips能测出一秒钟内某程序运行了多少次。

  察看/proc/cpuinfo文件中的最后一行也能得到这个数值。

  上面这个输出,在所有的linux系统启动中都会打印出来。

  进入内存初始化

mem_init(void), [arch/i386/mm/init.c] 
Memory: 80MB = 80MB total
Memory: 76592KB available (1724K code, 2565K data, 72K init)

  当前内存使用情况,将列出总的内存大小, 及分配给内核的内存大小:包括代码部分,数据部分,初始化部分,总共刚好4M。请留意此处的内核的内存大小的各个值。

  进入虚拟文件系统VFS初始化

vfs_caches_init()
Dentry cache hash table entries: 16384 (order: 5, 131072 bytes)
Inode cache hash table entries: 8192 (order: 4, 65536 bytes)
Mount cache hash table entries: 512 (order: 0, 4096 bytes)
Buffer cache hash table entries: 4096 (order: 2, 16384 bytes)
Page-cache hash table entries: 32768 (order: 5, 131072 bytes)

  名词: 

  ① Dentry:目录数据结构

  ② Inode:i节点

  ③ Mount cache:文件系统加载缓冲

  ④ buffer cache:内存缓冲区

  ⑤ Page Cache:页缓冲区

  Dentry目录数据结构(目录入口缓存),提供了一个将路径名转化为特定的dentry的一个快的查找机制,Dentry只存在于RAM中;
i节点(inode)数据结构存放磁盘上的一个文件或目录的信息,i节点存在于磁盘驱动器上;存在于RAM中的i节点就是VFS的i节点,dentry所包含的指针指向的就是它;

  buffer cache内存缓冲区,类似kupdated,用来在内存与磁盘间做缓冲处理;

  Page Cache 用来加快对磁盘上映像和数据的访问。

  在内存中建立各个缓冲hash表,为kernel对文件系统的访问做准备。

  VFS(virtual filesystem  switch )虚拟文件切换目录树有用到类似这样的结构表。

  上面的输出信息,在一般的linux启动过程中都会看到。

  POSIX conformance testing by UNIFIX

  conformance:顺应, 一致。即POSIX适应性检测。UNIFIX是一家德国的技术公司,Linux 原本要基于 POSIX.1 的, 但是 POSIX 不是免费的, 而且 POSIX.1 证书相当昂贵. 这使得 Linux 基于 POSIX 开发相当困难. Unifix公司(Braunschweig, 德国) 开发了一个获得了 FIPS 151-2 证书的 Linux 系统. 这种技术用于 Unifix 的发行版 Unifix Linux 2.0 和 Lasermoon 的 Linux-FT。

  在2.6的内核中就将上面的这句输出给拿掉了。










结合:

当PC启动时,Intel系列的CPU首先进入的是实模式,并开始执行位于地址0xFFFF0处的代码,也就是ROM-BIOS起始位置的代码。BIOS先进行一系列的系统自检,然后初始化位于地址0的中断向量表。最后BIOS将启动盘的第一个扇区装入到0x7C00,并开始执行此处的代码.这就是对内核初始化过程的一个最简单的描述。

     最初,Linux核心的最开始部分是用8086汇编语言编写的。当开始运行时,核心将自己装入到绝对地址0x90000,再将其后的2k字节装入到地址0x90200处,最后将核心的其余部分装入到0x10000。

     当系统装入时,会显示Loading...信息。装入完成后,控制转向另一个实模式下的汇编语言代码boot/Setup.S。Setup部分首先设置一些系统的硬件设备,然后将核心从0x10000处移至0x1000处。这时系统转入保护模式,开始执行位于0x1000处的代码。

     接下来是内核的解压缩。0x1000处的代码来自于文件Boot/head.S,它用来初始化寄存器和调用decompress_kernel( )程序。decompress_kernel( )程序由Boot/inflate.c, Boot/unzip.c 和Boot/misc.c组成。解压缩后的数据被装入到了0x100000处,这也是Linux不能在内存小于2M的环境下运行的主要原因。

     解压后的代码在0x1010000处开始执行,紧接着所有的32位的设置都将完成: IDT、GDT和LDT将被装入,处理器初始化完毕,设置好内存页面,最终调用start_kernel过程。这大概是整个内核中最为复杂的部分。

    [系统开始运行]

     Linux kernel 最早的C代码从汇编标记startup_32开始执行

    |startup_32:

     |start_kernel

     |lock_kernel

     |trap_init

     |init_IRQ

     |sched_init

     |softirq_init

     |time_init

     |console_init

     |#ifdef CONFIG_MODULES

     |init_modules

     |#endif

     |kmem_cache_init

     |sti

     |calibrate_delay

     |mem_init

     |kmem_cache_sizes_init

     |pgtable_cache_init

     |fork_init

     |proc_caches_init

     |vfs_caches_init

     |buffer_init

     |page_cache_init

     |signals_init

     |#ifdef CONFIG_PROC_FS

     |proc_root_init

     |#endif

     |#if defined(CONFIG_SYSVIPC)

     |ipc_init

     |#endif

     |check_bugs

     |smp_init

     |rest_init

     |kernel_thread

     |unlock_kernel

     |cpu_idle

    ·startup_32 [arch/i386/kernel/head.S]

    ·start_kernel [init/main.c]

    ·lock_kernel [include/asm/smplock.h]

    ·trap_init [arch/i386/kernel/traps.c]

    ·init_IRQ [arch/i386/kernel/i8259.c]

    ·sched_init [kernel/sched.c]

    ·softirq_init [kernel/softirq.c]

    ·time_init [arch/i386/kernel/time.c]

    ·console_init [drivers/char/tty_io.c]

    ·init_modules [kernel/module.c]

    ·kmem_cache_init [mm/slab.c]

    ·sti [include/asm/system.h]

    ·calibrate_delay [init/main.c]

    ·mem_init [arch/i386/mm/init.c]

    ·kmem_cache_sizes_init [mm/slab.c]

    ·pgtable_cache_init [arch/i386/mm/init.c]

    ·fork_init [kernel/fork.c]

    ·proc_caches_init

    ·vfs_caches_init [fs/dcache.c]

    ·buffer_init [fs/buffer.c]

    ·page_cache_init [mm/filemap.c]

    ·signals_init [kernel/signal.c]

    ·proc_root_init [fs/proc/root.c]

    ·ipc_init [ipc/util.c]

    ·check_bugs [include/asm/bugs.h]

    ·smp_init [init/main.c]

    ·rest_init

    ·kernel_thread [arch/i386/kernel/process.c]

    ·unlock_kernel [include/asm/smplock.h]

    ·cpu_idle [arch/i386/kernel/process.c]

     start_kernel( )程序用于初始化系统内核的各个部分,包括:

     *设置内存边界,调用paging_init( )初始化内存页面。

     *初始化陷阱,中断通道和调度。

     *对命令行进行语法分析。

     *初始化设备驱动程序和磁盘缓冲区。

     *校对延迟循环。

    最后的function'rest_init' 作了以下工作:

     ·开辟内核线程'init'

     ·调用unlock_kernel

     ·建立内核运行的cpu_idle环, 如果没有调度,就一直死循环

    实际上start_kernel永远不能终止.它会无穷地循环执行cpu_idle.

     最后,系统核心转向move_to_user_mode( ),以便创建初始化进程(init)。此后,进程0开始进入无限循环。

     初始化进程开始执行/etc/init、/bin/init 或/sbin /init中的一个之后,系统内核就不再对程序进行直接控制了。之后系统内核的作用主要是给进程提供系统调用,以及提供异步中断事件的处理。多任务机制已经建立起来,并开始处理多个用户的登录和fork( )创建的进程。

    [init]

     init是第一个进程,或者说内核线程

    |init

     |lock_kernel

     |do_basic_setup

     |mtrr_init

     |sysctl_init

     |pci_init

     |sock_init

     |start_context_thread

     |do_init_calls

     |(*call())->; kswapd_init

     |prepare_namespace

     |free_initmem

     |unlock_kernel

     |execve

    [目录]

    --------------------------------------------------------------------------------

    启动步骤

    系统引导:

    涉及的文件

    ./arch/$ARCH/boot/bootsect.s

    ./arch/$ARCH/boot/setup.s

    bootsect.S

     这个程序是linux kernel的第一个程序,包括了linux自己的bootstrap程序,

    但是在说明这个程序前,必须先说明一般IBM PC开机时的动作(此处的开机是指

    "打开PC的电源"

    

    :

      一般PC在电源一开时,是由内存中地址FFFF:0000开始执行(这个地址一定

    在ROM BIOS中,ROM BIOS一般是在FEOOOh到FFFFFh中),而此处的内容则是一个

    jump指令,jump到另一个位於ROM BIOS中的位置,开始执行一系列的动作,包

    括了检查RAM,keyboard,显示器,软硬磁盘等等,这些动作是由系统测试代码

    (system test code)来执行的,随着制作BIOS厂商的不同而会有些许差异,但都

    是大同小异,读者可自行观察自家机器开机时,萤幕上所显示的检查讯息。

      紧接着系统测试码之后,控制权会转移给ROM中的启动程序

    (ROM bootstrap routine),这个程序会将磁盘上的第零轨第零扇区读入

    内存中(这就是一般所谓的boot sector,如果你曾接触过电脑病

    毒,就大概听过它的大名),至於被读到内存的哪里呢? --绝对

    位置07C0:0000(即07C00h处),这是IBM系列PC的特性。而位在linux开机

    磁盘的boot sector上的正是linux的bootsect程序,也就是说,bootsect是

    第一个被读入内存中并执行的程序。现在,我们可以开始来

    看看到底bootsect做了什么。

    第一步

     首先,bootsect将它"自己"从被ROM BIOS载入的绝对地址0x7C00处搬到

    0x90000处,然后利用一个jmpi(jump indirectly)的指令,跳到新位置的

    jmpi的下一行去执行,

    第二步

     接着,将其他segment registers包括DS,ES,SS都指向0x9000这个位置,

    与CS看齐。另外将SP及DX指向一任意位移地址( offset ),这个地址等一下

    会用来存放磁盘参数表(disk para- meter table )

    第三步

     接着利用BIOS中断服务int 13h的第0号功能,重置磁盘控制器,使得刚才

    的设定发挥功能。

    第四步

     完成重置磁盘控制器之后,bootsect就从磁盘上读入紧邻着bootsect的setup

    程序,也就是setup.S,此读入动作是利用BIOS中断服务int 13h的第2号功能。

    setup的image将会读入至程序所指定的内存绝对地址0x90200处,也就是在内存

    中紧邻着bootsect 所在的位置。待setup的image读入内存后,利用BIOS中断服

    务int 13h的第8号功能读取目前磁盘的参数。

    第五步

     再来,就要读入真正linux的kernel了,也就是你可以在linux的根目录下看

    到的"vmlinuz" 。在读入前,将会先呼叫BIOS中断服务int 10h 的第3号功能,

    读取游标位置,之后再呼叫BIOS 中断服务int 10h的第13h号功能,在萤幕上输

    出字串"Loading",这个字串在boot linux时都会首先被看到,相信大家应该觉

    得很眼熟吧。

    第六步

     接下来做的事是检查root device,之后就仿照一开始的方法,利用indirect

    jump 跳至刚刚已读入的setup部份

    第七步

     setup.S完成在实模式下版本检查,并将硬盘,鼠标,内存参数写入到 INITSEG

    中,并负责进入保护模式。

    第八步

     操作系统的初始化。

    [目录]

    --------------------------------------------------------------------------------

    bootsect.S

    1.将自己移动到0x9000:0x0000处,为内核调入留出地址空间;

    2.建立运行环境(ss=ds=es=cs=0x9000, sp=0x4000-12),保证起动程序运行;

    3.BIOS初始化0x1E号中断为软盘参数表,将它取来保存备用;

    4.将setup读到0x9000:0x0200处;

    5.测试软盘参数一个磁道有多少个扇区(也没有什么好办法,只能试试36, 18, 15, 9对不对了);

    6.打印“Loading”;

    7.读入内核到0x1000:0000(如果是bzImage, 则将每个64K移动到0x100000处,在实模式下,只能调用0x15号中断了,这段代码无法放在bootsect中所以只能放在setup中,幸好此时setup已经读入了);

    8.到setup去吧

    发发信人: seis (矛), 信区: Linux

    标 题: Linux操作系统内核引导程序详细剖析

    发信站: BBS 水木清华站 (Fri Feb 2 14:12:43 2001)

    ! bootsect.s (c) 1991, 1992 Linus Torvalds 版权所有

    ! Drew Eckhardt修改过

    ! Bruce Evans (bde)修改过

    !

    ! bootsect.s 被bios-启动子程序加载至0x7c00 (31k)处,并将自己

    ! 移到了地址0x90000 (576k)处,并跳转至那里。

    !

    ! bde - 不能盲目地跳转,有些系统可能只有512k的低

    ! 内存。使用中断0x12来获得(系统的)最高内存、等。

    !

    ! 它然后使用BIOS中断将setup直接加载到自己的后面(0x90200)(576.5k),

    ! 并将系统加载到地址0x10000处。

    !

    ! 注意! 目前的内核系统最大长度限制为(8*65536-4096)(508k)字节长,即使是在

    ! 将来这也是没有问题的。我想让它保持简单明了。这样508k的最大内核长度应该

    ! 是足够了,尤其是这里没有象minix中一样包含缓冲区高速缓冲(而且尤其是现在

    ! 内核是压缩的

    

    !

    ! 加载程序已经做的尽量地简单了,所以持续的读出错将导致死循环。只能手工重启。

    ! 只要可能,通过一次取得整个磁道,加载过程可以做的很快的。

    #include /* 为取得CONFIG_ROOT_RDONLY参数 */

    !! config.h中(即autoconf.h中)没有CONFIG_ROOT_RDONLY定义!!!?

    #include

    .text

    SETUPSECS = 4 ! 默认的setup程序扇区数(setup-sectors)的默认值;

    BOOTSEG = 0x7C0 ! bootsect的原始地址;

    INITSEG = DEF_INITSEG ! 将bootsect程序移到这个段处(0x9000) - 避开;

    SETUPSEG = DEF_SETUPSEG ! 设置程序(setup)从这里开始(0x9020);

    SYSSEG = DEF_SYSSEG ! 系统加载至0x1000(65536)(64k)段处;

    SYSSIZE = DEF_SYSSIZE ! 系统的大小(0x7F00): 要加载的16字节为一节的数;

    !! 以上4个DEF_参数定义在boot.h中:

    !! DEF_INITSEG 0x9000

    !! DEF_SYSSEG 0x1000

    !! DEF_SETUPSEG 0x9020

    !! DEF_SYSSIZE 0x7F00 (=32512=31.75k)*16=508k

    ! ROOT_DEV & SWAP_DEV 现在是由"build"中编制的;

    ROOT_DEV = 0

    SWAP_DEV = 0

    #ifndef SVGA_MODE

    #define SVGA_MODE ASK_VGA

    #endif

    #ifndef RAMDISK

    #define RAMDISK 0

    #endif

    #ifndef CONFIG_ROOT_RDONLY

    #define CONFIG_ROOT_RDONLY 1

    #endif

    ! ld86 需要一个入口标识符,这和通常的一样;

    .globl _main

    _main:

    #if 0 /* 调试程序的异常分支,除非BIOS古怪(比如老的HP机)否则是无害的 */

    int 3

    #endif

    mov ax,#BOOTSEG !! 将ds段寄存器置为0x7C0;

    mov ds,ax

    mov ax,#INITSEG !! 将es段寄存器置为0x9000;

    mov es,ax

    mov cx,#256 !! 将cx计数器置为256(要移动256个字, 512字节);

    sub si,si !! 源地址 ds:si=0x07C0:0x0000;

    sub di,di !! 目的地址es:di=0x9000:0x0000;

    cld !! 清方向标志;

    rep !! 将这段程序从0x7C0:0(31k)移至0x9000:0(576k)处;

    movsw !! 共256个字(512字节)(0x200长);

    jmpi go,INITSEG !! 间接跳转至移动后的本程序go处;

    ! ax和es现在已经含有INITSEG的值(0x9000);

    go: mov di,#0x4000-12 ! 0x4000(16k)是>;=bootsect + setup 的长度 +

    ! + 堆栈的长度 的任意的值;

    ! 12 是磁盘参数块的大小 es:di=0x94000-12=592k-12;

    ! bde - 将0xff00改成了0x4000以从0x6400处使用调试程序(bde)。如果

    ! 我们检测过最高内存的话就不用担心这事了,还有,我的BIOS可以被配置为将wini驱动

    表

    ! 放在内存高端而不是放在向量表中。老式的堆栈区可能会搞乱驱动表;

    mov ds,ax ! 置ds数据段为0x9000;

    mov ss,ax ! 置堆栈段为0x9000;

    mov sp,di ! 置堆栈指针INITSEG:0x4000-12处;

    /*

    * 许多BIOS的默认磁盘参数表将不能

    * 进行扇区数大于在表中指定

    * 的最大扇区数( - 在某些情况下

    * 这意味着是7个扇区)后面的多扇区的读操作。

    *

    * 由于单个扇区的读操作是很慢的而且当然是没问题的,

    * 我们必须在RAM中(为第一个磁盘)创建新的参数表。

    * 我们将把最大扇区数设置为36 - 我们在一个ED 2.88驱动器上所能

    * 遇到的最大值。

    *

    * 此值太高是没有任何害处的,但是低的话就会有问题了。

    *

    * 段寄存器是这样的: ds=es=ss=cs - INITSEG,(=0X9000)

    * fs = 0, gs没有用到。

    */

    ! 上面执行重复操作(rep)以后,cx为0;

    mov fs,cx !! 置fs段寄存器=0;

    mov bx,#0x78 ! fs:bx是磁盘参数表的地址;

    push ds

    seg fs

    lds si,(bx) ! ds:si是源地址;

    !! 将fs:bx地址所指的指针值放入ds:si中;

    mov cl,#6 ! 拷贝12个字节到0x9000:0x4000-12开始处;

    cld

    push di !! 指针0x9000:0x4000-12处;

    rep

    movsw

    pop di !! di仍指向0x9000:0x4000-12处(参数表开始处);

    pop si !! ds =>; si=INITSEG(=0X9000);

    movb 4(di),*36 ! 修正扇区计数值;

    seg fs

    mov (bx),di !! 修改fs:bx(0000:0x007

    

    处磁盘参数表的地址为0x9000:0x4000-12;

    seg fs

    mov 2(bx),es

    ! 将setup程序所在的扇区(setup-sectors)直接加载到boot块的后面。!! 0x90200开始处

    ;

    ! 注意,es已经设置好了。

    ! 同样经过rep循环后cx为0

    load_setup:

    xor ah,ah ! 复位软驱(FDC);

    xor dl,dl

    int 0x13

    xor dx,dx ! 驱动器0, 磁头0;

    mov cl,#0x02 ! 从扇区2开始,磁道0;

    mov bx,#0x0200 ! 置数据缓冲区地址=es:bx=0x9000:0x200;

    ! 在INITSEG段中,即0x90200处;

    mov ah,#0x02 ! 要调用功能号2(读操作);

    mov al,setup_sects ! 要读入的扇区数SETUPSECS=4;

    ! (假释所有数据都在磁头0、磁道0);

    int 0x13 ! 读操作;

    jnc ok_load_setup ! ok则继续;

    push ax ! 否则显示出错信息。保存ah的值(功能号2);

    call print_nl !! 打印换行;

    mov bp,sp !! bp将作为调用print_hex的参数;

    call print_hex !! 打印bp所指的数据;

    pop ax

    jmp load_setup !! 重试!

    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    !!INT 13 - DISK - READ SECTOR(S) INTO MEMORY

    !! AH = 02h

    !! AL = number of sectors to read (must be nonzero)

    !! CH = low eight bits of cylinder number

    !! CL = sector number 1-63 (bits 0-5)

    !! high two bits of cylinder (bits 6-7, hard disk only)

    !! DH = head number

    !! DL = drive number (bit 7 set for hard disk)

    !! ES:BX ->; data buffer

    !! Return: CF set on error

    !! if AH = 11h (corrected ECC error), AL = burst length

    !! CF clear if successful

    !! AH = status (see #00234)

    !! AL = number of sectors transferred (only valid if CF set for some

    !! BIOSes)

    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

    ok_load_setup:

    ! 取得磁盘驱动器参数,特别是每磁道扇区数(nr of sectors/track);

    #if 0

    ! bde - Phoenix BIOS手册中提到功能0x08只对硬盘起作用。

    ! 但它对于我的一个BIOS(1987 Award)不起作用。

    ! 不检查错误码是致命的错误。

    xor dl,dl

    mov ah,#0x08 ! AH=8用于取得驱动器参数;

    int 0x13

    xor ch,ch

    !!!!!!!!!!!!!!!!!!!!!!!!!!!

    !! INT 13 - DISK - GET DRIVE PARAMETERS (PC,XT286,CONV,PS,ESDI,SCSI)

    !! AH = 08h

    !! DL = drive (bit 7 set for hard disk)

    !!Return: CF set on error

    !! AH = status (07h) (see #00234)

    !! CF clear if successful

    !! AH = 00h

    !! AL = 00h on at least some BIOSes

    !! BL = drive type (AT/PS2 floppies only) (see #00242)

    !! CH = low eight bits of maximum cylinder number

    !! CL = maximum sector number (bits 5-0)

    !! high two bits of maximum cylinder number (bits 7-6)

    !! DH = maximum head number

    !! DL = number of drives

    !! ES

    

    I ->; drive parameter table (floppies only)

    !!!!!!!!!!!!!!!!!!!!!!!!!!!!

    #else

    ! 好象没有BIOS调用可取得扇区数。如果扇区36可以读就推测是36个扇区,

    ! 如果扇区18可读就推测是18个扇区,如果扇区15可读就推测是15个扇区,

    ! 否则推测是9. [36, 18, 15, 9]

    mov si,#disksizes ! ds:si->;要测试扇区数大小的表;

    probe_loop:

    lodsb !! ds:si所指的字节 =>;al, si=si+1;

    cbw ! 扩展为字(word);

    mov sectors, ax ! 第一个值是36,最后一个是9;

    cmp si,#disksizes+4

    jae got_sectors ! 如果所有测试都失败了,就试9;

    xchg ax,cx ! cx = 磁道和扇区(第一次是36=0x0024);

    xor dx,dx ! 驱动器0,磁头0;

    xor bl,bl !! 设置缓冲区es:bx = 0x9000:0x0a00(578.5k);

    mov bh,setup_sects !! setup_sects = 4 (共2k);

    inc bh

    shl bh,#1 ! setup后面的地址(es=cs);

    mov ax,#0x0201 ! 功能2(读),1个扇区;

    int 0x13

    jc probe_loop ! 如果不对,就试用下一个值;

    #endif

    got_sectors:

    ! 恢复es

    mov ax,#INITSEG

    mov es,ax ! es = 0x9000;

    ! 打印一些无用的信息(换行后,显示Loading)

    mov ah,#0x03 ! 读光标位置;

    xor bh,bh

    int 0x10

    mov cx,#9

    mov bx,#0x0007 ! 页0,属性7 (normal);

    mov bp,#msg1

    mov ax,#0x1301 ! 写字符串,移动光标;

    int 0x10

    ! ok, 我们已经显示出了信息,现在

    ! 我们要加载系统了(到0x10000处)(64k处)

    mov ax,#SYSSEG

    mov es,ax ! es=0x01000的段;

    call read_it !! 读system,es为输入参数;

    call kill_motor !! 关闭驱动器马达;

    call print_nl !! 打印回车换行;

    ! 这以后,我们来检查要使用哪个根设备(root-device)。如果已指定了设备(!=0)

    ! 则不做任何事而使用给定的设备。否则的话,使用/dev/fd0H2880 (2,32)或/dev/PS0

    (2,2

    

    ! 或者是/dev/at0 (2,

    

    之一,这取决于我们假设我们知道的扇区数而定。

    !! |__ ps0?? (x,y)--表示主、次设备号?

    seg cs

    mov ax,root_dev

    or ax,ax

    jne root_defined

    seg cs

    mov bx,sectors !! sectors = 每磁道扇区数;

    mov ax,#0x0208 ! /dev/ps0 - 1.2Mb;

    cmp bx,#15

    je root_defined

    mov al,#0x1c ! /dev/PS0 - 1.44Mb !! 0x1C = 28;

    cmp bx,#18

    je root_defined

    mov al,0x20 ! /dev/fd0H2880 - 2.88Mb;

    cmp bx,#36

    je root_defined

    mov al,#0 ! /dev/fd0 - autodetect;

    root_defined:

    seg cs

    mov root_dev,ax !! 其中保存由设备的主、次设备号;

    ! 这以后(所有程序都加载了),我们就跳转至

    ! 被直接加载到boot块后面的setup程序去:

    jmpi 0,SETUPSEG !! 跳转到0x9020:0000(setup程序的开始位置);

    ! 这段程序将系统(system)加载到0x10000(64k)处,

    ! 注意不要跨越64kb边界。我们试图以最快的速度

    ! 来加载,只要可能就整个磁道一起读入。

    !

    ! 输入(in): es - 开始地址段(通常是0x1000)

    !

    sread: .word 0 ! 当前磁道已读的扇区数;

    head: .word 0 ! 当前磁头;

    track: .word 0 ! 当前磁道;

    read_it:

    mov al,setup_sects

    inc al

    mov sread,al !! 当前sread=5;

    mov ax,es !! es=0x1000;

    test ax,#0x0fff !! (ax AND 0x0fff, if ax=0x1000 then zero-flag=1 );

    die: jne die ! es 必须在64kB的边界;

    xor bx,bx ! bx 是段内的开始地址;

    rp_read:

    #ifdef __BIG_KERNEL__

    #define CALL_HIGHLOAD_KLUDGE .word 0x1eff, 0x220 ! 调用 far * bootsect_kludge

    ! 注意: as86不能汇编这;

    CALL_HIGHLOAD_KLUDGE ! 这是在setup.S中的程序;

    #else

    mov ax,es

    sub ax,#SYSSEG ! 当前es段值减system加载时的启始段值(0x1000);

    #endif

    cmp ax,syssize ! 我们是否已经都加载了?(ax=0x7f00 ?);

    jbe ok1_read !! if ax ;缓冲区,al=要读的扇区数,也即当前磁道未读的扇区数;

    mov cx,ax !! ax仍为调用read_track之前的值,即为读入的扇区数;

    add ax,sread !! ax = 当前磁道已读的扇区数;

    cmp ax,sectors !! 已经读完当前磁道上的扇区了吗?

    jne ok3_read !! 没有,则跳转;

    mov ax,#1

    sub ax,head !! 当前是磁头1吗?

    jne ok4_read !! 不是(是磁头0)则跳转(此时ax=1);

    inc track !! 当前是磁头1,则读下一磁道(当前磁道加1);

    ok4_read:

    mov head,ax !! 保存当前磁头号;

    xor ax,ax !! 本磁道已读扇区数清零;

    ok3_read:

    mov sread,ax !! 存本磁道已读扇区数;

    shl cx,#9 !! 刚才一次读操作读入的扇区数 * 512;

    add bx,cx !! 调整数据缓冲区的起始指针;

    jnc rp_read !! 如果该指针没有超过64K的段内最大偏移量,则跳转继续读操作;

    mov ax,es !! 如果超过了,则将段地址加0x1000(下一个64K段);

    add ah,#0x10

    mov es,ax

    xor bx,bx !! 缓冲区地址段内偏移量置零;

    jmp rp_read !! 继续读操作;

    read_track:

    pusha !! 将寄存器ax,cx,dx,bx,sp,bp,si,di压入堆栈;

    pusha

    mov ax,#0xe2e ! loading... message 2e = . !! 显示一个.

    mov bx,#7

    int 0x10

    popa

    mov dx,track !! track = 当前磁道;

    mov cx,sread

    inc cx !! cl = 扇区号,要读的起始扇区;

    mov ch,dl !! ch = 磁道号的低8位;

    mov dx,head !!

    mov dh,dl !! dh = 当前磁头号;

    and dx,#0x0100 !! dl = 驱动器号(0);

    mov ah,#2 !! 功能2(读),es:bx指向读数据缓冲区;

    push dx ! 为出错转储保存寄存器的值到堆栈上;

    push cx

    push bx

    push ax

    int 0x13

    jc bad_rt !! 如果出错,则跳转;

    add sp, #8 !! 清(放弃)堆栈上刚推入的4个寄存器值;

    popa

    ret

    bad_rt: push ax ! 保存出错码;

    call print_all ! ah = error, al = read;

    xor ah,ah

    xor dl,dl

    int 0x13

    add sp,#10

    popa

    jmp read_track

    /*

    * print_all是用于调试的。

    * 它将打印出所有寄存器的值。所作的假设是

    * 从一个子程序中调用的,并有如下所示的堆栈帧结构

    * dx

    * cx

    * bx

    * ax

    * error

    * ret ; dest := dest + src + c )

    daa

    int 0x10

    loop print_digit

    ret

    /*

    * 这个过程(子程序)关闭软驱的马达,这样

    * 我们进入内核后它的状态就是已知的,以后也就

    * 不用担心它了。

    */

    kill_motor:

    push dx

    mov dx,#0x3f2

    xor al,al

    outb

    pop dx

    ret

    !! 数据区

    sectors:

    .word 0 !! 当前每磁道扇区数。(36||18||15||9)

    disksizes: !! 每磁道扇区数表

    .byte 36, 18, 15, 9

    msg1:

    .byte 13, 10

    .ascii "Loading"

    .org 497 !! 从boot程序的二进制文件的497字节开始

    setup_sects:

    .byte SETUPSECS

    root_flags:

    .word CONFIG_ROOT_RDONLY

    syssize:

    .word SYSSIZE

    swap_dev:

    .word SWAP_DEV

    ram_size:

    .word RAMDISK

    vid_mode:

    .word SVGA_MODE

    root_dev:

    .word ROOT_DEV

    boot_flag: !! 分区启动标志

    .word 0xAA55

    [目录]

    --------------------------------------------------------------------------------

    setup.S

    1、按规定得有个头,所以一开始是惯用的JMP;

    2、头里边内容很丰富,具体用法走着瞧;

    3、自我检测,不知道有什么用,防伪造?防篡改?

    4、如果装载程序不对,只好死掉!以下终于走入正题;

    5、获取内存容量(使用了三种办法,其中的E820和E801看不明白,int 15倒是老朋友了--应该是上个世纪80年代末认识的了,真佩服十年过去了,情意依旧,不过遇上一些不守规矩的BIOS,不知道还行不行);

    6、将键盘重复键的重复率设为最大,灵敏一点?

    7、检测硬盘,不懂,放这里干什么?

    8、检测MCA总线(不要问我这是什么);

    9、检测PS/2鼠标,用int 11,只是不知道为何放这里;

    10、检测电源管理BIOS;唉,书到用时方恨少,不懂的太多了,真不好意思;不过也没有关系, 不懂的就别去动它就行了;以下要进入内核了;

    11、 在进入保护模式之前,可以调用一个你提供的试模式下的过程,让你最后在看她一眼,当然你要是不提供,那就有个默认的,无非是塞住耳朵闭上眼睛禁止任何中断,包括著名的NMI ;

   &nb


你可能感兴趣的:(linux)