View on GitHub

Computer Architecture and Operating Systems

Course taught at Faculty of Computer Science of Higher School of Economics

Lecture 3

System calls

Lecture

Slides (PDF, PPTX).

Outline:

System Call Types

There are several types of system calls. Each type solves a specific kind of task:

These tasks will be discussed in upcoming lectures and workshops.

Workshop

Outline

Theory

System calls are operations (functions) provided by the operating system kernel, which are available to user applications. They are designed and documented by operating system kernel developers. System calls are typically executed with the help of so-called wrapper functions, which can be conveniently used in user-mode applications (e.g. the glibc library in Linux). System calls allow executing kernel tasks upon user’s requests. Modern operating systems isolate kernel memory from user applications. Therefore, when a user application needs to make a request to the kernel (open a file, create a process, send data to network, etc.), a switch between kernel and user modes is required. This makes system calls much slower than regular function calls. System calls are made in architecture-dependent way, employing specific features of the instruction set architecture. A basic solution for system calls is employing the processor interrupt feature. However, a processor can also provide special instructions for this job. Arguments are passed via registers if they are available, extra arguments are passed via stack. The operating system kernel saves and restores execution state (e.g. registers) when switching between the use and the kernel modes.

System call

System calls in RARS (RISC-V Assembly)
  1. open (1024): opens a file with the specified path

    Input: a0 = Null terminated string for the path, a1 = flags

    Output: a0 = the file descriptor or -1 if an error occurred

    Supported flags: read-only (0), write-only (1), and write-append (9). The write-only flag creates a file if it does not exist, so it is technically write-create. The write-append flag will start writing at end of an existing file.

  2. close (57): closes a file

    Input: a0 = the file descriptor to close

    Output: N/A

  3. read (63): reads from a file descriptor into a buffer

    Input: a0 = the file descriptor, a1 = address of the buffer, a2 = maximum length to read.

    Output: a0 = the length read or -1 if error.

  4. write (64): writes to a file from a buffer

    Input: a0 = the file descriptor, a1 = the buffer address, a2 = the length to write.

    Output: a0 = the number of characters written.

  5. sbrk (9): allocates heap memory

    Input: a0 = amount of memory in bytes

    Output: a0 = address to the allocated block

Examples

Writing text to a file:

  .data
fout:   
  .asciz "testout.txt" # filename for output

buffer:
  .asciz "The quick brown fox jumps over the lazy dog."
  .text

  # Open (for writing) a file that does not exist
  li   a7, 1024     # system call for open file
  la   a0, fout     # output file name
  li   a1, 1        # Open for writing (flags are 0: read, 1: write)
  ecall             # open a file (file descriptor returned in a0)
  mv   s6, a0       # save the file descriptor

  # Write to file just opened
  li   a7, 64       # system call for write to file
  mv   a0, s6       # file descriptor
  la   a1, buffer   # address of buffer from which to write
  li   a2, 44       # hardcoded buffer length
  ecall             # write to file

  # Close the file
  li   a7, 57       # system call for close file
  mv   a0, s6       # file descriptor to close
  ecall             # close file

Reading text from a file:

  .data
fin:   
  .asciz "testouts.txt" # filename for input
error:
  .asciz "Error: failed to open a file."

buffer:
  .space 33

  .text
main:
  la   s0, buffer   # address of buffer to which to write
  li   s1, 32       # buffer size

  # Open (for reading) an existing file
  li   a7, 1024     # system call for open file
  la   a0, fin      # input file name
  li   a1, 0        # open for reading (flags are 0: read, 1: write)
  ecall             # open a file (file descriptor returned in a0)

  bltz a0, main.error # exit on errord exit
  mv   s6, a0        # save the file descriptor

main.loop:
  # Write to file just opened
  li   a7, 63       # system call for read from file
  mv   a0, s6       # file descriptor
  mv   a1, s0       # address of buffer to which to write
  mv   a2, s1       # buffer length
  ecall             # read from a file

  bltz a0, main.close # close the file on error and exit
  mv   t0, a0       # save size read 
  add  t1, s0, a0   # write zero terminator to end of buffer
  sb   zero, 0(t1)  #

  # Print the buffer read from a file
  li   a7, 4
  mv   a0, s0
  ecall

  # If bytes read = 32, loop. 
  beq  t0, s1, main.loop

main.close:
  # Close the file
  li   a7, 57       # system call for close file
  mv   a0, s6       # file descriptor to close
  ecall             # close file

main.exit:
  li   a7, 10
  ecall

main.error:
  li   a7, 4
  la   a0, error
  ecall

Allocating memory in the heap:

  .macro new_line
    li a7, 11
    li a0, '\n'
    ecall
  .end_macro

  .macro sbrk(%bytes)
    li a7, 9
    li a0, %bytes
    ecall
  .end_macro

  .text
  # Allocates 16 bytes in the heap
  sbrk(16)
  li a7, 34
  ecall
  new_line

  # Allocates 32 bytes in the heap
  sbrk(32)
  li a7, 34
  ecall
  new_line

  # Allocates 64 bytes in the heap
  sbrk(64)
  li a7, 34
  ecall
  new_line
System calls in Linux API (C language)

Linux provides the following facilities to execute system calls:

API-functions that perform system calls

Functions that are called in user program to execute system calls are defined in special header files. To make them available, a corresponding header must be included into the program source code and then a function can be called.

#include <sys/stat.h>
#include <fcntl.h>

int open(const char *path, int oflag, ...);
#include <stdio.h>

FILE *fopen(const char *pathname, const char *mode);
#include <unistd.h>
#include <sys/syscall.h>   /* For SYS_xxx definitions */

long syscall(long number, ...);

In Ubuntu 20.04 LTS, more system call declarations can be found in the following file: /usr/src/linux-headers-5.4.0-53/include/linux/syscalls.h.

Examples

Example 1: Using the printf glibc function:

#include <stdio.h>
int main () {
    printf("Hello World\n");
    return 0;
}

Example 2: Using the write POSIX function:

#include <fcntl.h>
#include <unistd.h>
int main () {
    write (1, "Hello World\n", 12);
    return 0;
}

  Example 3: Using the syscall function:

#include <unistd.h>
#include <sys/syscall.h>
int main () {
    syscall (1, 1, "Hello World\n", 12);
    return 0;
}

All the three examples, do the same: they print the “Hello World” message to the console. To compile and run them, the following commands need to be executed:

acos@acos-vm:~$ gcc test.c –o test
acos@acos-vm:~$ ./test

Tasks

System calls in RARS (RISC-V Assembly):

  1. Write a program that creates a copy of the specified file. Input arguments:
    • The name of the source and target files are read from the standard input (use system call 8).
    • The buffer to store data being copied is allocated in the heap (use system call 9). The buffer size is specified in standard input.
    • Buffers for storing source and target names are also allocated in the heap (their size is 256 bytes).

System calls in C:

  1. Read documentation on the read and write system calls. Note descriptors standard numbers for stdin, stdout, and stderr. Write a program that reads chars (note the &c notation) from stdin, increments them by 1, and writes them to stdout. Do not forget to include all the headers mentioned in the manual. To close stdin from the terminal, use the ^D key combination (it is not passed to program, but interpreted by operating system as end of output).

  2. Read documentation on the open system call. Take notice of flags, which are used to indicate how the file is opened. Flags are bits and can be combined with bitwise OR (|). The O_RDONLY flag is used to open a file for reading. The O_WRONLY|O_CREAT|O_TRUNC combination is used for open a file for writing. The mode parameter is required when creating the file. It specifies file access rights. For example, the S_IRUSR flag means that user has read permission, the S_IRGRP means that group has read permission, the S_IROTH flag means others have read permission. Write a program that reads 100 words from stdin and writes them to a file named outfile. Do not forget to close the file.

  3. Modify the previous program to accept command-line arguments (argc/argv). Pass via command-line arguments the number of words (use sscanf to get an integer from argv[1] and the name of output file (argv[2]).

Homework

TODO

References