向后 | 教程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: 系统调用和执行磁盘程序 |
---|
你可以自由使用我的代码,如有疑问请联系我。