Autumn 2021
Jerry Cain
PDF
execvp effectively reboots a process to run a different program from scratch.
path is relative or absolute pathname 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]generally 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 😱.execvp has many variants (execle, execlp, and so forth. Type man execvp to see all of them). We typically rely on execvp in this course.int execvp(const char *path, char *argv[]);mysystem function is just the first example where fork, execvp, and waitpid all work together to do something genuinely useful.
mysystem is operationally a miniature shell.fork, waitpid, and execvp work in practice.stsh, for Stanford shell—to imitate the functionality of the shell (c-shell aka csh, or bash-shell aka bash, or z-shell aka zsh, or tc-shell aka tcsh, etc. are all different shell implementations) you've been using since you started using Unix.simplesh.
fork, waitpid, and execvp that I can think of: a miniature shell not unlike those you've been using since the first time you logged into a myth.simplesh operates as a read-eval-print loop—often called a repl—which itself responds to the many things we type in, typically by forking off child processes.
simplesh process.ls, cp, find, make, or even emacs.emacs &—is an instruction to execute the new process in the background without forcing the shell to wait for it to finish. That means we can launch other programs from the foreground before that background process finishes.simplesh is presented on the next slide. Where helper functions don't rely on CS110 concepts, I omit their implementations (but describe them in adequate detail in lecture).simplesh (full implementation is right here):int main(int argc, char *argv[]) {
while (true) {
char command[kMaxCommandLength + 1]; // room for \0 as well
readCommand(command, kMaxCommandLength);
char *arguments[kMaxArgumentCount + 1];
int count = parseCommandLine(command, arguments, kMaxArgumentCount);
if (count == 0) continue;
if (strcmp(arguments[0], "quit") ==) break; // hardcoded builtin to exit shell
bool isbg = strcmp(arguments[count - 1], "&") == 0;
if (isbg) arguments[--count] = NULL; // overwrite "&"
pid_t pid = fork();
if (pid == 0) execvp(arguments[0], arguments);
if (isbg) { // background process, don't wait for child to finish
printf("%d %s\n", pid, command);
} else { // otherwise block until child process is complete
waitpid(pid, NULL, 0);
}
}
printf("\n");
return 0;
}
xargs (type man xargs for the full read) is useful when one program is needed to programmatically generate the argument vector for a second.
xargs reads tokens from standard input (delimited by spaces and newlines).xargs then appends those tokens to the end of its original argument list and executes the full list of arguments—original plus those read from standard input—as if we typed them all in by hand.factor program, which prints out the prime factorizations of all of its numeric arguments, as with:poohbear@myth62:~$ factor 720
720: 2 2 2 2 3 3 5
poohbear@myth62:~$ factor 9 16 2047 870037764750
9: 3 3
16: 2 2 2 2
2047: 23 89
870037764750: 2 3 3 5 5 5 7 7 7 7 11 11 11 11 11
poohbear@myth62:~$ printf "720" | ./xargs factor
720: 2 2 2 2 3 3 5
poohbear@myth62:~$ printf "2047 1000\n870037764750" | ./xargs factor 9 16
9: 3 3
16: 2 2 2 2
2047: 23 89
1000: 2 2 2 5 5 5
870037764750: 2 3 3 5 5 5 7 7 7 7 11 11 11 11 11
poohbear@myth62:~$printf—is a brute force representative of an executable capable of supplying or extending the argument vector of a second executable—in this case, factor—through xargs.
printf or factor; they can be anything that works.assign2 solution , I might use xargs to do this:poohbear@myth62:~$ ls /usr/class/cs110/staff/master_repos/assign2/*.c | ./xargs wc
78 1792 90 /usr/class/cs110/staff/master_repos/assign2/chksumfile.c
35 1178 121 /usr/class/cs110/staff/master_repos/assign2/directory.c
266 8015 111 /usr/class/cs110/staff/master_repos/assign2/diskimageaccess.c
31 731 86 /usr/class/cs110/staff/master_repos/assign2/diskimg.c
35 1193 144 /usr/class/cs110/staff/master_repos/assign2/file.c
72 2751 134 /usr/class/cs110/staff/master_repos/assign2/inode.c
33 987 152 /usr/class/cs110/staff/master_repos/assign2/pathname.c
45 1287 91 /usr/class/cs110/staff/master_repos/assign2/unixfilesystem.c
595 17934 152 totalstatic void pullAllTokens(istream& in, vector<string>& tokens);int main(int argc, char *argv[]) {
vector<string> tokens;
pullAllTokens(cin, tokens);
char *xargsv[argc + tokens.size()];
for (size_t i = 0; i < argc - 1; i++)
xargsv[i] = argv[i + 1];
for (size_t i = 0; i < tokens.size(); i++)
xargsv[argc - 1 + i] = (char *) tokens[i].c_str();
xargsv[argc + tokens.size() - 1] = NULL;
execvp(xargsv[0], xargsv);
cerr << xargsv[0] << ": command not found, so xargs can't do its job!" << endl;
return 0;
}int pipe(int fds[]);pipe system call.
pipe system call takes an uninitialized array of two integers—we'll call it fds—and populates it with two file descriptors such that everything written to fds[1] can be read from fds[0].pipe is particularly useful for allowing parent processes to communicate with spawned child processes.
pipe work?
pipe works and how messages can be passed from one process to a second, let's consider the following program (available for play right here):int main(int argc, char *argv[]) {
int fds[2];
pipe(fds);
pid_t pid = fork();
if (pid == 0) {
close(fds[1]);
char buffer[6];
read(fds[0], buffer, sizeof(buffer)); // assume one call is enough
printf("Read from pipe bridging processes: %s.\n", buffer);
close(fds[0]);
return 0;
}
close(fds[0]);
write(fds[1], "hello", 6);
close(fds[1]);
waitpid(pid, NULL, 0);
return 0;
}pipe and fork work together in this example?
fds is shared with the call to pipe.pipe allocates two descriptors, setting the first to read from a resource and the second to write to that same resource. Think of this resource as an unnamed file that only the OS and its support for pipe know about.pipe then plants copies of those two descriptors into indices 0 and 1 of the supplied array before it returns.fork call creates a child process, which itself inherits a shallow copy of the parent's fds array.
fork call, anything printed to fds[1] is readable from the parent's fds[0] and the child's fds[0].fds[1].pipe and fork work together in this example?
fds[0] before it writes to anything to fds[1] to emphasize the fact that the parent has no need to read anything from the pipe.fds[1] before it reads from fds[0] to emphasize the fact that it has zero interest in publishing anything to the pipe. It's imperative all write endpoints of the pipe be closed if not being used, else the read end will never know if more text is to come or not.write in the parent presses all six bytes of "hello" ('\0' included) in a single call. Similarly, I assume the one call to read pulls in those same six bytes into its local buffer with just the one call.close calls as I do in both the child and the parent before allowing them to exit.