30天自制操作系统

基本概念
  • Bios 是basic input output system 是基本的输入输出系统,为操作系统开发人员提供功能使用的,因此占用了一些内存区域。所以我们在使用内存的时候,某些区域是不可以使用的,以免发生冲突。
  • 系统本身需要有一个引导程序,来把自己搬运到内存当中
  • 所以内存里面前512字节是作为启动区的,在书籍当中,作者把程序的位置放置在“Ox8200-Ox83ff”的地方,理由是“Ox8000-Ox81ff”作为启动区,其实这里作者表述有误,启动区的内存装载地址已经指明了7c00,怎么可能还是0x8000呢。
  • Origin的Ox7c00是指启动区内容的装载地址,Ox00007c00-Ox00007dff
  • 因此电脑启动的时候需要自动的把磁盘里面的引导程序加载到内存中,然后引导程序开始运行,把剩余的真正的运行程序加载到内存当中
  • 默认的来讲,电脑是从硬盘的第一个扇区来加载引导程序的【有待考察,据作者所言,程序自身检验第一个扇区的倒数两个字节是否是55aa,如果不是,就认为他不是引导程序。不会运行。】,每一个扇区是512字节,有80[0-79]个柱面,每个柱面有18个扇区【1-18,鬼知道为什么不是从0开始计数啊。】上下两个柱头。
  • 所以218512*80 = 1474560 byte = 1440 kb
  • IPL启动区是C0H0S1 读取内容的时候自然是从 C0H0S2
  • IPL 的意思是指initial program loader 因为操作系统很大,一般都是把加载的程序放置到启动区,不过作者的程序很小,直接放到启动区运行即可,没有加载程序。
  • 如果不指定,DS被默认作为段寄存器,因此需要初始化为0
思考
  • 当我第三天继续看代码的时候,关于C语言的代码可以很快理解,但是汇编语言的代码我就基本忘记了。
  • 譬如有些操作需要关闭中断,又是需要读取对应寄存区的位,这些都是涉及到硬件cpu和内存方面的一些设定。
  • 而且在书籍中,作者也是反复查阅资料才知道了这些细节。
显卡内存
  • VRAM指的是显卡内存video RAM,即用来显示画面的内存,这一块内存可以像一般的内存一样存储数据,但功能不限于此,它的各个地址都对应着画面上的像素。
  • MOV DWORD [VRAM], 0x000a0000
  • 它的初始地址是0x 000a0000
中断
  • INT 是软件中断指令,BIOS为操作系统开发人员准备了各种函数,而INT就是用来调用这些函数的指令
  • INT后面是个数字,使用不同的数字可以调用不同的函数
  • INT 0x10(16) 号函数,功能是控制显卡
		MOV		AL,0x13			; VGAグラフィックス、320x200x8bitカラー
		MOV		AH,0x00
		INT		0x10
  • https://zh.wikipedia.org/wiki/INT_10H
  • INT 10h,INT 10H或INT 16是BIOS中断调用的第10H功能的简写, 在基于x86的计算机系统中属于第17中断向量。BIOS通常在此创建了一个中断处理程序提供了实模式下的视频服务。此类服务包括设置显示模式,字符和字符串输出,和基本图形(在图形模式下的读取和写入像素)功能。
  • 要使用这个功能的调用,在寄存器AH赋予子功能号,其它的寄存器赋予其它所需的参数,并用指令INT 10H调用。INT 10H的执行速度是相当缓慢的,所以很多程序都绕过这个BIOS例程而直接访问显示硬件。设置显示模式并不经常使用,可以通过BIOS来实现,而一个游戏在屏幕上绘制图形,需要做得很快,所以直接访问显存比用BIOS调用每个像素更适合。
  • 电传打字机输出 AH=0EH AL=字符,BH=页码,BL=颜色(只适用于图形模式)
GDT与IDT
  • GDT与IDT都是为了对CPU进行设定,以便我们使用32位模式
  • 段的信息分为段的大小,段的起始地址,段的管理属性这三部分信息
  • GDT是【global segment descriptor table】全局段号记录表,段的信息有8个字节64位,可是段寄存器是16位的无法直接存储这些信息,所以用类似调色板的形式进行记录。因此段号的信息被存储在内存的某块固定地址,然后将内存的起始地址和有效设定个数放在CPU内被称为GDTR的特殊寄存器中。
  • 所以我们使用的时候,在段寄存器里面指定我们要获取第n段的信息,然后用GDTR的起始地址+(n-1)*8即可获得该段的信息在内存中的位置
void init_gdtidt(void)
{
	struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) 0x00270000;
	struct GATE_DESCRIPTOR    *idt = (struct GATE_DESCRIPTOR    *) 0x0026f800;
	int i;

	/* GDTの初期化 */
	for (i = 0; i < 8192; i++) {
		set_segmdesc(gdt + i, 0, 0, 0);   //内部包含乘法运算 gdt已经被声明为指针的情况下
	}
	set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, 0x4092);   //指定段的信息——上限,基址,访问权限
	set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, 0x409a);
	load_gdtr(0xffff, 0x00270000);  //C语言不能给gdtr赋值 有效设定个数 起始地址 

	/* IDTの初期化 */
	for (i = 0; i < 256; i++) {
		set_gatedesc(idt + i, 0, 0, 0);
	}
	
	load_idtr(0x7ff, 0x0026f800); 

	return;
}

// 初始化GDT
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)
{
	if (limit > 0xfffff) {
		ar |= 0x8000; /* G_bit = 1 */
		limit /= 0x1000;
	}
	sd->limit_low    = limit & 0xffff;
	sd->base_low     = base & 0xffff;
	sd->base_mid     = (base >> 16) & 0xff;
	sd->access_right = ar & 0xff;
	sd->limit_high   = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0);
	sd->base_high    = (base >> 24) & 0xff;
	return;
}

  1. IDT是中断记录表【interrupt descriptor table】,当我们用键盘或者鼠标的时候,需要中断CPU,必须设定IDT,IDT记录了0-255的中断号码与调用函数的对应关系
  2. 使用两个函数来分别注册GDT和IDT,段的信息本身是8个字节,对于段的基址是32位4个字节,外加段的大小4GB即32位,4个字节,这样就直接8个字节没有地方可以存放访问属性了】因此,把段的大小设置为20位, 当设置GD状态位时表示页,然后剩余12位用来表示访问属性
//  初始化IDT
void set_gatedesc(struct GATE_DESCRIPTOR *gd, int offset, int selector, int ar)
{
	gd->offset_low   = offset & 0xffff;
	gd->selector     = selector;
	gd->dw_count     = (ar >> 8) & 0xff;
	gd->access_right = ar & 0xff;
	gd->offset_high  = (offset >> 16) & 0xffff;
	return;
}
PIC
  1. 在设置GDT与IDT之后还要设置PIC,PIC本身是一个8位的寄存器,8个输入引脚,一个输出引脚。
  2. PIC本身是programmable interrupt controller 可编程中断控制器,IRQ的意思是interrupt request
  3. 因为CPU只能处理一个中断信号,数量不够用,因此增加了PIC。
  4. 他的8个输入引脚只要有一个有信号,那么就输出中断信号给CPU,CPU如果可以处理这个中断,就会命令PIC发送 2字节的数据,CPU把数据当命令运行发生中断
  5. PIC本身需要进行初始化,这些都是固定的处理方式,唯一可以进行独特设定的是ICW2,决定以哪一号中断通知CPU
  6. INT Ox20-Ox2f 对应 IRQ 0-15 的中断信号,之所以不用Ox01-Ox0f,是因为这几个中断信号被CPU用来作为系统的自我保护中断信号了。
lgdt寄存器
  • load_gdtr(0xffff, 0x00270000); 将指定的段上限和地址赋给48位寄存器
  • LGDT函数,指定一个地址然后读取6个字节给寄存器
  • MOV WORD [678], 123 在这里123会被解释成16位的数值,左边是高位,右边是低位,然后高位被存储在679.低位是678
  • 我这里不太明白ESP+4的意思
_load_gdtr:		; void load_gdtr(int limit, int addr);
		MOV		AX,[ESP+4]		; limit
		MOV		[ESP+6],AX
		LGDT	[ESP+6]
		RET

_load_idtr:		; void load_idtr(int limit, int addr);
		MOV		AX,[ESP+4]		; limit
		MOV		[ESP+6],AX
		LIDT	[ESP+6]
		RET
剩余步骤
  • 作者用c语言写了一个函数,来显示带背景的一行文字
void inthandler21(int *esp)
/* PS/2キーボードからの割り込み */
{
	struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
	boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 0, 0, 32 * 8 - 1, 15);
	putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, "INT 21 (IRQ-1) : PS/2 keyboard");
	for (;;) {
		io_hlt();
	}
}
  • 因为在中断处理状态下,不能使用RET指令,必须执行IRETD指令,还不能用C语言写,因此用汇编语言对其进行了一层包装
_asm_inthandler21:
		PUSH	ES
		PUSH	DS
		PUSHAD
		MOV		EAX,ESP
		PUSH	EAX
		MOV		AX,SS
		MOV		DS,AX
		MOV		ES,AX
		CALL	_inthandler21
		POP		EAX
		POPAD
		POP		DS
		POP		ES
		IRETD
  • 然后再把这个函数注册到IDT中
set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
  • 这里并没有处理指定ICW2的关系,也就是说,我按下键盘,他对应的是哪一个IRQ,IRQ本身又是对应哪一个idt的函数号。
加速方法
  • 不要一开始纠结细节,先从整体上理解。
困惑
JMP		entry
		DB		0x90
		DB		"HARIBOTE"		; ブートセクタの名前を自由に書いてよい(8バイト)
		DW		512				; 1セクタの大きさ(512にしなければいけない)
		DB		1				; クラスタの大きさ(1セクタにしなければいけない)
		DW		1				; FATがどこから始まるか(普通は1セクタ目からにする)
		DB		2				; FATの個数(2にしなければいけない)
		DW		224				; ルートディレクトリ領域の大きさ(普通は224エントリにする)
		DW		2880			; このドライブの大きさ(2880セクタにしなければいけない)
		DB		0xf0			; メディアのタイプ(0xf0にしなければいけない)
		DW		9				; FAT領域の長さ(9セクタにしなければいけない)
		DW		18				; 1トラックにいくつのセクタがあるか(18にしなければいけない)
		DW		2				; ヘッドの数(2にしなければいけない)
		DD		0				; パーティションを使ってないのでここは必ず0
		DD		2880			; このドライブ大きさをもう一度書く
		DB		0,0,0x29		; よくわからないけどこの値にしておくといいらしい
		DD		0xffffffff		; たぶんボリュームシリアル番号
		DB		"HARIBOTEOS "	; ディスクの名前(11バイト)
		DB		"FAT12   "		; フォーマットの名前(8バイト)
		RESB	18				; とりあえず18バイトあけておく
  1. DB是写入一个字节的意思,但是第三行的DB "HARIBOTE"明显超过一个字节了。
  2. 在代码的一开始就使用JMP语句进行跳转,后面的这些DB语句相当于没有执行,那他们的作用是什么。
  3. 段寄存器表明我们要把从软盘上读出的数据装载到内存的哪个位置。
  4. 在代码的一开始,指定ES的读入地址
MOV AX, Ox0820
MOV ES,AX
  1. ES本身指定的是内存的位置,可是为什么在往后读扇区的时候,在他身上加512呢,不是应该加软盘的地址吗?【除非每次读取是以整个扇区512字节为单位的,所以读完以后扇头直接指到下一个扇区的头部,这个时候就需要同步更新放置的内存位置。即ES加上512字节】,当然我们也可以给BX加上512
  2. 原来计算机读写软盘的时候,是以512字节为一个单位进行读写
  3. 作者将操作系统的内容和引导程序分离开来,分别是IPL与SYS,然后用makefile放置在一个文件夹中,他根据观察发现,生成的镜像文件里面,文件内容位于4200的地方,注意这个4200是相对于整个img镜像文件而言。而前面我们已经说过,内存位置相对于磁盘的起始点是Ox800,所以Ox0800+Ox4200就是系统程序的放置位置【可是如果内存太小的话,根本不可能一下子把软盘的东西全部放置到内存里面啊!】
  4. 下面会实现颜色每隔6列循环,我不太明白
i&OxOf
  1. 汇编程序和C程序之间的互相调用
  2. C指针的一些说明
  3. CPU的32位模式
  4. 颜色的调色板模式代码
  5. static unsigned char table_rgb[16*3] 静态的无符号字符数组,容量为48
  6. 色号使用8位数,从0到255,可颜色本身是用6位十六进制数字表示,所以由我们随意指定0-255数字所对应颜色
  7. 作者用汇编语言的形成写成了函数,而且这些函数可以给C语言程序进行调用
  8. bookpack.hrb是什么意思
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值