Linux Kernel学习笔记启动.docx
- 文档编号:17526328
- 上传时间:2023-07-26
- 格式:DOCX
- 页数:36
- 大小:36.48KB
Linux Kernel学习笔记启动.docx
《Linux Kernel学习笔记启动.docx》由会员分享,可在线阅读,更多相关《Linux Kernel学习笔记启动.docx(36页珍藏版)》请在冰点文库上搜索。
LinuxKernel学习笔记启动
LinuxKernel学习笔记——启动
启动
当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组成。
解压缩后的数据被装入到了0x处,这也是Linux不能在内存小于2M的环境下运行的主要原因。
解压后的代码在0x处开始执行,紧接着所有的32位的设置都将完成:
IDT、GDT和LDT将被装入,处理器初始化完毕,设置好内存页面,最终调用start_kernel过程。
这大概是整个内核中最为复杂的部分。
[系统开始运行]
Linuxkernel最早的C代码从汇编标记startup_32开始执行
|startup_32:
|start_kernel
|lock_kernel
|trap_init
|init_IRQ
|sched_init
|softirq_init
|time_init
|console_init
|#ifdefCONFIG_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
|#ifdefCONFIG_PROC_FS
|proc_root_init
|#endif
|#ifdefined(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
这个程序是linuxkernel的第一个程序,包括了linux自己的bootstrap程序,
但是在说明这个程序前,必须先说明一般IBMPC开机时的动作(此处的开机是指
"打开PC的电源"):
一般PC在电源一开时,是由内存中地址FFFF:
0000开始执行(这个地址一定
在ROMBIOS中,ROMBIOS一般是在FEOOOh到FFFFFh中),而此处的内容则是一个
jump指令,jump到另一个位於ROMBIOS中的位置,开始执行一系列的动作,包
括了检查RAM,keyboard,显示器,软硬磁盘等等,这些动作是由系统测试代码
(systemtestcode)来执行的,随着制作BIOS厂商的不同而会有些许差异,但都
是大同小异,读者可自行观察自家机器开机时,萤幕上所显示的检查讯息。
紧接着系统测试码之后,控制权会转移给ROM中的启动程序
(ROMbootstraproutine),这个程序会将磁盘上的第零轨第零扇区读入
内存中(这就是一般所谓的bootsector,如果你曾接触过电脑病
毒,就大概听过它的大名),至於被读到内存的哪里呢?
--绝对
位置07C0:
0000(即07C00h处),这是IBM系列PC的特性。
而位在linux开机
磁盘的bootsector上的正是linux的bootsect程序,也就是说,bootsect是
第一个被读入内存中并执行的程序。
现在,我们可以开始来
看看到底bootsect做了什么。
第一步
首先,bootsect将它"自己"从被ROMBIOS载入的绝对地址0x7C00处搬到
0x90000处,然后利用一个jmpi(jumpindirectly)的指令,跳到新位置的
jmpi的下一行去执行,
第二步
接着,将其他segmentregisters包括DS,ES,SS都指向0x9000这个位置,
与CS看齐。
另外将SP及DX指向一任意位移地址(offset),这个地址等一下
会用来存放磁盘参数表(diskpara-metertable)
第三步
接着利用BIOS中断服务int13h的第0号功能,重置磁盘控制器,使得刚才
的设定发挥功能。
第四步
完成重置磁盘控制器之后,bootsect就从磁盘上读入紧邻着bootsect的setup
程序,也就是setup.S,此读入动作是利用BIOS中断服务int13h的第2号功能。
setup的image将会读入至程序所指定的内存绝对地址0x90200处,也就是在内存
中紧邻着bootsect所在的位置。
待setup的image读入内存后,利用BIOS中断服
务int13h的第8号功能读取目前磁盘的参数。
第五步
再来,就要读入真正linux的kernel了,也就是你可以在linux的根目录下看
到的"vmlinuz"。
在读入前,将会先呼叫BIOS中断服务int10h的第3号功能,
读取游标位置,之后再呼叫BIOS中断服务int10h的第13h号功能,在萤幕上输
出字串"Loading",这个字串在bootlinux时都会首先被看到,相信大家应该觉
得很眼熟吧。
第六步
接下来做的事是检查rootdevice,之后就仿照一开始的方法,利用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移动到0x处,在实模式下,只能调用0x15号中断了,这段代码无法放在bootsect中所以只能放在setup中,幸好此时setup已经读入了);
8.到setup去吧
发发信人:
seis(矛),信区:
Linux
标题:
Linux操作系统内核引导程序详细剖析
发信站:
BBS水木清华站(FriFeb214:
12:
432001)
!
bootsect.s(c)1991,1992LinusTorvalds版权所有
!
DrewEckhardt修改过
!
BruceEvans(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_INITSEG0x9000
!
!
DEF_SYSSEG0x1000
!
!
DEF_SETUPSEG0x9020
!
!
DEF_SYSSIZE0x7F00(=32512=31.75k)*16=508k
!
ROOT_DEV&SWAP_DEV现在是由"build"中编制的;
ROOT_DEV=0
SWAP_DEV=0
#ifndefSVGA_MODE
#defineSVGA_MODEASK_VGA
#endif
#ifndefRAMDISK
#defineRAMDISK0
#endif
#ifndefCONFIG_ROOT_RDONLY
#defineCONFIG_ROOT_RDONLY1
#endif
!
ld86需要一个入口标识符,这和通常的一样;
.globl_main
_main:
#if0/*调试程序的异常分支,除非BIOS古怪(比如老的HP机)否则是无害的*/
int3
#endif
movax,#BOOTSEG!
!
将ds段寄存器置为0x7C0;
movds,ax
movax,#INITSEG!
!
将es段寄存器置为0x9000;
moves,ax
movcx,#256!
!
将cx计数器置为256(要移动256个字,512字节);
subsi,si!
!
源地址ds:
si=0x07C0:
0x0000;
subdi,di!
!
目的地址es:
di=0x9000:
0x0000;
cld!
!
清方向标志;
rep!
!
将这段程序从0x7C0:
0(31k)移至0x9000:
0(576k)处;
movsw!
!
共256个字(512字节)(0x200长);
jmpigo,INITSEG!
!
间接跳转至移动后的本程序go处;
!
ax和es现在已经含有INITSEG的值(0x9000);
go:
movdi,#0x4000-12!
0x4000(16k)是>=bootsect+setup的长度+
!
+堆栈的长度的任意的值;
!
12是磁盘参数块的大小es:
di=0x94000-12=592k-12;
!
bde-将0xff00改成了0x4000以从0x6400处使用调试程序(bde)。
如果
!
我们检测过最高内存的话就不用担心这事了,还有,我的BIOS可以被配置为将wini驱动
表
!
放在内存高端而不是放在向量表中。
老式的堆栈区可能会搞乱驱动表;
movds,ax!
置ds数据段为0x9000;
movss,ax!
置堆栈段为0x9000;
movsp,di!
置堆栈指针INITSEG:
0x4000-12处;
/*
*许多BIOS的默认磁盘参数表将不能
*进行扇区数大于在表中指定
*的最大扇区数(-在某些情况下
*这意味着是7个扇区)后面的多扇区的读操作。
*
*由于单个扇区的读操作是很慢的而且当然是没问题的,
*我们必须在RAM中(为第一个磁盘)创建新的参数表。
*我们将把最大扇区数设置为36-我们在一个ED2.88驱动器上所能
*遇到的最大值。
*
*此值太高是没有任何害处的,但是低的话就会有问题了。
*
*段寄存器是这样的:
ds=es=ss=cs-INITSEG,(=0X9000)
*fs=0,gs没有用到。
*/
!
上面执行重复操作(rep)以后,cx为0;
movfs,cx!
!
置fs段寄存器=0;
movbx,#0x78!
fs:
bx是磁盘参数表的地址;
pushds
segfs
ldssi,(bx)!
ds:
si是源地址;
!
!
将fs:
bx地址所指的指针值放入ds:
si中;
movcl,#6!
拷贝12个字节到0x9000:
0x4000-12开始处;
cld
pushdi!
!
指针0x9000:
0x4000-12处;
rep
movsw
popdi!
!
di仍指向0x9000:
0x4000-12处(参数表开始处);
popsi!
!
ds=>si=INITSEG(=0X9000);
movb4(di),*36!
修正扇区计数值;
segfs
mov(bx),di!
!
修改fs:
bx(0000:
0x0078)处磁盘参数表的地址为0x9000:
0x4000-12;
segfs
mov2(bx),es
!
将setup程序所在的扇区(setup-sectors)直接加载到boot块的后面。
!
!
0x90200开始处
;
!
注意,es已经设置好了。
!
同样经过rep循环后cx为0
load_setup:
xorah,ah!
复位软驱(FDC);
xordl,dl
int0x13
xordx,dx!
驱动器0,磁头0;
movcl,#0x02!
从扇区2开始,磁道0;
movbx,#0x0200!
置数据缓冲区地址=es:
bx=0x9000:
0x200;
!
在INITSEG段中,即0x90200处;
movah,#0x02!
要调用功能号2(读操作);
moval,setup_sects!
要读入的扇区数SETUPSECS=4;
!
(假释所有数据都在磁头0、磁道0);
int0x13!
读操作;
jncok_load_setup!
ok则继续;
pushax!
否则显示出错信息。
保存ah的值(功能号2);
callprint_nl!
!
打印换行;
movbp,sp!
!
bp将作为调用print_hex的参数;
callprint_hex!
!
打印bp所指的数据;
popax
jmpload_setup!
!
重试!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
INT13-DISK-READSECTOR(S)INTOMEMORY
!
!
AH=02h
!
!
AL=numberofsectorstoread(mustbenonzero)
!
!
CH=loweightbitsofcylindernumber
!
!
CL=sectornumber1-63(bits0-5)
!
!
hightwobitsofcylinder(bits6-7,harddiskonly)
!
!
DH=headnumber
!
!
DL=drivenumber(bit7setforharddisk)
!
!
ES:
BX->databuffer
!
!
Return:
CFsetonerror
!
!
ifAH=11h(correctedECCerror),AL=burstlength
!
!
CFclearifsuccessful
!
!
AH=status(see#00234)
!
!
AL=numberofsectorstransferred(onlyvalidifCFsetforsome
!
!
BIOSes)
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
ok_load_setup:
!
取得磁盘驱动器参数,特别是每磁道扇区数(nrofsectors/track);
#if0
!
bde-PhoenixBIOS手册中提到功能0x08只对硬盘起作用。
!
但它对于我的一个BIOS(1987Award)不起作用。
!
不检查错误码是致命的错误。
xordl,dl
movah,#0x08!
AH=8用于取得驱动器参数;
int0x13
xo
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Linux Kernel学习笔记启动 Kernel 学习 笔记 启动