In a question from an Exam in operating system I have been trying to trace through the following code with no success.
The question says that the assumptions are:
at least the STDOUT is open.
foo.txt has the string "abcdef" 6 bytes
bar.txt has the string "567"
the output in the answer is a567b.
Could someone trace through this code and draw the file descriptor array for me?. Thanks in advance ..
main() {
char buf[1024];
int fd_foo = open("foo.txt", O_RDONLY);
if (fd_foo != 4) {
dup2(fd_foo, 4);
close(fd_foo);
}
int fd_bar = open("bar.txt", O_RDONLY);
if (fd_bar != 0) {
close(0);
dup(fd_bar);
close(fd_bar);
}
switch (fork()) {
case -1: exit(1);
case 0:
dup2(4, 5);
close(4);
execl("child", "child", (char *)NULL);
break;
default:
wait(NULL);
read(4, buf, 1);
write(1, buf, 1);
}
} // main
child source file content.
int main() {
char buf[3];
read(5, buf, 1);
write(1, buf, 1);
read(0, buf, 3);
write(1, buf, 3);
}
Lets look at the main()
in the main file first then look at the flow of the main()
in the child file.
Before we begin, lets review the standard file handle assignments for a C application when it starts up under Linux, from stdout(3) - Linux man page.
On program startup, the integer file descriptors associated with the streams stdin, stdout, and stderr are 0, 1, and 2, respectively. The preprocessor symbols STDIN_FILENO, STDOUT_FILENO, and STDERR_FILENO are defined with these values in . (Applying freopen(3) to one of these streams can change the file descriptor number associated with the stream.)
Next lets review what the dup()
system call does, from DUP(2) Linux Programmer's Manual.
The dup() system call creates a copy of the file descriptor oldfd, using the lowest-numbered unused file descriptor for the new descriptor.
After a successful return, the old and new file descriptors may be used interchangeably. They refer to the same open file description (see open(2)) and thus share file offset and file status flags; for example, if the file offset is modified by using lseek(2) on one of the file descriptors, the offset is also changed for the other.
The main()
in the main file looks as follows with annotations as comments:
main() {
char buf[1024];
// open the file foo.txt and then dup() the file handle received from the open()
// to be file handle number 4. Close the original file handle received.
int fd_foo = open("foo.txt", O_RDONLY);
if (fd_foo != 4) {
dup2(fd_foo, 4);
close(fd_foo);
}
// at this point the file handle 4 refers to the file foo.txt
// open the file bar.txt and make sure that the file handle received is file handle
// handle 0. if not then we close file handle 0 and dup the file handle to bar.txt
// File handle 0 is Standard Input or STDIN.
int fd_bar = open("bar.txt", O_RDONLY);
if (fd_bar != 0) {
close(0);
dup(fd_bar);
close(fd_bar);
// Since dup() looks for the lowest numbered file descriptor and we have
// just closed file descriptor 0, the result of dup() is to now have the
// file bar.txt to also be accessed through file handle 0.
}
// at this point we have the following file assignments:
// - file handle 0 which was to Standard In is now file bar.txt
// - file handle 1 is to Standard Out
// - file handle 2 is to Standard Error
// - file handle 4 is to file foo.txt
// now do a fork and the forked process will then execute the program whose
// source code is in the child source file. the child process will
// inherit our open file handles since we did not specify otherwise.
switch (fork()) {
case -1: exit(1); // if error just exit.
case 0:
// we be the forked process so we now
// - dup file handle 4 to file handle 5 and close 4
// - load in the child process on top of ourselves
// - loaded child process will inherit our open file handles
dup2(4, 5);
close(4);
execl("child", "child", (char *)NULL);
// at this point we now jump to the source code of the child source file
break;
default:
// we are the parent process so now lets just wait for the child to
// finish. Once it has finished we will then do some final file I/O
// then exit.
// Note: while the forked process closed file handle 4, the parent process
// has not so file handle 4 is still valid for the parent.
wait(NULL);
read(4, buf, 1); // read 1 character from file foo.txt
write(1, buf, 1); // write it to Standard Output
}
} // main
child process that is started up.
First of all look at the environment for the child process that is set up by the forked child before the child application is loaded with execl()
.
The child file source code is
int main() {
char buf[3];
read(5, buf, 1); // read one character from foo.txt, the letter "a" from the string "abcdef"
write(1, buf, 1); // write it to Standard Out
read(0, buf, 3); // read three characters from bar.txt, the string "567"
write(1, buf, 3); // write them to Standard out
}
The result of all of this is the following I/O.
main process starts up, sets up file descriptors, forks, and loads child
main process waits for child to finish
child process reads "a" from the file foo.txt leaving "bcdef" unread.
child process writes "a" to Standard Output
child process reads "567" from the file bar.txt leaving nothing unread
child process writes "567" to Standard Output
child process exits
main process continues running
main process reads "b" from file foo.txt leaving "cdef" unread
main process writes "b" to Standard Out
main process exits
The result of this is that "a567b" is written to Standard Out by these two cooperating processes. They share the same two files, though foo.txt is accessed by two different file descriptors, and they share the same Standard Output.