OS is one such abstraction; actually, it provides several abstractions for different parts of the system. Consider a multi-processing system running an editor, a browser, a window manager, and so on. This system can be written as one large event-driven loop. However, that would be extremely complex to manage, as it would need to implement diverse functionalities, yet interoperate cleanly. Instead, an OS provides abstractions, whereby these different functionalities can be implemented as different programs, and run as different processes. The OS implements services as system calls, which a process can invoke. The OS also ensures safety among these processes. This course is about the design of these abstractions/services that OS provides, their resulting tradeoffs, and the implementation of these abstractions on modern ISAs.
One of the must successful set of abstractions, were the ones used in the UNIX operating system in the 1970s. Unlike its predecessors (e.g., DOS), UNIX was a multi-processing operating system, i.e., multiple processes could co-exist at the same time. Most production operating systems today are based on UNIX abstractions. We will begin by studying the UNIX abstractions.
while (1) { write (1, "$ ", 2); // 1 = STDOUT_FILENO readcommand (0, command, args); // parse user input, 0 = STDIN_FILENO if ((pid = fork ()) == 0) { // child? exec (command, args, 0); } else if (pid > 0) { // parent? wait (0); // wait for child to terminate } else { perror ("Failed to fork\n"); } }
read
, write
,
fork
, exec
, wait
.
conventions: -1 return value signals error,
error code stored in errno
,
perror
prints out a descriptive error
message based on errno
.
$ ls
read (0, buf, bufsize)
write (1, "hello\n", strlen("hello\n"))
fcntl(fd, F_SETFD, FD_CLOEXEC)
$ ls > tmp1just before exec insert:
close(1); creat("tmp1", 0666); // fd will be 1
The kernel always uses the first free file descriptor, 1 in this case.
Could use dup2()
to clone a file descriptor to a new number.