Processes
The Basics |
Prof. David Bernstein |
Computer Science Department |
bernstdh@jmu.edu |
main()
:
-a
all processes associated with terminals-A
all processes-l
generates a long listing#include <stdio.h> #include <stdlib.h> // Define an initialized constant with file scope const int constant_data = 11; // Define an initialized variable with file scope int initialized_data = 22; // Tentatively define a variable with global (i.e., file) scope // which is in the Block Started by Symbol (BSS) section (a.k.a., the // unitialized data block) int bss_data; // Remember, argc and argv are copies of the actual parameters // (since parameters are passed by value) int main(int argc, char* argv[]) { int local_variable = 99; int* heap_firstcall; int* heap_secondcall; // Note that memory allocators often use blocks of different sizes, // the smallest of which is often 16 or 32 bytes. So, even though an // int requires less space, malloc() will allocate more (i.e., the // amount of the smallest block) heap_firstcall = (int*)(malloc(sizeof(int))); heap_secondcall = (int*)(malloc(sizeof(int))); // The %p specifier prints a pointer as a hex character sequence printf("Stack %p\n", &heap_secondcall); // Note the & printf(". %p\n", &heap_firstcall); // Note the & printf(". %p\n", &local_variable); printf(". %p\n", &argc); printf(". %p\n", &argv); printf("\n"); printf(". %p\n", heap_secondcall); // Note the lack of an & printf("Heap: %p\n", heap_firstcall); // Note the lack of an & printf("BSS: %p\n", &bss_data); printf("Initialized: %p\n", &initialized_data); printf("Constants: %p\n", &constant_data); printf("Code: %p\n", &main); // Cleanup free(heap_firstcall); free(heap_secondcall); // Done return 0; }
name=value
export
name
export
name=value
setenv
name
value
printenv
unset
(or
unsetenv
in the C shell)int setenv(const char *name, const char* value, int overwrite)
name
|
A pointer to the name of the environment variable |
value
|
A pointer to the value of the environment variable |
ovewrite
|
0 not to overwrite an existing variable; nonzero to always set/reset |
Return | 0 on success; -1 on error |
Note: There is also a putenv()
function but you must be much more
careful when using it because it does not duplicate the string it
just points to it. Hence, the string must not be an automatic
variable (i.e., a character on the stack) or an array that might change.
pid_t fork(void)
Return | Parent: PID of the child or -1. Child: 0 |
Note: fork()
is called from the parent process but creates
a duplicate child process (containing the same instructions). So, there
is a return in both the parent and the child.
fork()
)fork()
:
main()
void exit(int status)
status
|
0 for a successful termination; positive otherwise |
Return | Does not return |
Note: exit()
calls exit handlers (in reverse order of
registration), flushes the standard files, and calls _exit()
.
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { const char* fname; FILE *input_file; int arguments, grads, total; pid_t pid; pid = fork(); // The assignment will be performed in the parent and the child switch (pid) { case -1: exit(1); case 0: fname = "college_1990.dat"; break; default: fname = "college_2000.dat"; break; } total = 0; input_file = fopen(fname, "r"); while (!feof(input_file)) { arguments = fscanf(input_file, "%d\n", &grads); if (arguments == 1) total += grads; } fclose(input_file); printf("Total College Grads in %s: %d\n", fname, total); exit(0); }
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { const char* fname; FILE *input_file; float commute, total_commute; int arguments, grads, n, total_grads; pid_t pid; pid = fork(); // The assignment will be performed in the parent and the child switch (pid) { case -1: exit(1); case 0: fname = "college_2000.dat"; total_grads = 0; input_file = fopen(fname, "r"); while (!feof(input_file)) { arguments = fscanf(input_file, "%d\n", &grads); if (arguments == 1) total_grads += grads; } fclose(input_file); printf("Total College Grads in %s: %d\n", fname, total_grads); break; default: fname = "commute_2000.dat"; total_commute = 0.0; n = 0; input_file = fopen(fname, "r"); while (!feof(input_file)) { arguments = fscanf(input_file, "%f\n", &commute); if (arguments == 1) { total_commute += commute; ++n; } } fclose(input_file); printf("Average Commute in %s: %f\n", fname, total_commute/n); break; } exit(0); }
#include "stats_lib.h" #include <stdio.h> float mean(const char *fname) { FILE *input_file; float datum, total; int arguments, n; total = 0.0; n = 0; input_file = fopen(fname, "r"); while (!feof(input_file)) { arguments = fscanf(input_file, "%f\n", &datum); if (arguments == 1) { total += datum; ++n; } } fclose(input_file); return total / (double)n; } int total(const char *fname) { FILE *input_file; int arguments, datum, total; total = 0; input_file = fopen(fname, "r"); while (!feof(input_file)) { arguments = fscanf(input_file, "%d\n", &datum); if (arguments == 1) total += datum; } fclose(input_file); return total; }
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include "stats_lib.h" int main(int argc, char *argv[]) { const char* fname; pid_t pid; pid = fork(); // The assignment will be performed in the parent and the child switch (pid) { case -1: exit(1); case 0: fname = "college_1990.dat"; printf("Total College Grads in %s: %d\n", fname, total(fname)); break; default: fname = "college_2000.dat"; printf("Total College Grads in %s: %d\n", fname, total(fname)); break; } exit(0); }
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include "stats_lib.h" int main(int argc, char *argv[]) { const char* fname; pid_t pid; pid = fork(); // The assignment will be performed in the parent and the child switch (pid) { case -1: exit(1); case 0: fname = "college_2000.dat"; printf("Total College Grads in %s: %d\n", fname, total(fname)); break; default: fname = "commute_2000.dat"; printf("Average Commute in %s: %f\n", fname, mean(fname)); break; } exit(0); }
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include "stats_lib.h" int main(int argc, char *argv[]) { const char* fname; pid_t pid, qid; pid = fork(); // fork once switch (pid) { case -1: exit(1); case 0: fname = "college_1990.dat"; printf("Total College Grads in %s: %d\n", fname, total(fname)); break; default: fname = "commute_1990.dat"; printf("Average Commute in %s: %f\n", fname, mean(fname)); break; } qid = fork(); // fork again switch (qid) { case -1: exit(1); case 0: fname = "college_2000.dat"; printf("Total College Grads in %s: %d\n", fname, total(fname)); break; default: fname = "commute_2000.dat"; printf("Average Commute in %s: %f\n", fname, mean(fname)); break; } exit(0); }
fork()
there are two.
Then, both the parent and child call fork()
resulting in two more.
commute_2000.dat
main()
functions)
exist for the tasks we want to perform in
the differentexec
familyint execve(const char *path, char *const argv[], char *const envp[]);
path
|
The path name of the new program |
argv
|
The NULL-terminated (so the length can be determined) array of arguments to be passed to the new program |
envp
|
The NULL-terminated (so the length can be determined) array of name-value pairs specifying the environment list |
Return | Does not return on success; returns -1 on error |
Note: The process ID remains the same and the file descriptors are left open.
errno
ENOENT
|
The file doesn't exist |
ENOEXEC
|
The file isn't in a recognizable format |
ETXTBSY
|
The file is open for writing |
E2BIG
|
The argument list and/or environment space are too big |
execve()
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include "stats_lib.h" int main(int argc, char *argv[]) { const char* fname; fname = argv[1]; printf("Total College Grads in %s: %d\n", fname, total(fname)); exit(0); }
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include "stats_lib.h" int main(int argc, char *argv[]) { const char* fname; fname = argv[1]; printf("Average Commute in %s: %f\n", fname, mean(fname)); exit(0); }
execve()
(cont.)#include <stdio.h> #include <stdlib.h> #include <unistd.h> // Note: There is no reason to #include the headers for grads and // commute because neither the compiler nor linker uses them. int main(int argc, char *argv[]) { char *argvec[3]; char *envvec[] = {NULL}; pid_t pid; argvec[2] = NULL; pid = fork(); // The assignment will be performed in the parent and the child switch (pid) { case -1: exit(1); case 0: argvec[0] = (char*)"college"; argvec[1] = (char*)"college_2000.dat"; break; default: argvec[0] = (char*)"commute"; argvec[1] = (char*)"commute_2000.dat"; break; } execve(argvec[0], argvec, envvec); }
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { char *argvec[3]; char *envvec[] = {NULL}; pid_t pid; argvec[2] = NULL; pid = fork(); switch (pid) { case -1: exit(1); case 0: argvec[0] = (char*)"college"; argvec[1] = (char*)"college_2000.dat"; break; default: argvec[0] = (char*)"commute"; argvec[1] = (char*)"commute_2000.dat"; break; } execve(argvec[0], argvec, envvec); pid = fork(); switch (pid) { case -1: exit(1); case 0: argvec[0] = (char*)"college"; argvec[1] = (char*)"college_1990.dat"; break; default: argvec[0] = (char*)"commute"; argvec[1] = (char*)"commute_1990.dat"; break; } execve(argvec[0], argvec, envvec); exit(0); }
fork()
there are two.
Then the text/code segment of both processes is changed
by execve()
so the second call
to fork()
is never executed.
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { char *argvec[3]; char *envvec[] = {NULL}; pid_t pid; argvec[2] = NULL; pid = fork(); if (pid == -1) exit(1); else if (pid == 0) // First child { argvec[0] = (char*)"commute"; argvec[1] = (char*)"commute_1990.dat"; execve(argvec[0], argvec, envvec); } pid = fork(); if (pid == -1) exit(1); else if (pid == 0) // Second child { argvec[0] = (char*)"commute"; argvec[1] = (char*)"commute_2000.dat"; execve(argvec[0], argvec, envvec); } pid = fork(); if (pid == -1) exit(1); else if (pid == 0) // Third child { argvec[0] = (char*)"college"; argvec[1] = (char*)"college_1990.dat"; execve(argvec[0], argvec, envvec); } // Parent argvec[0] = (char*)"college"; argvec[1] = (char*)"college_2000.dat"; execve(argvec[0], argvec, envvec); exit(0); }
fork()
creates a child
exec()
Familyexecve()
are inconvenientexec
functions with slightly
different signatrues (e.g., execle()
,
execlp()
, execvp()
,
execv()
, and execl()
)waitpid()
pid_t waitpid(pid_t pid, int *status, int options);
pid
|
The process to wait for (see below) |
status
|
An outbound parameter used to indicate how the child terminated |
options
|
A bit mask |
Return | The PID of the child or 0; -1 on error |
Note: If pid
is greater than 0 then this call returns
when the specific process ID terminates. If pid
is 0
then this call returns when any child in the same process as the
caller group terminates. If pid
is less than -1 then
this call returns when any child in the group of the absolute value
of the PID terminates. If pid
is -1 then it returns
when any child terminates.
WNOHANG
|
Return immediately (with a value of 0) if no member of the wait set has already terminated. |
WUNTRACED
|
Suspend execution of the calling process until a running member of the wait set is terminated or stopped. |
WCONTINUED
|
Suspend execution of the calling process until a cunning member of the wait set is terminated or a stopped member has been resumed. |
pid_t wait(int *status);
status
|
An outbound parameter used to indicate how the child terminated |
Return | The PID of the child or 0; -1 on error |
Note: Calling wait(&status)
is equivalent to calling
waitpid(-1, &status; 0)
.
WIFEXITED(status)
|
Returns true if the child exited normally. |
WIFSIGNALED(status)
|
Returns true if the child was
killed by a signal (and WTERMSIG(status) returns the
signal number).
|
WIFSTOPPED(status)
|
Returns true if the child
that caused the return is stopped (and WSTOPSIG(status)
then returns the signal number).
|
WIFCONTINUED(status)
|
Returns true if the child that caused the return was resumed. |
init
- the ancestor of all processes)wait()
(and has most of its resources returned
to the system but the process ID and termination status
are maintained)wait()
/waitpid()
wait()
/waitpid()
init
)wait()
/waitpid()
to
ensure that they don't create long-lived zombies
(this is known as reaping)#include <stdio.h> #include <stdlib.h> #include <unistd.h> #define N 5 int main(void) { pid_t pid; printf("\nWhile the parent is sleeping, execute the following command"); printf("(and note the <defunct> indicator).\n"); printf("ps -l --ppid %ld\n\n", (long)getpid()); for (int i=0; i<N; i++) { pid = fork(); if (pid == 0) // This is a child process { printf("I'm child %d with a PID of %ld\n", i, (long)getpid()); exit(0); // Terminate (and become a zombie) } } // Executed in the parent only sleep(20); printf("\nAfter the parent terminates, execute the command again.\n"); printf("ps -l --ppid %ld\n\n", (long)getpid()); exit(0); }
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #define N 5 int main(void) { pid_t pid; printf("\nWhile the parent is sleeping, execute the command:\n"); printf("ps --ppid %ld\n\n", (long)getpid()); pid = fork(); if (pid == 0) // This is a child process { // When the parent terminates, the child becomes an orphan // and is inherited by init (which has a PID of 1) printf("\nThe child has a PID of %ld\n", (long)getpid()); printf("After the parent terminates, execute the command:\n"); printf("ps -l --pid %ld\n", (long)getpid()); sleep(30); // When the child also terminates it becomes a zombie // and will be reaped by init printf("\nThe child has now terminated.\n"); printf("Execute the command again:\n"); printf("ps -l --pid %ld\n", (long)getpid()); exit(0); } // Executed in the parent only sleep(20); printf("\nThe parent has now terminated.\n"); exit(0); }
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #define N 5 int main(void) { pid_t pid; printf("While the parent is sleeping, execute the following command.\n"); printf("ps -l --ppid %ld\n\n", (long)getpid()); for (int i=0; i<N; i++) { pid = fork(); if (pid == 0) // This is a child process { printf("I'm child %d with a PID of %ld\n", i, (long)getpid()); sleep(1); exit(0); // Terminate (and become a zombie) } } // Executed in the parent only // Reap a single child ("at random") // // -1 indicates the wait set is all child processes // 0 indicates execution should be suspended until one element terminates // This call returns immediately if the child is a zombie, otherwise // it waits. // int status; pid_t reaped; reaped = waitpid(-1, &status, 0); // Equivalent to wait(&status) printf("Reaped child with PID %ld\n", (long)reaped); sleep(20); exit(0); }
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #define N 5 int main(void) { pid_t pid; printf("While the parent is sleeping, execute the command:\n"); printf("ps -l --ppid %ld\n\n", (long)getpid()); for (int i=0; i<N; i++) { pid = fork(); if (pid == 0) // This is a child process { printf("I'm child %d with a PID of %ld\n", i, (long)getpid()); sleep(1); exit(0); // Terminate (and become a zombie) } } // Executed in the parent only // Reap all children ("in random order") // int status; pid_t reaped; while ((reaped = waitpid(-1, &status, 0)) > 0) { printf("Reaped child with PID %ld\n", (long)reaped); } sleep(20); exit(0); }