| 向后 | 教程09: 系统调用和执行磁盘程序 |
|---|
在8节教程之后,我们几乎为内核准备了所有的事情,大体吧……现在是时间让shell接过控制权了,实际上是getty()打印提示符并等待用户登录。当然,我不准备写一个shell或getty()。然而在本节中,我要给Skelix从文件系统载入文件并执行的能力,实际上,它给了操作系统内核加载并执行磁盘任务(类*nix系统中的init文件或shell)的能力。
因为Skelix的虚拟内存部分没有完成,所以所有的任务共享相同的内存空间,简化起见,从磁盘载入的程序将被放置在内存0x100000处,文件就放置在/下。
在这之前,还有一件事需要注意。实际上,用户任务不能存取内核内存空间,因此像kprintf、print_c之类的函数我们不能再使用了(实际上是可以的,因为内存保护部分没有完成,先假装不可以)。在实际中,所有的操作系统内核都提供API或者系统调用之类的东西。所有我们也要让我们的程序使用系统调用来完成一些任务,因为只是写个例子,我就只写一个系统调用,
09/isr.s
sys_print:
pushl %esi # bg color
pushl %edi # fg color
pushl %ebx # character to be printed
cli
不像中断门,陷阱门不自动设置EFLAGS的IF位,所以我们显式的调用cli。
call print_c
sti
addl $12, %esp
ret
很简单,对吧?这个系统调用接受三个参数:ESI存储了背景色、EDI存储了前景色、
EBX存储了我们要输出的字符。最终,这个系统调用只是用给定的颜色输出一个给定的字符。它就是print_c的外壳。
理论上,用户不能访问print_c,实际上他们都不会知道它的存在。系统调用是用户任务和内核交流的唯一手段,所以我们必须让用户知道这个系统调用。
DOS用来提供系统服务,Linux用int 21提供系统调用,我们准备用一个IDT项来允许用户使用系统调用,像int 0x80这样。int 0x80
在IDT中我们只用了34个项,其余的还可以使用,但是我准备使用0x80。所以我们必须在IDT中设置一个386陷阱门。386陷阱门的格式和中断门很相似,只是类型域是E而不是8,并且它的DPL必须是3以允许用户使用。
09/init.c
static void
sys_call_install(void) {
unsigned long sys_call_entry = 0x0000ef0000080000ULL |long
((unsigned long)CODE_SEL<<16);long
sys_call_entry |= ((unsigned long)sys_call<<32) & long
0xffff000000000000ULL;
sys_call_entry |= ((unsigned long)sys_call) & 0xffff;long
idt[SYS_CALL] = sys_call_entry;
}
宏SYS_CALL被定义为0x80。sys_call被用作所有系统调用的外壳。
09/isr.s
sys_call:
cmpl $1, %eax
jb 1f
iret
1:
pushal
call *sys_call_table(, %eax, 4)
popal
iret
我尽量让它简单,它仅仅检查系统调用的序号,并把它存于EAX,接着调用数组sys_call_table中的相应程序,sys_call_table是一个保存了所有系统调用的数组。
09/syscall.c
void (*sys_call_table[VALID_SYSCALL])(void) = {sys_print};
现在看一下这个准备存于硬盘,并要载入内存执行的程序。
09/color.c
void
color(void) {
int i, j;
for (i=0; i<16; ++i)
for (j=0; j<16; ++j)
__asm__ ("int $0x80"::"S"(i),"D"(j),"b"('X'),"a"(0));
for (;;)
;
}
简单吧?我们让程序在color处入口,就像正常的C文件的入口是main一样。这个程序用不同的前后背景色组合打印字符x。
然而,这里有个问题,我们怎样把它写入硬盘呢,我们没有shell,所以不能用cp blablabla blablalb,所以我决定当建立文件系统时直接把它写入磁盘。
首先写一个可以输出文件内容到数组格式的程序,你需要编译09/ghex/ghex.c和09/color.c。在Makefile中,color应该这样写,
09/Makefile
all: final.img color
color: color.o
${LD} --oformat binary -N -e color -Ttext 0x100000 -o color $<
让它知道它会在0x100000处载入。
像下面这样把这些十六进制数拷到源文件中去,我们准备把color程序写入磁盘。
09/fs.c
void
install_color(void) {
struct SUPER_BLOCK sb;
char sect[512] = {0};
struct DIR_ENTRY *de = NULL;
int inode = -1;
struct INODE clnode;
unsigned int blk = 0;
unsigned char color[] = {0x57,0x56,0x53,0x83,0xec,0x08,0xc7,0x44,
0x24,0x04,0x00,0x00,0x00,0x00,0x83,0x7c,0x24,0x04,0x0f,0x7f,
0x2e,0xc7,0x04,0x24,0x00,0x00,0x00,0x00,0x83,0x3c,0x24,0x0f,
0x7f,0x19,0x8b,0x74,0x24,0x04,0x8b,0x3c,0x24,0xbb,0x58,0x00,
0x00,0x00,0xb8,0x00,0x00,0x00,0x00,0xcd,0x80,0x89,0xe0,0xff,
0x00,0xeb,0xe1,0x8d,0x44,0x24,0x04,0xff,0x00,0xeb,0xcb,0xeb,
0xfe};
sb.sb_start = *(unsigned int *)(HD0_ADDR);
hd_rw(ABS_SUPER_BLK(sb), HD_READ, 1, sect);
memcpy(&sb, sect, sizeof(struct SUPER_BLOCK));
inode = alloc_inode(&sb);
assert(inode > 0);
blk = alloc_blk(&sb);
assert(blk != 0);
clnode.i_block[0] = blk;
hd_rw(blk, HD_WRITE, 1, color);
clnode.i_mode = FT_NML;
clnode.i_size = sizeof color;
iput(&sb, &clnode, inode);
为color程序分配新的inode和块,在inode中写入信息后,把此inode写回磁盘。
hd_rw(iroot.i_block[0], HD_READ, 1, sect);
de = &((struct DIR_ENTRY *)sect)[2];
strcpy(de->de_name, "color");
de->de_inode = inode;
hd_rw(iroot.i_block[0], HD_WRITE, 1, sect);
在/中加入关于color的新项。
iget(&sb, &iroot, 0);
iroot.i_size = 3*sizeof(struct DIR_ENTRY);
iput(&sb, &iroot, 0);
}
新文件color已经在目录/下建立了,/的inode也被改变,把它写回磁盘。
现在color文件在磁盘上了,我们必须把它载入地址0x100000。
void
load_color(void) {
struct INODE inode;
struct SUPER_BLOCK sb;
char sect[512] = {0};
sb.sb_start = *(unsigned int *)(HD0_ADDR);
hd_rw(ABS_SUPER_BLK(sb), HD_READ, 1, sect);
memcpy(&sb, sect, sizeof(struct SUPER_BLOCK));
iget(&sb, &inode, 1);
/* for simplicity, just load the first sector of color to 0x100000 */
hd_rw(inode.i_block[0], HD_READ, 1, (void *)0x100000);
}
很简单,我们只需载入color文件的第一个扇区,再把它拷贝到0x100000处。
记得task1_run和task2_run吗?它们只是很无聊的在屏幕上翻字符,现在我要让任务1载入并执行color。
09/init.c
void
do_task1(void) {
__asm__ ("incb 0xb8000+160*24+2");
load_color();
__asm__ ("jmp 0x100000");
}
把它们全放在check_root中。
if (! testb(sect, 0)) {
kprintf(KPL_DUMP, "/ has not been created, creating....\t\t\t\t\t ");
if (alloc_inode(&sb) != 0) {
kprintf(KPL_PANIC, "\n/ must be inode 0!!!\n");
halt();
}
iroot.i_block[0] = alloc_blk(&sb);
iput(&sb, &iroot, 0);
hd_rw(iroot.i_block[0], HD_READ, 1, sect);
de = (struct DIR_ENTRY *)sect;
strcpy(de->de_name, ".");
de->de_inode = 0;
++de;
strcpy(de->de_name, "..");
de->de_inode = -1;
hd_rw(iroot.i_block[0], HD_WRITE, 1, sect);
kprintf(KPL_DUMP, "[DONE]");
if (iroot.i_size == 2*sizeof(struct DIR_ENTRY))
install_color();
}
在目录/下保存color,并重新获取/的文件信息,我们应该看到color文件。
用最后一个Makefile,
| 向后 | 教程09: 系统调用和执行磁盘程序 |
|---|
你可以自由使用我的代码,如有疑问请联系我。