我们的目标

在本节中,Skelix将有能力从磁盘载入程序(可以是shell)并执行它,用户程序可以通过系统调用的方式使用内核程序。

下载代码


系统调用

在8节教程之后,我们几乎为内核准备了所有的事情,大体吧……现在是时间让shell接过控制权了,实际上是getty()打印提示符并等待用户登录。当然,我不准备写一个shell或getty()。然而在本节中,我要给Skelix从文件系统载入文件并执行的能力,实际上,它给了操作系统内核加载并执行磁盘任务(类*nix系统中的init文件或shell)的能力。

因为Skelix的虚拟内存部分没有完成,所以所有的任务共享相同的内存空间,简化起见,从磁盘载入的程序将被放置在内存0x100000处,文件就放置在/下。

在这之前,还有一件事需要注意。实际上,用户任务不能存取内核内存空间,因此像kprintfprint_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用int 21来提供系统服务,Linux用int 0x80提供系统调用,我们准备用一个IDT项来允许用户使用系统调用,像int 0x80这样。

在IDT中我们只用了34个项,其余的还可以使用,但是我准备使用0x80。所以我们必须在IDT中设置一个386陷阱门。386陷阱门的格式和中断门很相似,只是类型域是E而不是8,并且它的DPL必须是3以允许用户使用。

09/init.c

static void

sys_call_install(void) {

        unsigned long long sys_call_entry = 0x0000ef0000080000ULL |

                        ((unsigned long long)CODE_SEL<<16);

        sys_call_entry |= ((unsigned long long)sys_call<<32) & 

                0xffff000000000000ULL;

        sys_call_entry |= ((unsigned long long)sys_call) & 0xffff;

        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.c09/color.c。在Makefile中,color应该这样写,

09/Makefile

all: final.img color

 

color: color.o

    ${LD} --oformat binary -N -e color -Ttext 0x100000 -o color $<

让它知道它会在0x100000处载入。

ghex

像下面这样把这些十六进制数拷到源文件中去,我们准备把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_runtask2_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,

making process of tutorial09 possible colors


你可以自由使用我的代码,如有疑问请联系我