Logo  

CS471/571 - Operating Systems

Lesson 6

Adding a System Call to Xv6

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:

int hello(char *name, int num);

To add our system call we need to add to the following files in the xv6 directory:

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

syscall.h

First in syscall.h we add an entry at the bottom such as:

#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.

syscall.c

Then we add to syscall.c the following:

extern int sys_hello(void);

and

[SYS_hello] sys_hello,

to the static int (*syscalls[])(void) array: It might look something like:

...
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
};

...

usys.S

At the bottom of the usys.S file we add a macro for our hello system call:

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.

.
.
SYSCALL(getpid)
SYSCALL(sbrk)
SYSCALL(sleep)
SYSCALL(uptime)
SYSCALL(hello)

sysproc.c

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)

  • Fetches the nth argument as an integer.

int argstr(int n, char **pp)

  • Fetches the nth argument as a string pointer

int argptr(int n, char **pp, int size)

  • Fetches the nth argument as a pointer to size amount of memory

int argfd(int n, int *pfd, struct file **pf)

  • Fetches an integer which represents a file descriptor. The descriptor is used to obtain a pointer to the processes internal struct file structure that the kernel uses for opened files. pdf if not NULL will be the file descriptor value itself.

user.h

To the user.h file we add our prototype of the system call:

.
.
char* sbrk(int);
int sleep(int);
int uptime(void);
int hello(char *, int);     // Add the prototype for the syscall
.
.

hello.c

Then finally modify the hello.c file we created last time to use our new system call instead of using 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.

$ hello
Hello, Steve Baker (42)
$