unixoperating-systemsystem-callsxv6

Order of execution of system call in xv6


When we use the system call (at the user level), we never put the sys_ prefix, but why when we call the system call function, first the system call handler function (which is prefixed with sys_ )is called.Here we called the sleep function, but it enters the sys_sleep() function and then enters the main sleep() function. : for example in user level program :

int main(int argc,char* argv[])
{  
    sleep(2);
    return 0
}

Here we called the sleep function, but it enters the sys_sleep() function and then enters the main sleep function. :

int
sys_sleep(void)
{
  int n;
  uint ticks0;

  if(argint(0, &n) < 0)
    return -1;
  acquire(&tickslock);
  ticks0 = ticks;
  while(ticks - ticks0 < n){
    if(myproc()->killed){
      release(&tickslock);
      return -1;
    }
    sleep(&ticks, &tickslock); //Here the main sleep() function is called.
  }
  release(&tickslock);
  return 0;
}

How does this happen? We didn't write call sys_sleep() in the user level program, but it entered there first, then the main sleep() function was called.


Solution

  • The sleep() function is a standard UNIX function normally found in unistd.h. Most functions in the standard library are implemented by providing a shared library that is linked to your executable. The functions in this shared library will use system calls to ask the OS kernel to provide services related to the function you called.

    The name of the function itself is often masked with macros. In the header that you include, the macro is defined like

    #define sleep sys_sleep
    

    You are calling sleep from your code but the compiler replaces every occurence of sleep with sys_sleep. The shared library itself isn't the system call. The shared library is a wrapper to simplify making system calls and modularize the compiler. The compiler doesn't meddle with syscalls as long as you don't call in that shared library and link it with your code. When you do this, the shared library contains code such as

    mov rax, SYSCALL_NUMBER
    syscall
    

    The first instruction puts the syscall number in a register and syscall is executed which makes the processor jump at the syscall handler setup by the OS at boot. The handler looks at rax and switches on the value to determine what syscall it is handling. To help you understand, I made a small Linux example:

    Create the 3 following files:

    main.c

    #include "shared.h"
    
    int main(int argc, char* argv[]){
        print("hello\n");
    }
    

    shared.c

    #include <stdio.h>
    #include "shared.h"
    
    void sys_print(char* str){
        printf(str);
    }
    

    shared.h

    #ifndef SHARED
    #define SHARED
    #define print sys_print
    
    void sys_print(char* str);
    
    #endif
    

    Now run the following:

    gcc -g -fPIC -shared -o libshared.so shared.c
    gcc -g main.c -omain -I. -L. -lshared
    export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH
    gdb -ex start --args ./main
    

    You should see something like:

    Reading symbols from ./main...
    Temporary breakpoint 1 at 0x115c: file main.c, line 4.
    Starting program: /home/user/main 
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
    
    Temporary breakpoint 1, main (argc=1, argv=0x7fffffffe0f8) at main.c:4
    4       print("hello\n");
    (gdb)
    

    Now you can type layout asm and press enter to see the assembly while you step in it. Type si then press enter repeatedly to single step instructions. You'll see that you get similar results than with your sleep() syscall. This is exactly what is done with the standard library implementation except that the shared library will be linked with small assembly snippets such as the ones containing the instruction syscall.