Prev | Tutorial 09: System Call and Executing Programs on Disk |
---|
In this tutorial, Skelix has the ability of loading programs(a shell if you like) from disk and execute them, the user programs can invoke kernel functions via system calls.
After last 8 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 it is getty()
function prints a login prompt and waits 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 be aware of. 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),
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.s
sys_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 call 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 talking to kernel is via system calls, so we have to make this system call visible to users.
DOS provides
for it's system services, and Linux provides int
21
to provides system calls, we are going to use an entry in IDT to enable users use system calls by instruction int
0x80
.int
0x80
We only used 34 entries in IDT, rest of them all are available, 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.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;
}
The macro SYS_CALL
has been defined to 0x80. We can see the sys_call
we used as a wrapper of all system calls.
09/isr.s
sys_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 checks 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.c
void
(*sys_call_table[VALID_SYSCALL])(void
) = {sys_print};
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.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
(;;)
;
}
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 color.
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 the creation of file system.
First, I wrote a program to print the content of a file in array format, certainly you have to compile 09/ghex/ghex.c
and 09/color.c
at first.
In Makefile, the entry for color
should be written like this,
09/Makefile
all: 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.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);
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.
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);
}
Very simple, we just load the first sector of color file, and copy it to address 0x100000.
Still remember task1_run
and task2_run
? It's been so boring for them just flipping letters on screen,
so I'm going to let task1 load and execute color
.
09/init.c
void
do_task1(void
) {
__asm__
("incb
0xb8000+160*24+2");
load_color();
__asm__
("jmp
0x100000");
}
Now we put them together in 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();
}
we save file color
in directory /
, and we restat the content of directory /
,
we shoule see a new file color
was found.
Now make it with last Makefile,
Prev | Tutorial 09: System Call and Executing Programs on Disk |
---|
Feel free to use my code. Please contact me if you have any questions.