⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 head1.txt

📁 Linux0.00版的完全注释这是引导扇区代码
💻 TXT
字号:
;2005.10.27
;这是被引导扇区代码读取到物理地址0x10000H处的代码,他被编译后写在第2个扇区开始的地方,
;	在这里写代码就不用顾忌512字节的限制了,随便写几个扇区,嘿嘿!
;	从这时候开始已经是在保护模式下了。
;	Linus从这里开始就使用了AT&T的汇编语法,我就比照着还是用NASM的语法实现吧!

KRN_BASE 	equ 0x10000
;注意这里是物理地址,不要少写一个零哦!:)

TSS0_SEL 	equ 0x20
LDT0_SEL	equ 0x28
TSS1_SEL		equ 0x30
LDT1_SEL	equ 0x38
;全局描述符表在引导扇区的实现中只有三项,第一项当然是空选择符,第二项是基址为0x10000、长度为8M的代码段,
;	第三项是基址为0x10000、长度为8M的数据段,也就是别名技术的代码段了!
;	而在这里的代码中重新设置了全局描述符表,里面留了两个TSS和LDT的位置,
;	现在这里的TSS0_SEL就将要指向新的全局描述符表中的偏移位置为0x20也就是第0个、第1个......第4个描述符!
;	(直接说第4个的话,总会误解为从1开始的第4个:) )同样地,LDT0_SEL指向了第5个描述符,随后就是TSS1和LDT1了。
[BITS 32]
	mov eax,0x10
	mov ds,ax
	mov es,ax
	mov fs,ax
	mov gs,ax
	lss esp,[stack_ptr]
	;0x10也就是第2个全局描述符(从0开始算起),这里指向的是基址为KRN_BASE长度为8M的数据段
	;	堆栈段也指向这里,根据后面的定义可知,堆底和GDT之间有128个双字的空间!
	
	mov ebx,KRN_BASE
	mov ecx,gdt
	lea eax,[tss0]
	mov edi,TSS0_SEL
	call set_base
	;设置第0个任务的TSS段描述符的基址,在后面的GDT中这些个描述符是未完全初始化的!
	
	lea eax,[ldt0]
	mov edi,LDT0_SEL
	call set_base
	;同上,这是第0个任务的LDT段!
	
	lea eax,[tss1]
	mov edi,TSS1_SEL
	call set_base
	;同上,设置第1个任务的TSS段描述符的基址!
	
	lea eax,[ldt1]
	mov edi,LDT1_SEL
	call set_base
	;同上,这是第0个任务的LDT段!
	
	;好了,以上操作完成后,GDT中的8个描述符就完整了,可以正常使用了!
		
	call setup_idt
	;引导扇区代码中曾有过一个IDT,那是假的,这里重新搞了一个,先执行这个调用把256个中断描述符表通通初始化,
	;	使之指向同一个处理过程,那就是ignore_int!然后用lidt指令装入IDT寄存器。
	
	call setup_gdt
	;后面的GDT中留了8个全局描述符的位置,
	;	第0个是不能用的;
	;	第1个指向了基址为0x10000限长为8M的代码段;
	;	第2个指向了基址为0x10000限长为8M的数据段;
	;	第3个很特别,指向了显存的位置,即基址为0xB80000处限长为8K,准备用在直接屏幕显示的新中断调用上;
	;	第4个用于TSS0;
	;	第5个用于LDT0;
	;	第6个用于TSS1;
	;	第7个用于LDT1;
	;这里的setup_gdt实现很简单的功能,就是装入GDT寄存器:
	;	lgdt [lgdt_opcode]
	
	;刚刚装入了两个重要的寄存器GDTR和IDTR,现在当然得重新设置一下各个段寄存器了!
	mov eax,0x10
	mov ds,ax
	mov es,ax
	mov fs,ax
	mov gs,ax
	lss esp,[stack_ptr]
	
	;现在又来了难懂的代码,设置时钟芯片8253!好象是设置成每秒产生100次中断吧...
	mov al,0x36
	mov edx,0x43
	out dx,al
	
	mov eax,11930
	mov edx,0x40
	out dx,al
	mov al,ah
	out dx,al
	
	;设置0x20时钟中断和0x80系统调用中断描述符...
	mov eax,0x00080000
	mov ax,timer_interrupt
	mov dx,0x8E00
	mov ecx,0x20
	lea esi,[idt+0x100]
	;第0x20号中断(也就是时钟中断)在中断描述符表中的位置= 0x20*8=0x100!
	;	Linus的源代码在这里用AT&T的汇编语法是这样写的: lea idt(,ecx,8),esi ,但我不知道怎么用 NASM 的语法实现,
	;	所以干脆就直接把乘的结果写出来了 :)
	
	mov [esi],eax
	mov [esi+4],edx
	mov ax,system_interrupt
	mov dx,0xEF00
	mov ecx,0x80
	lea esi,[idt+0x400]
	;同上,第0x80号中断(也就是系统中断)在中断描述符表中的位置= 0x80*8=0x400!

	mov [esi],eax
	mov [esi+4],edx
	
	;umask the timer interrupt,难懂的代码,不知道什么意思,和硬件有关,启动实时时钟???
	mov edx,0x21
	in al,dx
	and al,0xFE
	out dx,al
	
	;用模拟中断返回的方式转到级别3的任务0中去运行!为什么这样呢?可能是因为核心不算是一个完整的任务,所以无法使用任务切换吧...
	;	这里的代码也有点像手动切换的意思...
	pushF
	and dword [esp],0xFFFFBFFF
	popF
	
	mov eax,TSS0_SEL
	ltr ax
	mov eax,LDT0_SEL
	lldt ax
	mov dword [current],0
	sti
	;开中断,此时实时时钟的中断已经可以响应了...
	
	push 0x17
	push stack0_ptr
	pushF
	push 0x0F
	push task0
	iret
	;这里使用的是核心自身的堆栈 stack_ptr ,里面的数据如下:
	;	116C  <=== 堆顶,是EIP,任务0的代码段的偏移地址
	;	000F  <=== CS ,任务0的代码段选择子
	;	0282  <=== 在任务0中用的EFLAGS寄存器的值
	;	1380  <=== 在任务0中用的堆栈段的ESP,也就是 stack0_ptr
	;	0017  <=== 在任务0中用的堆栈段的SS,换成二进制 0001 0111,是指向当前局部描述符表中的第0x10偏移处(第2位为1嘛),也就是任务0的数据段
	;
	;执行 iret 指令,模拟了中断返回的效果.结果是SS、ESP、CS、EIP、EFLAGS被设置,且CS中指示了当前运行在3这个级别上,
	;	这不同于任务切换,任务切换是要进行一系列操作的,譬如把当前寄存器的值写入TSS中、把下一条代码的地址写入TSS的EIP域中等等.
	
setup_gdt:
	lgdt [lgdt_opcode]
	ret

setup_idt:
	lea edx,[ignore_int]
	mov eax,0x00080000
	mov ax,dx
	;EAX寄存器中高16位存放了下面要用到的段选择子0x0008,也就是指向GDT中的第1个代码段描述符,
	;	基址为0x10000,限长为8M
	;EAX寄存器中低16位存放了下面要用到的偏移地址 ignore_int 。
	
	mov dx,0x8E00
	;这里是下面要用到的属性字节,0x8E00的意思是"有效的386中断门,特权级为0"。
	
	lea edi,[idt]
	;EDI指向了中断描述符表
	
	mov ecx,256
	;循环256次,设置256个中断描述符
rp_sidt:
	mov [edi],eax
	mov [edi+4],edx
	
	;中断描述符表中的每一个描述符的格式是:
	;	1、每个描述符占8个字节;
	;	2、第0、1个字节是偏移地址的低8位;
	;	3、第2、3个字节是段选择子;
	;	4、第4、5个字节是属性字节;
	;	5、第6、7个字节是偏移地址的高8位。
		
	add edi,8
	dec ecx
	jne rp_sidt
	
	lidt [lidt_opcode]
	ret
	
set_base:
	add eax,ebx
	;EAX中现在存放的是将要放入描述符中的32位基址
	
	add edi,ecx
	;EDI中指向要设置的那个描述符
	
	;描述符中第0到第7个字节中,基址的低16位在第2、3个字节中,
	;	高16位中的低8个字节在第4个字节中,
	;	高16位中的高8个字节在第7个字节中,
	mov [edi+2],ax
	;设置了低16位
	
	ror eax,16
	;把EAX中的高16位转移到AX中
	
	mov [edi+4],al
	;设置了高16位中的低8位
	
	mov [edi+7],ah
	;设置了高16位中的高8位

	ror eax,16
	;还原了EAX,整个调用结束后,EAX寄存器中的值未变!
	
	ret
	
write_char:
	;调用本程序前,把将要显示的字符放到AL寄存器中,同时DS选择子要指向全局描述符表中第2个数据段,
	;	因为本子程序中用到了变量 scr_loc ,该变量是在这个段中的!
	
	push gs
	push ebx
	push eax
	;保存在这个子过程中将要用到的寄存器
	
	mov ebx,0x18
	;这是第3个全局描述符,指向显存0xB80000处
	
	mov gs,bx
	;现在GS段指向了显存!
	
	mov ebx,[scr_loc]
	;scr_loc在后面定义为了一个 long 型的数据,初始值为0。
	;	Linus的源代码在这里是 movl scr_loc,%bx, 但是scr_loc是LONG啊,
	;	难道他不担心数据溢出么?奇怪,这里我为保险起见,还是用了 EBX!
	
	shl ebx,1
	mov [gs:ebx],al
	shr ebx,1
	;把要显示的字符(在AL寄存器中)写入了scr_loc处,因为在显存中一个字符要2个字节来说明,
	;	第一个字节是显示的属性,第二个字节才是要显示的字符,这里跳过了属性字节不改变,而只是改变字符字节,很有意思!
	
	inc ebx
	;因为显示了一个字节,所以位置要加1,以便后面的显示
	
	;上面使用了 SHL 和 SHR 指令,所以要显示第1个字符,就把字符写入显存中第0*2的位置,
	;	显示第2个字符,就把字符写入显存中第1*2的位置,
	;	显示第3个字符,就把字符写入显存中第2*2的位置,以此类推。
	
	cmp ebx,2000
	jb x1
	;小于2000则跳转,显示超过了2000个字符后,光标位置清零,不然显存的那个段(0xB8000)的 8K 长度可能不够的喔,呵呵! :)
	;	另外,从这里可以看出,2000=0x7D0,所以长度是超出了BX的范围的,
	;	Linus是故意这样的么,还是疏忽了呢?呵呵...:)
	
	mov ebx,0
x1:
	mov [scr_loc],ebx
	pop eax
	pop ebx
	pop gs
	ret
	
align 2
ignore_int:
	;这是缺省的中断处理程序,256个中断描述符均指向这里
	push ds
	push eax
	
	mov eax,0x10
	mov dx,ax
	mov eax,67
	;以上设置了调用write_char的两个参数: AL和DS。
	;	67=字符C的 ASCII码。
	
	call write_char
		
	pop eax
	pop ds
	iret
align 2
timer_interrupt:
	;这是时钟中断处理程序,本系统的关键的调度程序
	push ds
	push edx
	push ecx
	push ebx
	push eax
	
	mov eax,0x10
	mov ds,ax
	
	mov al,0x20
	out 0x20,al
	;开中断,因为一进入这个中断程序,系统自动屏蔽了这个中断!没办法,这是 x86 体系规定了的! :(
	
	mov eax,1
	cmp [current],eax
	je x2
	mov [current],eax
	jmp TSS1_SEL:0
	jmp y2			;在调用上面一条指令进行切换时,CPU自动把这条指令的地址写入了任务0的TSS中的EIP域中,
				;	当切换回任务0的时候将在这里继续进行...
x2:
	mov dword [current],0
	jmp TSS0_SEL:0
y2:
	pop eax
	pop ebx
	pop ecx
	pop edx
	pop ds
	iret
	
align 2
system_interrupt:
	;这是本系统中唯一的一个系统调用的实现代码,
	push ds
	push edx
	push ecx
	push ebx
	push eax
	
	mov edx,0x10
	mov ds,dx
	call write_char
	;这里没有设置要打印的字符,因为这是系统调用,调用者会在 AL 寄存器中存放调用的参数,也就是要显示的字符
	
	pop eax
	pop ebx
	pop ecx
	pop edx
	pop ds
	iret
	
current dd 0
scr_loc dd 0

align 2
dw 0
;很显然,以上指令是为了下面的数据能双字对齐。

lidt_opcode:
	;这里定义的6个字节是给 IDT寄存器 用的,使用的指令是 lidt lidt_opcode
	dw 256*8-1
	dd idt+KRN_BASE

align 2
dw 0
;同样地,以上指令是为了下面的数据能双字对齐。

lgdt_opcode:
	;这里定义的6个字节是给 GDT寄存器 用的,使用的指令是 lgdt lgdt_opcode
	dw (end_gdt-gdt)-1
	dd gdt+KRN_BASE

align 3
idt:
	times 256 dd 0
	times 256 dd 0
gdt:
	dw 0x0000,0x0000,0x0000,0x0000	;空选择子
	dw 0x07FF,0x0000,0x9A01,0x00C0	;基址 0x10000 限长 8M 的代码段
	dw 0x07FF,0x0000,0x9201,0x00C0	;基址 0x10000 限长 8M 的数据段
	dw 0x0002,0x8000,0x920B,0x00C0	;基址 0xB8000 限长 12K 的显存数据段
		
	dw 0x0068,0x0000,0xE901,0x0000	;对应于TSS0的描述符,基址暂定0x10000,但会被设置为指向 tss0 处,限长为0x68,即102个字节。
	dw 0x0040,0x0000,0xE201,0x0000	;对应于LDT0的描述符,基址暂定0x10000,但会被设置为指向 ldt0 处,限长为0x40,即64个字节。
	dw 0x0068,0x0000,0xE901,0x0000	;对应于TSS1的描述符,基址暂定0x10000,但会被设置为指向 tss1 处,限长为0x68,即102个字节。
	dw 0x0040,0x0000,0xE201,0x0000	;对应于LDT1的描述符,基址暂定0x10000,但会被设置为指向 ldt1 处,限长为0x40,即64个字节。
end_gdt:
	times 128 dd 0
	;给堆栈使用
stack_ptr:
	dd stack_ptr
	dw 0x10
align 3
;
ldt0:
	dw 0x0000,0x0000,0x0000,0x0000
	dw 0x03FF,0x0000,0xFA01,0x00C0	;基址为0x10000、限长为4M字节、DPL为3的代码段
	dw 0x03FF,0x0000,0xF201,0x00C0	;基址为0x10000、限长为4M字节、DPL为3的数据段
tss0:
	dd 0
	dd stack0_krn_ptr,0x10		;任务0的核心态使用的堆栈,就紧跟在 TSS0 的后面!好像 Linux0.11也和这个类似...
	dd 0,0
	dd 0,0
	dd 0
	dd task0				;确保第一次切换到任务0的时候EIP从这里取值,所以就从 task0 处开始运行,
					;	因为任务0的基址为 0x10000,和核心一样,不指定这个的话,
					;	第一次切换进来就会跑去执行 0x10000处的核心代码了,
					;	除非ldt0中的代码段描述符中的基址改成 task0 处的线性地址,
					;	那这里就可以设为0.
	dd 0x200
	dd 0,0,0,0
	dd stack0_ptr,0,0,0
	dd 0x17,0x0F,0x17,0x17,0x17,0x17
	dd LDT0_SEL
	dd 0x08000000
	times 128 dd 0
stack0_krn_ptr:
	dd 0

align 3
;
ldt1:
	dw 0x0000,0x0000,0x0000,0x0000
	dw 0x03FF,0x0000,0xFA01,0x00C0
	dw 0x03FF,0x0000,0xF201,0x00C0
tss1:
	dd 0
	dd stack1_krn_ptr,0x10
	dd 0,0
	dd 0,0
	dd 0
	dd task1
	dd 0x200
	dd 0,0,0,0
	dd stack1_ptr,0,0,0
	dd 0x17,0x0F,0x17,0x17,0x17,0x17
	dd LDT1_SEL
	dd 0x08000000
	times 128 dd 0
stack1_krn_ptr:
	dd 0

task0:
	mov eax,0x17
	mov ds,ax
	mov al,'X'
	int 0x80
	mov ecx,0xFFF
x3:
	loop x3
	jmp task0
	
	times 128 dd 0
stack0_ptr:
	dd 0

task1:
	mov eax,0x17
	mov ds,ax
	mov al,'M'
	int 0x80
	mov ecx,0xFFF
x4:
	loop x4
	jmp task1
	
	times 128 dd 0
stack1_ptr:
	dd 0

;2005.10.30 晚上23点47分终于完成了调试!哈哈!真是开心,Linus 当年也是这样的心情么?呵呵! :)
;	通过调试学到了很多细节,这几天没有白忙!调试代码很繁琐,但确实很有乐趣!  :)

;数据结构

;GDT[里面有8个全局描述符]:
;
;	第0个[0x00]描述符==>>空选择子
;	第1个[0x08]描述符==>>基址 0x10000 限长 8M 的代码段
;	第2个[0x10]描述符==>>基址 0x10000 限长 8M 的数据段
;	第3个[0x18]描述符==>>基址 0xB8000 限长 12K 的显存数据段
;		
;	第4个[0x20]描述符==>>指向一个系统段描述符,也就是第0个任务的TSS描述符,基址设置为 tss0 处,限长为0x68,即102个字节。
;	第5个[0x28]描述符==>>指向一个系统段描述符,也就是第0个任务的LDT描述符,基址设置为 ldt0 处,限长为0x40,即64个字节。
;	第6个[0x30]描述符==>>指向一个系统段描述符,也就是第1个任务的TSS描述符,基址设置为 tss1 处,限长为0x68,即102个字节。
;	第7个[0x38]描述符==>>指向一个系统段描述符,也就是第1个任务的LDT描述符,基址设置为 ldt1 处,限长为0x40,即64个字节。
;
;LDT0[第0个任务的局部描述符表,里面有3个描述符]:
;
;	第0个[0x00]描述符==>>空选择子
;	第1个[0x08]描述符==>>基址为0x10000、限长为4M字节、DPL为3的代码段
;	第2个[0x10]描述符==>>基址为0x10000、限长为4M字节、DPL为3的数据段

;TSS0[第0个任务的任务状态段,紧跟着放了一个长度为128个双字的核心堆栈,供0级使用]
;
;LDT1[第1个任务的局部描述符表,和LDT0一样]:
;
;TSS1[第1个任务的任务状态段,紧跟着放了一个长度为128个双字的核心堆栈,供0级使用]

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -