CSCB09: Software Tools and Systems Programming
Software Tools and Systems Programming
Hello, dear friend, you can consult us at any time if you have any questions, add WeChat: THEend8_
CSCB09:
Software Tools
and Systems
Programming
2Processes can create other
processes
• The fork system call creates a child process:
#include
pid_t fork(void);
• Fork creates a duplicate of the currently running program.
• Both processes run concurrently and independently.
• After fork() both execute the next instruction after fork.
fork()
Process
A
Process
A
Process
A’
3waitpid
• What if a process wants to wait for a particular
child (rather than any child)
• What if a process does not want to block when
no child has terminated?
pid_t waitpid(pid_t pid, int *status, int options);
• First parameter specifies PID of child to wait for
• If options is 0, waitpid blocks (just like wait)
• If options is WNOHANG, it immediately returns 0 instead
of blocking when no terminated child
4How does a child become a
zombie?
• When a child terminates, but its parent process is
not waiting for it
• The child (its exit code) is kept around as a zombie
until parent collects its exit code through wait
– or until parent terminates
• Shows up as Z in ps
5How does a child become an
orphan?
• If the parent process terminates before the child
• Who is now the new parent?
– Orphans get adopted by the init process
– init is the first process started during booting
– It’s the root of the process hierarchy
– init has a PID of 1
– The PPID of orphans is 1
6Wait a second ….
• Fork creates a duplicate of the current process
• How do we actually create a new process that runs
a different program???
7exec()
• exec() is not one specific function, but a family of functions:
execl(char *path, char *arg0, …, (char *)NULL);
execv(char *path, char *argv[]);
execlp(char *file, char *arg0, …,(char *)NULL);
execvp(char *file, char *argv[]);
• First parameter: name of executable; then commandline
parameters for executable; these are passed as argv[0],
argv[1], …, to the main program of the executable.
• execl and execv differ from each other only in how the
arguments for the new program are passed
• execlp and execvp differ from execl and execv only in
that you don’t have to specify full path to new program
8Processes often need to
communicate
•E.g. the different worker processes of a parallel program
need to exchange data and/or synchronize
•Options:
•Exit code – limited solution…
•Cannot use variables, since after fork each process has separate
copy of variables
•Use files --- coordination is difficult
9Solution: Pipes
parent child
kernel
pipe
Writes to pipeReads from pipe
• Pipes are a one-way (half-duplex) communication channel
• Pipes are buffers managed by the OS
• Processes use low-level file descriptors for pipe operations
Recall: I/O mechanisms in C
• So far we used: File pointers (regular files):
– You use a pointer to a file structure (FILE *) as handle to a file.
– The file struct contains a file descriptor and a buffer.
– Use for regular files
FILE *fp = fopen(“my_file.txt”, “w”);
fprintf(fp, “Hello!\n”);
fclose(fp);
• Now we need: File descriptors (low-level):
– Each open file is identified by a small integer
– Operations: open, close, read, write
File I/O with file descriptors
• int open(const char *pathname, int flags);
– Returns a file descriptor
– Flags: O_RDONLY, O_WRONLY, or O_RDWR to open the file read-only, write-only, or
read/write (and a few more, see man pages)
• ssize_t read(int fd, void *buf, size_t count);
– Returns #bytes read, 0 for EOF, -1 for error
• ssize_t write(int fd, const void *buf, size_t count);
– Returns #bytes actually written, -1 for error
• int close(int fd);
– Returns 0 on success, -1 on error
• Example (error checking omitted …):
char buf[6];
int fd = open (“filename.txt”, O_RDONLY);
read (fd, buf, 6);
close(fd);
Back to pipes
• How do you open/create a pipe?
int pipe(int pipefd[2]);
• You pass a pointer to two integers (i.e.
an array or a malloc of two ints) and
pipe fills it with two newly opened FDs.
• Returns 0 on success, -1 on error
• Example:
int p[2];
if (pipe(p)) == -1 ) {
perror (“pipe”);
exit(1);
}
• p[0] is now open for reading
• p[1] is now open for writing
pipe
kernel
p[1]p[0]
user process
writeread
Example: Process talking to itself
#include
#include
#include
#define MSG_SIZE 13
char *msg = "hello, world\n";
int main(void) {
char buf[MSG_SIZE];
int p[2];
if(pipe(p) == -1) {
perror("pipe");
exit(1);
}
write(p[1], msg, MSG_SIZE);
read(p[0], buf, MSG_SIZE);
write (STDOUT_FILENO, buf, MSG_SIZE);
return 0;
}
Now lets first write msg to the pipe
and then read from the pipe
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
14
pipe
kernel
p[1]p[0]
user process
writeread
A process talking to itself is not very useful
How do we get
two processes
talking to each
other using pipes?
15
The OS manages file descriptors
Code
Static data
Heap
Stack Process control block (PCB)
pc (program counter)
sp (stack pointer)
fd table Global file table
fd 0
fd 1
fd 2
fd 3
...
file status flags
inode pointer
current file offset
• Per-process fd table has all open files of a process
• Global file table has all files open system-wide
• On fork, a child gets a copy of parent’s fd table, so it will have same files open
• The fd table is preserved on exec
16
On fork, child gets copy of parent’s
file descriptors, including pipes
kernel
fd[1]fd[0]fd[1]fd[0]
pipe
writeread writeread
parent child
How can we make the
child send a message
to the parent?
Example 1: Child talking to parent
Pipes and EOF
• What if the parent does not know beforehand when and how much
data a child will write?
• If no writing end is open, read detects EOF and returns 0.
• read blocks until data is available in the pipe.
• All open pipes (and other FDs) are closed when a process exits.
Example 2 : Child talking to parent
Parent hangs, even
after child exits.
Why is EOF not
detected?
Example 3 : Child talking to parent
Parent needs to close
its writing end.
21
Closing ends of pipes
• In general, each process will read or write a
pipe (not both)
• Close the end you are not using
– Before reading: close p[1]
– Before writing: close p[0]
22
Direction of data flow?
parent child
kernel
fd[1]fd[0]fd[1]fd[0]
pipechild to parent
close fd[1] in parent
and fd[0] in child)
read write
23
Direction of data flow?
parent child
kernel
fd[1]fd[0]fd[1]fd[0]
pipe
parent to child
(close fd[0] in parent
and fd[1] in child)
write read
24
Summary:
Pipes and File Descriptors
• A forked child inherits file descriptors from its
parent
• pipe() creates an OS internal system buffer
and two file descriptors, one for reading and
one for writing.
• After the pipe call, the parent and child should
close the file descriptors for the opposite
direction.
Now off to the work sheet …
ssize_t read(int fd, void *buf, size_t count);
Returns #bytes read, 0 for EOF, -1 for error
ssize_t write(int fd, const void *buf, size_t count);
Returns #bytes actually written, -1 for error
int close(int fd);
Returns 0 on success, -1 on error
int pipe(int pipefd[2]);
Returns 0 on success, -1 on error