We'll add a system call to Xv6 called 'hello()' that prints "Hello: name (#)" to the console, where name is a user defined string and # is a user defined integer. The prototype for hello() is thus:
hello()
name
#
int hello(char *name, int num);
To add our system call we need to add to the following files in the xv6 directory:
xv6
syscall.h // Defined the system call numbers syscall.c // Defines the system call table usys.S // Creates the assembly for the system call routine user.h // Defines the prototype used by the user-space programs
Then we need to define the system call function somewhere, say: sysproc.c
sysproc.c
First in syscall.h we add an entry at the bottom such as:
syscall.h
#define SYS_hello 22
This defines the system call number for our hello() system call and should be the next available number (in this case 22.) The number is the value that is put into the eax register prior to the user-space program calling the 'int 0x40' instruction, which makes the transition to kernel space which looks at eax to determine which system call to perform.
eax
int 0x40
Then we add to syscall.c the following:
syscall.c
extern int sys_hello(void);
and
[SYS_hello] sys_hello,
to the static int (*syscalls[])(void) array: It might look something like:
static int (*syscalls[])(void)
... extern int sys_wait(void); extern int sys_write(void); extern int sys_uptime(void); extern int sys_hello(void); // Add the sys-call prototype static int (*syscalls[])(void) = { [SYS_fork] sys_fork, [SYS_exit] sys_exit, . . . [SYS_mkdir] sys_mkdir, [SYS_close] sys_close, [SYS_hello] sys_hello, // Add to the syscall table }; ...
At the bottom of the usys.S file we add a macro for our hello system call:
usys.S
SYSCALL(hello)
this setups a jump table entry for adding the system call number to the eax register then calling int 0x40 which makes the system call, then calls ret which returns to the calling function after the system call.
ret
. . SYSCALL(getpid) SYSCALL(sbrk) SYSCALL(sleep) SYSCALL(uptime) SYSCALL(hello)
We could place the system call almost anywhere in the kernel, but the sysproc.c file has many other system calls, so we will add it here at the bottom.
int sys_hello(void) { char *str; int num; // Fetches a character pointer as the first parameter if (argstr(0, &str) < 0) return -1; // Fetches an integer as the second parameter: if (argint(1, &num) < 0) return -1; // cprintf() is the kernel console print function: cprintf("Hello, %s (%d)\n", str, num); return 0; }
Notice how the system call function takes no parameters itself (i.e. void parameter list.) Parameters to system calls are pushed onto the user space processes stack, then the kernel accesses them via the processes stack pointer.
We have access to three helper functions to pull values off the user stack in our system call function, these are:
int argint(int n, int *ip)
int argstr(int n, char **pp)
int argptr(int n, char **pp, int size)
size
int argfd(int n, int *pfd, struct file **pf)
struct file
pdf
To the user.h file we add our prototype of the system call:
user.h
. . char* sbrk(int); int sleep(int); int uptime(void); int hello(char *, int); // Add the prototype for the syscall . .
Then finally modify the hello.c file we created last time to use our new system call instead of using printf():
hello.c
printf()
#include "types.h" #include "stat.h" #include "user.h" int main(void) { hello("Steve Baker", 42); exit(); }
After running make and make qemu we can run the new hello and see the console output.
make
make qemu
hello
$ hello Hello, Steve Baker (42) $