本章主要介绍了对 _alloca 函数的兼容,日文的显示,以及着重介绍了文件系统操作。
一 对_alloca的支持
首先作者写了一个小应用程序,功能是找出并打印1000以内的质数:
#include <stdio.h>
#include "apilib.h"
#define MAX 1000
void HariMain(void)
{
char flag[MAX], s[8];
int i, j;
for (i = 0; i < MAX; i++) {
flag[i] = 0;
}
for (i = 2; i < MAX; i++) {
if (flag[i] == 0) {
/*没有标记的为质数*/
sprintf(s, "%d ", i);
api_putstr0(s);
for (j = i * 2; j < MAX; j += i) {
flag[j] = 1; /*给它的倍数做上标记*/
}
}
}
api_end();
}
当我们需要显示10000以内的质数,将MAX置为10000时,问题就出现了,编译出现一条警告“Warning: can't link __alloca”。出现问题的原因在于,C语言编译器规定,如果栈中的变量超过4Kb,则需要调用 __alloca 函数(该函数的主要功能是根据操作系统的规格来获取栈中的空间),而操作系统中并未对此做特殊支持。
为解决这个问题,我们需要编写一个 __alloca 函数,只对ESP进行减法运算,而不做其他任何多余的操作。
为了理解上面的操作,下面列举几种错误示范:
【1】错误示范一:
SUB ESP, EAX
RET
这个程序是无法运行的,因为 RET 返回的地址保存在了 ESP 中,而 ESP 的值被改变了,于是会读取到错误的返回值(RET 其实相当于 POP EIP)。
【2】错误示范二:
SUB ESP, EAX
JMP DWORD [ESP + EAX]
这样貌似不错,ESP 似乎又回到了 SUB 之前的值。但是在相当于 RET 指令的 POP EIP 指令中,实际上又包含两条指令:
MOV EIP, [ESP]
ADD ESP, 4
也就是说我们忘了给ESP + 4。
【3】错误示范三:
SUB ESP, EAX
JMP DWPRD[ESP + EAX] ; replace RET
ADD ESP, 4
这个程序的问题在于ADD指令的位置,将ADD指令放在了JMP指令的后面,所以是不可能被执行的,因此也失败了。
【4】正确示范:
结合上面失败的教训,编写如下的 alloca.nas 文件,将其放在 apilib 中:
__alloca:
ADD ESP, -4
SUB ESP, EAX ; 要执行的操作从栈中分配EAX个字节的内存空间
JMP DWORD [ESP + EAX] ; replace RET
这样便兼容了 __alloca 函数。
二 文件系统操作
在任何操作系统中,文件系统FS的操作无疑是很重要的一部分。文件系统操作主要分为打开(open)、定位(seek)、读取(read)、写入(write)、关闭(close)。目前操作系统还不支持对磁盘的写入操作,所以先设计能读取文件内容的API。
【1】设计API:
【2】依据此修改 console.c 与 bootpack.h:
/* bootpack.h */
struct TASK {
// ...
struct FILEHANDLE *fhandle; //用来存放应用程序所打开文件的信息
int *fat;
// ...
};
struct FILEHANDLE {
char *buf;
int size;
int pos;
};
/* console.c */
void console_task(struct SHEET *sheet, int memtotal) {
// ...
struct FILEHANDLE fhandle[8];
// ...
for (i = 0; i < 8; i++) {
fhandle[i].buf = 0; /*未使用标记*/
}
task->fhandle = fhandle;
task->fat = fat;
// ...
}
void cmd_app(...) {
// ...
for (i = 0; i < 8; i++) { /*将未关闭的文件关闭*/
if (task->fhandle[i].buf != 0) {
memman_free_4k(memman, (int) task->fhandle[i].buf, task->fhandle[i].size);
task->fhandle[i].buf = 0;
}
}
// ...
}
void hrb_api(...) {
else if (edx == 21) { /* load file */
for (i = 0; i < 8; i++) {
if (task->fhandle[i].buf == 0) {
break;
}
}
fh = &task->fhandle[i];
reg[7] = 0;
if (i < 8) {
finfo = file_search((char *) ebx + ds_base, (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);
if (finfo != 0) {
reg[7] = (int) fh;
fh->buf = (char *) memman_alloc_4k(memman, finfo->size);
fh->size = finfo->size;
fh->pos = 0;
file_loadfile(finfo->clustno, finfo->size, fh->buf, task->fat, (char *) (ADR_DISKIMG + 0x003e00));
}
}
} else if (edx == 22) { /* close file */
fh = (struct FILEHANDLE *) eax;
memman_free_4k(memman, (int) fh->buf, fh->size);
fh->buf = 0;
} else if (edx == 23) { /* locate file */
// ebx : offset
fh = (struct FILEHANDLE *) eax;
if (ecx == 0) {
fh->pos = ebx;
} else if (ecx == 1) {
fh->pos += ebx;
} else if (ecx == 2) {
fh->pos = fh->size + ebx;
}
if (fh->pos < 0) {
fh->pos = 0;
}
if (fh->pos > fh->size) {
fh->pos = fh->size;
}
} else if (edx == 24) { /* get file's size */
fh = (struct FILEHANDLE *) eax;
if (ecx == 0) {
reg[7] = fh->size;
} else if (ecx == 1) {
reg[7] = fh->pos;
} else if (ecx == 2) {
reg[7] = fh->pos - fh->size;
}
} else if (edx == 25) { /* read file */
fh = (struct FILEHANDLE *) eax;
for (i = 0; i < ecx; i++) {
if (fh->pos == fh->size) {
break;
}
*((char *) ebx + ds_base + i) = fh->buf[fh->pos];
fh->pos++;
}
reg[7] = i;
}
}
【3】添加几个API的汇编:
;api021.nas节选(打开文件api)
_api_fopen: ; int api_fopen(char *fname);
PUSH EBX
MOV EDX,21
MOV EBX,[ESP+8] ; fname
INT 0x40
POP EBX
RET
;api022.nas节选(关闭文件api)
_api_fclose: ; void api_fclose(int fhandle);
MOV EDX,22
MOV EAX,[ESP+4] ; fhandle
INT 0x40
RET
;api023.nas节选(文件定位api)
_api_fseek: ; void api_fseek(int fhandle, int offset, int mode);
PUSH EBX
MOV EDX,23
MOV EAX,[ESP+8] ; fhandle
MOV ECX,[ESP+16] ; mode
MOV EBX,[ESP+12] ; offset
INT 0x40
POP EBX
RET
;api024.nas节选(获取文件大小api)
_api_fsize: ; int api_fsize(int fhandle, int mode);
MOV EDX,24
MOV EAX,[ESP+4] ; fhandle
MOV ECX,[ESP+8] ; mode
INT 0x40
RET
;api025.nas节选(文件读取api)
_api_fread: ; int api_fread(char *buf, int maxsize, int fhandle);
PUSH EBX
MOV EDX,25
MOV EAX,[ESP+16] ; fhandle
MOV ECX,[ESP+12] ; maxsize
MOV EBX,[ESP+8] ; buf
INT 0x40
POP EBX
RET
【4】最后是应用程序的实现,功能是通过上述的几个文件系统API,打开文件 ipl10.nas ,并将其内容打印出来:
#include "apilib.h"
void HariMain(void)
{
int fh;
char c;
fh = api_fopen("ipl10.nas");
if (fh != 0) {
for (;;) {
if (api_fread(&c, 1, fh) == 0) {
break;
}
api_putchar(c);
}
}
api_end();
}
实现效果如下:
三 命令行API
与之前文章中提到过的type命令有区别(【操作系统】30天自制操作系统--(18)应用程序),这边的typeipl是应用程序,而非命令行命令。之前的type命令,能够查找文件,并从对应的内存区间把文件内容拎出来。而我们这边的应用程序,不仅可以将文件内容打印出来,还可以通过 Shift + F1命令强制结束。所以,我们考虑用应用程序代替type命令。
现在该应用程序只能显示ipl10.nas文件,要想做到通用,我们需要实现能任意指定文件名的功能。例如我们输入“type ipl10.nas”这样的命令时,获取后面的文件名,因此我们就要编写一个API来获取命令行。
【1】设计API:
【2】据此修改 console.c 和 bootpack.h:
//bootpack.h
struct TASK{
//中略
char *cmdline;
}
//console.c
void console_task(struct SHEET *sheet, int memtotal)
{
//中略
task->cons = &cons;
task->cmdline = cmdline; //这里
//中略
}
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
//中略
} else if (edx == 26) {
i = 0;
for (;;) {
*((char *) ebx + ds_base + i) = task->cmdline[i];
if (task->cmdline[i] == 0) {
break;
}
if (i >= ecx) {
break;
}
i++;
}
reg[7] = i;
}
return 0;
}
【3】API汇编:
_api_cmdline: ; int api_cmdline(char *buf, int maxsize);
PUSH EBX
MOV EDX,26
MOV ECX,[ESP+12] ; maxsize
MOV EBX,[ESP+8] ; buf
INT 0x40
POP EBX
RET
【4】最后是应用程序 type.c 的实现:
#include "apilib.h"
void HariMain(void)
{
int fh;
char c, cmdline[30], *p;
api_cmdline(cmdline, 30);
for (p = cmdline; *p > ' '; p++) { } /*跳过之前的内容,直到遇到空格*/
for (; *p == ' '; p++) { } /*跳过空格*/
fh = api_fopen(p);
if (fh != 0) {
for (;;) {
if (api_fread(&c, 1, fh) == 0) {
break;
}
api_putchar(c);
}
} else {
api_putstr0("File not found.\n");
}
api_end();
}
该应用程序可以自动识别命令后面跟着的文件名,查找该文件并读取其内容。命令行执行“type ipl10.nas”效果如下:
四 日文显示
日文的显示没啥好看的,就不详细介绍了,有兴趣自己去看书好了。