execvp
Principles of Computer Systems
Winter 2020
Stanford University
Computer Science Department
Instructors: Chris Gregg and
Nick Troccoli
Diagram from last lecture:
open()
system call, a new entry is generated for the open file table./**
* Reads the specified sector (e.g. block) from the disk. Returns the number of bytes read,
* or -1 on error.
*/
int diskimg_readsector(int fd, int sectorNum, void *buf);
/**
* Gets the location of the specified file block of the specified inode.
* Returns the disk block number on success, -1 on error.
*/
int inode_indexlookup(struct unixfilesystem *fs, struct inode *inp, int blockNum);
fork()
system call is used to spawn a child process. Once you call fork()
you now have two identical processes that each continue at the next line of code. The only difference is that the return value from fork()
is 0 if the process is the child, and it is the child's process id (pid) if the process is the parent. Otherwise, everything else is the same, and a complete copy of the memory system is made for the child. Let's investigate this copy a bit more:int main(int argc, char *argv[]) {
char str[128];
strcpy(str, "SpongeBob");
printf("str's addres is %p\n", str);
pid_t pid = fork();
if (pid == 0) { // child
printf("I am the child. str's address is %p\n", str);
strcpy(str, "SquarePants");
printf("I am the child and I changed str to %s. str's address is still %p\n", str, str);
} else {
printf("I am the parent. str's address is %p\n", str);
printf("I am the parent, and I'm going to sleep for 2 seconds.\n");
sleep(2);
printf("I am the parent. I just woke up. str's address is %p, and it's value is %s\n", str, str);
}
return 0;
}
$ ./fork-copy
str's addres is 0x7ffe092639d0
I am the parent. str's address is 0x7ffe092639d0
I am the parent, and I'm going to sleep for 2 seconds.
I am the child. str's address is 0x7ffe092639d0
I am the child and I changed str to SquarePants. str's address is still 0x7ffe092639d0
I am the parent. I just woke up. str's address is 0x7ffe092639d0, and it's value is SpongeBob
Output:
What? How can two processes have the same pointer but the value the pointer points to has different values in each process?
$ ./fork-copy
str's addres is 0x7ffe092639d0
I am the parent. str's address is 0x7ffe092639d0
I am the parent, and I'm going to sleep for 2 seconds.
I am the child. str's address is 0x7ffe092639d0
I am the child and I changed str to SquarePants. str's address is still 0x7ffe092639d0
I am the parent. I just woke up. str's address is 0x7ffe092639d0, and it's value is SpongeBob
Output:
What? How can two processes have the same pointer but the value the pointer points to has different values in each process?
$ ./fork-copy
str's addres is 0x7ffe092639d0
I am the parent. str's address is 0x7ffe092639d0
I am the parent, and I'm going to sleep for 2 seconds.
I am the child. str's address is 0x7ffe092639d0
I am the child and I changed str to SquarePants. str's address is still 0x7ffe092639d0
I am the parent. I just woke up. str's address is 0x7ffe092639d0, and it's value is SpongeBob
Output:
Virtualization. There is a translation that happens between a process and the OS and hardware. For both processes above, the pointer value is 0x7ffe092639d0
. But, in physical memory, there has been a translation, so that there are actually two different memory locations. In other words: a pointer does not necessarily reflect the physical memory address.
Your next question might be: wait, isn't that expensive? Copying everything owned by a process when it forks? Yes, and no.
$ ./fork-copy
str's addres is 0x7ffe092639d0
I am the parent. str's address is 0x7ffe092639d0
I am the parent, and I'm going to sleep for 2 seconds.
I am the child. str's address is 0x7ffe092639d0
I am the child and I changed str to SquarePants. str's address is still 0x7ffe092639d0
I am the parent. I just woke up. str's address is 0x7ffe092639d0, and it's value is SpongeBob
Output:
waitpid
waitpid
can be used to temporarily block one process until a child process exits.waitpid
can return.NULL
if we don't care for the information).waitpid
should only return when a process in the supplied wait set exits.waitpid
was called and there were no child processes in the supplied wait set.pid_t waitpid(pid_t pid, int *status, int options);
waitpid
fork
really gets used in practice (full program, with error checking, is right here):int main(int argc, char *argv[]) {
printf("Before.\n");
pid_t pid = fork();
printf("After.\n");
if (pid == 0) {
printf("I am the child, and the parent will wait up for me.\n");
return 110; // contrived exit status
} else {
int status;
waitpid(pid, &status, 0)
if (WIFEXITED(status)) {
printf("Child exited with status %d.\n", WEXITSTATUS(status));
} else {
printf("Child terminated abnormally.\n");
}
return 0;
}
}
waitpid
waitpid
.waitpid
call, and uses the WIFEXITED
WEXITSTATUS
macro to extract the lower eight bits of its argument to produce the child return value (which we can see is, and should be, 110).waitpid
call also donates child process-oriented resources back to the system. myth60$ ./separate
Before.
After.
After.
I am the child, and the parent will wait up for me.
Child exited with status 110.
myth60$
fork
multiple times, provided it reaps the child processes (via waitpid
) once they exit. If we want to reap processes as they exit without concern for the order they were spawned, then this does the trick (full program checking right here):int main(int argc, char *argv[]) {
for (size_t i = 0; i < 8; i++) {
if (fork() == 0) exit(110 + i);
}
while (true) {
int status;
pid_t pid = waitpid(-1, &status, 0);
if (pid == -1) { assert(errno == ECHILD); break; }
if (WIFEXITED(status)) {
printf("Child %d exited: status %d\n", pid, WEXITSTATUS(status));
} else {
printf("Child %d exited abnormally.\n", pid);
}
}
return 0;
}
waitpid
. That -1 states we want to hear about any child as it exits, and pids are returned in the order their processes finish.waitpid
correctly returns -1 to signal there are no more processes under the parent's jurisdiction.waitpid
returns -1, it sets a global variable called errno
to the constant ECHILD
to signal waitpid
returned -1 because all child processes have terminated. That's the "error" we want.myth60$ ./reap-as-they-exit
Child 1209 exited: status 110
Child 1210 exited: status 111
Child 1211 exited: status 112
Child 1216 exited: status 117
Child 1212 exited: status 113
Child 1213 exited: status 114
Child 1214 exited: status 115
Child 1215 exited: status 116
myth60$
myth60$ ./reap-as-they-exit
Child 1453 exited: status 115
Child 1449 exited: status 111
Child 1448 exited: status 110
Child 1450 exited: status 112
Child 1451 exited: status 113
Child 1452 exited: status 114
Child 1455 exited: status 117
Child 1454 exited: status 116
myth60$
int main(int argc, char *argv[]) {
pid_t children[8];
for (size_t i = 0; i < 8; i++) {
if ((children[i] = fork()) == 0) exit(110 + i);
}
for (size_t i = 0; i < 8; i++) {
int status;
pid_t pid = waitpid(children[i], &status, 0);
assert(pid == children[i]);
assert(WIFEXITED(status) && (WEXITSTATUS(status) == (110 + i)));
printf("Child with pid %d accounted for (return status of %d).\n",
children[i], WEXITSTATUS(status));
}
return 0;
}
reap-in-fork-order
executable. The pids change between runs, but even those are guaranteed to be published in increasing order.myth60$ ./reap-as-they-exit
Child with pid 4689 accounted for (return status of 110).
Child with pid 4690 accounted for (return status of 111).
Child with pid 4691 accounted for (return status of 112).
Child with pid 4692 accounted for (return status of 113).
Child with pid 4693 accounted for (return status of 114).
Child with pid 4694 accounted for (return status of 115).
Child with pid 4695 accounted for (return status of 116).
Child with pid 4696 accounted for (return status of 117).
myth60$
execvp
execvp
effectively reboots a process to run a different program from scratch. Here is the prototype:
path
identifies the name of the executable to be invoked.argv
is the argument vector that should be funneled through to the new executable's main
function.path
and argv[0]
end up being the same exact string.execvp
fails to cannibalize the process and install a new executable image within it, it returns -1 to express failure.execvp
succeeds, it never returns in the calling process. #deepexecvp
has many variants (execle
, execlp
, and so forth. Type man
execvp
to see all of them). We generally rely on execvp
in this course.execvp
int execvp(const char *path, char *argv[]);
execvp
? An implementation mysystem
to emulate the behavior of the libc function called system
.
mysystem
function, which executes the supplied command
as if we typed it out in the terminal ourselves, ultimately returning once the surrogate command
has finished.command
exits normally (either via an exit
system call, or via a normal return statement from main
), then our mysystem
implementation should return that exact same exit value.SIGSEGV
).execvp
mysystem
spawns a child process to perform some task and waits for it to complete.execvp
, because we know that if it returns at all, it returns a -1. If that happens, we need to handle the error and make sure the child process terminates, via an exposed exit(0)
call.execvp
inside parent and forgo the child process altogether? Becauseexecvp
would consume the calling process, and that's not what we want.execvp
static int mysystem(const char *command) {
pid_t pid = fork();
if (pid == 0) {
char *arguments[] = {"/bin/sh", "-c", (char *) command, NULL};
execvp(arguments[0], arguments);
printf("Failed to invoke /bin/sh to execute the supplied command.");
exit(0);
}
int status;
waitpid(pid, &status, 0);
return WIFEXITED(status) ? WEXITSTATUS(status) : -WTERMSIG(status);
}
fgets
is a somewhat overflow-safe variant on scanf
that knows to read everything up through and including the newline character.
mysystem
.execvp
static const size_t kMaxLine = 2048;
int main(int argc, char *argv[]) {
char command[kMaxLine];
while (true) {
printf("> ");
fgets(command, kMaxLine, stdin);
if (feof(stdin)) break;
command[strlen(command) - 1] = '\0'; // overwrite '\n'
printf("retcode = %d\n", mysystem(command));
}
printf("\n");
return 0;
}
mysystem
implementation is working as expected: