Notes following this MIT course and is based on the xv6 toy operating system.

Operating System Interfaces

  • Operating systems serve a number of purposes
    • Abstracts away lower level hardware, so that programs don’t have to care about what disk you are writing to
    • Allows multiple programs to run “at the same time”
    • Provide well defined interface for programs to interact with each other
  • For the interface, we want a simple interface that is easy to implement, but allows high sophisticated operations to happen
    • UNIX philosophy of composing many simple programs together helps keep this problem trackable
  • Can divide operating system into kernel space and user space
    • kernel space is the program which provides services to other programs. It’s given hardware level privileges which user programs don’t have access to
    • Each running program under the kernel’s manager (called a process) gets it’s own memory which houses the instructions (ie. the CPU assembly instructions), the data (variables created when running the program) and the stack (organizes program procedure calls)
    • if process needs a kernel service, it invokes a system call, which briefly transfers ownership to the kernel, which executes the syscall, and then returns ownership to the user program
      • Called user space and kernel space
  • Typically, people use a shell to allow user input. The shell resides in user space, so you have flexibility in what shell you run

Processes and Memory

  • For each running process, there is some user-space memory (instructions, data, stack) and kernel-space memory (which the user’s can’t access)
    • One of the kernel-space things is the PID (process identifier)
  • Xv6 has it’s own custom syscalls, which are somewhat stripped down versions of the UNIX ones
    • Unless otherwise stated, all of these calls return 0 for no error and -1 if there’s an error
Call Sig Desc
int fork() Create a process, return child’s PID. Makes an exact copy of user-space. returns in both parent and child process. Returns child’s PID in parent process, and 0 in child process
int exit(int status) Terminate the current process; Releases all held resources. Exit status reported to wait(). No return.
int wait(int *status) Wait for a child to exit; exit status in *status (out parameter); returns child PID. If caller has no children, immediately returns -1. If parent don’t care about child exit status, pass 0 address (ie. (int *) 0) to wait
int kill(int pid) Terminate process PID. Returns 0, or -1 for error.
int getpid() Return the current process’s PID.
int sleep(int n) Pause for n clock ticks.
int exec(char *file, char *argv[]) Load a file and execute it with arguments; only returns if error. This replaces the current process’s memory with that of the file. For Xv6, the ELF format is used.
char *sbrk(int n) Grow process’s memory by n bytes. Returns start of new memory.
int open(char *file, int flags) Open a file; flags indicate read/write; returns an fd (file descriptor).
int write(int fd, char *buf, int n) Write n bytes from buf to file descriptor fd; returns n.
int read(int fd, char *buf, int n) Read n bytes into buf; returns number read; or 0 if end of file.
int close(int fd) Release open file fd.
int dup(int fd) Return a new file descriptor referring to the same file as fd.
int pipe(int p[]) Create a pipe, put read/write file descriptors in p[0] and p[1].
int chdir(char *dir) Change the current directory.
int mkdir(char *dir) Create a new directory.
int mknod(char *file, int, int) Create a device file.
int fstat(int fd, struct stat *st) Place info about an open file into *st.
int stat(char *file, struct stat *st) Place info about a named file into *st.
int link(char *file1, char *file2) Create another name (file2) for the file file1.
int unlink(char *file) Remove a file.

Forking

  • As a small case study, look at the following C snippet
int pid = fork();
if(pid > 0){
printf("parent: child=%d\n", pid);
pid = wait((int *) 0);
printf("child %d is done\n", pid);
} else if(pid == 0){
printf("child: exiting\n");
exit(0);
} else {
printf("fork error\n");
}
  • After the fork(), the pid let’s you determine what the parent and the child do
    • NOTE: While after forking, the memory and register content of both processes is the same, they located in different locations which can evolve independently from each other
    • Don’t expect the output to be the same. Whichever process gets to printf first will output first (parent and child output could also be interleaved)

Exec

  • Another case study of exec

char *argv[3];
argv[0] = "echo";
argv[1] = "hello";
argv[2] = 0;
exec("/bin/echo", argv);
printf("exec error\n");
  • If echo is functioning properly, then the last printf will never get executed
  • Argv[0] is typically reserved for the program name