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.
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
retQuite 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
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};
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};
/* 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.