Linux for Programmers and Users
12.4. Process Management
A Linux process is a unique instance of a running or runable program. Every process in a Linux system has the following attributes:
When Linux is first started, there's only one visible process in the system. This process is called "init," and is PID 1. The only way to create a new process in Linux is to duplicate an existing process, so "init" is the ancestor of all subsequent processes. When a process duplicates, the parent and child processes are virtually identical (except for things like PIDs, PPIDs, and runtimes); the child's code, data, and stack are a copy of the parent's, and they even continue to execute the same code. A child process may, however, replace its code with that of another executable file, thereby differentiating itself from its parent. When a child process terminates, its death is communicated to its parent so that the parent may take some appropriate action. It's very common for a parent process to suspend until one of its children terminates. For example, when a shell executes a utility in the foreground, it duplicates into two shell processes; the child shell process replaces its code with that of the utility, whereas the parent shell waits for the child process to terminate. When the child terminates, the original parent process awakens and presents the user with the next shell prompt. Figure 12-31 illustates the way that a shell executes a utility; I've indicated the system calls that are responsible for each phase of the execution. Figure 12-31. How a shell runs a utility.
Let's look at some simple programs that introduce the system calls one by one. The next few subsections describe the system calls listed in Figure 12-32.
12.4.1. Creating a New Process: fork ()
A process may duplicate itself by using fork () (Figure 12-33).
fork () is a strange system call, because one process (the original) calls it, but two processes (the original and its child) return from it. Both processes continue to run the same code concurrently, but they have completely separate stack and data spaces. This reminds me of a great sci-fi story I read once, about a man who comes across a fascinating booth at a circus. The vendor at the booth tells the man that the booth is a matter-replicator; anyone who walks through the booth is duplicated. The original person walks out of the booth unharmed, but the duplicate person walks out onto the surface of Mars as a slave of the Martian construction crews. The vendor then tells the man that he'll be given a million dollars if he allows himself to be replicated, and he agrees. He happily walks through the machine, looking forward to collecting the million dollars ... and walks out onto the surface of Mars. Meanwhile, back on Earth, his duplicate is walking off with a stash of cash. The question is this: If you came across the booth, what would you do? Linux adds a clone () system call that is, for most uses, the same as fork (). clone () provides for parts of the execution context to be shared (rather than merely copied as with fork () ) between parent and child. A process may obtain its own process ID and parent process ID numbers by using the getpid () and getppid () system calls, respectively (Figure 12-34).
To illustrate the operation of fork (), here's a small program that duplicates and then branches based on the return value of fork (): $ cat myfork.c ...list the program. #include <stdio.h> main () { int pid; printf ("I'm the original process with PID %d and PPID %d.\n", getpid (), getppid ()); pid = fork (); /* Duplicate. Child and parent continue from here */ if (pid != 0) /* pid is non-zero, so I must be the parent */ { printf ("I'm the parent process with PID %d and PPID %d.\n", getpid (), getppid ()); printf ("My child's PID is %d\n", pid); } else /* pid is zero, so I must be the child */ { printf ("I'm the child process with PID %d and PPID %d.\n", getpid (), getppid ()); } printf ("PID %d terminates.\n", getpid () ); /* Both processes execute this */ } $ ./myfork ...run the program. I'm the original process with PID 13292 and PPID 13273. I'm the parent process with PID 13292 and PPID 13273. My child's PID is 13293. I'm the child process with PID 13293 and PPID 13292. PID 13293 terminates. ...child terminates. PID 13292 terminates. ...parent terminates. $ _ The PPID of the parent refers to the PID of the shell that executed the "myfork" program. Here is a warning: As you will soon see, it is dangerous for a parent to terminate without waiting for the death of its child. The only reason that the parent doesn't wait for its child in this example is because I haven't yet described the wait () system call! 12.4.2. Orphan Processes
If a parent dies before its child, the child is automatically adopted by the original "init" process, PID 1. To illustrate this, I modified the previous program by inserting a sleep statement into the child's code. This ensured that the parent process terminated before the child. Here's the program and the resultant output: $ cat orphan.c ...list the program. #include <stdio.h> Figure 12-35 illustrates the orphaning effect: Figure 12-35. Process adoption.
12.4.3. Terminating a Process: exit ()
A process may terminate at any time by executing exit () (Figure 12-36).
The termination code of a child process may be used for a variety of purposes by the parent process. Shells may access the termination code of their last child process via one of their special variables. For example, the C shell stores the termination code of the last command in the variable $status: % cat myexit.c ...list the program. #include <stdio.h> main () { printf ("I'm going to exit with return code 42\n"); exit (42); } % ./myexit ...run the program. I'm going to exit with return code 42 % echo $status ...display the termination code. 42 % _
In all other shells, the return value is returned in the special shell variable $?. 12.4.4. Zombie Processes
A process that terminates cannot leave the system until its parent accepts its return code. If its parent process is already dead, it will already have been adopted by the "init" process, which always accepts its children's return codes. However, if a process's parent is alive but never executes a wait (), the process's return code will never be accepted and the process will remain a zombie. A zombie process doesn't have any code, data, or stack, so it doesn't use up many system resources, but it does continue to inhabit the system's task list. Too many zombie processes can require the system administrator to intervene; see Chapter 14, "System Administration," for more details. The following program created a zombie process, which was indicated in the output from the ps utility. When I killed the parent process, the child was adopted by "init" and allowed to rest in peace. $ cat zombie.c ...list the program. #include <stdio.h> main () { int pid; pid = fork (); /* Duplicate */ if (pid != 0) /* Branch based on return value from fork () */ { while (1) /* Never terminate, and never execute a wait () */ sleep (1000); } else { exit (42); /* Exit with a silly number */ } } $ ./zombie & ...execute the program in the background. [1] 15896 $ ps ...obtain process status. PID TTY TIME CMD 15870 pts2 00:00:00 bash ...the shell. 15896 pts2 00:00:00 zombie ...the parent. 15897 pts2 00:00:00 zombie <defunct> ...the zombie. 15898 pts2 00:00:00 ps $ kill 15896 ...kill the parent process. [1] + Terminated ./zombie $ ps ...notice the zombie is gone now. PID TTY TIME CMD 15870 pts2 00:00:00 bash 15901 pts2 00:00:00 ps $ _ 12.4.5. Waiting for a Child: wait ()
A parent process may wait for one of its children to terminate and then accept its child's termination code by executing wait () (Figure 12-37).
In the following example, the child process terminated before the end of the program by executing an exit () with return code 42. Meanwhile, the parent process executed a wait () and suspended until it received its child's termination code. At this point, the parent displayed information about its child's demise and executed the rest of the program: $ cat mywait.c ...list the program. #include <stdio.h> main () { int pid, status, childPid; printf ("I'm the parent process and my PID is %d\n", getpid ()); pid = fork (); /* Duplicate */ if (pid != 0) /* Branch based on return value from fork () */ { printf ("I'm the parent process with PID %d and PPID %d\n", getpid (), getppid ()); childPid = wait (&status); /* Wait for a child to terminate. */ printf ("A child with PID %d terminated with exit code %d\n", childPid, status >> 8); } else { printf ("I'm the child process with PID %d and PPID %d\n", getpid (), getppid ()); exit (42); /* Exit with a silly number */ } printf ("PID %d terminates\n", getpid () ); } 12.4.6. Differentiating a Process: exec
A process may replace its current code, data, and stack with those of another executable by using one of the exec family of system calls. When a process executes an exec, its PID and PPID numbers stay the sameonly the code that the process is executing changes. The exec family of system calls work as described in Figure 12-38.
The exec family listed in Figure 12-38 aren't really system callsthey're C library functions that invoke the execve () system call. execve () is hardly ever used directly, as it contains some rarely used options. In the following example, the program displayed a small message and then replaced its code with that of the "ls" executable. Note that the execl () was successful and therefore never returned: $ cat myexec.c ...list the program. #include <stdio.h> main () { printf ("I'm process %d and I'm about to exec an ls -l\n",getpid ()); execl ("/bin/ls", "ls", "-l", NULL); /* Execute ls */ printf ("This line should never be executed\n"); } $ ./myexec ...run the program. I'm process 13623 and I'm about to exec an ls -l total 125 -rw-r--r-- 1 glass cs 277 Feb 15 00:47 myexec.c -rwxr-xr-x 1 glass cs 24576 Feb 15 00:48 myexec $ _ 12.4.7. Changing Directories: chdir ()
Every process has a current working directory that is used when processing a relative pathname. A child process inherits its current working directory from its parent. For example, when a utility is executed from a shell, its process inherits the shell's current working directory. To change a process' current working directory, use chdir () (Figure 12-39).
In the following example, the process printed its current working directory before and after executing chdir () by executing pwd using the system () library function: $ cat mychdir.c ...list the source code. #include <stdio.h> main () { system ("pwd"); /* Display current working directory */ chdir ("/"); /* Change working directory to root directory */ system ("pwd"); /* Display new working directory */ chdir ("/home/glass"); /* Change again */ system ("pwd"); /* Display again */ }
12.4.8. Changing Priorities: nice ()
Every process has a priority value between -20 and +19 that affects the amount of CPU time that it's allocated. In general, the smaller the priority value, the faster the process will run. Only super-user and kernel processes (described in Chapter 13, "Linux Internals") can have a negative priority value, and login shells start with priority 0. A child process inherits its priority value from its parent, and may change it by using nice () (Figure 12-40).
In the following example, the process executes ps commands before and after a couple of nice () calls. Notice that when the process's priority changes, the next invocation of the ps command also has the lower priority. $ cat mynice.c ...list the source code. #include <stdio.h> main () { printf ("original priority\n"); system ("ps -l"); /* Execute a ps */ nice (0); /* Add 0 to my priority */ printf ("running at priority 0\n"); system ("ps -l"); /* Execute another ps */ nice (10); /* Add 10 to my priority */ printf ("running at priority 10\n"); system ("ps -l"); /* Execute the last ps */ } $ mynice ...execute the program. original priority F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY CMD 0 S 500 1290 1288 0 76 0 - 552 rt_sig pts/4 ksh
12.4.9. Accessing User and Group IDs
Figure 12-41 lists the system calls that allow you to read a process's real and effective IDs.
Figure 12-42 lists the system calls that allow you to set a process's real and effective IDs.
12.4.10. Sample Program: Background Processing
Here's a sample program that makes use of fork () and exec () to execute a program in the background. The original process creates a child to "exec" the specified executable and then terminates. The orphaned child is automatically adopted by "init." Notice how I craftily passed the argument list from main () to execvp () by passing &argv[1] as the second argument to execvp (). Note also that I used execvp () instead of execv () so that the program could use $PATH to find the executable file: $ cat background.c ...list the program. #include <stdio.h> main (argc, argv) int argc; char* argv []; { if (fork () == 0) /* Child */ { execvp (argv[1], &argv[1]); /* Execute other program */ fprintf (stderr, "Could not execute %s\n", argv[1]); } } $ background sleep 60 ...run the program. $ ps ...confirm that it is in background. PID TTY TIME CMD 10742 pts0 00:00:00 bash 10936 pts0 00:00:01 ksh 15669 pts0 00:00:00 csh 16073 pts0 00:00:00 sleep 60 16074 pts0 00:00:00 ps $ _
12.4.11. Redirection
When a process forks, the child inherits a copy of its parent's file descriptors. When a process execs, all non-close-on-exec file descriptors remain unaffected, including the standard input, output, and error channels. The Linux shells use these two pieces of information to implement redirection. For example, say you type the following command at a terminal: $ ls > ls.out
To perform the redirection, the shell performs the following series of actions:
To redirect the standard error channel in addition to standard output, the shell would simply have to duplicate the "ls.out" descriptor twice; once to descriptor 1 and once to descriptor 2. Here's a small program that does approximately the same kind of redirection as a Linux shell. When invoked with the name of a file as the first parameter and a command sequence as the remaining parameters, the program "redirect" redirects the standard output of the command to the named file. $ cat redirect.c ...list the program. #include <stdio.h> #include <fcntl.h> main (argc, argv) int argc; char* argv []; { int fd; /* Open file for redirection */ fd = open (argv[1], O_CREAT | O_TRUNC | O_WRONLY, 0600); dup2 (fd, 1); /* Duplicate descriptor to standard output */ close (fd); /* Close original descriptor to save descriptor space */ execvp (argv[2], &argv[2]); /* Invoke program; will inherit stdout */ perror ("main"); /* Should never execute */ } $ redirect ls.out ls -lG ...redirect "ls -lG" to "ls.out". $ cat ls.out ...list the output file. total 5 -rw-r-xr-x 1 glass 0 Feb 15 10:35 ls.out -rw-r-xr-x 1 glass 449 Feb 15 10:35 redirect.c -rwxr-xr-x 1 glass 3697 Feb 15 10:33 redirect $ _
|
Категории