Download presentation
Presentation is loading. Please wait.
Published byJewel Miller Modified over 6 years ago
1
Slides adapted from: Randy Bryant of Carnegie Mellon University
Processes Slides adapted from: Randy Bryant of Carnegie Mellon University
2
Processes Definition: A process is an instance of a running program.
One of the most profound ideas in computer science Not the same as “program” or “processor” Process provides each program with two key abstractions: Logical control flow Each program seems to have exclusive use of the CPU Private virtual address space Each program seems to have exclusive use of main memory How are these Illusions maintained? Process executions interleaved (multitasking) Address spaces managed by virtual memory system
3
What is a process? A process is the OS's abstraction for execution
18/02/08 What is a process? A process is the OS's abstraction for execution A process represents a single running application on the system Process has three main components: Address space The memory that the process can access Consists of various pieces: the program code, static variables, heap, stack, etc. Processor state The CPU registers associated with the running process Includes general purpose registers, program counter, stack pointer, etc. OS resources Various OS state associated with the process Examples: open files, network sockets, etc.
4
Process Address Space Virtual memory of a process includes
18/02/08 Process Address Space Virtual memory of a process includes the code of the running program the data of the running program (static variables and heap) the execution stack storing local variables and saved registers for each procedure call Stack Heap Initialized vars (data segment) Code (text segment) Address space 0x 0xFFFFFFFF pointer Program counter Uninitialized vars (BSS segment) (Reserved for OS)
5
18/02/08 Process Address Space This is the process's own view of the address space physical memory may not be laid out this way at all. The virtual memory system provides this illusion to each process. Stack Heap Initialized vars (data segment) Code (text segment) Address space 0x 0xFFFFFFFF pointer Program counter Uninitialized vars (BSS segment) (Reserved for OS)
6
Execution State (context) of a Process
18/02/08 Execution State (context) of a Process Each process has an execution state (context) Indicates what the process is currently doing Running: Process is currently using the CPU Ready: Currently waiting to be assigned to a CPU That is, the process could be running, but another process is using the CPU Waiting (or sleeping): Process is waiting for an event Such as completion of an I/O, a timer to go off, etc. Why is this different than “ready” ? As the process executes, it moves between these states What state is the process in most of the time?
7
Process State (Context) Transitions
18/02/08 Process State (Context) Transitions What causes schedule and unschedule transitions? create New Ready I/O done unschedule Waiting schedule Terminated kill or exit Running I/O, page fault, etc.
8
18/02/08 Process Control Block OS maintains a Process Control Block (PCB) for each process The PCB is a big data structure with many fields: Process ID User ID Execution state ready, running, or waiting Saved CPU state CPU registers saved the last time the process was suspended. OS resources Open files, network sockets, etc. Memory management info Scheduling priority Give some processes higher priority than others Accounting information Total CPU time, memory usage, etc.
9
Context Switching Processes are managed by a shared chunk of OS code called the kernel Important: the kernel is not a separate process, but rather runs as part of some user process Control flow passes from one process to another via a context switch Process A Process B user code kernel code context switch Time user code kernel code context switch user code
10
Context Switching in Linux
18/02/08 Context Switching in Linux Process A Process A is happily running along... time
11
Context Switching in Linux
18/02/08 Context Switching in Linux Process A Timer interrupt handler 1) Timer interrupt fires 2) PC saved on stack User Kernel time
12
Context Switching in Linux
18/02/08 Context Switching in Linux Process A 1) Timer interrupt fires 2) PC saved on stack User Kernel 3) Rest of CPU state saved in PCB Timer interrupt handler Scheduler 4) Call schedule() routine time
13
Context Switching in Linux
18/02/08 Context Switching in Linux Process A Process B 7) Return from interrupt handler – process CPU state restored 1) Timer interrupt fires 2) PC saved on stack User Kernel 3) Rest of CPU state saved in PCB Timer interrupt handler Timer interrupt handler 6) Resume Process B (suspended within timer interrupt handler!) 4) Call schedule() routine 5) Decide next process to run Scheduler time
14
Context Switch Overhead
18/02/08 Context Switch Overhead Context switches are not cheap Generally have a lot of CPU state to save and restore Also must update various flags in the PCB Picking the next process to run – scheduling – is also expensive Context switch overhead in Linux About 5-7 usec This is equivalent to about 10,000 CPU cycles!
15
18/02/08 State Queues The OS maintains a set of state queues for each process state Separate queues for ready and waiting states Generally separate queues for each kind of waiting process One queue for processes waiting for disk I/O, another for network I/O, etc. PC Registers PID 4277 State: Ready PC Registers PID 4391 State: Ready Ready queue PC Registers PID 4110 State: Waiting PC Registers PID 4002 State: Waiting PC Registers PID 4923 State: Waiting Disk I/O queue
16
State Queue Transitions
18/02/08 State Queue Transitions PCBs move between these queues as their state changes When scheduling a process, pop the head off of the ready queue When I/O has completed, move PCB from waiting queue to ready queue PC Registers PID 4277 State: Ready PC Registers PID 4391 State: Ready PC Registers PID 4923 State: Ready Ready queue PC Registers PID 4110 State: Waiting PC Registers PID 4002 State: Waiting PC Registers PID 4923 State: Waiting Disk I/O queue Disk I/O completes
17
Concurrent Processes Each process is a logical control flow.
Two processes run concurrently (are concurrent) if their flows overlap in time Otherwise, they are sequential Examples (running on single core): Concurrent: A & B, A & C Sequential: B & C Process A Process B Process C Time
18
User View of Concurrent Processes
Control flows for concurrent processes are physically disjoint in time However, we can think of concurrent processes as running in parallel with each other Process A Process B Process C Time
19
Creating Processes Parent process creates a new running child process by calling fork int fork(void) Returns 0 to the child process, child’s PID to parent process Child is almost identical to parent: Child get an identical (but separate) copy of the parent’s virtual address space. Child gets identical copies of the parent’s open file descriptors Child has a different PID than the parent fork is interesting (and often confusing) because it is called once but returns twice
20
fork Example Call once, return twice Concurrent execution
Can’t predict execution order of parent and child int main(int argc, char** argv) { pid_t pid; int x = 1; pid = fork(); if (pid == 0) { /* Child */ printf("child : x=%d\n", ++x); return 0; } /* Parent */ printf("parent: x=%d\n", --x); fork.c linux> ./fork parent: x=0 child : x=2 linux> ./fork child : x=2 parent: x=0 linux> ./fork parent: x=0 child : x=2 linux> ./fork parent: x=0 child : x=2
21
fork Example Call once, return twice Concurrent execution
int main(int argc, char** argv) { pid_t pid; int x = 1; pid = fork(); if (pid == 0) { /* Child */ printf("child : x=%d\n", ++x); return 0; } /* Parent */ printf("parent: x=%d\n", --x); Call once, return twice Concurrent execution Can’t predict execution order of parent and child Duplicate but separate address space x has a value of 1 when fork returns in parent and child Subsequent changes to x are independent fork.c linux> ./fork parent: x=0 child : x=2 parent: x=-1 child : x=3 linux> ./fork parent: x=0 child : x=2
22
fork Example Call once, return twice Concurrent execution
int main(int argc, char** argv) { pid_t pid; int x = 1; pid = fork(); if (pid == 0) { /* Child */ printf("child : x=%d\n", ++x); /* printf("child : x=%d\n", ++x); */ return 0; } /* Parent */ printf("parent: x=%d\n", --x); Call once, return twice Concurrent execution Can’t predict execution order of parent and child Duplicate but separate address space x has a value of 1 when fork returns in parent and child Subsequent changes to x are independent Shared open files stdout is the same in both parent and child fork.c linux> ./fork parent: x=0 child : x=2
23
Modeling fork with Process Graphs
A process graph is a useful tool for capturing the partial ordering of statements in a concurrent program: Each vertex is the execution of a statement a -> b means a happens before b Edges can be labeled with current value of variables printf vertices can be labeled with output Each graph begins with a vertex with no inedges Any topological sort of the graph corresponds to a feasible total ordering. Total ordering of vertices where all edges point from left to right
24
Process Graph Example fork.c int main(int argc, char** argv) {
pid_t pid; int x = 1; pid = fork(); if (pid == 0) { /* Child */ printf("child : x=%d\n", ++x); return 0; } /* Parent */ printf("parent: x=%d\n", --x); child: x=2 Child printf exit x==1 parent: x=0 Parent main fork printf exit fork.c
25
Interpreting Process Graphs
Original graph: Relabeled graph: child: x=2 main fork printf x==1 exit parent: x=0 a b e c f d Feasible total ordering: a b f d c e a b e c f d Infeasible total ordering:
26
fork Example: Two consecutive forks
printf fork Bye L0 L1 void fork2() { printf("L0\n"); fork(); printf("L1\n"); printf("Bye\n"); } forks.c Feasible output: L0 L1 Bye Infeasible output: L0 Bye L1 Run ./forks 2 (Similarly for other examples)
27
fork Example: Nested forks in parent
void fork4() { printf("L0\n"); if (fork() != 0) { printf("L1\n"); printf("L2\n"); } printf("Bye\n"); printf fork L0 Bye L1 L2 Feasible output: L0 L1 Bye L2 Infeasible output: L0 Bye L1 L2 forks.c
28
fork Example: Nested forks in children
void fork5() { printf("L0\n"); if (fork() == 0) { printf("L1\n"); printf("L2\n"); } printf("Bye\n"); printf fork L0 L2 Bye L1 Feasible output: L0 Bye L1 L2 Infeasible output: L0 Bye L1 L2 forks.c
29
Why have fork() at all? Why make a copy of the parent process?
18/02/08 Why have fork() at all? Why make a copy of the parent process? Don't you usually want to start a new program instead? Where might “cloning” the parent be useful? Web server – make a copy for each incoming connection Parallel processing – set up initial state, fork off multiple copies to do work UNIX philosophy: System calls should be minimal. Don't overload system calls with extra functionality if it is not always needed. Better to provide a flexible set of simple primitives and let programmers combine them in useful ways.
30
What if fork’ing gets out of control?
18/02/08 What if fork’ing gets out of control? void forkbomb() { while (1) fork(); }
31
18/02/08 Memory concerns OS aggressively tries to share memory between processes. Especially processes that are fork()'d copies of each other Copies of a parent process do not actually get a private copy of the address space... ... though that is the illusion that each process gets. Instead, they share the same physical memory, until one of them makes a change. The virtual memory system is behind these tricks. We will discuss this in much detail later in the course
32
Terminating Processes
Process becomes terminated for one of three reasons: Receiving a signal whose default action is to terminate (more later) Returning from the main routine Calling the exit function void exit(int status) Terminates with an exit status of status Convention: normal return status is 0, nonzero on error Another way to explicitly set the exit status is to return an integer value from the main routine exit is called once but never returns. atexit() registers functions to be executed upon exit void cleanup(void) { printf("cleaning up\n"); } void fork() { atexit(cleanup); fork(); exit(0);
33
Reaping Child Processes
Idea When process terminates, it still consumes system resources Examples: Exit status, various OS tables Called a “zombie” Living corpse, half alive and half dead Reaping Performed by parent on terminated child (using wait or waitpid) Parent is given exit status information Kernel then deletes zombie child process What if parent doesn’t reap? If any parent terminates without reaping a child, then the orphaned child will be reaped by init process (pid == 1) So, only need explicit reaping in long-running processes e.g., shells and servers
34
Zombie Example ps shows child process as “defunct” (i.e., a zombie)
void fork7() { if (fork() == 0) { /* Child */ printf("Terminating Child, PID = %d\n", getpid()); exit(0); } else { printf("Running Parent, PID = %d\n", getpid()); while (1) ; /* Infinite loop */ } linux> ./forks 7 & [1] 6639 Running Parent, PID = 6639 Terminating Child, PID = 6640 linux> ps PID TTY TIME CMD 6585 ttyp :00:00 tcsh 6639 ttyp :00:03 forks 6640 ttyp :00:00 forks <defunct> 6641 ttyp :00:00 ps linux> kill 6639 [1] Terminated 6642 ttyp :00:00 ps linux> ./forks 7 & [1] 6639 Running Parent, PID = 6639 Terminating Child, PID = 6640 linux> ./forks 7 & [1] 6639 Running Parent, PID = 6639 Terminating Child, PID = 6640 linux> ps PID TTY TIME CMD 6585 ttyp :00:00 tcsh 6639 ttyp :00:03 forks 6640 ttyp :00:00 forks <defunct> 6641 ttyp :00:00 ps forks.c ps shows child process as “defunct” (i.e., a zombie) Killing parent allows child to be reaped by init
35
Non- terminating Child Example
void fork8() { if (fork() == 0) { /* Child */ printf("Running Child, PID = %d\n", getpid()); while (1) ; /* Infinite loop */ } else { printf("Terminating Parent, PID = %d\n", exit(0); } forks.c linux> ./forks 8 Terminating Parent, PID = 6675 Running Child, PID = 6676 linux> ps PID TTY TIME CMD 6585 ttyp :00:00 tcsh 6676 ttyp :00:06 forks 6677 ttyp :00:00 ps linux> kill 6676 6678 ttyp :00:00 ps linux> ./forks 8 Terminating Parent, PID = 6675 Running Child, PID = 6676 linux> ps PID TTY TIME CMD 6585 ttyp :00:00 tcsh 6676 ttyp :00:06 forks 6677 ttyp :00:00 ps linux> ./forks 8 Terminating Parent, PID = 6675 Running Child, PID = 6676 Child process still active even though parent has terminated Must kill child explicitly, or else will keep running indefinitely
36
wait: Synchronizing with Children
Parent reaps a child by calling the wait function int wait(int *child_status) Suspends current process until one of its children terminates Parent Process Kernel code Exception And, potentially other user processes, including a child of parent syscall … Returns
37
wait: Synchronizing with Children
Parent reaps a child by calling the wait function int wait(int *child_status) Suspends current process until one of its children terminates Return value is the pid of the child process that terminated If child_status != NULL, then the integer it points to will be set to a value that indicates reason the child terminated and the exit status: Checked using macros defined in wait.h WIFEXITED, WEXITSTATUS, WIFSIGNALED, WTERMSIG, WIFSTOPPED, WSTOPSIG, WIFCONTINUED See man pages for details
38
Process completion status
int WIFEXITED (int status) returns a nonzero value if the child process terminated normally with exit or _exit. int WEXITSTATUS (int status) If WIFEXITED is true of status, this macro returns the low-order 8 bits of the exit status value from the child process. int WIFSIGNALED (int status) returns a nonzero value if the child process terminated because it received a signal that was not handled int WTERMSIG (int status) If WIFSIGNALED is true of status, this macro returns the signal number of the signal that terminated the child process. int WCOREDUMP (int status) Returns a nonzero value if the child process terminated and produced a core dump. int WIFSTOPPED (int status) returns a nonzero value if the child process is stopped. int WSTOPSIG (int status) If WIFSTOPPED is true of status, this macro returns the signal number of the signal that caused the child process to stop.
39
wait: Synchronizing with Children
void fork9() { int child_status; if (fork() == 0) { printf("HC: hello from child\n"); exit(0); } else { printf("HP: hello from parent\n"); wait(&child_status); printf("CT: child has terminated\n"); } printf("Bye\n"); printf wait fork exit HP HC CT Bye forks.c Feasible output: HC HP CT Bye Feasible output(s): HC HP HP HC CT CT Bye Bye Infeasible output: HP CT Bye HC
40
Another wait Example If multiple children completed, will take in arbitrary order Can use macros WIFEXITED and WEXITSTATUS to get information about exit status void fork10() { pid_t pid[N]; int i, child_status; for (i = 0; i < N; i++) if ((pid[i] = fork()) == 0) { exit(100+i); /* Child */ } for (i = 0; i < N; i++) { /* Parent */ pid_t wpid = wait(&child_status); if (WIFEXITED(child_status)) printf("Child %d terminated with exit status %d\n", wpid, WEXITSTATUS(child_status)); else printf("Child %d terminate abnormally\n", wpid); Will consistently terminate in order, even with random delays. But, can turn off delays on parent with setenv PARENT 0 Then see variations in termination order forks.c
41
waitpid: Waiting for a Specific Process
pid_t waitpid(pid_t pid, int *status, int options) Suspends current process until specific process terminates Various options (see man page) void fork11() { pid_t pid[N]; int i; int child_status; for (i = 0; i < N; i++) if ((pid[i] = fork()) == 0) exit(100+i); /* Child */ for (i = N-1; i >= 0; i--) { pid_t wpid = waitpid(pid[i], &child_status, 0); if (WIFEXITED(child_status)) printf("Child %d terminated with exit status %d\n", wpid, WEXITSTATUS(child_status)); else printf("Child %d terminate abnormally\n", wpid); } Will always terminate in reverse order forks.c
42
execve: Loading and Running Programs
int execve(char *filename, char *argv[], char *envp[]) Loads and runs in the current process: Executable file filename Can be object file or script file beginning with #!interpreter (e.g., #!/bin/bash) …with argument list argv By convention argv[0]==filename …and environment variable list envp “name=value” strings (e.g., USER=droh) getenv, putenv, printenv Overwrites code, data, and stack Retains PID, open files and signal context Called once and never returns …except if there is an error
43
fork() and execve() execve() does not fork a new process!
18/02/08 fork() and execve() execve() does not fork a new process! Rather, it replaces the address space and CPU state of the current process Loads the new address space from the executable file and starts it from main() So, to start a new program, use fork() followed by execve()
44
execl and exec Family int execl(char *path, char *arg0, char *arg1, …, 0) Loads and runs executable at path with args arg0, arg1, … path is the complete path of an executable object file By convention, arg0 is the name of the executable object file “Real” arguments to the program start with arg1, etc. List of args is terminated by a (char *)0 argument Environment taken from char **environ, which points to an array of “name=value” strings: USER=ganger LOGNAME=ganger HOME=/afs/cs.cmu.edu/user/ganger Returns -1 if error, otherwise doesn’t return! Family of functions includes execv, execve (base function), execvp, execl, execle, and execlp
45
exec: Using fork followed by exec
int main(int argc, char **argv) { int rv; if (fork()) { /* Parent process */ wait(&rv); } else { /* Child process */ char *newargs[3]; printf(“Hello, I am the child process.\n”); newargs[0] = “/bin/echo”; /* Convention! Not required!! */ newargs[1] = “some random string”; newargs[2] = NULL; /* Indicate end of args array */ if (execv(“/bin/echo”, newargs)) { printf(“warning: execve returned an error.\n”); exit(-1); } printf(“Child process should never get here\n”); exit(42);
46
Linux Process Hierarchy
[0] init [1] … … … Daemon e.g. httpd Login shell Login shell Child Run pstree command. Child Child Grandchild Grandchild Note: you can view the hierarchy using the Linux pstree command
47
Summary Process Spawning processes Process completion
is an instance of program in execution At any given time, a system has multiple active processes Only one can execute at a time, though Each process appears to have total control of processor + private memory space Spawning processes Call to fork One call, two returns Process completion Call exit One call, no return Reaping and waiting for Processes Call wait or waitpid Loading and running Programs Call execl (or variant) One call, (normally) no return
48
File Abstraction Most of the following slides are adapted from slides of Randy Bryant of Carnegie Mellon Univ.
49
18/02/08 UNIX File Abstraction In UNIX, the file is the basic abstraction used for I/O Used to access disks, CDs, DVDs, USB and serial devices, network sockets, even memory!
50
Unix I/O and C Standard I/O
Most useful for reading/writing files in applications Provides buffering between program and actual files Unix I/O Lower level Required for system and network programming fopen fdopen fread fwrite fscanf fprintf sscanf sprintf fgets fputs fflush fseek fclose C application program Standard I/O functions open read write lseek stat close Unix I/O functions (accessed via system calls)
51
Unix I/O Overview A Linux file is a sequence of m bytes:
B0 , B1 , .... , Bk , .... , Bm-1 Cool fact: All I/O devices are represented as files: /dev/sda2 (/usr disk partition) /dev/tty2 (terminal) Even the kernel is represented as a file: /boot/vmlinuz generic (kernel image) /proc (kernel data structures)
52
Unix I/O Overview Elegant mapping of files to devices allows kernel to export simple interface called Unix I/O: Opening and closing files open()and close() Reading and writing a file read() and write() Changing the current file position (seek) indicates next offset into file to read or write lseek() B0 B1 • • • Bk-1 Bk Bk+1 Current file position = k
53
Opening Files Opening a file informs the kernel that you are getting ready to access that file Returns a small identifying integer file descriptor fd == -1 indicates that an error occurred int fd; /* file descriptor */ if ((fd = open("/etc/hosts", O_RDONLY)) < 0) { perror("open"); exit(1); }
54
18/02/08 stdin, stdout, stderr In UNIX, every process has three “special” files already open: standard input (stdin) – filehandle 0 standard output (stdout) – filehandle 1 standard error (stderr) – filehandle 2 By default, stdin and stdout are connected to the terminal device of the process. Originally, terminals were physically connected to the computer by a serial line These days, we use “virtual terminals” using ssh VT100 terminal
55
How the Unix Kernel Represents Open Files
Two descriptors referencing two distinct open disk files. Descriptor 1 (stdout) and 2 (stderr) points to terminal, and descriptor 4 points to file opened on the disk. KERNEL SPACE Descriptor table [one table per process] Open file table [shared by all processes] v-node table [shared by all processes] File A (terminal) stdin fd 0 File access stdout fd 1 Info in stat struct File pos File size stderr fd 2 refcnt=1 File type fd 3 fd 4 ... ... File B (disk) File access File size File pos File type refcnt=1 ... File pos is maintained per open file ...
56
File Sharing Two distinct descriptors sharing the same disk file through two distinct open file table entries E.g., Calling open twice with the same filename argument KERNEL SPACE Descriptor table [one table per process] Open file table [shared by all processes] v-node table [shared by all processes] File A (terminal) stdin fd 0 File access stdout fd 1 File pos File size stderr fd 2 refcnt=1 File type fd 3 fd 4 ... ... File B (disk) File pos refcnt=1 ... Different logical but same physical file
57
How Processes Share Files: Fork()
A child process inherits its parent’s open files Note: situation unchanged by exec() functions Before fork() call: KERNEL SPACE Descriptor table [one table per process] Open file table [shared by all processes] v-node table [shared by all processes] File A (terminal) stdin fd 0 File access stdout fd 1 File pos File size stderr fd 2 refcnt=1 File type fd 3 fd 4 ... ... File B (disk) File access File size File pos File type refcnt=1 ... ...
58
How Processes Share Files: fork()
A child process inherits its parent’s open files After fork(): Child’s table same as parents, and +1 to each refcnt KERNEL SPACE Descriptor table [one table per process] Open file table [shared by all processes] v-node table [shared by all processes] Parent File A (terminal) fd 0 File access fd 1 File pos File size fd 2 refcnt=2 File type fd 3 fd 4 ... ... File B (disk) Child File access fd 0 File size fd 1 File pos fd 2 File type refcnt=2 fd 3 ... ... fd 4 File is shared between processes
59
18/02/08 Shell redirection The shell allows stdin, stdout, and stderr to be redirected (say, to or from a file). > ./myprogram > somefile.txt Connects stdout of “myprogram” to somefile.txt > ./myprogram < input.txt > somefile.txt Connects stdin to input.txt and stdout to somefile.txt > ./myprogram 2> errors.txt Connects stderr to errors.txt In this case, the shell simply opens the file, making sure the file handle is 0, 1, or 2, as appropriate. Problem: open() decides what the file handle number is. How do we coerce the filehandle to be 0, 1, or 2?
60
[shared by all processes] [shared by all processes]
Initially stdout prints to the Display of the terminal as default. KERNEL SPACE Descriptor table For myprogram Open file table [shared by all processes] v-node table [shared by all processes] Display stdin fd 0 File access stdout fd 1 Info in stat struct File pos File size stderr fd 2 Refcnt=1 File type fd 3 fd 4 ... ...
61
All we need to do is to point stdout to a file
Question: But the Descriptor table is kernel space, and we cannot modify it directly. Need to use system calls! KERNEL SPACE Descriptor table For myprogram Open file table [shared by all processes] v-node table [shared by all processes] Display stdin fd 0 File access stdout fd 1 Info in stat struct File pos File size stderr fd 2 refcnt=1 File type fd 3 fd 4 ... ... foo.txt (disk) File access File size File pos File type refcnt=1 ... ...
62
[shared by all processes] [shared by all processes]
dup() : before #include <unistd.h> int dup(int filedes); //dup() returns lowest available file descriptor, now referring to whatever filedes refers to newfd = dup(1); // newfd will be 3. KERNEL SPACE Descriptor table For myprogram Open file table [shared by all processes] v-node table [shared by all processes] Display stdin fd 0 File access stdout fd 1 Info in stat struct File pos File size stderr fd 2 refcnt=1 File type fd 3 fd 4 ... ...
63
[shared by all processes] [shared by all processes]
dup() : after #include <unistd.h> int dup(int filedes); //dup() returns lowest available file descriptor, now referring to whatever filedes refers to newfd = dup(1); // newfd will be 3. KERNEL SPACE Descriptor table For myprogram Open file table [shared by all processes] v-node table [shared by all processes] Display stdin fd 0 File access stdout fd 1 Info in stat struct File pos File size stderr fd 2 refcnt=2 File type fd 3 fd 4 ... ...
64
[shared by all processes] [shared by all processes]
dup2() : before #include <unistd.h> int dup2(int oldfd, int newfd); //Copies descriptor table entry oldfd to entry newfd int foofd = open(”foo.txt", O_WRONLY); //foofd becomes 3. if (dup2(foofd, stdout)>0) printf(“printing to foo.txt\n”); KERNEL SPACE Descriptor table For myprogram Open file table [shared by all processes] v-node table [shared by all processes] Display stdin fd 0 File access stdout fd 1 Info in stat struct File pos File size stderr fd 2 refcnt=1 File type fd 3 fd 4 ... ... foo.txt (disk) File access File size File pos File type refcnt=1 ... ...
65
[shared by all processes] [shared by all processes]
dup2() : after #include <unistd.h> int dup2(int oldfd, int newfd); //Copies descriptor table entry oldfd to entry newfd int foofd = open(”foo.txt", O_WRONLY); //foofd becomes 3. if (dup2(foofd, stdout)>0) printf(“printing to foo.txt\n”); KERNEL SPACE Descriptor table For myprogram Open file table [shared by all processes] v-node table [shared by all processes] Display stdin fd 0 File access stdout fd 1 Info in stat struct File pos File size stderr fd 2 refcnt=1 File type fd 3 fd 4 ... ... foo.txt (disk) File access File size File pos File type refcnt=2 ... ...
66
dup() and dup2()pseudocode
dup(fd) returns lowest available file descriptor, now referring to whatever oldfd refers to refers to. dup2(oldfd,newfd) copies descriptor table entry oldfd to entry newfd. //Descriptor table void *DT[maxFd]; int dup2(int oldfd, int newfd){ DP[newfd]=DP[oldfd]; return(newfd); } //Descriptor table void *DT[maxFd]; int dup(int oldfd){ //get the lowest available //file descriptor newfd = lowestFd(DT); DT(newfd)=DT(oldfd); return(newfd); } If oldfd is not a valid file descriptor, then the call fails, and newfd is not closed. If oldfd is a valid file descriptor, and newfd has the same value as oldfd, then dup2() does nothing, and returns newfd.
67
I/O and Redirection Example
#include "csapp.h" int main(int argc, char *argv[]) { int fd1, fd2, fd3; char c1, c2, c3; char *fname = argv[1]; fd1 = open(fname, O_RDONLY, 0); fd2 = open(fname, O_RDONLY, 0); fd3 = open(fname, O_RDONLY, 0); dup2(fd2, fd3); read(fd1, &c1, 1); read(fd2, &c2, 1); read(fd3, &c3, 1); printf("c1 = %c, c2 = %c, c3 = %c\n", c1, c2, c3); return 0; } ffiles1.c What would this program print for file containing “abcde”?
68
I/O and Redirection Example
#include "csapp.h" int main(int argc, char *argv[]) { int fd1, fd2, fd3; char c1, c2, c3; char *fname = argv[1]; fd1 = Open(fname, O_RDONLY, 0); fd2 = Open(fname, O_RDONLY, 0); fd3 = Open(fname, O_RDONLY, 0); Dup2(fd2, fd3); Read(fd1, &c1, 1); Read(fd2, &c2, 1); Read(fd3, &c3, 1); printf("c1 = %c, c2 = %c, c3 = %c\n", c1, c2, c3); return 0; } c1 = a, c2 = a, c3 = b dup2(oldfd, newfd) ffiles1.c What would this program print for file containing “abcde”?
69
Master Class: Process Control and I/O
#include "csapp.h" int main(int argc, char *argv[]) { int fd1; int s = getpid() & 0x1; char c1, c2; char *fname = argv[1]; fd1 = Open(fname, O_RDONLY, 0); Read(fd1, &c1, 1); if (fork()) { /* Parent */ sleep(s); Read(fd1, &c2, 1); printf("Parent: c1 = %c, c2 = %c\n", c1, c2); } else { /* Child */ sleep(1-s); printf("Child: c1 = %c, c2 = %c\n", c1, c2); } return 0; ffiles2.c What would this program print for file containing “abcde”?
70
Master Class: Process Control and I/O
#include "csapp.h" int main(int argc, char *argv[]) { int fd1; int s = getpid() & 0x1; char c1, c2; char *fname = argv[1]; fd1 = Open(fname, O_RDONLY, 0); Read(fd1, &c1, 1); if (fork()) { /* Parent */ sleep(s); Read(fd1, &c2, 1); printf("Parent: c1 = %c, c2 = %c\n", c1, c2); } else { /* Child */ sleep(1-s); printf("Child: c1 = %c, c2 = %c\n", c1, c2); } return 0; Child: c1 = a, c2 = b Parent: c1 = a, c2 = c Parent: c1 = a, c2 = b Child: c1 = a, c2 = c Bonus: Which way does it go? ffiles2.c What would this program print for file containing “abcde”?
71
For Further Information
The Unix bible: W. Richard Stevens & Stephen A. Rago, Advanced Programming in the Unix Environment, 2nd Edition, Addison Wesley, 2005 Updated from Stevens’ 1993 book Stevens is arguably the best technical writer ever. Produced authoritative works in: Unix programming TCP/IP (the protocol that makes the Internet work) Unix network programming Unix IPC programming
72
Bonus material
73
System call error handling
74
System Call Error Handling
On error, Linux system-level functions typically return -1 and set global variable errno to indicate cause. Hard and fast rule: You must check the return status of every system-level function Only exception is the handful of functions that return void Example: if ((pid = fork()) < 0) { fprintf(stderr, "fork error: %s\n", strerror(errno)); exit(-1); }
75
Error-reporting functions
Can simplify somewhat using an error-reporting function: void unix_error(char *msg) /* Unix-style error */ { fprintf(stderr, "%s: %s\n", msg, strerror(errno)); exit(-1); } Note: csapp.c exits with 0. if ((pid = fork()) < 0) unix_error("fork error");
76
Error-handling Wrappers
We simplify the code we present to you even further by using Stevens-style error-handling wrappers: NOT what you generally want to do in a real application pid_t Fork(void) { pid_t pid; if ((pid = fork()) < 0) unix_error("Fork error"); return pid; } pid = Fork();
77
System Call Error Handling
On error, Linux system-level functions typically return -1 and set global variable errno to indicate cause. Hard and fast rule: You must check the return status of every system-level function Only exception is the handful of functions that return void Example: if ((pid = fork()) < 0) { fprintf(stderr, "fork error: %s\n", strerror(errno)); exit(-1); }
78
Error-reporting functions
Can simplify somewhat using an error-reporting function: void unix_error(char *msg) /* Unix-style error */ { fprintf(stderr, "%s: %s\n", msg, strerror(errno)); exit(-1); } Note: csapp.c exits with 0. if ((pid = fork()) < 0) unix_error("fork error");
79
Error-handling Wrappers
We simplify the code we present to you even further by using Stevens-style error-handling wrappers: NOT what you generally want to do in a real application pid_t Fork(void) { pid_t pid; if ((pid = fork()) < 0) unix_error("Fork error"); return pid; } pid = Fork();
80
I/O streams
81
Standard I/O Streams Standard I/O models open files as streams
Abstraction for a file descriptor and a buffer in memory. Similar to buffered RIO (later) C programs begin life with three open streams (defined in stdio.h) stdin (standard input) stdout (standard output) stderr (standard error) #include <stdio.h> extern FILE *stdin; /* standard input (descriptor 0) */ extern FILE *stdout; /* standard output (descriptor 1) */ extern FILE *stderr; /* standard error (descriptor 2) */ int main() { fprintf(stdout, "Hello, world\n"); }
82
Buffering in Standard I/O
Standard I/O functions use buffered I/O Buffer flushed to output fd on “\n” or fflush() call printf("h"); printf("e"); printf("l"); printf("l"); printf("o"); buf printf("\n"); h e l l o \n . . fflush(stdout); write(1, buf, 6);
83
Standard I/O Buffering in Action
You can see this buffering in action for yourself, using the always fascinating Unix strace program: #include <stdio.h> int main() { printf("h"); printf("e"); printf("l"); printf("o"); printf("\n"); fflush(stdout); exit(0); } linux> strace ./hello execve("./hello", ["hello"], [/* ... */]). ... write(1, "hello\n", 6...) = 6 _exit(0) = ? strace: a debugging tool in Linux. When you start a program using strace, it prints a list of system calls made by the program.
84
Fork Example #2 (Earlier Lecture)
Key Points Both parent and child can continue forking void fork2() { printf("L0\n"); fork(); printf("L1\n"); printf("Bye\n"); } Bye L1 L0
85
Fork Example #2 (modified)
Removed the “\n” from the first printf As a result, “L0” gets printed twice void fork2a() { printf("L0"); fork(); printf("L1\n"); printf("Bye\n"); } Bye L0L1 L0L1
86
Repeated Slide: Reading Files
Reading a file copies bytes from the current file position to memory, and then updates file position Returns number of bytes read from file fd into buf Return type ssize_t is signed integer nbytes < 0 indicates that an error occurred short counts (nbytes < sizeof(buf) ) are possible and are not errors! char buf[512]; int fd; /* file descriptor */ int nbytes; /* number of bytes read */ /* Open file fd ... */ /* Then read up to 512 bytes from file fd */ if ((nbytes = read(fd, buf, sizeof(buf))) < 0) { perror("read"); exit(1); }
87
Dealing with Short Counts
Short counts can occur in these situations: Encountering (end-of-file) EOF on reads Reading text lines from a terminal Reading and writing network sockets or Unix pipes Short counts never occur in these situations: Reading from disk files (except for EOF) Writing to disk files
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.