lunduniversity.lu.se

Computer Science

Faculty of Engineering, LTH

Denna sida på svenska This page in English

Lab2: Basic PintOS System Calls

Introduction

System calls are an interface to kernel services that the kernel provides to user-space programs. Normally, system calls should be used only in user-space code, while kernel code should use other ways to access the same services.

On most CPU architectures, system calls are implemented by placing the arguments to the system call function on the stack or in predefined registers and then raising a software interrupt, that transfers control to the kernel code. The kernel code then interprets the arguments to the system call function and provides the requested service.

Learning outcomes

After completing this assignment, you will understand the interface between user-space and kernel-space programs. In addition, you will also be able to extend a kernel interface with new services.

Assignment

In this assignment, you should implement the following system calls:

  • bool create (const char *file, unsigned initial_size) - create a file with a given name and size;
  • bool remove (const char *file) - delete a file with a given name;
  • int open (const char *file) - returns a file handle to a file identified by its name;
  • void close (int fd) - closes a file identified by a file handle;
  • int read (int fd, void *buffer, unsigned length) - reads a requested amount of bytes from a file identified by a handle;
  • int write (int fd, const void *buffer, unsigned length) - writes a requested amount of bytes from a file identified by a handle;
  • void seek (int fd, unsigned position) - moves the cursor to a given position inside a file;
  • int filesize (int fd) - returns the file size in bytes;
  • void halt(void) - shuts down the operating system.

Task: Read sections 3.1, 3.3.4 and 3.5 from the PintOS documentation.

First, we compile a user-space program that exercises these system calls. The source of the program is in src/examples/test-syscalls.c. We build that program by running:

$ cd src/examples
$ make

which produces an executable, src/examples/test-syscalls. To run that executable, do

$ cd src/userprog
$ make
$ pintos -k -v -T 60 --qemu --filesys-size=4 -p \
       /examples/test-syscalls \
       -a t1 -- -q -f ls run t1

The output of the emulator also contains the output of program t1, starting after a line containing the string Executing 't1':.

Task: Identify the output of the program (if any) and take note of it.

The user-space implementation of system calls is provided in src/lib/user/syscall.c. Study this implementation as well as the implementation of the syscall macros in the same file.

Task: Provide a written description of the syscall macros. What are they doing? What are their arguments? How are they placed on the stack?

Before we start implementing the handling of system calls, let's locate the syscall_handler function and study its contents. For now, it should print an error message and then exit. However, that error string is not to be found in the output of the emulator. That is because the kernel does not wait for the process running the program to exit.

Task: Locate the function where the kernel waits for a process to terminate1 and provide an implementation that never exits2. Now, after you are done implementing the waiting function, run the emulator again and look for the output of the t1 program. Make sure that the error message from syscall_handler is printed at least once.

Now, we continue with the implementation of the system call handlers for the required system calls.

Task: Provide the kernel-space implementation of system calls by implementing the function static void syscall_handler(struct intr_frame *f), defined in src/userprog/syscall.c. We recommend that you implement each system call in a separate function and just use the syscall_handler function to dispatch to the proper implementation. You should start by implementing the write system call, in particular for the case when fd == STDOUT_FILENO. This enables the use of printf is user-space code and thus facilitates debugging. Some system calls receive pointers as arguments, so the system call handler must ensure that those pointers point into the memory that is accessible for the user. This is achieved by calling the validate_user_addr_range function.

Observe that the argument to the syscall_handler is a pointer to a struct intr_frame. This structure contains information about the CPU state at the moment when the interrupt occurred. For implementing system calls, you will need to read the arguments starting from the address in f->esp, while a return value, if it exists, has to be written to f->eax.

In the figure below, the memory layout for the arguments to the create system call is given. These arguments are made accessible to the system call handler at the address f->esp. The first word (4 bytes) represents the system call number (no), the second word, a pointer to a null-terminated array of characters (fn), while the third word is an integer representing the size of the file.

Fig: Memory layout of the arguments for the create system call

To avoid the use of pointer arithmetic to access these values, we recommend that you overlay a structure at the address f->esp. The members of this structure depend on the system call that is handled. For the create system call this can be achieved as in the following code snippet:

struct create_args {
    int id;
    const char *file;
    unsigned initial_size;
};

struct create_args *args = (struct create_args *) f->esp;

Task: You may have observed that there is no test code for filesize, remove, seek and halt. Extend the test in src/examples/test-syscalls.c to demonstrate the these system calls work as expected.

Hand-in

  • Answers to the questions listed above.
  • The modified PintOS source code that is passing the tests implemented in src/examples/test-syscalls.c.

Hints:

  • This assignment does not require writing any assembly code.
  • You do not have to use any containers (e.g. lists, hash sets) to solve this exercise; simple structs and arrays are enough.
  • The estimated time needed to solve this exercise is 16h.
  • The approximate size of the code changes is:
src/examples/test-syscalls.c |  33 +++++++++++++++--
src/threads/thread.c         |  69 ++++++++++++++++++-----------------
src/threads/thread.h         |  13 +++++++
src/userprog/Make.vars       |   4 +-
src/userprog/process.c       |  69 ++++++++++++++++++-----------------
src/userprog/syscall.c       | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----

  1. In src/userprog/process.c.↩︎

  2. Hint: a simple infinite loop will do.↩︎