30天自制操作系统(第20天)

20.1 显示单个字符的API1

需要实现:当在命令窗口输入 ‘hlt’ 时,打印一个字符。思考一下,需要在执行hlt.nas文件时打印的字符,所以需要在文件中规定要打印的字符。打印字符需要调用cons_putchar函数,而该函数需要3个输入参数,还需要另外两个参数,怎么传递呢?参考函数farjmp和asm_inthandler20等中断函数,是通过汇编语言实现的,这里采用了类似做法。
; hlt.nas
    MOV 	AL,'A'
	CALL 	2*8:0xbe3	        ;_asm_cons_putchar函数地址

; naskfunc.nas
;void cons_putchar(struct CONSOLE *cons, int chr, char move);
_asm_cons_putchar:
		PUSH	1				; move=1,占4字节
		AND		EAX,0xff		; 将AH和EAX的高位置0,将EAX置为已存入字符编码的状态
		PUSH	EAX				; chr = [EAX]地址存的元素,占4字节
		PUSH	DWORD [0x0fec]	; 读取内存并PUSH该值,该地址值就是输入参数cons,占4字节
		CALL	_cons_putchar
		ADD		ESP,12			; 所以是移动12位将栈中的数据丢弃
		RETF                    ; 
代码中对AL、AH和EAX高16位进行了设置,先后压入1、‘A’和cons(该值在函数 console_task进行了规定,是该函数中的cons值放到地址0x0fec处),再调用函数cons_putchar时将对栈中元素进行依次读出,形成其输入参数并执行。
应用程序对API执行 CALL的时候,千万不能忘记加上段号应用程序所在的段为“1003 * 8”,而 操作系统所在的段为“2 * 8”,因此我们不能使用普通的CALL,而应该使用far-CALL。 far-CALL实际上和far-JMP一样,只要同时指定段和偏移量即可。既然用了far-CALL,就必须相应地使用far-RET,也就是RETF指令。
注释:CALL  2*8:0xbe3代码行中的0xbe3,是bootpack.map文件中的_asm_cons_putchar的地址,由于每个电脑生成的bootpack.map文件中的地址不相同,所以需要根据自身情况进行调整。

20.2 结束应用程序

实现的功能位:当应用程序执行完成后,返回操作系统。执行完CALL     2*8:0xbe3后执行RETF就可以返回操作系统了。 相应地,操作系统这边也需要用 CALL 来代替 JMP 启动应用程序才对。
/*                console.c                */
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) {
		/* 找到了与”HLT.HRB“字符串相同的文件 */
		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 {
		/* 未找到与”HLT.HRB“字符串相同的文件 */
		putfonts8_asc_sht(cons->sht, 8, cons->cur_y, COL8_FFFFFF, COL8_000000, "File not found.", 15);
		cons_newline(cons);
	}
	cons_newline(cons);
	return;
}

/*                naskfunc.nas                */
_farcall: 						; void farcall(int eip, int cs);
		CALL 	FAR [ESP+4] 	; eip, cs
		RET

/*                hlt.nas                */
[BITS 32]
	MOV 	AL,'A'
	CALL 	2*8:0xbf1	;_asm_cons_putchar函数地址,指定段和偏移量
	RETF

20.3 不随操作系统版本而改变的API

在20.2节中提到 CALL  2*8:0xbe3代码行中的0xbe3数值是不确定的,如何在不修改的情况下准确得到该地址呢?可以考虑用中断,在 MOV     AL,'A'后执行中断(中断代码中有asm_cons_putchar函数地址)。就像文件dsctbl.c中鼠标和键盘中断一样,进行规定,按照设想0x20~0x2f是存放 IRQ的,所以用0x40号。
/*                dsctbl.c                */
void init_gdtidt(void)
{
    ( 中略)
    /* IDT的设置*/
    set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);
    set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
    set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
    set_gatedesc(idt + 0x40, (int) asm_cons_putchar, 2 * 8, AR_INTGATE32); /*这里!*/
    return;
}

/*                hlt.nas                */
[BITS 32]
    MOV  AL,'h'
    INT  0x40   ;这里
    MOV  AL,'e'
    INT  0x40   ;这里
    MOV  AL,'l'
    INT  0x40   ;这里
    MOV  AL,'l'
    INT  0x40   ;这里
    MOV  AL,'o'
    INT  0x40   ;这里
    RETF

/*                naskfunc.nas                */
_asm_cons_putchar:
		PUSH	1				; move=1,占4字节
		AND		EAX,0xff		; 将AH和EAX的高位置0,将EAX置为已存入字符编码的状态
		PUSH	EAX				; chr = [EAX]地址存的元素,占4字节
		PUSH	DWORD [0x0fec]	; 读取内存并PUSH该值,该地址值就是输入参数cons,占4字节
		CALL	_cons_putchar
		ADD		ESP,12			; 所以是移动12位将栈中的数据丢弃
		IRETD       ;这里

20.4 为应用程序自由命名

从小节标题可看出,按照在命令窗口中的输入字符进行查找文件。在之前章节中已经实现了 mem cls dir 、type等命令,按照设想当输入命令时查找文件,如果有则执行该文件,否则显示"Bad command.'',并不仅仅对'hlt'命令有效。
在原函数 cmd_hlt 的基础上稍作修改后得到 cmd_app 函数,其中输入参数增加了个cmdline(命令字符串),并在函数内对命令进行处理(文件名有无后缀)。
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++){//将cmdline转换成name
		if(cmdline[i] <= ' ')break;//空格32 换行回车等操作的ASCII码均小于32
		name[i] = cmdline[i];
	}
	name[i] = 0;
	finfo = file_search(name, (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);//寻找与name相同的文件
	if(finfo == 0 && name[i-1] != '.'){//没找到,则增加后缀名再次查找
		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) {
		/* 找到了与字符串name相同的文件 */
		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);
		cons_newline(cons);
		return 1;
	}
	return 0;
}

20.5 当心寄存器

修改hello.nas文件,用一个循环持续向AL赋值。INT 0x40之后ECX寄存器的值可能会发生变化所导致的,因此加上了PUSHAD和POPAD,确保可以将全部寄存器的值还原。
/*                hello.nas                */
[INSTRSET "i486p"]
[BITS 32]
	MOV 	ECX,msg
	
putloop:
	MOV		AL,[CS:ECX]
	CMP 	AL,0		;AL==0则跳转
	JE 		fin
	INT 	0x40
	ADD 	ECX,1
	JMP 	putloop
fin:
	RETF
msg:
	DB		"hello",0
/*                naskfunc.nas                */
_asm_cons_putchar:
		STI
		PUSHAD	                ;这里				
		PUSH	1				; move=1,占4字节
		AND		EAX,0xff		; 将AH和EAX的高位置0,将EAX置为已存入字符编码的状态
		PUSH	EAX				; chr = [EAX]地址存的元素,占4字节
		PUSH	DWORD [0x0fec]	; 读取内存并PUSH该值,该地址值就是输入参数cons,占4字节
		CALL	_cons_putchar
		ADD		ESP,12			; 所以是移动12位将栈中的数据丢弃
		POPAD                   ;这里
		IRETD

20.6 用API显示字符串

从其他操作系统的显示字符串的 API来看,一般有两种方式:一种是显示一串字符,遇到字符编码0则结束,如函数cons_putstr0;另一种是先指定好要显示的字符串的长度再显示,如函数cons_putstr1。
//打印字符串
void cons_putstr0(struct CONSOLE *cons, char *s){
	for(; *s != 0; s++)
		cons_putchar(cons, *s, 1);
	return;
}
//打印s中前l个字符
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_runcmd、cmd_mem、cmd_dir、cmd_type进行修改,调用函数 cons_putstr0或cons_putstr1,这里就不做展示了。
以前调用的是cons_putchar函数只能打印一个字符,有了上述两个函数,并参考BIOS的调用方式,执行INT 0x40时就可以直接调用这些函数实现打印字符串。
功能号 1…… 显示单个字符( AL = 字符编码)
功能号 2…… 显示字符串 0 EBX = 字符串地址)
功能号 3…… 显示字符串 1 EBX = 字符串地址, ECX = 字符串长度)
思路如下:cmd_app函数调整执行IDT中1003 * 8地址,该地址存放的是要执行的文件数据(下面以文件hello为例)。文件hello中对ecx等寄存器进行赋值,并产生中断0x40,该中断调用函数asm_hrb_api中的hrb_api,而hrb_api是根据edx寄存器的值执行相应字符或字符串的打印操作。
/*                hello.nas                */
[INSTRSET "i486p"]
[BITS 32]
	MOV 	ECX,msg
	MOV 	EDX,1
putloop:
	MOV		AL,[CS:ECX]
	CMP 	AL,0		;AL==0则跳转
	JE 		fin
	INT 	0x40
	ADD 	ECX,1
	JMP 	putloop
fin:
	RETF
msg:
	DB		"hello",0

/*                dsctbl.c                */
set_gatedesc(idt + 0x40, (int) asm_hrb_api,      2 * 8, AR_INTGATE32);

/*                naskfunc.nas                */
_asm_hrb_api:
		STI
		PUSHAD					; 用于保存寄存器值的PUSH
		
		PUSHAD					; 用于向hrb_api传值的PUSH
		CALL 	_hrb_api
		ADD		ESP,32
		POPAD
		IRETD

/*                console.c                */
//根据输入参数打印数据 eax为当前字符 ebx为字符串 ecx长度
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);
	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;
}
  • 16
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值