9-Process Relationships

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

9.1 Introduction

9.2 Terminal Logins

BSD Terminal Logins

  • The system administrator creates a file(usually /etc/ttys) that has one line per terminal device. Each line specifies the name of the device and other parameters that are passed to the getty program.
  • When the system is bootstrapped, the kernel creates process ID 1, the init process, and it is init that brings the system up in multiuser mode. The init process reads the file /etc/ttys and, for every terminal device that allows a login, does a fork followed by an exec of the program getty. This gives us the processes shown in Figure 9.1.

  • All the processes shown in Figure 9.1 have a real user ID of 0 and an effective user ID of 0(i.e., they all have superuser privileges). The init process also execs the getty program with an empty environment.
  • It is getty that calls open for the terminal device which is opened for reading and writing. Once the device is open, file descriptors 0, 1, and 2 are set to the device. Then getty outputs something like login: and waits for us to enter our user name. If the terminal supports multiple speeds, getty can detect special characters that tell it to change the terminal’s speed(baud rate).
  • When we enter our user name, getty’s job is complete, and it then invokes the login program, similar to
    execle("/bin/login", "login", "-p", username, (char *)0, envp);
    init invokes getty with an empty environment; getty creates an environment for login (the envp argument) with the name of the terminal(something like TERM=foo, where the type of terminal foo is taken from the gettytab file) and any environment strings that are specified in the gettytab. The -p flag to login tells it to preserve the environment that it is passed and to add to that environment, not replace it. Figure 9.2 shows the state of these processes right after login has been invoked.

  • All the processes shown in Figure 9.2 have superuser privileges, since the original init process has superuser privileges. The process ID of the bottom three processes in Figure 9.2 is same, since the process ID does not change across an exec. All the processes other than the original init process have a parent process ID of 1.
  • The login program does many things. Since it has our user name, it can call getpwnam to fetch our password file entry. Then login calls getpass(3) to display the prompt Password: and read our password. It calls crypt(3) to encrypt the password that we entered and compares the encrypted result to the pw_passwd field from our shadow password file entry. If the login attempt fails because of an invalid password after a few tries, login calls exit with an argument of 1. This termination will be noticed by the parent(init), and it will do another fork followed by an exec of getty, starting the procedure over again for this terminal.
  • This is the traditional authentication procedure used on UNIX. Modern UNIX have evolved to support multiple authentication procedures.
  • Linux support a scheme known as PAM(Pluggable Authentication Modules). PAM allows an administrator to configure the authentication methods to be used to access services that are written to use the PAM library.
  • If our application needs to verify that a user has the appropriate permission to perform a task, we can either hard code the authentication mechanism in the application or use the PAM library to give us the equivalent functionality. The advantage to using PAM is that administrators can configure different ways to authenticate users for different tasks, based on the local site policies.
  • If we log in correctly, login will
    -Change to our home directory(chdir)
    -Change the ownership of our terminal device(chown) so we own it
    -Change the access permissions for our terminal device so we have permission to read from and write to it
    -Set our group IDs by calling setgid and initgroups
    -Initialize the environment with all the information that login has: our home directory (HOME), shell (SHELL), user name (USER and LOGNAME), and a default path (PATH)
    -Change to our user ID (setuid) and invoke our login shell, as in
    execl("/bin/sh", "-sh", (char *)0);
  • The minus sign as the first character of argv[0] is a flag to all the shells that indicates they are being invoked as a login shell. The shells can look at this character and modify their start-up accordingly.
  • setuid(Section 8.11) is called by a superuser process and it changes all three user IDs: real user ID, effective user ID, and saved set-user-ID. The call to setgid that was done earlier by login has the same effect on all three group IDs.
  • At this point, our login shell is running. Its parent process ID is the original init process(process ID 1), so when our login shell terminates, init is notified(it is sent a SIGCHLD signal) and it starts the whole procedure over again for this terminal. File descriptors 0, 1, and 2 for our login shell are set to the terminal device. Figure 9.3 shows this arrangement.

  • Our login shell now reads its start-up files. These start-up files usually change some of the environment variables and add many other variables to the environment.

Linux Terminal Logins

  • The Linux login procedure is similar to the BSD procedure. The main difference is in the way the terminal configuration is specified.
  • Ubuntu ship with a version of init that is known as ‘‘Upstart.’’ It uses configuration files named *.conf that are stored in the /etc/init directory. For example, the specifications for running getty on /dev/tty1 might be found in the file /etc/init/tty1.conf. Depending on the version of getty in use, the terminal characteristics are specified either on the command line (as with agetty) or in the file /etc/gettydefs (as with mgetty).

9.3 Network Logins

  • The main(physical) difference between logging in to a system through a serial terminal and logging in to a system through a network is that the connection between the terminal and the computer isn’t point-to-point. In this case, login is simply a service available.
  • With the terminal logins in the previous section, init knows which terminal devices are enabled for logins and spawns a getty process for each device. In the case of network logins, all the logins come through the kernel’s network interface drivers (e.g., the Ethernet driver), and we don’t know ahead of time how many of these will occur. Instead of having a process waiting for each possible login, we now have to wait for a network connection request to arrive.
  • To allow the same software to process logins over both terminal logins and network logins, a software driver called a pseudo terminal is used to emulate the behavior of a serial terminal and map terminal operations to network operations, and vice versa.

BSD Network Logins

  • In BSD, a single process waits for most network connections: the inetd process, called the Internet superserver.
  • As part of the system start-up, init invokes a shell that executes the shell script /etc/rc. One of the daemons that is started by this shell script is inetd. Once the shell script terminates, the parent process of inetd becomes init; inetd waits for TCP/IP connection requests to arrive at the host. When a connection request arrives for it to handle, inetd does a fork and exec of the appropriate program.
  • Assume that a TCP connection request arrives for the TELNET server. TELNET is a remote login application that uses the TCP protocol. A user on another host(that is connected to the server’s host through a network of some form) or on the same host initiates the login by starting the TELNET client: telnet hostname
  • The client opens a TCP connection to hostname, and the program that’s started on hostname is called the TELNET server. The client and the server then exchange data across the TCP connection using the TELNET application protocol. What has happened is that the user who started the client program is now logged in to the server’s host. (This assumes the user has a valid account on the server’s host.)
  • Figure 9.4 shows the sequence of processes involved in executing the TELNET server, called telnetd.

  • The telnetd process then opens a pseudo terminal device and splits into two processes using fork. The parent handles the communication across the network connection, and the child does an exec of the login program. The parent and the child are connected through the pseudo terminal.
  • Before doing the exec, the child sets up file descriptors 0, 1, and 2 to the pseudo terminal. If we log in correctly, login performs the same steps we described in Section 9.2: it changes to our home directory and sets our group IDs, user ID, and our initial environment. Then login replaces itself with our login shell by calling exec. Figure 9.5 shows the arrangement of the processes at this point.

  • The important thing to understand is that whether we log in through a terminal (Figure 9.3) or a network(Figure 9.5), we have a login shell with its standard input, standard output, and standard error connected to either a terminal device or a pseudo terminal device.

Linux Network Logins

  • Network logins under Linux are the same as under BSD, except that some distributions use an alternative inetd process called the extended Internet services daemon, xinetd. The xinetd process provides a finer level of control over services it starts compared to inetd.

9.4 Process Groups

  • Each process belongs to a process group that is a collection of one or more processes, usually associated with the same job, that can receive signals from the same terminal.
  • Each process group has a unique process group ID that is a positive integer and can be stored in a pid_t data type.
#include <unistd.h>
pid_t getpgrp(void);
Returns: process group ID of calling process
  • getpgrp returns the process group ID of the calling process.
#include <unistd.h>
pid_t getpgid(pid_t pid);
Returns: process group ID if OK, -1 on error
  • If pid is 0, the process group ID of the calling process is returned. getpgid(0) = getpgrp();
  • Each process group have a process group leader whose process ID = its process group ID.
  • It is possible for a process group leader to create a process group, create processes in the group, and then terminate. The process group still exists, as long as at least one process is in the group, regardless of whether the group leader terminates. This is called the process group lifetime: the period of time that begins when the group is created and ends when the last remaining process leaves the group. The last remaining process in the process group can terminate or enter some other process group.
  • A process joins an existing process group or creates a new process group by calling setpgid.
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);
Returns: 0 if OK, -1 on error
  • This function sets the process group ID to pgid in the process whose process ID equals pid.
  • If pid = pgid: the process specified by pid becomes a process group leader.
    If pid = 0: the process ID of the caller is used.
    If pgid = 0: the process ID specified by pid is used as the process group ID.
  • A process can set the process group ID of only itself or any of its children. It can’t change the process group ID of one of its children after that child has called one of the exec functions.
  • In most job-control shells, this function is called after a fork to have the parent set the process group ID of the child, and to have the child set its own process group ID.
  • One of these calls is redundant, but by doing both, we are guaranteed that the child is placed into its own process group before either process assumes that this has happened. If we didn’t do this, we would have a race condition, since the child’s process group membership would depend on which process executes first.

9.5 Sessions

  • A session is a collection of one or more process groups. Figure 9.6: 3 process groups in a single session.

  • The processes in a process group are placed there by a shell pipeline. Figure 9.6 could have been generated by shell commands:
    proc1 | proc2 &
    proc3 | proc4 | proc5
  • A process establishes a new session by calling the setsid function.
#include <unistd.h>
pid_t setsid(void);
Returns: process group ID if OK, -1 on error
  • If the calling process is not a process group leader, this function creates a new session. Three things happen.
    1. The process becomes the session leader(the process that creates a session) of this new session. The process is the only process in this new session.
    2. The process becomes the process group leader of a new process group. The new process group ID is the process ID of the calling process.
    3. The process has no controlling terminal. If the process had a controlling terminal before calling setsid, that association is broken.
  • This function returns an error if the caller is already a process group leader. To ensure this is not the case, the practice is to call fork and have the parent terminate and the child continue. The child cannot be a process group leader because the process group ID of the parent is inherited by the child, but the child gets a new process ID.
  • A session ID is the process ID of the session leader. The getsid function returns the process group ID of a process’s session leader.
#include <unistd.h>
pid_t getsid(pid_t pid);
Returns: session leader’s process group ID if OK, -1 on error
  • If pid is 0, getsid returns the process group ID of the calling process’s session leader.

9.6 Controlling Terminal

  • Sessions and process groups have a few other characteristics.
    1. A session can have a single controlling terminal that is established automatically when we log in. This is usually the terminal device (if terminal login) or pseudo terminal device(if network login) on which we log in.
    2. The session leader that establishes the connection to the controlling terminal is called the controlling process.
    3. The process groups within a session can be divided into a single foreground process group and one or more background process groups.
    4. If a session has a controlling terminal, it has a single foreground process group and all other process groups in the session are background process groups.
    5. Whenever we press the terminal’s interrupt key(DELETE or Control-C), the interrupt signal is sent to all processes in the foreground process group.
    6. Whenever we press the terminal’s quit key(Control-backslash), the quit signal is sent to all processes in the foreground process group.
    7. If a modem(or network) disconnect is detected by the terminal interface, the hang-up signal is sent to the controlling process(the session leader).
  • These characteristics are shown in Figure 9.7.

  • Figure 9.8 summarizes the way each platform discussed in this book allocates a controlling terminal.

  • There are times when a program wants to talk to the controlling terminal, regardless of whether the standard input or standard output is redirected. The way a program guarantees that it is talking to the controlling terminal is to open the file /dev/tty.
  • This special file is a synonym within the kernel for the controlling terminal. Naturally, if the program doesn’t have a controlling terminal, the open of this device will fail.
  • Example: getpass(3) reads a password. This function is called by the crypt(1) program and can be used in a pipeline. For example:
    crypt < salaries | lpr
    decrypts the file salaries and pipes the output to the print spooler. Because crypt reads its input file on its standard input, the standard input can’t be used to enter the password. Also, crypt is designed so that we have to enter the encryption password each time we run the program, to prevent us from saving the password in a file.

9.7 tcgetpgrp, tcsetpgrp, and tcgetsid Functions

  • We need a way to tell the kernel which process group is the foreground process group, so that the terminal device driver knows where to send the terminal input and the terminal-generated signals(Figure 9.7).
#include <unistd.h>
pid_t tcgetpgrp(int fd);
Returns: process group ID of foreground process group if OK, -1 on error
int tcsetpgrp(int fd, pid_t pgrpid);
Returns: 0 if OK, -1 on error
  • tcgetpgrp returns the process group ID of the foreground process group associated with the terminal open on fd.
  • If the process has a controlling terminal, the process can call tcsetpgrp to set the foreground process group ID to pgrpid. The value of pgrpid must be the process group ID of a process group in the same session, and fd must refer to the controlling terminal of the session.
#include <termios.h>
pid_t tcgetsid(int fd);
Returns: session leader’s process group ID if OK, -1 on error
  • tcgetsid allows an application to obtain the process group ID for the session leader given a file descriptor for the controlling TTY.
  • Applications that need to manage controlling terminals can use tcgetsid to identify the session ID of the controlling terminal’s session leader(= the session leader’s process group ID).

9.8 Job Control

  • Job control allows us to start multiple jobs(groups of processes) from a single terminal and to control which jobs can access the terminal and which jobs are run in the background. Job control requires three forms of support:
    1. A shell that supports job control.
    2. The terminal driver in the kernel must support job control.
    3. The kernel must support certain job-control signals.
  • When using job control from a shell, we can start a job in either the foreground or the background. A job is a collection of processes, often a pipeline of processes. For example,
    vi main.c
    starts a job consisting of one process in the foreground. The commands
    pr *.c | lpr &
    make all &
    start two jobs in the background. All the processes invoked by these background jobs are in the background.
  • When we start a background job, the shell assigns it a job identifier and prints one or more of the process IDs. The following script shows how the Korn shell handles this:
$ make all > Make.out &
[1]     1475
$ pr *.c | lpr &
[2]     1490
$                  #just press RETURN
[2] + Done          pr *.c | lpr &
[1] + Done          make all > Make.out &
  • The make is job number 1 and the starting process ID is 1475. The next pipeline is job number 2 and the process ID of the first process is 1490. When the jobs are done and we press RETURN, the shell tells us that the jobs are complete. The reason we have to press RETURN is to have the shell print its prompt. The shell doesn’t print the changed status of background jobs at any random time, only right before it prints its prompt, to let us enter a new command line.
  • The terminal driver looks for three special characters, which generate signals to the foreground process group.
    1. The interrupt character(DELETE or Control-C) generates SIGINT.
    2. The quit character(Control-backslash) generates SIGQUIT.
    3. The suspend character(Control-Z) generates SIGTSTP. Entering this character causes the terminal driver to send the SIGTSTP signal to all processes in the foreground process group. The jobs in any background process groups aren’t affected.
  • Only foreground job receives the characters that we enter at the terminal. It is not an error for a background job to try to read from the terminal, but the terminal driver detects this and sends signal(SIGTTIN) to the background job that normally stops the background job. By using the shell, we are notified of this event and can bring the job into the foreground so that it can read from the terminal. Example:
$ cat > temp.foo &     #start in background, but it’ll read from standard input
[1]     1681
$                      #we press RETURN
[1] + Stopped (SIGTTIN)     cat > temp.foo &
$ fg %1                    #bring job number 1 into the foreground
cat > temp.foo          #the shell tells us which job is now in the foreground
hello, world                #enter one line
ˆD                      #type the end-of-file character
$ cat temp.foo         #check that the one line was put into the file
hello, world
  • The shell starts the cat process in the background, but when cat tries to read standard input, the terminal driver sends the SIGTTIN signal to cat since it is a background job. The shell detects this change in status of its child and tells us that the job has been stopped. We then move the stopped job into the foreground with the shell’s fg command. Doing this causes the shell to place the job into the foreground process group(tcsetpgrp) and send the continue signal(SIGCONT) to the process group. Since it is now in the foreground process group, the job can read from the controlling terminal.
  • What happens if a background job sends its output to the controlling terminal?
    This is an option that we can allow or disallow. We use stty(1) command to change this option. Example:
$ cat temp.foo &       #execute in background
[1] 1719
$ hello, world     #the output from the background job appears after the prompt
#we press RETURN
[1] + Done  cat temp.foo &
$ stty tostop          #disable ability of background jobs to output to controlling terminal
$ cat temp.foo &       #try it again in the background
[1] 1721
$                  #we press RETURN and find the job is stopped
[1] + Stopped(SIGTTOU)      cat temp.foo &
$ fg %1                #resume stopped job in the foreground
cat temp.foo            #the shell tells us which job is now in the foreground
hello, world            #and here is its output
  • When we disallow background jobs from writing to the controlling terminal, cat will block when it tries to write to its standard output, because the terminal driver identifies the write as coming from a background process and sends the job the SIGTTOU signal. When we use the shell’s fg command to bring the job into the foreground, the job completes.

  • Figure 9.9 summarizes some of the features of job control that we’ve been describing. The solid lines through the terminal driver box mean that the terminal I/O and the terminal-generated signals are always connected from the foreground process group to the actual terminal. The dashed line corresponding to the SIGTTOU signal means that whether the output from a process in the background process group appears on the terminal is an option.

9.9 Shell Execution of Programs

  • First, we use a shell that doesn’t support job control. If we execute
$ ps -o pid,ppid,pgid,sid,comm
  PID   PPID    PGID    SID     COMMAND
 3962   3956    3962    3962    bash
 3989   3962    3989    3962    ps
  • The parent of ps command is the shell. Both the shell and the ps command are in the same session and foreground process group(3962). 3962 is the foreground process group because that is what you get when you execute a command with a shell that doesn’t support job control.
  • Linux: The process group ID associated with the session’s controlling terminal is shown under in TPGID column.
$ ps -o pid,ppid,pgid,sid,tpgid,comm
  PID   PPID    PGID    SID     TPGID   COMMAND
 3962   3956    3962    3962    3997    bash
 3997   3962    3997    3962    3997    ps
  • It is misleading to associate a process with a terminal process group ID(the TPGID column). A process does not have a terminal process control group. A process belongs to a process group, and the process group belongs to a session. The session may or may not have a controlling terminal. If the session does have a controlling terminal, then the terminal device knows the process group ID of the foreground process. This value can be set in the terminal driver with the tcsetpgrp function, as show in Figure 9.9. The foreground process group ID is an attribute of the terminal, not the process. This value from the terminal device driver is what ps prints as the TPGID. If it finds that the session doesn’t have a controlling terminal, ps prints either 0 or −1, depending on the platform.
  • If we execute the command in the background, the only value that changes is the process ID of the command:
$ ps -o pid,ppid,pgid,sid,comm &
  PID   PPID  PGID   SID        COMMAND
 3962   3956  3962      3962    bash
 3977   3962  3977      3962    ps
  • This shell doesn’t know about job control, so the background job is not put into its own process group and the controlling terminal isn’t taken away from the background job.
  • Let’s look at how the Bourne shell handles a pipeline. When we execute
$ ps -o pid,ppid,pgid,sid,comm | ./cat1 
  PID   PPID    PGID    SID     COMMAND
 3962   3956    3962    3962    bash
 4036   3962    4036    3962    ps
 4037   3962    4036    3962    cat1

  • cat1 is a copy of the standard cat program, with a different name. When we have two copies of cat in a pipeline, the different names let us differentiate between the two programs.
  • The last process in the pipeline is the child of the shell and the first process in the pipeline is a child of the last process. It appears that the shell forks a copy of itself and that this copy then forks to make each of the previous processes in the pipeline.
  • If we execute the pipeline in the background, only the process IDs change. Since the shell doesn’t handle job control, the process group ID of the background processes remains 3962, as does the process group ID of the session.
$ ps -o pid,ppid,pgid,sid,comm | ./cat1 &
  PID   PPID    PGID    SID     COMMAND
 3962   3956    3962    3962    bash
 4075   3962    4075    3962    ps
 4076   3962    4075    3962    cat1
  • What happens if a background process tries to read from its controlling terminal?
    cat > temp.foo &
  • With job control: shell places the background job into a background process group, which causes the signal SIGTTIN to be generated if the background job tries to read from the controlling terminal.
    Without job control: shell automatically redirects the standard input of a background process to /dev/null, if the process doesn’t redirect standard input itself. A read from /dev/null generates an end of file. This means that our background cat process immediately reads an end of file and terminates normally.
  • What happens if a background process specifically opens /dev/tty and reads from the controlling terminal?
    Answer is ‘‘It depends,’’ but the result is probably not what we want. Example:
    crypt < salaries | lpr &
    We run it in the background, but the crypt program opens /dev/tty, changes the terminal characteristics to disable echoing, reads from the device, and resets the terminal characteristics. When we execute this background pipeline, the prompt Password: from crypt is printed on the terminal, but what we enter(the encryption password) is read by the shell, which tries to execute a command of that name. The next line we enter to the shell is taken as the password, and the file is not encrypted correctly, sending junk to the printer. Here we have two processes trying to read from the same device at the same time, and the result depends on the system.
  • Job control handles this multiplexing of a single terminal between multiple processes in a better fashion.
  • If we execute three processes in the pipeline, we can examine the process control used by this shell:
$ ps -o pid,ppid,pgid,sid,comm | ./cat1 | ./cat2
  PID   PPID    PGID    SID     COMMAND
 3962   3956    3962    3962    bash
 4134   3962    4134    3962    ps
 4135   3962    4134    3962    cat1
 4136   3962    4134    3962    cat2

  • The last process in the pipeline is the child of the shell, and all previous processes in the pipeline are children of the last process. Figure 9.10 shows what is happening.

  • Since the last process in the pipeline is the child of the login shell, the shell is notified when that process cat2 terminates.
  • Now let’s examine the same examples using a job-control shell running on Linux. This shows the way these shells handle background jobs.
$ ps -o pid,ppid,pgid,sid,tpgid,comm
  PID   PPID    PGID    SID     TPGID   COMMAND
 3962   3956    3962    3962    4178    bash
 4178   3962    4178    3962    4178    ps
  • The Bourne-again shell places the foreground job(ps) into its own process group(4178). The ps command is the process group leader and the only process in this process group. This process group is the foreground process group, since it has the controlling terminal. Our login shell is a background process group while the ps command executes. Both process groups, 3962 and 4178, are members of the same session. Indeed, the session never changes through our examples in this section.
  • Executing this process in the background,
$ ps -o pid,ppid,pgid,sid,tpgid,comm &
  PID   PPID    PGID    SID     TPGID   COMMAND
 3962   3956    3962    3962    3962    bash
 4194   3962    4194    3962    3962    ps
  • Again, the ps command is placed into its own process group, but this time the process group(4194) is no longer the foreground process group, it is a background process group. The TPGID of 3962 indicates that the foreground process group is our login shell.
  • Executing two processes in a pipeline, as in
$ ps -o pid,ppid,pgid,sid,tpgid,comm | ./cat1
  PID   PPID    PGID    SID     TPGID   COMMAND
 3962   3956    3962    3962    4206    bash
 4206   3962    4206    3962    4206    ps
 4207   3962    4206    3962    4206    cat1
  • Both processes, ps and cat1, are placed into a new process group(4206), and this is the foreground process group. Another difference with the Bourne shell example: Bourne shell created the last process in the pipeline first, and this final process was the parent of the first process. Here, the Bourne-again shell is the parent of both processes. If we execute this pipeline in the background, the results are similar, but now ps and cat1 are placed in the same background process group:
$ ps -o pid,ppid,pgid,sid,tpgid,comm | ./cat1 &
  PID   PPID    PGID    SID     TPGID   COMMAND
 3962   3956    3962    3962    3962    bash
 4221   3962    4221    3962    3962    ps
 4222   3962    4221    3962    3962    cat1

9.10 Orphaned Process Groups

  • A process whose parent terminates is called an orphan and is inherited by the init process.
  • Consider a process that forks a child and then terminates, what happens if the child is stopped(using job control) when the parent terminates? How will the child ever be continued, and does the child know that it has been orphaned? Figure 9.11: parent fork a child that stops, and the parent is about to exit.

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

void Exit(char *string)
{
    printf("%s\n", string);
    exit(1);
}

static void sig_hup(int signo)
{
    printf("SIGHUP received, pid = %ld\n", (long)getpid());
}

static void pr_ids(char *name)
{
    printf("%s: pid = %ld, ppid = %ld, pgrp = %ld, tpgrp = %ld\n",
           name, (long)getpid(), (long)getppid(), (long)getpgrp(),
           (long)tcgetpgrp(STDIN_FILENO));
    fflush(stdout);
}

int main()
{
    char c;
    pid_t pid;

    pr_ids("parent");
    if((pid = fork()) < 0)
    {
        Exit("fork error");
    }
    else if(pid > 0)
    {
        sleep(3);
    }
    else
    {
        pr_ids("child");
        signal(SIGHUP, sig_hup);
        kill(getpid(), SIGTSTP);
        pr_ids("child");
        if(read(STDIN_FILENO, &c, 1) != 1)
        {
            printf("read error %d on controlling TTY\n", errno);
        }
    }
    exit(0);
}
  • The shell places the foreground process into its own process group(6099) and the shell stays in its own process group(2837). The child inherits the process group of its parent(6099). After the fork,
  • The parent sleeps for 5 seconds to let the child execute before the parent terminates.
  • The child establishes a signal handler for the hang-up signal(SIGHUP) so we can see whether it is sent to the child.
  • The child sends itself the stop signal(SIGTSTP) with the kill function. This stops the child, similar to stop a foreground job with terminal’s suspend character(Control-Z).
  • When the parent terminates, the child is orphaned, so the child’s parent process ID becomes 1, which is the init process ID.
  • At this point, the child is now a member of an orphaned process group. The POSIX.1 definition of an orphaned process group is one in which the parent of every member is either itself a member of the group or is not a member of the group’s session. In other words, the process group is not orphaned as long as a process in the group has a parent in a different process group but in the same session. If the process group is not orphaned, there is a chance that one of those parents in a different process group but in the same session will restart a stopped process in the process group that is not orphaned. Here, the parent of every process in the group(e.g., process 1 is the parent of process 6100) belongs to another session.
  • Since the process group is orphaned when the parent terminates, and the process group contains a stopped process, POSIX.1 requires that every process in the newly orphaned process group be sent the hang-up signal(SIGHUP) followed by the continue signal(SIGCONT).
  • This causes the child to be continued, after processing the hang-up signal. The default action for the hang-up signal is to terminate the process, so we have to provide a signal handler to catch the signal. We expect the printf in the sig_hup function to appear before the printf in the pr_ids function.
  • Here is the output from the program shown in Figure 9.13:
$ ./a.out
parent: pid = 6099, ppid = 2837, pgrp = 6099, tpgrp = 6099
child: pid = 6100, ppid = 6099, pgrp = 6099, tpgrp = 6099
$ SIGHUP received, pid = 6100
child: pid = 6100, ppid = 1, pgrp = 6099, tpgrp = 2837
read error 5 on controlling TTY
  • Note that our shell prompt appears with the output from the child, since two processes—our login shell and the child—are writing to the terminal. As we expect, the parent process ID of the child has become 1.
  • After calling pr_ids in the child, the program tries to read from standard input. As we saw earlier in this chapter, when a process in a background process group tries to read from its controlling terminal, SIGTTIN is generated for the background process group. But here we have an orphaned process group; if the kernel were to stop it with this signal, the processes in the process group would probably never be continued.
  • POSIX.1 specifies that the read is to return an error with errno set to EIO (whose value is 5 on this system) in this situation.
  • Finally, note that our child was placed in a background process group when the parent terminated, since the parent was executed as a foreground job by the shell. We’ll see another example of orphaned process groups in Section 19.5 with the pty program.

9.11 FreeBSD Implementation

9.12 Summary

  • This chapter has described the relationships between groups of processes — sessions, which are made up of process groups. Job control is a feature supported by most UNIX systems today, and we’ve described how it’s implemented by a shell that supports job control. The controlling terminal for a process, /dev/tty, is also involved in these process relationships.
  • We’ve made numerous references to the signals that are used in all these process relationships. The next chapter continues the discussion of signals, looking at all the UNIX System signals in detail.

Exercises 9.1

Refer back to our discussion of the utmp and wtmp files in Section 6.8. Why are the logout records written by the init process? Is this handled the same way for a network login?

  • The init process learns when a terminal user logs out, because init is the parent of the login shell and receives the SIGCHLD signal when the login shell terminates.
  • For a network login, however, init is not involved. Instead, the login entries in the utmp and wtmp files, and their corresponding logout entries, are usually written by the process that handles the login and detects the logout (telnetd in our example).

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值