Chapter 1 Operating system interfaces


1.0 Overview

The job of an operating system

How does OS interact with user programs?

XV6

Process

System calls

Syscalls Collections

  • The job of an operating system is to share a computer among multiple programs and to provide a more useful set of services than the hardware alone supports.
  • An operating system manages and abstracts the low-level hardware(管理和抽象), so that, for example, a word processor(字处理器) need not concern itself with which type of disk hardware is being used.
  • An operating system shares the hardware among multiple programs so that they run (or appear to run) at the same time.
  • Finally, operating systems provide controlled ways for programs to interact, so that they can share data or work together.

An operating system provides services to user programs through an interface. Designing a good interface turns out to be difficult.

  1. On the one hand, we would like the interface to be simple and narrow because that makes it easier to get the implementation right.
  2. On the other hand, we may be tempted to offer many sophisticated features to applications.The trick in resolving this tension is to design interfaces that rely on a few mechanisms that can be combined to provide much generality(通用性).

This book uses a single operating system as a concrete example to illustrate operating system concepts. That operating system, xv6, provides the basic interfaces introduced by Ken Thompson and Dennis Ritchie’s Unix operating system, as well as mimicking Unix’s internal design.

Unix provides a narrow interface whose mechanisms combine well, offering a surprising degree of generality.

This interface has been so successful that modern operating systems—BSD, Linux, macOS, Solaris, and even, to a lesser extent, Microsoft Windows—have Unix-like interfaces. Understanding xv6 is a good start toward understanding any of these systems and many others.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HiuQkRXm-1681471282287)(Chapter%201%20Operating%20system%20interfaces%203d5f6832710a4b05b469d872f128a86f/Untitled.png)]

As Figure 1.1 shows, xv6 takes the traditional form of a kernel, a special program that provides services to running programs.

Process:

Each running program, called a process, has memory containing instructions, data, and a stack.

  • The instructions implement the program’s computation.
  • The data are the variables on which the computation acts.
  • The stack(堆栈) organizes the program’s procedure calls(过程调用).

A given computer typically has many processes but only a single kernel.

System Call:

When a process needs to invoke a kernel service, it invokes a system call, one of the calls in the operating system’s interface. ( 2 steps )

  1. The system call enters the kernel
  2. The kernel performs the service and returns.

Thus a process alternates(交替) between executing in user space and kernel space.

Syscalls Collections:

The collection of system calls that a kernel provides is the interface that user programs see. The xv6 kernel provides a subset of the services and system calls that Unix kernels traditionally offer. Figure 1.2 lists all of xv6’s system calls.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f0bPx36A-1681471282287)(Chapter%201%20Operating%20system%20interfaces%203d5f6832710a4b05b469d872f128a86f/Untitled%201.png)]

System call Description
int fork() Create a process, return child’s PID.
int exit(int status) Terminate the current process; status reported to wait(). No return.
int wait(int *status) Wait for a child to exit; exit status in *status; returns child PID.
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.
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.

The rest of this chapter outlines xv6’s services—processes, memory, file descriptors, pipes, and a file system—and illustrates(说明) them with code snippets and discussions of how the shell, Unix’s command-line user interface, uses them. The shell’s use of system calls illustrates how carefully they have been designed.

Shell:

The shell is an ordinary program that reads commands from the user and executes them. The fact that the shell is a user program, and not part of the kernel, illustrates the power of the system call interface: there is nothing special about the shell. It also means that the shell is easy to replace; as a result, modern Unix systems have a variety of shells to choose from, each with its own user interface and scripting features. The xv6 shell is a simple implementation of the essence of the Unix Bourne shell. Its implementation can be found at [(user/sh.c:1)](https://www.notion.so/user-sh-c-3af9ee81a4524539bbf8b30137bfafee).

1.1 Process and memory

Process

Syscall fork

Syscall exit

Syscall wait

Code Example of fork, wait and exit

Syscall exec

Shell

  • User & Kernel space:An xv6 process consists of user-space memory (instructions, data, and stack) and per-process state private to the kernel(内核私有).
  • Xv6 time-shares processes: it transparently switches the available CPUs among the set of processes waiting to execute.(分时进程)
  • Saving data:When a process is not executing, xv6 saves its CPU registers, restoring them when it next runs the process.
  • PID:The kernel associates a process identifier, or PID, with each process.

A process may create a new process using the fork system call. **Fork gives the new process exactly the same memory contents (both instructions and data) as the calling process.**

Fork returns in both the original and new processes.

  1. In the original process, fork returns the new process’s PID.
  2. In the new process, fork returns zero.

The original and new processes are often called the parent and child.

The exit system call causes the calling process to stop executing and to release resources such as memory and open files.

Exit takes an integer status argument, conventionally 0 to indicate success and 1 to indicate failure.

The wait system call returns the PID of an exited (or killed) child of the current process and copies the exit status of the child to the address passed to wait;

  1. If none of the caller’s children has exited, wait waits for one to do so.
  2. If the caller has no children, wait immediately returns -1.
  3. If the parent doesn’t care about the exit status of a child, it can pass a 0 address to wait.

For example, consider the following program fragment written in the C programming language :

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");
}// output:
// parent: child=1234
// child: exiting
// parent: child 1234 is done

Code explanation:

In the example, the output lines might come out in either order (or even intermixed), depending on whether the parent or child gets to its print call first.

  • After the child exits, the parent’s wait returns, causing the parent to print parent: child 1234 is done.
  • Although the child has the same memory contents as the parent initially, the parent and child are executing with different memory and different registers: changing a variable in one does not affect the other.
  • For example, when the return value of wait is stored into pid in the parent process, it doesn’t change the variable pid in the child. The value of pid in the child will still be zero.

The exec system call replaces the calling process’s memory with a new memory image(新的内存映像) loaded from a file stored in the file system.

ELF format:

The file must have a particular format(格式), which specifies which part of the file holds instructions, which part is data, at which instruction to start, etc.

Xv6 uses the ELF format, which Chapter 3 discusses in more detail.

When exec succeeds, it does not return to the calling program; instead, the instructions loaded from the file start executing at the entry point declared in the ELF header.

Code explanation:

  1. Exec takes two arguments: the name of the file containing the executable and an array of string arguments. For example:
char *argv[3];
argv[0] = "echo";
argv[1] = "hello";
argv[2] = 0;
exec("/bin/echo", argv);
printf("exec error\n");
  1. This fragment(片段) replaces the calling program with an instance(实例) of the program /bin/echo running with the argument list echo hello.
  2. Most programs ignore the first element of the argument array, which is conventionally the name of the program.

Shell 是怎么工作的?

The xv6 shell uses the above calls to run programs on behalf of users.

  • The main structure of the shell is simple; see main (user/sh.c:145).
  • The main loop reads a line of input from the user with getcmd.
  • Then it calls fork, which creates a copy of the shell process.
  • The parent calls wait, while the child runs the command.