Ce diaporama est élaboré à partir de la présentation de l'université de stanford
fork()
pid_t pidOrZero = fork();
// both parent and child run code here onwards
printf("This is printed by two processes.\n");Comment le parent et l'enfant peuvent-ils utiliser la même adresse pour stocker des données différentes ?
waitpid()
Une fonction qu'un parent peut appeler pour attendre que son enfant sorte(meurt) :
pid_t waitpid(pid_t pid, int *status, int options);pid : le PID de l'enfant à attendre (nous verrons d'autres options plus tard)
status : où placer les informations sur la fin de l'enfant (ou NULL)
options : indicateurs facultatifs pour personnaliser le comportement (toujours 0 pour l'instant)
la fonction retourne lorsque le processus enfant spécifié se termine
la valeur de retour est le PID de l'enfant qui s'est terminé, ou -1 en cas d'erreur (par exemple, aucun enfant à attendre)
Si le processus enfant est déjà terminé, cela renvoie immédiatement - sinon, il se bloque
waitpid()
int main(int argc, char *argv[]) {
printf("Before.\n");
pid_t pidOrZero = fork();
if (pidOrZero == 0) {
sleep(2);
printf("I (the child) slept and the parent still waited up for me.\n");
} else {
pid_t result = waitpid(pidOrZero, NULL, 0);
printf("I (the parent) finished waiting for the child. This always prints last.\n");
}
return 0;
}$ ./waitpid
Before.
I (the child) slept and the parent still waited up for me.
I (the parent) finished waiting for the child. This always prints last.
$waitpid()
Le résultat sera toujours le même ! Le parent attendra toujours que l'enfant ait fini avant de continuer.
int main(int argc, char *argv[]) {
pid_t pid = fork();
if (pid == 0) {
printf("I'm the child, and the parent will wait up for me.\n");
return 110; // contrived exit status (not a bad number, though)
} else {
int status;
int result = waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("Child exited with status %d.\n", WEXITSTATUS(status));
} else {
printf("Child terminated abnormally.\n");
}
return 0;
}
}
Passez l'adresse d'un entier comme deuxième paramètre pour obtenir le statut de l'enfant.
$ ./separate
I am the child, and the parent will wait up for me.
Child exited with status 110.
$Un processus parent doit toujours attendre ses processus enfants.
waitpid()
(Attends, quoi ?)
Et si nous voulions attendre les enfants dans l'ordre dans lequel ils ont été créés ?
Consultez le programme abrégé ci-dessous (lien vers le programme complet en bas) :
int main(int argc, char *argv[]) {
pid_t children[kNumChildren];
for (size_t i = 0; i < kNumChildren; i++) {
children[i] = fork();
if (children[i] == 0) return 110 + i;
}
for (size_t i = 0; i < kNumChildren; i++) {
int status;
pid_t pid = waitpid(children[i], &status, 0);
assert(WIFEXITED(status));
printf("Child with pid %d accounted for (return status of %d).\n", children[i], WEXITSTATUS(status));
}
return 0;
}$ ./reap-in-fork-order
Child with pid 12649 accounted for (return status of 110).
Child with pid 12650 accounted for (return status of 111).
Child with pid 12651 accounted for (return status of 112).
Child with pid 12652 accounted for (return status of 113).
Child with pid 12653 accounted for (return status of 114).
Child with pid 12654 accounted for (return status of 115).
Child with pid 12655 accounted for (return status of 116).
Child with pid 12656 accounted for (return status of 117).
$
Un parent peut appeler fork plusieurs fois, mais doit récupérer tous les processus enfants.
Démo : voyons comment nous pourrions utiliser cela.
L'utilisation la plus courante de fork n'est pas de générer plusieurs processus pour diviser le travail, mais plutôt d'exécuter un programme complètement séparé sous votre contrôle et de communiquer avec lui.
execvp()
execvp is a function that lets us run another program in the current process.
int execvp(const char *path, char *argv[]);execvp()
It runs the executable at the specified path, completely cannibalizing the current process.
To run another executable, we must specify the (NULL-terminated) arguments to be passed into its main function, via the argv parameter.
execvp has many variants (execle, execlp, and so forth. Type man execvp for more). We rely on execvp in CS110.
int main(int argc, char *argv[]) {
char *args[] = {"/bin/ls", "-l", "/usr/class/cs110/lecture-examples", NULL};
execvp(args[0], args);
printf("This only prints if an error occurred.\n");
return 0;
}$ ./execvp-demo
total 26
drwx------ 2 troccoli operator 2048 Jan 11 21:03 cpp-primer
drwx------ 3 troccoli operator 2048 Jan 15 12:43 cs107review
drwx------ 2 troccoli operator 2048 Jan 13 14:15 filesystems
drwx------ 2 troccoli operator 2048 Jan 13 14:14 lambda
drwxr-xr-x 3 poohbear root 2048 Nov 19 13:24 map-reduce
drwx------ 2 poohbear root 4096 Nov 19 13:25 networking
drwxr-xr-x 2 poohbear root 6144 Jan 22 08:58 processes
drwxr-xr-x 2 poohbear root 2048 Oct 29 06:57 threads-c
drwxr-xr-x 2 poohbear root 4096 Oct 29 06:57 threads-cpp
$execvp()
execvp is a function that lets us run another program in the current process.
int execvp(const char *path, char *argv[]);A shell is essentially a program that repeats asking the user for a command and running that command (Demo: first-shell-soln.c)
system()
The built-in system function can execute a given shell command.
int system(const char *command);int main(int argc, char *argv[]) {
int status = system(argv[1]);
printf("system returned %d\n", status);
return 0;
}$ ./system-demo "ls -l"
total 26
drwx------ 2 troccoli operator 2048 Jan 11 21:03 cpp-primer
drwx------ 3 troccoli operator 2048 Jan 15 12:43 cs107review
drwx------ 2 troccoli operator 2048 Jan 13 14:15 filesystems
drwx------ 2 troccoli operator 2048 Jan 13 14:14 lambda
drwxr-xr-x 3 poohbear root 2048 Nov 19 13:24 map-reduce
drwx------ 2 poohbear root 4096 Nov 19 13:25 networking
drwxr-xr-x 2 poohbear root 6144 Jan 21 19:38 processes
drwxr-xr-x 2 poohbear root 2048 Oct 29 06:57 threads-c
drwxr-xr-x 2 poohbear root 4096 Oct 29 06:57 threads-cpp
system returned 0
$mysystem()
We can implement our own version of system with fork(), waitpid() and execvp()!
int mysystem(const char *command);One twist; not all shell commands are executable programs, and some need parsing.
execvp inside parent and forgo the child process altogether? Becauseexecvp would consume the calling process, and that's not what we want.static int mysystem(char *command) {
pid_t pidOrZero = fork();
if (pidOrZero == 0) {
char *arguments[] = {"/bin/sh", "-c", command, NULL};
execvp(arguments[0], arguments);
// If the child gets here, there was an error
exitIf(true, kExecFailed, stderr, "execvp failed to invoke this: %s.\n", command);
}
// If we are the parent, wait for the child
int status;
waitpid(pidOrZero, &status, 0);
return WIFEXITED(status) ? WEXITSTATUS(status) : -WTERMSIG(status);
}Here's the implementation, with minimal error checking (the full version is linked at the bottom):
mysystem()
first-shell Takeaways
fork, execvp, and waitpid to do this!stsh ("Stanford shell") with much of the functionality of real Unix shells.Shells have a variety of supported commands:
Let's make an updated version of mysystem called executeCommand.
static void executeCommand(char *command, bool inBackground) {
pid_t pidOrZero = fork();
if (pidOrZero == 0) {
// If we are the child, execute the shell command
char *arguments[] = {"/bin/sh", "-c", command, NULL};
execvp(arguments[0], arguments);
// If the child gets here, there was an error
exitIf(true, kExecFailed, stderr, "execvp failed to invoke this: %s.\n", command);
}
// If we are the parent, either wait or return immediately
if (inBackground) {
printf("%d %s\n", pidOrZero, command);
} else {
waitpid(pidOrZero, NULL, 0);
}
}static void executeCommand(char *command, bool inBackground) {
pid_t pidOrZero = fork();
if (pidOrZero == 0) {
// If we are the child, execute the shell command
char *arguments[] = {"/bin/sh", "-c", command, NULL};
execvp(arguments[0], arguments);
// If the child gets here, there was an error
exitIf(true, kExecFailed, stderr, "execvp failed to invoke this: %s.\n", command);
}
// If we are the parent, either wait or return immediately
if (inBackground) {
printf("%d %s\n", pidOrZero, command);
} else {
waitpid(pidOrZero, NULL, 0);
}
}Line 1: Now, the caller can optionally run the command in the background.
static void executeCommand(char *command, bool inBackground) {
pid_t pidOrZero = fork();
if (pidOrZero == 0) {
// If we are the child, execute the shell command
char *arguments[] = {"/bin/sh", "-c", command, NULL};
execvp(arguments[0], arguments);
// If the child gets here, there was an error
exitIf(true, kExecFailed, stderr, "execvp failed to invoke this: %s.\n", command);
}
// If we are the parent, either wait or return immediately
if (inBackground) {
printf("%d %s\n", pidOrZero, command);
} else {
waitpid(pidOrZero, NULL, 0);
}
}Lines 11-16: The parent waits on a foreground child, but not a background child.
int main(int argc, char *argv[]) {
char command[kMaxLineLength];
while (true) {
printf("> ");
fgets(command, sizeof(command), stdin);
// If the user entered Ctl-d, stop
if (feof(stdin)) {
break;
}
// Remove the \n that fgets puts at the end
command[strlen(command) - 1] = '\0';
if (strcmp(command, "quit") == 0) break;
bool isbg = command[strlen(command) - 1] == '&';
if (isbg) {
command[strlen(command) - 1] = '\0';
}
executeCommand(command, isbg);
}
printf("\n");
return 0;
}In main, we add two additional things:
Note that a background child isn't reaped! This is a problem - one we'll learn how to fix soon.
Next time: interprocess communication
What if we want to spawn a single child and wait for that child before spawning another child?
static const int kNumChildren = 8;
int main(int argc, char *argv[]) {
for (size_t i = 0; i < kNumChildren; i++) {
pid_t pidOrZero = fork();
if (pidOrZero == 0) {
printf("Hello from child %d!\n", getpid());
return 110 + i;
}
int status;
pid_t pid = waitpid(pidOrZero, &status, 0);
if (WIFEXITED(status)) {
printf("Child with pid %d exited normally with status %d\n", pid, WEXITSTATUS(status));
} else {
printf("Child with pid %d exited abnormally\n", pid);
}
}
return 0;
}
Check out the abbreviated program below (link to full program at bottom):