Skelix OS Tutorial
Prev Tutorial 09: System Call and Executing Programs on Disk    

Our Goal

In this tutorial, Skelix has the ability of loading programs from disk and execute them, the user programs can invoke kernel functions via system calls.

Download source file

System Call

After all those tutorials, we almost set up everything we should set up for a kernel, em, roughly, quite roughly of course, now it is the time for shell to take over the control, actually is getty() function to print a login prompt and waiting for user to login, certainly, I'm not going to write a shell, neither a getty(). However, in this tutorial, I'm going to give Skelix the ability of loading a file from file system and executing it, in practice, it give the general OS kernel the ability of loading and executing tasks resident on disks(init file on *nix-like systems or shells).

Because the virtual memory part of Skelix is still undergoing, so all tasks share the same memory space, so for simplicity, the program from disk will be loaded to a fixed address 0x100000, and the program will be stored right under the directory '/'.

Before that, there is another thing we have to notice, in reality, the user task can not access kernel memory space, so those functions like kprintf, print_c etc which we used before we can not use them anymore(actually, at this moment we still can, because I still have not worked out the virtual memory part, but we just pretend we can not use them), so in reality, all OS kernel provides APIs or system calls or whatever you call them. So we are going to let our program uses the system call to perform some operation, because it is just for giving an example, so I just write one system call:
09/isr.ssys_print:
        pushl    %esi            # bg color
        pushl    %edi            # fg color
        pushl    %ebx            # character to be printed
        cli
Does not like interrupt gate, the trap gate do not set the IF flag in EFLAGS automatically, so we use cli explicitly.        call    print_c
        sti
        addl    $12,    %esp
        ret
Quite simple, right? This system call accepts three arguments: ESI stores the background color and EDI stores the foreground color, EBX stores the character we are going to print. So eventually, this system call just print a given character, use given colors. It is nothing more than a wrapper to print_c function.

Theoretically, user can not access print_c function, actually they do not even know the existence of this function. The only way for user tasks taking to kernel is via system calls, so we have to make this system call visible to users.

DOS provides int 21 for it's system services, and Linux provides int 0x80 to provides system calls, we also going to use an entry in IDT table to enable users use system calls by instruction "int 0x80".

We only used 34 entries in IDT, so actually all rest of them are available for use, but I am going to use 0x80 entry. So we have to set up an 386 trap gate in IDT. The format of 386 trap gate is quite similar to interrupt gate, just the type field is E instead of 8, and we have to set it's DPL field to 3 to let normal users use it.
09/init.cstatic 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;
}
The macro SYS_CALL has been defined to 0x80. We can see the sys_call we used here as an offset is the wrapper of all system calls
09/isr.ssys_call:
        cmpl    $1,        %eax
        jb        1f
        iret
1:
        pushal
        call    *sys_call_table(, %eax, 4)
        popal
        iret
I keep it as simple as I can, it just check the system call number which stores in EAX, then call the corresponding function in array sys_call_table, the sys_call_table is an array of all system call function pointers
09/syscall.cvoid (*sys_call_table[VALID_SYSCALL])(void) = {sys_print};VALID_SYSCALL is defined to 1, of course

Color

Now let's check out the program we are going to store it on disk and will be loaded and executed in memory
09/color.cvoid
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 (;;)
        ;
}
Simple, right, we let the entry of this program to be color, just like the normal entry for ordinary C file is main. This program will print letter 'X' using different combinations of background and foreground colors.

However, here is the problem, how can we store it on disk, we do not have a shell, we can not just type "cp blablabla blablalb", so I decide to write the program on disk directly after creating of file system in this way:
First, I wrote a program to print the content of a file in specific format, that is for a char array in C code, well certainly you have to compile ghex/ghex.c and color.c at first, in Makefile, the entry for color should be written like this:
09/Makefileall: final.img color

color: color.o
    ${LD} --oformat binary -N -e color -Ttext 0x100000 -o color $<
Let it know it will be loaded at offset 0x100000
ghex
Then copy those hex number to source code like following, we are going to write the color program to disk
09/fs.cvoid
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);
Allocates new inode and block for color program and set the correct information in its inode and write the inode back to disk.
    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);
Add the new entry about color to directory '/'
    iget(&sb, &iroot, 0);
    iroot.i_size = 3*sizeof(struct DIR_ENTRY);
    iput(&sb, &iroot, 0);
One new file "color" has been created under directory '/', so, the inode of directory '/' has been changed, write it back to disk
}
Now we have color file on disk, we have to load it to address 0x100000.
09/fs.cvoid
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);
}
Very simple, we just load the first sector of color file, and copy it to address 0x100000

Remember task1_run and task2_run, that's so boring for them just flipping letters on screen, so I'm going to let task1 load and execute color program.
09/init.cvoid
do_task1(void) {
    __asm__ ("incb 0xb8000+160*24+2");
    load_color();
    __asm__ ("jmp 0x100000");
}

Now we integrate them together in check_root
09/fs.c    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();
    }
we save file color in directory '/', and we restat the content of directory '/', we actually can see new file color was found.

Now make it with last Makefile
make t9
possible colors

Subject:

Your Name:

Your Email Address:

Comments:


Prev Home    
Up