自制操作系统日志——第二十天

自制操作系统日志——第二十天

今天我们主要的任务就是制作API。然后利用api来调用系统进行显示字符。话不多说,一起加油吧。



一、显示单个字符的api

再正式开始之前我把之前的console.c代码进行了优化的设置,详情请查看代码。

API 是应用程序对操作系统功能的调用,也可以称之为系统调用。即应用程序调用操作系统的来完成某些操作系统级别的某种操作。

目前我们实现API的基本思路就是:将要显示的字符编码存入AL寄存器中,然后就直接调用操作系统的函数就可以显现了。也就是说,我们可以在之前的那个程序里使用call命令进行调用我们系统里缩写的cons_putchar()这个函数即可!但是由于cons_putchar是用c写的,该函数无法接收,因此我们还需建立一个函数进行调用,即大致思路如下:
在这里插入图片描述

我们建立asm_cons_putchar函数将寄存器的值进行保存到栈中,然后再调用cons_putchar函数即可,以下是修改的代码:
naskfunc.nas:

_farcall: ;void farcall(int eip, int cs)
    CALL FAR [ESP+4]
	RET
	
_asm_cons_putchar:
    PUSH 1
	AND  EAX,0xff				;将AH和EAX的高位置都置为0,将EAX置为已存入字符编码的状态
	PUSH EAX
	PUSH DWORD [0x0fec]			;读取内存并push该值
	CALL _cons_putchar
	ADD ESP,12 					;将栈栈中数据丢弃
	RETF

console.c:

void console_task(struct SHEET *sheet, unsigned int memtotal)
{*((int *) 0x0fec) = (int) &cons;//将对应的cons值保存到bootinfo事先预留好的地址处}
	
//启动应用程序hlt.hrb
void cmd_hlt(struct CONSOLE *cons, int *fat)
{
	struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
	struct FILEINFO *finfo = file_search("HLT.HRB", (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);
	struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;
	char *p;
	if (finfo != 0) {
		p = (char *) memman_alloc_4k(memman, finfo->size);
		file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));
		set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER);
		farcall(0, 1003 * 8);
		memman_free_4k(memman, (int) p, finfo->size);
	} else {
		putfonts8_asc_sht(cons->sht, 8, cons->cur_y, COL8_FFFFFF, COL8_000000, "File not found.", 15);
		cons_newline(cons);
	}
	cons_newline(cons);
	return;
}	

以上,我们增加了farcall这个函数,是因为我们想要再应用程序中显示完字符后还能返回到原先的地址,call命令是将跳转前的信息进行保存的!!

然后,修改前一天的那个程序:
hlt.nas

[BITS 32]
    MOV AL,'Y'
    CALL 2*8:0xD6C
	MOV AL,'U'
    CALL 2*8:0xD6C
	MOV AL,'A'
    CALL 2*8:0xD6C
	MOV AL,'N'
    CALL 2*8:0xD6C
	MOV AL,'-'
    CALL 2*8:0xD6C
	MOV AL,'O'
    CALL 2*8:0xD6C
	MOV AL,'S'
    CALL 2*8:0xD6C
	RETF

这里由两点:
第一是我们的 CALL 2*8:0xD6C 这个地址是我们再程序中想要跳转到_asm_cons_putchar地址的所在处,具体的我们是通过bootpack.map中查询到的:
在这里插入图片描述

其二,我们选择跳转到地址,一定要加上段地址,这是因为该程序所在的地址是1003 * 8 而代码的程序这是在 2 * 8这个地址处!

最后,让我们看看运行后的效果怎么样:
在这里插入图片描述

利用中断的API

在前面叙述中,我们是事先找到了对应的asm_cons_putchar函数的地址。但是,如果又添加了新函数,或者操作系统版本更改了,那么对应的地址就会发送变化。因此,我们需要设计一个能够不随版本改变而变化的API。

在前面的中断处理里,我们写了一个专门用于注册函数的IDT表。对于CPU而言,用于通知异常中断的最多只有32种(目前最新技术的话不太确定,需要查一下),但是呢,对于IDT表而言,却可以注册256种函数。因此我们可以借用一下IDT表来注册一下函数,一遍我们在程序里可以直接利用int进行调用该函数。

dsctbl.c:

    set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 << 3, AR_INTGATE32);
    set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 << 3, AR_INTGATE32);
    set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 << 3, AR_INTGATE32);
    set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 << 3, AR_INTGATE32);
	set_gatedesc(idt + 0x40, (int) asm_cons_putchar, 2 << 3, AR_INTGATE32);

hlt.nas:

[BITS 32]
    MOV AL,'Y'
    int 0x40
	MOV AL,'U'
    int 0x40
	MOV AL,'A'
    int 0x40
	MOV AL,'N'
    int 0x40
	MOV AL,'-'
    int 0x40
	MOV AL,'O'
    int 0x40
	MOV AL,'S'
    int 0x40
	RETF

由于,我们使用的是中断进行调用(此时,cpu会设置关闭中断),因此这里我们还需要使用利用IRTED进行返回,且在程序里开放中断的权限。
naskfunc.nas:

_asm_cons_putchar:
    STI
    PUSH 1
	AND  EAX,0xff				;将AH和EAX的高位置都置为0,将EAX置为已存入字符编码的状态
	PUSH EAX
	PUSH DWORD [0x0fec]			;读取内存并push该值
	CALL _cons_putchar
	ADD ESP,12 					;将栈栈中数据丢弃
	IRETD

然后,运行!你会发现一点即hlt.hrb这个程序会比之前的大小要小很多。嘿嘿嘿,这是因为far-call指令主要7字节,而int指令只需要2字节。

二、为自由程序命名并优化程序

以下,我们将让系统支持其他程序的应用名。
首先在cmd_run中将判断是否是程序名的任务交给新建的函数cmd_app:
cmd_run:

else if (cmdline[0] != 0) {
		if (cmd_app(cons, fat, cmdline) == 0) {
			/*不是命令,不是应用程序,也不是空行*/
			putfonts8_asc_sht(cons->sht, 8, cons->cur_y, COL8_FFFFFF, COL8_000000, "Bad command", 12);
			cons_newline(cons);
			cons_newline(cons);
		}
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
	struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
	struct FILEINFO *finfo;
	struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;
	char name[18], *p;
	int i;

	/*根据命令行生成文件名*/
	for (i = 0; i < 13; i++) {
		if (cmdline[i] <= ' ') {
			break;
		}
		name[i] = cmdline[i];
	}
	name[i] = 0; /*暂且将文件名的后面置为0*/

	/*寻找文件 */
	finfo = file_search(name, (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);
	if (finfo == 0 && name[i -1]!= '.') {
		/*由于找不到文件,故在文件名后面加上“.hrb”后重新寻找*/
		name[i ] = '.';
		name[i + 1] = 'H';
		name[i + 2] = 'R';
		name[i + 3] = 'B';
		name[i + 4] = 0;
		finfo = file_search(name, (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);
	}

	if (finfo != 0) {
		/*找到文件的情况*/
		p = (char *) memman_alloc_4k(memman, finfo->size);
		file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));
		set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER);
		farjmp(0, 1003 * 8);
		memman_free_4k(memman, (int) p, finfo->size);
		cons_newline(cons);
		return 1;
	}
	/*没有找到文件的情况*/
	return 0;
}

然后,进行修改名称即可!
在这里插入图片描述

这里,我们进一步的对程序进行优化一下,直接采用循环进行处理:
hello.nas

[INSTRSET "i486p"]
[BITS 32]
    MOV ECX,msg

putloop: 
    MOV AL,[CS:ECX]
    CMP AL,0
    JE fin
    int 0x40
    ADD ECX,1
    JMP putloop
fin:
    RETF
msg:
   DB "hello-yuan-os",0

这里,我们需要使用ecx寄存器进行累加计数,以确保所有的单个字符都可以遍历到。然后,还需要修改一下_asm_cons_putchar这个函数,需要在进入时,先保存一下所有的寄存器的值,以防止在调用_cons_putchar这个函数时,将我们之前设置的ECX的值打乱:

_asm_cons_putchar:
    STI
	PUSHAD
    PUSH 1
	AND  EAX,0xff				;将AH和EAX的高位置都置为0,将EAX置为已存入字符编码的状态
	PUSH EAX
	PUSH DWORD [0x0fec]			;读取内存并push该值
	CALL _cons_putchar
	ADD ESP,12 					;将栈栈中数据丢弃
	POPAD
	IRETD

然后,make run一下估计没啥问题!

用api显示字符串

接下来我们制作两个显示字符串的函数,其差别主要在于,一个是遇到字符编码0后结束,一个是根据给定的长度进行显示的:

void cons_putstr0(struct CONSOLE *cons, char *s)
{
	for (; *s != 0; s++) {
		cons_putchar(cons, *s, 1);
	}
	return;
}

void cons_putstr1(struct CONSOLE *cons, char *s, int l)
{
	int i;
	for (i = 0; i < l; i++) {
		cons_putchar(cons, s[i], 1);
	}
	return;
}

至此,我们就可以把前面的都更改成cons-putstr0这个函数进行显示了!!!更加的简洁便利。当然,我们还需要能够让应用程序能够调用我们这两个字符串显示的函数,我们这里决定效仿bios的调用,通过设置0x40函数的子功能进行调用,代码中我们讲利用dx寄存器进行存放子功能的函数:
naskfunc.nas:

_asm_hrb_api:
    STI
	PUSHAD						;用于保存寄存器值的push
	PUSHAD						;用于向hrb_api传值的push
	CALL _hrb_api
	ADD ESP,32
	POPAD
	IRETD

console.c:

//用edx来指向是哪一号的子功能
void hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
	struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);//前面讲cons的内容保存到了0xfec当中
	if (edx == 1) {
		cons_putchar(cons, eax & 0xff, 1);
	} else if (edx == 2) {
		cons_putstr0(cons, (char *) ebx);
	} else if (edx == 3) {
		cons_putstr1(cons, (char *) ebx, ecx);
	}
	return;
}

然后还要修改IDT的设设置:

set_gatedesc(idt + 0x40, (int) asm_hrb_api,      2 << 3, AR_INTGATE32);

最后,我们来制作一下程序,看看能否显示成功:
hello.nas

[INSTRSET "i486p"]
[BITS 32]
    MOV ECX,msg
    MOV EDX,1

putloop: 
    MOV AL,[CS:ECX]
    CMP AL,0
    JE fin
    int 0x40
    ADD ECX,1
    JMP putloop
fin:
    RETF
msg:
   DB "hello-yuan-os",0

hello2.nas

[INSTRSET "i486p"]
[BITS 32]
		MOV		EDX,2
		MOV		EBX,msg
		INT		0x40
		RETF
msg:
		DB	"hello-yuan-os",0

在这里插入图片描述

咦?这里的hello2并未显示。让我们来分析看看吧。

首先,在hello中我们利用的是[cs:ecx]的方式进行指定了msg里的内容。但是我们在显示字符串那边却无法指定对应的段地址,这就会让cpu默认段地址是ds,从而从错误的地方取出了数据。

因此,我们需要做的事情,就是将完整的内存地址传递给显示字符串的函数中。在整个代码里,我们主要是在cmd_app里设置了程序的段地址,因此我们需要将该段地址的数据保存在一个空闲的地方。emm…就放在之前bootinfo里的0fe8里好了。

int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{if (finfo != 0) {
		/*找到文件的情况*/
		p = (char *) memman_alloc_4k(memman, finfo->size);
		*((int *) 0xfe8) = (int) p;}

//用edx来指向是哪一号的子功能
void hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
	int cs_base = * ( (int *) 0xfe8);
	struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);//前面讲cons的内容保存到了0xfec当中
	if (edx == 1) {
		cons_putchar(cons, eax & 0xff, 1);
	} else if (edx == 2) {
		cons_putstr0(cons, (char *) ebx + cs_base );
	} else if (edx == 3) {
		cons_putstr1(cons, (char *) ebx + cs_base , ecx);
	}
	return;
}

在这里插入图片描述

噢噢噢噢,成功了!嘿嘿嘿。


总结

以上就是今天的全部内容,虽然有点小累了,但是看着这一天天的逐渐成型还是很高兴的。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值