Linux for Programmers and Users

[Page 473 (continued)]

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:

  • some code (a.k.a. text)

  • some data

  • a stack

  • a unique process ID number (PID)

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.


[Page 474]

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.

Figure 12-32. Linux process-oriented system calls.

Name

Function

fork

Duplicates a process.

getpid

Obtains a process's ID number.

getppid

Obtains a parent process's ID number.

exit

Terminates a process.

wait

Waits for a child process.

exec..

Replaces the code, data, and stack of a process.

12.4.1. Creating a New Process: fork ()

A process may duplicate itself by using fork () (Figure 12-33).


[Page 475]

Figure 12-33. Description of the fork () system call.

System Call: pid_t fork (void)

fork () causes a process to duplicate. The child process is an almost-exact duplicate of the original parent process; it inherits a copy of its parent's code, data, stack, open file descriptors, and signal table. However, the parent and child have different process ID numbers and parent process ID numbers.

If fork () succeeds, it returns the PID of the child to the parent process, and returns 0 to the child process. If it fails, it returns -1 to the parent process, and no child is created.

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).

Figure 12-34. Description of the getpid () and getppid () system calls.

System Call: pid_t getpid (void)

pid_t getppid (void)

getpid () and getppid () return a process's ID and parent process's ID numbers, respectively. They always succeed. The parent process ID number of PID 1 is 1.

To illustrate the operation of fork (), here's a small program that duplicates and then branches based on the return value of fork ():


[Page 476]

$ 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>


[Page 477]
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) /* Branch based on return value from fork () */ { /* pid is nonzero, 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 */ sleep (5); /* Make sure that the parent terminates first */ 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 */ } $ ./orphan ...run the program. I'm the original process with PID 13364 and PPID 13346. I'm the parent process with PID 13364 and PPID 13346. PID 13364 terminates. I'm the child process with PID 13365 and PPID 1. ...orphaned! PID 13365 terminates. $ _

Figure 12-35 illustrates the orphaning effect:

Figure 12-35. Process adoption.


[Page 478]

12.4.3. Terminating a Process: exit ()

A process may terminate at any time by executing exit () (Figure 12-36).

Figure 12-36. Description of the exit () system call.

System Call: void exit (int status)

exit () closes all of a process's file descriptors, deallocates its code, data, and stack, and then terminates the process. When a child process terminates, it sends its parent a SIGCHLD signal and waits for its termination code status to be accepted. Only the lower eight bits of status are used, so values are limited to 0255. A process that is waiting for its parent to accept its return code is called a zombie process. A parent accepts a child's termination code by executing wait (), which is described shortly.

The kernel ensures that all of a terminating process's children are orphaned and adopted by "init" by setting their PPID to 1. The "init" process always accepts its children's termination codes.

exit () never returns.

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.


[Page 479]

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).


[Page 480]

Figure 12-37. Description of the wait () system call.

System Call: pid_t wait (int* status)

wait () causes a process to suspend until one of its children terminates. A successful call to wait () returns the pid of the child that terminated and places a status code into status that is encoded as follows:

If the rightmost byte of status is zero, the leftmost byte contains the low eight bits of the value returned by the child's call to exit () or return ().

If the rightmost byte is nonzero, the rightmost seven bits are equal to the number of the signal that caused the child to terminate, and the remaining bit of the rightmost byte is set to 1 if the child produced a core dump.

If a process executes a wait () and has no children, wait () returns immediately with -1. If a process executes a wait () and one or more of its children are already zombies, wait () returns immediately with the status of one of the zombies.

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 () ); }


[Page 481]
$ ./mywait ...run the program. I'm the parent process and my PID is 13464 I'm the child process with PID 13465 and PPID 13464 I'm the parent process with PID 13464 and PPID 13409 A child with PID 13465 terminated with exit code 42 PID 13465 terminates $ _

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.

Figure 12-38. Description of the exec family of library functions.

Library Function: int execl (const char* path, const char* arg0, const char* arg1, ..., const char* argn, NULL)

         int execv (const char* path, const char* argv[ ])

         int execlp (const char* path, const char* arg0, const char* arg1,..., const char* argn,

NULL)        int execvp (const char* path, const char* argv[ ])

The exec family of library functions replace the calling process's code, data, and stack from the executable whose pathname is stored in path.

execvl () is identical to execlp (), and execv () is identical to execvp (), except that execl () and execv () require the absolute or relative pathname of the executable file to be supplied, whereas execlp () and execvp () use the $PATH environment variable to find path.

If the executable is not found, the system call returns -1; otherwise, the calling process replaces its code, data, and stack from the executable and starts to execute the new code. A successful call to any of the exec system calls never returns.

execl () and execlp () invoke the executable with the string arguments pointed to by arg1..argn. arg0 must be the name of the executable file itself, and the list of arguments must be null terminated.

execv () and execvp () invoke the executable with the string arguments pointed to by argv[1]..argv[n], where argv[n+1] is NULL. argv[0] must be the name of the executable file itself.

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.


[Page 482]

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).

Figure 12-39. Description of the chdir () system call.

System Call: int chdir (const char* pathname)

chdir () sets a process's current working directory to the directory pathname. The process must have execute permission from the directory to succeed.

chdir () returns 0 if successful; otherwise, it returns -1.

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 */ }


[Page 483]
$ ./mychdir ...execute the program. /home/glass / /home/glass $ _

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).

Figure 12-40. Description of the nice () library function.

Library Function: int nice (int delta)

nice () adds delta to a process's current priority value. Only a super-user may specify a delta that leads to a negative priority value. Legal priority values lie between -20 and +19. If a delta is specified that takes a priority value beyond a limit, the priority value is truncated to the limit.

If nice () succeeds, it returns the new nice value; otherwise it returns -1. Note that this can cause problems, since a nice value of -1 is legal.

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


[Page 484]
0 S 500 1549 1290 0 76 0 - 583 wait4 pts/4 a.out 0 S 500 1550 1549 0 80 0 - 889 - pts/4 ps running at priority 0 ...adding 0 doesn't change it. 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 0 S 500 1549 1290 0 75 0 - 583 wait4 pts/4 a.out 0 S 500 1551 1549 0 78 0 - 638 - pts/4 ps running at priority 10 ...adding 10 makes them run slower. 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 0 S 500 1549 1290 0 90 10 - 583 wait4 pts/4 a.out 0 S 500 1552 1549 0 87 10 - 694 pts/4 ps $ _

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-41. Description of the getuid (), geteuid (), getgid (), and getegid () system calls.

System Call: uid_t getuid ()

uid_t geteuid ()

gid_t getgid ()

gid_t getegid ()

getuid () and geteuid () return the calling process's real and effective user ID, respectively. getgid () and getegid () return the calling process's real and effective group ID, respectively. The ID numbers correspond to the user and group IDs listed in the "/etc/passwd" and "/etc/group" files.

These calls always succeed.

Figure 12-42 lists the system calls that allow you to set a process's real and effective IDs.

Figure 12-42. Description of the setuid (), seteuid (), setgid (), and setegid () system calls.

System Call: int setuid (uid_t id)

int seteuid (uid_t id)

int setgid (gid_t id)

int setegid (gid_t id)

seteuid () and setegid () set the calling process's effective user and group ID, respectively. setuid () and setgid () set the calling process's effective and real user and group ID, respectively, to the specified value.

These calls succeed only if executed by a super-user, or if id is the real or effective user (group) ID of the calling process. They return 0 if successful; otherwise, they return -1.


[Page 485]

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:

  • The parent shell forks and then waits for the child shell to terminate.

  • The child shell opens the file "ls.out," creating it or truncating it as necessary.

  • The child shell then duplicates the file descriptor of "ls.out" to the standard output file descriptor, number 1, and then closes the original descriptor of "ls.out". All standard output is therefore redirected to "ls.out".


  • [Page 486]
  • The child shell then exec's the ls utility. Since the file descriptors are inherited during an exec (), all of the standard output of ls goes to "ls.out".

  • When the child shell terminates, the parent resumes. The parent's file descriptors are unaffected by the child's actions, as each process maintains its own private descriptor table.

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 $ _

Категории