CS111 Lecture 5 Scribe Notes - Winter 2012
by Elliot Daniel and Allison Le for a lecture by Professor Paul Eggert on January 25, 2012.
Orthogonality
Overview
In operating systems, we often find it desirable to keep various axes independent of each other. For instance, in a given OS API, we may have the following commands, separated into two groups:Process Area:
fork() waitpid() exec() exit()
File Area:
open() read() write() close()
Other axes, like signals, should also be considered for orthogonality.
FORK()
Callingp = fork()results in two processes, the parent proccess and the child (new) process. For the most part, the child is a direct clone of the parent, except:
- Return values
Child's = 0; Parent's = child's process ID - File descriptors
Caveat - file descriptions are shared - Accumulated execution times
- File locks
Child starts execution with none - Pending signals
Not copied to child process.
Each parent/child process has a process descriptor.
getpid() versus getppid()
getpid()
Returns current process's process IDgetppid()
Returns current process's parent process's process ID.
exec() Variants
Primary:
- execlp()
Example: execlp("/usr/bin/bate", args); - execvp()
Alternately:
- execl()
- execle()
- execv()
- execvpe()
Any one of the exec() family of functions starts a new process, ending execution of the current process at the point it is called. exec()s have no return value unless they fail, as the process they are called from no longer exists once they complete. exec()s do the following:
- Keep data that fork() clones, while destroying data that remains in a fork()'s parent process (to an approximation);
- Destroy code, data, stack, ip, etc., replacing with code from the leading argument.
crt0()
The C runtime initialization routine. Because fork() is the only way to create a new process, the first process must somehow exist.Upon starting:
- The C runtime immediately and automatically spawns crt0()
- crt0() immediately and automatically calls main() and exits, in a fashion similar to exec()
- main() becomes the new grand ancestor process for the program (in this case the entire OS), from which all other processes are directly or indirectly spawned.
fork() versus exec()
fork() and exec() have fully orthogonal (complementary) domains. Example shell scripts:#!/bin/sh exec date
#!/bin/sh date echo hello fork()s, runs date in child process, then returns to parent to run echo.
printdate()
_Noreturn void error (void); // Solves every problem // possible; never returns. void printdate (void) { pid_t p = fork(); switch (p) { case -1: error(); case 0: execvp(“/usr/bin/date”, (char* [2]){“date”, 0}); // int execvp(char const *, // char * const *) error(); // If execvp() succeeds this // point is never reached. default: int status; if (waitpid(p, &status, 0) < 0) error(); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) error(); } }
date.c
int main (void) { clock_gettime(CLOCK_REALTIME); . . . localtime( . . . ); . . . if (printf(“%4d-%2d-%2d”, Y, M, D ) > 0) return 0; else return 1; }
crt0.c
int argcMAGIC; char **argvMAGIC; void MAGIC (void) { _exit(main(argcMAGIC, argvMAGIC)); }
exit() versus _exit()
- exit()
Library call
Flushes stdout, does various other housekeeping tasks before exiting; - _exit()
True system call
No housekeeping, immediate brute-force exit; - atexit()
Library call
Tells the system what housekeeping to do upon exit.
logdate()
void logdate (void) { // similar starting code to printdate(); // insert this code before printdate()’s body int fd = open(“log”, O_WRONLY | O_APPEND); if (fd < 0) error(); // close(1); /* covered by dup2() */ dup2(fd, 1); if (fd != 1) error(); }
posix_spawnvp()
int posix_spawnvp (pid_t *restric pid, char const *restrict file, posix_spawnfile_action_t const *file_acts, posix_spawn_attr_t const *restrict attrp, char *const *restrict argv, char *const *restrict eavp); // restrict promises no aliasing; all pointers point to distinct objects
Infinite loops with exec() and fork()
Example 1:
int main (void) { execlp(“/usr/bin/date”, . . . ); // infinite loop }
Example 2:
while (fork() > 0) // infinite loop continue;
Example 3:
while (fork() >= 0) // “fork bomb”, number of processes continue; // increases exponentially
Access to devices and files
Issues and considerations:
- Accessing devices is slow - system call overhead is therefore acceptable
- Robustness is a real issue, system calls recommended
- Devices differ dramatically, so how can we have a clean interface to all of them?
Device categories:
Network- Mice, Keyboards, etc.
- Spontaneously generate data
- Usually sequential access only
- Potentially of infinite size
- Disk, Solid state drives, etc.
- Request/response paradigm - no spontaneous data generated
- Usually random access is possible
- Almost always of finite size
Device operations:
Networkopen() close() read() write()
lseek() pread() // lseek() followed pwrite() // by operation
Big Idea of UNIX
Perhaps the #1, overarching, Fundamental Theorem of UNIX is that all actions are accomplished by way of a relatively small number of system calls, all of which operate on file descriptors only.
File descriptors
- Represented as (fairly) small integers
- Mapped to files on a process-by-process basis
What can go wrong?
Example 1:
while (open(“/”, O_RDONLY) >= 0) continue; // Descriptor table and/or RAM can quickly become exhausted.
Example 2:
int fd = open(“file”, O_RDWR | O_CREAT, 0666); unlink(“file”); write(fd, . . . ); read(fd, . . . ); // Nameless files that are inaccessible except through fd but can’t be deleted.
Example 3:
char name[sizeof(“/tmp/sort”) + (sizeof(pid_t) * CHAR_BIT + 2) * 3]; sprintf(name, “/tmp/sort%d”, getpid()); open(name, O_RDWR | O_CREAT, 0600); unlink(name); // Potential obliteration of actual files named “/tmp/sort#” where # is a number.
Race Conditions
Two processes running in parallel access the same location and stomp on each others’ data, potentially causing undefined behavior or worse.
Example 1:
if (stat(“/tmp/f”, &st) != 0) open(“/tmp/f”, . . . ); else error(); // Between stat() and open(), another process can open(), // making this operation seem safer than it actually is.
Example 2:
int i; // Assume this is initialized somewhere f = new_file_name(i); while (stat(f, &st) == 0) f = new_file_name(++i); open(f, O_RDWR | O_CREAT, 0600); // Alternatively, open(f, O_RDWR | O_CREAT | O_EXCL, 0600); // removes the need for stat. If f exists, fail.
Example 3:
while(fd = open(f, O_RDWR | O_CREAT | O_EXCL, 0600) < 0) f = new_file_name(f); // Messy, but no more race condition