进程关系

终端登录

inti进程使系统进入多用户状态,init进程读/etc/ttys,对每一个允许登录的终端设备调用一次fork,它所产生的子进程执行getty程序

getty为终端调用open函数,以读写方式打开终端,一旦终端被打开,则文件描述符0,1,2就被设置到该终端。getty输出:“login:”提示用户输入用户名

当用户输入用户名后,getty,以execle(“/bin/login”,”login”,”-p”,username,(char*)0),envp)这行login

login得到用户名后,调用getpwnam获取对应的登录文件登录项。接着调用getpass显示”password:”提示,接着用户输入口令,接着调用crypt将用户输入的口令加密并与阴影口令中的登录项的pw_passwd字段比较。如果几次都失败,那么login以参数1调用exit(),表示登录失败,父进程init了解子进程终止后,重复上述2-4过程。

如果登录成功,login就执行下面工作:

  • 将当前工作目录设置为该用户的起始目录
  • 调用chown使登录用户名成为它的所有者
  • 将对该终端的访问权限改变成用户读写
  • 调用setgid和initgroups设置进程的组ID
  • 用login所得到的信息初始化环境:起始目录,shell,用户名,以及一个系统默认的PATH
  • 调用setuid设置用户ID,并调用该用户的登录shell:execl(“/bin/sh”,”-sh”,(char*)0)

至此,登录用户的shell开始运行,它的父进程是init,所以该shell终止时,init会受到通知它会重复上述所有步骤

网络登录

  • init调用shell,使其执行/etc/rc脚本,由此shell脚本启动守护进程inetd.
  • inetd等待TCP/IP 请求达到主机,当一个连接到达时,它执行一次fork,然后生成的子进程执行适当的程序(Telnet等,这里假设通过Telnet)。
  • telnetd进程打开一个伪终端,并fork一个子进程执行login程序,父子进程通过伪终端相连接。在exec之前,子进程使其文件描述符0,1,2与伪终端相连。后面的步骤与终端登录相同。

进程组

每个进程除了有一个进程ID外,还属于一个进程组,进程组是一个或多个进程的集合,每个进程组拥有一个唯一的进程组ID,进程组ID类似进程ID-它是一个正整数。获取进程组ID:

#include <unistd.h>
 pid_t getpgrp(void);
// The POSIX.1 getpgrp() always returns the PGID of the caller.

每个进程组都可以有一个进程组长,组长的标志是其进程ID等于进程组ID。组长进程可以创建一个进程组,然后就终止。只要进程组中有一个进程存在,那么进程组就存在,跟组长进程是否存在没有关系。进程组中的最后一个进程可以终止,或者转移到另一个进程组。

进程可以通过调用setpgid加入一个现有的组或者创建一个新的进程组。

#include <unistd.h>

 int setpgid(pid_t pid, pid_t pgid);

 //On success, setpgid() return zero.  On error, -1 is returned, and errno is  set  appropriately.

如果pid ==pgid,那么pid指定的进程变成进程组组长。如果pid==0,那么pid等于调用进程的进程ID。如果pgid==0,则由pid指定的进程ID将用作进程组ID。如果将一个进程组中的进程加入到另外一个进程组,那么这两个进程组必须在一个会话中。

一个进程只能为它自己或它的子进程设置进程组ID。在它的子进程调用了exec函数之后,它就不能改变该子进程的进程组ID。通常在执行fork后,父进程设置子进程的进程组ID,子进程设置自己的进程组ID,这么做是为了避免竞争,因为父子进程先后运行顺序不确定会造成一段时间内子进程组成员身份不确定。

会话

会话是一个或多个进程组的集合,通常是用shell的管道将几个进程编成一组。进程调用setsid函数建立一个新会话。

#include <unistd.h>

  pid_t setsid(void);
//On  success,  the  (new)  session  ID  of  the  calling process is returned.  On error, (pid_t) -1 is  returned, and errno is set to indicate the error.

如果调用此函数的进程不是进程组组长,则此函数就会创建一个新会话,结果将发生下面3件事:

该进程变成新会话session leader

该进程变成新进程组的group leader,新进程组ID是调用进程的进程ID

该进程没有控制终端,如果在调用setsid之前有一个控制终端,那么这种关系也会被中断。

如果调用进程已经是进程组组长,那么函数将会调用失败。为了保证不会发生这种情况,通常先调用fork,然后父进程终止,子进程继续,这就保证子进程不会是进程组长。

下面程序说明了这点:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
int main()
{
    printf("pid=%d,ppid=%d,pgrp=%d,sid=%d\n",getpid(),getppid(),getpgrp(),getsid(0));

    pid_t pid;
    int fd;

    if((pid = fork())< 0)
    {
        printf("fork error\n");
        return -1;
    }else if( pid == 0)
    {
        sleep(2);
        printf("pid=%d,ppid=%d,pgrp=%d,sid=%d\n",getpid(),getppid(),getpgrp(),getsid(0));
        if((fd = open("/dev/tty",O_RDWR)) < 0)
        {
            perror("reason:");
        }
        if(setsid()< 0)
        {
            printf("setpid error\n");
            return -1;
        }

        printf("pid=%d,ppid=%d,pgrp=%d,sid=%d\n",getpid(),getppid(),getpgrp(),getsid(0));

        if((fd = open("/dev/tty",O_RDWR)) < 0)
        {
            perror("reason:");
        }

        return 0;

    }else
    {
        _exit(0);
    }

    return 0;
}

获取进程的session leader ID:

#include <unistd.h>

 pid_t getsid(pid_t pid);
// On success, a session ID is returned.  On error, (pid_t) -1 will be returned, and errno is set appropriately.

如果pid == 0,返回调用进程的会话ID,其他的返回进程ID为pid的进程所在会话的会话ID。

控制终端

一个会话可以有一个控制终端,通常是登录到其上的终端设备或伪终端设备

建立与控制终端连接的会话首进程称为控制进程

一个会话中几个进程可被分成一个前台进程组,会话中的其他进程组则为后台进程组

如果会话有一个控制终端则它有个前台进程组,会话中的的其他进程组为后台进程组

无论何时键入终端的终端键,就会将中断信号发送给前台进程组的所有进程

无论何时键入终端的退出键,就会将退出信号发送给前台进程组的所有进程

如果终端检测到连接断开,则将挂断信号发送给控制进程

有时不管标准输入,标准输出是否重定向,程序都要与控制终端交互。保证程序能够读写终端的方法是打开/dev/tty,对内核而言/dev/tty就是控制终端的代名词。

获取前台进程组

#include <unistd.h>

 pid_t tcgetpgrp(int fd);
//-1 error

 int tcsetpgrp(int fd, pid_t pgrp);
//-1 error,0 sucess

函数tcgetgrp获取前台进程组ID,该前台进程组与在fd上打开的终端相关联

如果进程有一个控制终端,该进程可以调用函数tcsetpgrp将组ID为pgrp的进程组变成前台进程组,fd必须与控制终端相关联,pgrp必须是同一会话中的进程组ID。

#include <termios.h>

 pid_t tcgetsid(int fd);
// The  function  tcgetsid() returns the session ID of the current session that has the terminal associated to fd as controlling terminal.  This terminal must be the controlling terminal  of  the  calling process.

作业控制

作业控制必须满足下面要求:

1.支持作业控制Shell

2.内核的终端驱动程序必须支持作业控制

3.内核必须提供某些作业控制的信号支持

作业控制信号:

中断信号,(CTRL+C 或者DeLETE)产生SIGINT

退出信号,(CTRL+、)产生SIGQUIT

停止信号(CTRL+Z)产生SIGTSTP

后端作业读写终端:

后端作业读控制终端,驱动程序发送SIGTTIN给后端进程,停止该后台作业,并由shell通知用户

后端作业写控制终端,驱动程序发送SIGTTOU给后端进程,停止该后台进程,并由shell通知用户

孤儿进程组

该组中的每个成员的父进程要么是该组的一个成员,要么不是该组所属的会话的成员。一个进程组不是孤儿进程组的条件是,该组中有个进程,其父进程在属于用一个会话中的其他进程组。

孤儿进程组中停止的进程每个进程都会被发送SIGHUP信号,接着发送SIGCONT信号,SIGHUP信号默认终止进程,因此必须捕获该信号并处理。

原文地址:https://www.cnblogs.com/xiaofeifei/p/4106800.html