进程关系

一.终端登录

1.终端登录(硬件相连的终端)

Linux系统管理者可以创建名为/etc/inittab的文件,其中每一行对应了一个终端设备,当系统启动时,init进程会为每一个允许登陆的终端设备都调用一次fork,并exec getty程序,该程序会调用open函数打开终端设备,并等待用户输入用户名。当用户输入用户名后,getty程序调用execle,执行login程序。该程序等待用户输入正确的密码后,更改进程ID并调用该用户的登陆shell,登陆shell的文件描述符,会与终端设备相连。如下图所:

                                    

 

2.网络登录

网络登录不同于终端登录,其事先并不知道有多少这样的登录,因此必须等待一个网络连接请求的到达,而不能如终端登录那样为每个终端启动一个进程进行等待。Linux为了使同一程序可以即处理终端登录又处理网络登录,采用的方法是为每一个网络终端创建一个伪终端,其创建过程如下所述:首先linux会在启动后创建一个守护进程xinetd,该进程等待TCP/IP连接请求,当连接到达时fork一个程序,并exec telnetd程序,该程序创建一个伪终端,并fork一个子进程执行login程序(同前一节一样用于等待用户输入密码,及执行登录shell),而父进程(也就是自身)则负责处理与网络终端的网络通信。负责进程间通过伪终端相连。如下所示:

       

【注】:不论哪种终端,Linux都会为每个终端启动一个登录shell,用于执行用户的命令输入(进程的实际,有效ID为用户ID)。

 

二.进程组

每个进程都有一个进程ID(pid),一个线程组ID(tgid),还有一个进程组ID(signal->pgrp,因为与信号操作有关)。在这里应该要区分以下线程组ID与进程组ID的概念,虽然都是进程,但线程组中的是轻量级进程,线程组中的所有轻量级进程都拥有相同的tgid,却有着各自的进程ID,而进程组是一个个相互独立的进程,它们有着各自的进程ID,各自的线程组ID和相同的进程组ID。同一进程组中的各个进程接收来自同一终端的各种信号,具体如何接收在其它博文中进行讨论。可以通过函数getpgrp或getpgid获取进程组ID。

每个进程组都有一个进程组组组长,组长的进程组ID等于进程ID(这一点类似于线程组)。进程组组长可以创建一个进程组(通过setpgid函数将两个参数都设置为自己的进程ID)、创建该组中的进程。即使进程组组长终止了,但只要某进程组中有一个进程存在,则该进程组就存在。一个进程组中的最后一个进程可以终止也可以转入另一个进程(使用setpgid函数)。

一个进程只能为它自己或它的子进程设置进程组ID,且在其子进程调用exec后,它便不可在为该子进程设置进程组ID。

常常会在fork后设置子进程的进程组ID,此时由于父子进程的执行顺序不可确定,因此会出现竞态问题,即,若只在父进程中设置子进程的进程组ID,则子进程可能先运行,此时的进程组ID还未设置,若只在子进程中设置,亦是同理。因此为了防止这种竞态问题,一般会在父子进程中都进行一遍设置。

三.会话

会话是一个或多个进程组的集合,通常是由shell的管道将几个进程编程一组。比如有如下命令,那么此时一个会话中会有三个进程组,分别是:【登录shell】,【命令1的进程,命令2的进程】,【命令3的进程,命令4的进程,命令5的进程】

命令1 | 命令2

命令3 | 命令4 | 命令5

一个非组长进程可以调用setsid函数创建一个新会话。当调用该函数创建一个新的会话时会发生以下三件事:

(1)该进程会变成新会话的会话首进程(即创建该会话的进程),此时该进程是新会话中的唯一进程。

(2)该进程成为一个新进程组的组长进程,该进程组ID即该进程的ID。

(3)该进程没有控制终端,即使在调用setsid之前有,那么这种联系也会被切断。

只有一个非进程组组长进程才可以创建使用setsid函数创建会话,该进程会成为一个新进程组的组长,进程组ID就是该进程的ID,且会话ID也会被设置为该进程的ID,其在进程描述符中的存储方式为:signal->session 即会话领头进程的PID。

int main()
{
    if(fork() == 0) {
        std::cout<<"child process before setsid: "<<getpid() <<" "<<::syscall(SYS_gettid) << " "<<getpgrp()<<std::endl;
        if(setsid() == -1){
           std::cout<<"error"<<std::endl;
        }
        std::cout<<"child process after setsid: "<<getpid() <<" "<<::syscall(SYS_gettid) << " "<<getpgrp()<<std::endl;
    }
    else{
        std::cout<<"main process: "<<getpid() <<" "<<::syscall(SYS_gettid) << " "<<getpgid(0)<<std::endl;
    }
    return 0;
}

其允许结果如下:

     

     可以发现子进程的进程组ID发生了变化。

【问】:如何保证一个调用setsid的进程是非进程组组长?

【答】:通常先fork一个子进程,然后终止父进程,这样子进程与父进程的ID必不相同,因此子进程一定不是进程组组长,随后调用setsid创建一个新的会话便不会出现问题。

 

四.控制终端

1.什么是控制终端

控制终端通常就是第一章中所述的终端设备或网络终端设备。

 

2.会话和进程组以及控制终端间的关系

  • 一个会话可以有一个控制终端,也可以没有。
  • 拥有控制终端的会话的会话首进程称为控制进程
  • 如果一个会话拥有控制终端,则它有一个前台进程组,其它进程组为后台进程组。
  • 无论何时输入终端的中断键(通常是Delete或Ctrl + C),都会发送中断信号至前台进程组的所有进程。
  • 无论何时输入终端的退出键(常常是Ctrl + \),都会将退出信号发送至前台进程组的所有进程。
  • 若终端已经断开连接,那么将挂断信号发送至控制进程,即会话首进程。

【问】:那么如何创建一个守护进程呢?

【答】:由上可知,后台进程组不会收到中断信号和退出信号,若没有终端,那么会话首进程页不会收到挂断信号,因此可以fork一个子进程,并终止父进程,调用setsid创建一个新的会话,之后调用exec执行相应程序。

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值