📄 1.html
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML><HEAD> <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=gb2312"> <META NAME="GENERATOR" CONTENT="《良友》v2.1, 作者:安富国,http://winking.126.com"> <TITLE>启动</TITLE></HEAD><BODY style="font-family: 宋体; font-size: 9pt"> <CENTER><TABLE CELLSPACING=10 CELLPADDING=10 WIDTH="60%" BGCOLOR="#FFB693" ><TR><TD ALIGN=CENTER><FONT SIZE=+2><!--标题由此开始-->启动</TD></TR></TABLE></CENTER><p><h3>目 录</h3><!--目录由此开始--><A NAME="Content" ID="Content"></A><OL><LI><A HREF="#I354">启动</A></LI><OL><LI><A HREF="#I355">启动步骤</A></LI><LI><A HREF="#I356">bootsect.S</A></LI><LI><A HREF="#I357">setup.S</A></LI><LI><A HREF="#I358">head.S</A></LI><LI><A HREF="#I359">compressed/misc.c</A></LI><LI><A HREF="#I360">内核解压</A></LI><LI><A HREF="#I361">用网卡从并口启动(I386)</A></LI></OL></OL><hr><br><A NAME="I354" ID="I354"></A><center><b><font size=+2>启动</font></b></center><br> 当PC启动时,Intel系列的CPU首先进入的是实模式,并开始执行位于地址0xFFFF0处的代码,也就是ROM-BIOS起始位置的代码。BIOS先进行一系列的系统自检,然后初始化位于地址0的中断向量表。最后BIOS将启动盘的第一个扇区装入到0x7C00,并开始执行此处的代码.这就是对内核初始化过程的一个最简单的描述。<p> 最初,Linux核心的最开始部分是用8086汇编语言编写的。当开始运行时,核心将自己装入到绝对地址0x90000,再将其后的2k字节装入到地址0x90200处,最后将核心的其余部分装入到0x10000。<p> 当系统装入时,会显示Loading...信息。装入完成后,控制转向另一个实模式下的汇编语言代码boot/Setup.S。Setup部分首先设置一些系统的硬件设备,然后将核心从0x10000处移至0x1000处。这时系统转入保护模式,开始执行位于0x1000处的代码。<p> 接下来是内核的解压缩。0x1000处的代码来自于文件Boot/head.S,它用来初始化寄存器和调用decompress_kernel( )程序。decompress_kernel( )程序由Boot/inflate.c, Boot/unzip.c 和Boot/misc.c组成。解压缩后的数据被装入到了0x100000处,这也是Linux不能在内存小于2M的环境下运行的主要原因。<p> 解压后的代码在0x1010000处开始执行,紧接着所有的32位的设置都将完成: IDT、GDT和LDT将被装入,处理器初始化完毕,设置好内存页面,最终调用start_kernel过程。这大概是整个内核中最为复杂的部分。<p>[系统开始运行]<br> Linux kernel 最早的C代码从汇编标记startup_32开始执行<p>|startup_32:<br> |start_kernel<br> |lock_kernel<br> |trap_init<br> |init_IRQ<br> |sched_init<br> |softirq_init<br> |time_init<br> |console_init<br> |#ifdef CONFIG_MODULES<br> |init_modules<br> |#endif<br> |kmem_cache_init<br> |sti<br> |calibrate_delay<br> |mem_init<br> |kmem_cache_sizes_init<br> |pgtable_cache_init<br> |fork_init<br> |proc_caches_init<br> |vfs_caches_init<br> |buffer_init<br> |page_cache_init<br> |signals_init<br> |#ifdef CONFIG_PROC_FS<br> |proc_root_init<br> |#endif<br> |#if defined(CONFIG_SYSVIPC)<br> |ipc_init<br> |#endif<br> |check_bugs<br> |smp_init<br> |rest_init<br> |kernel_thread<br> |unlock_kernel<br> |cpu_idle<p><br>·startup_32 [arch/i386/kernel/head.S]<br>·start_kernel [init/main.c]<br>·lock_kernel [include/asm/smplock.h]<br>·trap_init [arch/i386/kernel/traps.c]<br>·init_IRQ [arch/i386/kernel/i8259.c]<br>·sched_init [kernel/sched.c]<br>·softirq_init [kernel/softirq.c]<br>·time_init [arch/i386/kernel/time.c]<br>·console_init [drivers/char/tty_io.c]<br>·init_modules [kernel/module.c]<br>·kmem_cache_init [mm/slab.c]<br>·sti [include/asm/system.h]<br>·calibrate_delay [init/main.c]<br>·mem_init [arch/i386/mm/init.c]<br>·kmem_cache_sizes_init [mm/slab.c]<br>·pgtable_cache_init [arch/i386/mm/init.c]<br>·fork_init [kernel/fork.c]<br>·proc_caches_init<br>·vfs_caches_init [fs/dcache.c]<br>·buffer_init [fs/buffer.c]<br>·page_cache_init [mm/filemap.c]<br>·signals_init [kernel/signal.c]<br>·proc_root_init [fs/proc/root.c]<br>·ipc_init [ipc/util.c]<br>·check_bugs [include/asm/bugs.h]<br>·smp_init [init/main.c]<br>·rest_init<br>·kernel_thread [arch/i386/kernel/process.c]<br>·unlock_kernel [include/asm/smplock.h]<br>·cpu_idle [arch/i386/kernel/process.c]<p> start_kernel( )程序用于初始化系统内核的各个部分,包括:<p> *设置内存边界,调用paging_init( )初始化内存页面。<br> *初始化陷阱,中断通道和调度。<br> *对命令行进行语法分析。<br> *初始化设备驱动程序和磁盘缓冲区。<br> *校对延迟循环。<p>最后的function'rest_init' 作了以下工作:<p> ·开辟内核线程'init'<br> ·调用unlock_kernel<br> ·建立内核运行的cpu_idle环, 如果没有调度,就一直死循环<p>实际上start_kernel永远不能终止.它会无穷地循环执行cpu_idle.<p> 最后,系统核心转向move_to_user_mode( ),以便创建初始化进程(init)。此后,进程0开始进入无限循环。<p> 初始化进程开始执行/etc/init、/bin/init 或/sbin /init中的一个之后,系统内核就不再对程序进行直接控制了。之后系统内核的作用主要是给进程提供系统调用,以及提供异步中断事件的处理。多任务机制已经建立起来,并开始处理多个用户的登录和fork( )创建的进程。<p>[init]<br> init是第一个进程,或者说内核线程<p>|init<br> |lock_kernel<br> |do_basic_setup<br> |mtrr_init<br> |sysctl_init<br> |pci_init<br> |sock_init<br> |start_context_thread<br> |do_init_calls<br> |(*call())-> kswapd_init<br> |prepare_namespace<br> |free_initmem<br> |unlock_kernel<br> |execve<center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I355" ID="I355"></A><center><b><font size=+2>启动步骤</font></b></center><br>系统引导:<p>涉及的文件<br>./arch/$ARCH/boot/bootsect.s<br>./arch/$ARCH/boot/setup.s<p>bootsect.S<br> 这个程序是linux kernel的第一个程序,包括了linux自己的bootstrap程序,<br>但是在说明这个程序前,必须先说明一般IBM PC开机时的动作(此处的开机是指<br>"打开PC的电源"):<p> 一般PC在电源一开时,是由内存中地址FFFF:0000开始执行(这个地址一定<br>在ROM BIOS中,ROM BIOS一般是在FEOOOh到FFFFFh中),而此处的内容则是一个<br>jump指令,jump到另一个位於ROM BIOS中的位置,开始执行一系列的动作,包<br>括了检查RAM,keyboard,显示器,软硬磁盘等等,这些动作是由系统测试代码<br>(system test code)来执行的,随着制作BIOS厂商的不同而会有些许差异,但都<br>是大同小异,读者可自行观察自家机器开机时,萤幕上所显示的检查讯息。<p> 紧接着系统测试码之后,控制权会转移给ROM中的启动程序<br>(ROM bootstrap routine),这个程序会将磁盘上的第零轨第零扇区读入<br>内存中(这就是一般所谓的boot sector,如果你曾接触过电脑病<br>毒,就大概听过它的大名),至於被读到内存的哪里呢? --绝对<br>位置07C0:0000(即07C00h处),这是IBM系列PC的特性。而位在linux开机<br>磁盘的boot sector上的正是linux的bootsect程序,也就是说,bootsect是<br>第一个被读入内存中并执行的程序。现在,我们可以开始来<br>看看到底bootsect做了什么。<p>第一步<br> 首先,bootsect将它"自己"从被ROM BIOS载入的绝对地址0x7C00处搬到<br>0x90000处,然后利用一个jmpi(jump indirectly)的指令,跳到新位置的<br>jmpi的下一行去执行,<p>第二步<br> 接着,将其他segment registers包括DS,ES,SS都指向0x9000这个位置,<br>与CS看齐。另外将SP及DX指向一任意位移地址( offset ),这个地址等一下<br>会用来存放磁盘参数表(disk para- meter table )<p>第三步<br> 接着利用BIOS中断服务int 13h的第0号功能,重置磁盘控制器,使得刚才<br>的设定发挥功能。<p>第四步<br> 完成重置磁盘控制器之后,bootsect就从磁盘上读入紧邻着bootsect的setup<br>程序,也就是setup.S,此读入动作是利用BIOS中断服务int 13h的第2号功能。<br>setup的image将会读入至程序所指定的内存绝对地址0x90200处,也就是在内存<br>中紧邻着bootsect 所在的位置。待setup的image读入内存后,利用BIOS中断服<br>务int 13h的第8号功能读取目前磁盘的参数。<p>第五步<br> 再来,就要读入真正linux的kernel了,也就是你可以在linux的根目录下看<br>到的"vmlinuz" 。在读入前,将会先呼叫BIOS中断服务int 10h 的第3号功能,<br>读取游标位置,之后再呼叫BIOS 中断服务int 10h的第13h号功能,在萤幕上输<br>出字串"Loading",这个字串在boot linux时都会首先被看到,相信大家应该觉<br>得很眼熟吧。<p>第六步<br> 接下来做的事是检查root device,之后就仿照一开始的方法,利用indirect<br>jump 跳至刚刚已读入的setup部份<p>第七步<br> setup.S完成在实模式下版本检查,并将硬盘,鼠标,内存参数写入到 INITSEG<br>中,并负责进入保护模式。<p>第八步<br> 操作系统的初始化。<p><p><br><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I356" ID="I356"></A><center><b><font size=+2>bootsect.S</font></b></center><br>1.将自己移动到0x9000:0x0000处,为内核调入留出地址空间;<br>2.建立运行环境(ss=ds=es=cs=0x9000, sp=0x4000-12),保证起动程序运行;<br>3.BIOS初始化0x1E号中断为软盘参数表,将它取来保存备用;<br>4.将setup读到0x9000:0x0200处;<br>5.测试软盘参数一个磁道有多少个扇区(也没有什么好办法,只能试试36, 18, 15, 9对不对了);<br>6.打印“Loading”;<br>7.读入内核到0x1000:0000(如果是bzImage, 则将每个64K移动到0x100000处,在实模式下,只能调用0x15号中断了,这段代码无法放在bootsect中所以只能放在setup中,幸好此时setup已经读入了);<br>8.到setup去吧<p>发发信人: seis (矛), 信区: Linux<br>标 题: Linux操作系统内核引导程序详细剖析<br>发信站: BBS 水木清华站 (Fri Feb 2 14:12:43 2001)<p>! bootsect.s (c) 1991, 1992 Linus Torvalds 版权所有<br>! Drew Eckhardt修改过<br>! Bruce Evans (bde)修改过<br>!<br>! bootsect.s 被bios-启动子程序加载至0x7c00 (31k)处,并将自己<br>! 移到了地址0x90000 (576k)处,并跳转至那里。<br>!<br>! bde - 不能盲目地跳转,有些系统可能只有512k的低<br>! 内存。使用中断0x12来获得(系统的)最高内存、等。<br>!<br>! 它然后使用BIOS中断将setup直接加载到自己的后面(0x90200)(576.5k),<br>! 并将系统加载到地址0x10000处。<br>!<br>! 注意! 目前的内核系统最大长度限制为(8*65536-4096)(508k)字节长,即使是在<br>! 将来这也是没有问题的。我想让它保持简单明了。这样508k的最大内核长度应该<br>! 是足够了,尤其是这里没有象minix中一样包含缓冲区高速缓冲(而且尤其是现在<br>! 内核是压缩的 :-)<br>!<br>! 加载程序已经做的尽量地简单了,所以持续的读出错将导致死循环。只能手工重启。<br>! 只要可能,通过一次取得整个磁道,加载过程可以做的很快的。<p>#include /* 为取得CONFIG_ROOT_RDONLY参数 */<br>!! config.h中(即autoconf.h中)没有CONFIG_ROOT_RDONLY定义!!!?<p>#include<p>.text<p>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -