Linux-0.12 源码剖析

Posted 码维世界

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux-0.12 源码剖析相关的知识,希望对你有一定的参考价值。


主引导程序源码:

!! SYS_SIZE is the number of clicks (16 bytes) to be loaded.! 0x3000 is 0x30000 bytes = 196kB, more than enough for current! versions of linux!#include <linux/config.h>/** <linux/config.h>* #define UTS_SYSNAME "Linux"* #define UTS_NODENAME "(none)" /* set by sethostname() */*  #define UTS_RELEASE "0"    /* patchlevel */* #define UTS_VERSION "0.12"* #define UTS_MACHINE "i386" /* hardware type */* #define DEF_INITSEG 0x9000* #define DEF_SYSSEG 0x1000* #define DEF_SETUPSEG 0x9020*  #define DEF_SYSSIZE  0x3000 */SYSSIZE = DEF_SYSSIZE ! DEF_SYSSIZE = 0x3000!! bootsect.s (C) 1991 Linus Torvalds! modified by Drew Eckhardt!! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves! iself out of the way to address 0x90000, and jumps there.! bootsect.s 被BIOS加载到内存条的0x7c00处,并将自己转移到了0x90000处! bootsect.s 放在软驱A的0磁头,0磁道,1扇区
! It then loads 'setup' directly after itself (0x90200), and the system! at 0x10000, using BIOS interrupts. !使用BIOS中断将‘setup’加载到了0x90200处,system模块加载到了0x10000处! setup 放在了软驱A的0磁头,0磁道,2~5扇区! system放在了软驱A的0磁头,0磁道,6~.. 扇区
! NOTE! currently system is at most 8*65536 bytes long. This should be no! problem, even in the future. I want to keep it simple. This 512 kB! kernel size should be enough, especially as this doesn't contain the! buffer cache as in minix
!8*65536 = 8 * 64KB = 8 * 0x10000 = 0x10000 ~ 0x90000


! The loader has been made as simple as possible, and continuos! read errors will result in a unbreakable loop. Reboot by hand. It! loads pretty fast by getting whole sectors at a time whenever possible.
.globl begtext, begdata, begbss, endtext, enddata, endbss.textbegtext:.databegdata:.bssbegbss:.text
SETUPLEN = 4 ! nr of setup-sectorsBOOTSEG = 0x07c0 ! original address of boot-sectorINITSEG = DEF_INITSEG ! we move boot here - out of the waySETUPSEG = DEF_SETUPSEG ! setup starts hereSYSSEG = DEF_SYSSEG ! system loaded at 0x10000 (65536).ENDSEG = SYSSEG + SYSSIZE ! where to stop loading ENDSEG = 0x1000 + 0x3000
! ROOT_DEV & SWAP_DEV are now written by "build"./** 根文件系统设备号和交换设备号由build程序写入* 主设备号:1-内存,2-磁盘,3-硬盘,4-ttyx,5-tty,6-并行口,7-非命名管道* 0x300 - /dev/hd0 - 代表整个第 1 个硬盘;* 0x301 - /dev/hd1 - 第 1 个盘的第 1 个分区;* * 0x304 - /dev/hd4 - 第 1 个盘的第 4 个分区;* 0x305 - /dev/hd5 - 代表整个第 2 个硬盘;* 0x306 - /dev/hd6 - 第 2 个盘的第 1 个分区;* * 0x309 - /dev/hd9 - 第 2 个盘的第 4 个分区;*/ROOT_DEV = 0SWAP_DEV = 0
entry startstart: mov ax,#BOOTSEG mov ds,ax !把数据段的段基址设置为0x07c0 mov ax,#INITSEG !INITSEG = 0x9000 mov es,ax !把附加段es的段基址设置为0x9000 mov cx,#256 sub si,si !把si寄存器置零 sub di,di !把di寄存器置零 rep movw !以字为单位的重复移动,源为:ds:si 目为:es:di,根据cx判断是否继续移动 !把本文件中的内容从ox07c00移动到0x90000处 !在实模式下,从0x00000~0x9FFFF都是映射在了内存条中 !512 == 0x200,所以0x90000~0x901FF是这个文件内容所占据的地址空间 jmpi go,INITSEG !进行绝对地址跳转,cs:0x9000,ip:go
go: mov ax,cs ! cs:0x9000 mov dx,#0xfef4 ! arbitrary value >>512 - disk parm size
mov ds,ax ! ds:0x9000 mov es,ax ! es:0x9000 mov ss,ax ! put stack at 0x9ff00 - 12. ss:0x9000 mov sp,dx ! sp:0xfef4,ss:sp == 0x9fef4 ! 0x9fef4 - 0x90200 = 0xfcf4 = 64756Byte /* * Many BIOS's default disk parameter tables will not * recognize multi-sector reads beyond the maximum sector number * specified in the default diskette parameter tables - this may * mean 7 sectors in some cases. * * Since single sector reads are slow and out of the question, * we must take care of this by creating new parameter tables * (for the first disk) in RAM. We will set the maximum sector * count to 18 - the most we will encounter on an HD 1.44. * * High doesn't hurt. Low does. * * Segments are as follows: ds=es=ss=cs - INITSEG, * fs = 0, gs = parameter table segment */

push #0 ! 将0压栈 pop fs ! 将0弹入fs寄存器 mov bx,#0x78 ! fs:bx is parameter table address,0x78 = 0x1E * 4 ! 0x1E是BIOS中断向量表中索引值,每个表项占4字节,这个表项中存储的地址 !指向ROM BIOS中的软盘参数表(共11字节大小),地址的高2字节为段地址,低2字节为偏移量 seg fs ! 段超越前缀,改变访问地址的段前缀,只能影响它的下一条指令 ! 即fs:bx = 0x00078,这个地址在低1024Byte内,是BIOS的中断向量表 lgs si,(bx) ! gs:si is source,同时给gs和si赋值,用从fs:bx指向的地址取出的值 ! 赋值给gs:si ,高2字节赋给gs,低2字节赋给si
mov di,dx ! es:di is destination es:di = 0x9fef4 mov cx,#6 ! copy 12 bytes cld ! 清除方向标志位,使其按正方向拷贝,从低地址到高地址,复制时,指针递增
rep seg gs movw ! gs:si -> es:di 从软盘参数表中拷贝12字节
mov di,dx ! di = 0xfef4 movb 4(di),*18 ! patch sector count,es:(di+4),把第5个字节数据改成18(十进制) ! 修改每磁道的最大扇区数为18个
seg fs ! fs = 0x0000 mov (bx),di ! fs:bx = 0x00078,di = 0xfef4,把软盘参数表地址的低2字节改为0xfef4 seg fs ! fs = 0x0000 mov 2(bx),es ! fs:(bx+2),es = 0x9000,把软盘参数表地址的高2字节改为0x9000 ! 让中断向量0x1E指向新地址 mov ax,es mov fs,ax ! fs = 0x9000 mov gs,ax ! gs = 0x9000 xor ah,ah ! reset FDC 将ah置零,ax = 0x0000 xor dl,dl ! 将dl置零,dx = 0xfe00  int   0x13 ! 0x13号中断-磁盘I/O中断, ! 入口参数 ah,dl ! ah = 0x00,0号功能-磁盘系统复位 ! dl = 0x00~0x7F(软盘),0x80~0xFF(硬盘),此处为第一个软驱 ! 出口参数:cf(进位标志) = 0-操作成功,ah = 0x00 ! 入口参数 ! ah = 0x02,2号功能-读扇区到内存 ! al 需要读出的扇区数量 ! ch = 柱面号的低8位 cl = 开始扇区(5~0),柱面号高2位(7~6) ! dh = 磁头号 dl = 驱动器号 ! es:bx -> 指向数据缓冲区 ! cf = 0-操作成功,ah=0x00,al传输的扇区, ! cf = 1 表示出错,ah=错误状态码
! load the setup-sectors directly after the bootblock.! Note that 'es' is already set up.
load_setup: xor dx, dx ! drive 0, head 0,dx置零,0号驱动,0号磁头 mov cx,#0x0002 ! sector 2, track 0,0号柱面,第二个扇区 mov bx,#0x0200 ! address = 512, in INITSEG mov ax,#0x0200+SETUPLEN ! service 2, nr of sectors,SETUPLEN = 4, ! ax = 0x0204,ah = 0x02,al = 0x04,读四个扇区 int 0x13 ! read it 调用0x13号中断,将读取的内容写入es:bx = 0x90200 开始的空间处 jnc ok_load_setup ! ok - continue ,jnc: cf != 1 ,即读扇区没出错就进行跳转
push ax ! dump error code 读取扇区出错,将错误状态码入栈 call print_nl ! 打印回车换行 mov bp, sp ! call print_hex ! 将错误码以16进制的形式打印到屏幕上 pop ax ! 把错误码弹回到ax中 xor dl, dl ! reset FDC xor ah, ah int 0x13 ! 磁盘系统复位后重新读取扇区 j load_setup ! j = jmp
ok_load_setup: ! 读扇区成功后执行
! Get disk drive parameters, specifically nr of sectors/track
xor dl,dl mov ah,#0x08 ! AH=8 is get drive parameters 0x08号功能是读取驱动器参数 ! 入口参数 ah(功能号),dl(驱动器) = 0x00~0x7F(软盘),0x80~0xFF(硬盘) ! 出口参数 cf(进位标志位)=0-成功,1-失败, ! ah状态码 ! bl = 0x01,0x02,0x03,0x04 ! ch = 最大柱面号的低八位 ! cl(7~6) = 最大柱面号的高2位 ! cl(5~0) = 每磁道最大扇区数 ! dh = 磁头数 ! dl = 驱动器数量 ! es:id = 磁盘驱动器参数表地址 int 0x13 xor ch,ch ! cx的高8位置零,因为驱动器为软盘,其最大柱面号不会超过256,8位足以表示 ! 所以cl的高2位位0,此时cx表示每磁道的最大扇区数 seg cs mov sectors,cx ! sectors 是一个word类型的变量,2字节 mov ax,#INITSEG mov es,ax
! Print some inane message
mov ah,#0x03 ! read cursor pos int 0x10 中断的0x03号功能,读取光标位置 ! 入口参数 bh = 页号 ! 出口参数 ! ch = 光标开始行 ! cl = 光标结束行 ! dh = 行 ! dl = 列 xor bh,bh int 0x10 mov cx,#9 ! 串长度 mov bx,#0x0007 ! page 0, attribute 7 (normal) bl = 0x07 属性 mov bp,#msg1 ! 待显示字符串偏移地址 mov ax,#0x1301 ! write string, move cursor ! ah = 0x13 al = 0x01 int 0x10 中断的0x13号功能 ! 显示字符串 ! 入口参数 ! es:bp = 串地址 ! cx = 串长度 ! dh,dl 起始行列 ! bh 页号 ! al = 0x00,bl = 属性 ! al = 0x01,bl = 属性 ! al = 0x02 ! al = 0x03 int 0x10
! ok, we've written the message, now! we want to load the system (at 0x10000)
mov ax,#SYSSEG ! SYSSEG = 0x1000 mov es,ax ! es = 0x1000 call read_it ! 读取system模块 call kill_motor ! 关闭驱动器马达 call print_nl
! After that we check which root-device to use. If the device is! defined (!= 0), nothing is done and the given device is used.! Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending! on the number of sectors that the BIOS reports currently.
seg cs mov ax,root_dev ! 获取自定义的根文件系统设备号 or ax,ax jne root_defined ! zf != 1 即 此处表示ax不为0 就跳转 ,此处不进行跳转 seg cs mov bx,sectors ! 获取每磁道最大的扇区数 mov ax,#0x0208 ! /dev/at0 - 1.2Mb ! 主设备号是软盘(2) ! 次设备号 = type * 4 + nr ! nr = 0~3(软驱A,B,C,D) ! type = (2->1.2MB,7->1.44MB) ! 设备号 = 主设备号*256 + 次设备号 = 2*256 + 2*4 + 0 = 0x0208 cmp bx,#15 ! 根据获取的设备参数来判断是那种设备 je root_defined mov ax,#0x021c ! /dev/PS0 - 1.44Mb ! 主设备号是软盘(2) ! 次设备号 = type * 4 + nr ! nr = 0~3(软驱A,B,C,D) ! type = (2->1.2MB,7->1.44MB) ! 设备号 = 主设备号*256 + 次设备号 = 2*256 + 7*4 + 0 = 0x021c cmp bx,#18 je root_definedundef_root: jmp undef_rootroot_defined: seg cs mov root_dev,ax
! after that (everyting loaded), we jump to! the setup-routine loaded directly after! the bootblock:
jmpi 0,SETUPSEG ! 执行到此处表示本文件(bootsect.s)执行完毕,程序跳转到SETUPSEG:0 = 0x90200处执行(setup)
! This routine loads the system at address 0x10000, making sure! no 64kB boundaries are crossed. We try to load it as fast as! possible, loading whole tracks whenever we can.!! in: es - starting address segment (normally 0x1000)!sread: .word 1+SETUPLEN ! sectors read of current trackhead: .word 0 ! current headtrack: .word 0 ! current track
read_it: ! 读取system模块,必须读取超过0x30000Byte才结束 mov ax,es ! ax = 0x1000 test ax,#0x0fff ! ax and 0x0fff = 0x0000 es必须位于64KB边界,即64KB对齐,即es的低12位为0die: jne die ! es must be at 64kB boundary jne = 不相等就跳转 zf != 1 xor bx,bx ! bx is starting address within segment bx = 0x0000 bx为段内偏移值rp_read: mov ax,es ! ax = 0x1000 cmp ax,#ENDSEG ! have we loaded all yet? ENDSEG = 0x1000 + 0x3000 = 0x4000 ! 段基址大小绝对不能超过0x4000,即system模块的总大小不能超过0x30000Byte jb ok1_read ! 小于就跳转 cf retok1_read: seg cs mov ax,sectors ! sectors中存储了每磁道中的最大扇区数 sub ax,sread ! sread 表示在当前磁道已经读取的扇区数,ax = 当前磁道剩余的扇区数(每磁道最大扇区只占6位) ! 所以al足以表示剩余扇区数,且ah = 0x00 mov cx,ax ! cx 中存储当前磁道剩余扇区数 shl cx,#9 ! shl逻辑左移,左移9位,左移9位代表 cx * 512 Byte,表示剩余扇区所占的字节数 add cx,bx ! 未读取前,提前判断当前磁道剩余字节数加上bx是否超过64KB jnc ok2_read ! jnc cf != 1 跳转,表明当前磁道剩余字节数加上bx没有超过64KByte je ok2_read ! je 相等,zf = 1 ,剩余扇区的总字节加上bx刚好够64KByte xor ax,ax ! 剩余扇区的总字节数大于了64kByte ax置零 sub ax,bx ! ax = 0x0000 - bx,表示了还差多少字节加上bx正好够64KByte shr ax,#9 ! 将ax逻辑右移9位,即 ax/512 = 所差字节中有几个整扇区(在al中) ! bx的增加一定都是512的整数倍,所以ax = 0x0000-bx = 64KB-bx ! ax/512 = 64KB/512 - bx/512 = 一定是个整数值,没有小数ok2_read: call read_track ! 调用读取扇区函数,读取的是system模块,放在了以0x10000开始的位置 mov cx,ax ! ax仅仅由ok1_read中决定,ax表示读取的扇区数 add ax,sread ! ax = ax + sread,ax = 目前总读取的扇区数 seg cs cmp ax,sectors ! sectors保存着每磁道中的最大扇区数,并与目前已经读取的扇区数比较 jne ok3_read ! 表示ax与sectors不相等(zf != 0),然后跳转
! 使用的时1.44MB的软驱,就一个盘片,两个盘面,上下两个磁头(0,1),各个盘面上划分出多个 ! 磁道(0~..), mov ax,#1 ! ax与sectors相等,表示目前总读取的扇区数与磁道最大扇区数相等,此磁道读完 sub ax,head ! ax = 0x01 - head jne ok4_read ! zf != 0 跳转到ok4_read,表示head为0,磁头为0, ! 那么去磁头1所在的磁道取数据, inc track ! 表示当前磁头为1,增加磁道号,并去磁头0所在的磁道读取数据ok4_read: mov head,ax ! 改变磁头号 xor ax,ax ! 重置ax = 0x0000ok3_read: mov sread,ax ! 更新已经读取的扇区数大小 shl cx,#9 ! cx中保存着调用read_track实际读取的扇区数大小 ! 将cx逻辑左移9位,表示cx * 512 Byte ,即cx中存有实际读取的字节大小 add bx,cx ! bx是system段的段内偏移量,此处表示将bx加上读取到的字节大小,进行偏移 ! bx刚开始式为0x0000 jnc rp_read ! jnc = cf != 1 表示没有进位,即总偏移要小于64KB,就跳转 ! 在这里只有两条转向,一个是总偏移小于64KB继续读取扇区,一个是总偏移正好是64KB mov ax,es ! 此处表示总偏移正好是64KB,将原来存储的system的段基址赋给ax ! 刚开始时,es = 0x1000 add ah,#0x10 ! 将段基址的高8位加上0x10, ! 例如(ax = 0x1000,0x10+0x10 = 0x20,则ax = 0x2000) mov es,ax ! 再将扩大后的段基址重新赋给es xor bx,bx ! 再将段内偏移量重新置零 jmp rp_read ! 再继续读取扇区
read_track: pusha ! 压入所有寄存器 pusha ! 压入所有寄存器 mov ax, #0xe2e ! loading... message 2e = . ! ah = 0x0e al = 0x2e ! int 0x10中断,ah = 0x0e 显示字符,光标自动前移 ! al = 0x2e 要显示的字符 '.' mov bx, #7 ! bh = 0x00 目前的显示页,bl = 0x07 前景色 int 0x10 popa ! 弹出所有寄存器
mov dx,track ! 磁道号,track = 0x0000,两个字节 mov cx,sread ! 已读的扇区数5 inc cx ! cx = cx + 1 mov ch,dl ! ch = 0x00 ,cl = 0x06 mov dx,head ! dx = 0x0000,head = 磁头号 0 mov dh,dl ! dh = 0x00 and dx,#0x0100 ! dx = 0x0000 mov ah,#2 ! ah = 0x02 al = 要读取的扇区数 push dx ! save for error dump push cx push bx push ax
int 0x13 ! ah = 0x02 2号功能,读扇区 ! 入口地址 ah, ! ch = 开始磁道号 cl = 开始扇区号 ! dh = 开始磁头号 dl = 驱动器号 0 ! es:bx = 目的地址,所读扇区的存储地址 es = 0x1000,bx = 0x0000 ! 出口地址 ! cf = 0-成功,al = 实际读取的扇区数,ah = 0x00 ! 1-失败 ah = 状态码 jc bad_rt ! cf = 1 跳转,表示读取扇区失败后跳转 add sp, #8 ! 读取成功,sp + 8 , popa ret
bad_rt: ! 读取包含system模块的扇区失败 push ax ! save error code 保存有错误码,存在AH中 call print_all ! ah = error, al = read ! @X:error ! AX:ax ! BX:bx ! CX:cx            ! DX:dx xor ah,ah xor dl,dl  int 0x13 ! 磁盘系统复位 add sp, #10 popa jmp read_track
/* * print_all is for debugging purposes. * It will print out all of the registers. The assumption is that this is * called from a routine, with a stack frame like * dx * cx * bx * ax * error * ret <- sp **/ print_all: mov cx, #5 ! error code + 4 registers mov bp, sp
print_loop: push cx ! save count left call print_nl ! nl for readability 回车换行 jae no_reg ! see if register name is needed cf = 0 跳转,大于等于,不需要借位 mov ax, #0xe05 + 0x41 - 1 ! ax = 0x0e45 ! ah = 0x0e al = 0x45 sub al, cl ! al = al -cl int 0x10 ! ah = 0x0e 显示字符,al = 要显示的字符
mov al, #0x58 ! X int 0x10
mov al, #0x3a ! : int 0x10
no_reg: add bp, #2 ! next register call print_hex ! print it pop cx loop print_loop ret
print_nl: mov ax, #0xe0d ! CR int10 是针对显示功能调用 ! ah = 0x0e 在屏幕上打印字符,光标位置自动移动,每显示一个字符,光标 ! 自动右移一个字符,若光标在屏幕最后一个字符处,屏幕会自动上卷 ! al = 0x0d ,表示要显示的字符 0x0d代表回车 int 0x10 mov al, #0xa ! LF al = 0x0a 代表换行 int 0x10 ret
/* * print_hex is for debugging purposes, and prints the word * pointed to by ss:bp in hexadecmial.*/
print_hex: mov cx, #4 ! 4 hex digits mov dx, (bp) ! load word into dx 从 ss:bp 读取2个字节 存入dxprint_digit: rol dx, #4 ! rotate so that lowest 4 bits are used 循环左移4位,把最高4位移到最低4位处 mov ah, #0xe ! ah = 0x0e mov al, dl ! mask off so we have only next nibble 把al 置为 dx的低8位 and al, #0xf ! 将al的高4位置零,低4位不变 add al, #0x30 ! convert to 0 based digit, '0',将al与0x30相加,可以确保al不小于0x30 ! 0x30 = '0' cmp al, #0x39 ! check for overflow 将所得al与0x39比较,0x39 = '9' jbe good_digit ! 如果al 小于等于0x39 表示al的ascii在'0' ~'9',并跳转到good_digit add al, #0x41 - 0x30 - 0xa ! 'A' - '0' - 0xa, ! 0x41+(al-0x30-0xa) = 0x41 + (0x0~0x5) -'A' ~'F'
good_digit: int 0x10 ! ah = 0x0e al 为要打印的字符 loop print_digit ! 循环打印4次,每次打印4位,共16位 ret

/* * This procedure turns off the floppy drive motor, so * that we enter the kernel in a known state, and * don't have to worry about it later. */kill_motor: push dx mov dx,#0x3f2 ! 0x03f2 是软驱控制器的一个输出端口 ! 这个端口是一个8位寄存器 ! 7~4: 分别用于控制4个软驱(D~A)的启动和关闭, ! 3~2:允许/禁止DMA和中断请求以及启动/复位软盘控制器FDC ! 1~0: 用于选择操作的软驱 xor al, al ! al = 0x00 选择A驱动器,关闭FDC,禁止DMA和中断请求,关闭马达 outb ! 将al中的内容输出到dx指定的端口 pop dx ret
sectors: .word 0
msg1: .byte 13,10 ! 13 - '0001101' 回车,10 - '0001010' 换行 .ascii "Loading"
.org 506swap_dev: .word SWAP_DEVroot_dev: .word ROOT_DEVboot_flag: .word 0xAA55
.textendtext:.dataenddata:.bssendbss:



以上是关于Linux-0.12 源码剖析的主要内容,如果未能解决你的问题,请参考以下文章

第一次作业 基于Linux 0.12的进程模型分析

第一次作业:基于Linux-0.12的进程分析

《Docker 源码分析》全球首发啦!

Mybatis源码剖析:传统开发方式源码剖析

Spark源码剖析:stage划分原理与源码剖析

C语言源码剖析与实现——strtok()系列函数实现