进程间关系(上)

进程间关系

引言

本章详细说明linux会话的概念。还将介绍登录shell和所有从登录shell启动进程之间的关系。

9.2终端登录

在早期的unix系统中用户用哑终端进行登录,硬件直接连接到服务器。终端是本地的或者是远程的,登录由内核中的终端设备驱动程序。

随着位映射终端图像的出现,开发除了窗口系统,而另一些平台则自动为用户启动窗口系统。

1.BSD终端登录

自举一词来自于人都是靠自身的“自举”机构站立起来的这一思想。计算机必须具备自举能力将自己所与的元件激活,以便能完成加载操作系统这一目的,然后再由操作系统承担起那些单靠自举代码无法完成的更复杂的任务。



自举只有两个功能:加电自检和磁盘引导。  加电自检:当我们按下计算机电源开关时,头几秒钟机器似乎什么反应也没有,其实,这时的计算机正在进行加电自检,以断定它的所有元件都在正确地工作。如果某个元件有故障,显示器上就会出现报警提示信息(如果显示器也不能正常工作,则以一串嘟嘟声来报警)。由于大多数计算机工作非常可靠,加电自检报警非常罕见。   磁盘引导:查找装有操作系统的磁盘驱动器。从磁盘加载操作系统的原因有二:一是操作系统升级简单容易,二是使用户拥有选择操作系统的自由。当以上功能完成时,自举操作就启动一个读写操作系统文件和将它们复制到随机存储器中的过程,此时的机器才是真正意义上的计算机。计算机的启动可以有冷启动和热启动两种方式 ,它们之间的差别是热启动不进行机器的自检(机器本事配置的检查与测试),当计算机在使用过程中由于某些原因造成死机时,可以对计算机进行热启动处理

系统管理者通常创建名字为/etc/ttys的文件,每个终端都有一行,每一行说明了设备名字和传到getty的参数。

系统自举的时候,内核创建id为1的进程,也就是init进程。init进程使用户进入多用户模式。init读取/etc/ttys对每一个允许登录的设备终端,init调用一次fork,他生成的子进程则exec getty程序。

getty对终端设备调用了open函数、以读写的方式打开终端。如果是调制解调器,则open可能在驱动程序中滞留,直到用户拨通号码。一旦设备被打开,则描述符0.1.2会被重置到这个设备。getty回输出login等待用户输入。如果终端支持多种速度,则getty可以支持多种速度,则getty可以测试特殊字符以便更改终端速度。如果用户输入了用户名,则getty类似于下面的方式调用了login

execle("/bin/login", "login", "-p", username, (char*)0, envp);

init用一个空环境调用getty。getty以终端名字和环境字符串login创建一个环境。-p标志通知login保留传递给他的环境,也可将其他字符串加到环境中,但是不要替换他。

大体过程是

调用getpwname取得相应的用户口令文件登录项。接着调用getpass显示调用password,接着读用户输入的口令。调用crtpt把口令加密,然后与阴影文件做对比,如果几次输入都不对exit退出。

教新的linux系统中使用PAM 可插入的身份验证模块。

PAM下如果用户正确login后,将会做下面的事情:

1.将当前的工作目录更改为起始目录,chdir

2.调用chown更改终端的所有权,使登录用户成为他的所有者

3.对终端设备的访问权限改为读写

4.调用setgid以及initgroups设置进程组id。

5.用login得到所有信息的初始化环境:起始目录、shell、用户名以及系统默认路径

6.login进程更改为登录用户的uid,setuid并将该用户设置为登录的shell。就好像调用了execl("/bin/sh","-sh",(char*)0)

setuid会更改实际用户id,有效用户id额保存用户id

9.3网络登录

1.BSD网络登录

在BSD中,有一个inted进程,等待大多数网络连接。

init调用shell,使shell调用/etc/rc,由此inted变成了一个守护进程,一旦shell停止,inted父进程就变为了init。inted等待tcp连接,一旦有请求则fork一次,然后调用exec

大体流程

init->inetd->fork+exec(telnetd)和客户端交换数据

起始就是用一个进程做了伪终端。

9.4进程组

每一个进程除了有一个进程id之外,还有一个进程组id,getpgrp返回了调用进程的进程组id 进程组是一个或多个进程的集合。 写一个demo

#include <unistd.h>
#include <iostream>

int main()
{
    std::cout<<getpgrp()<<std::endl;
}

如果只有一个主进程一般就是自己了

getpgid返回进程的进程组id,进程组id就是进程id #include <unistd.h> #include

int main()
{
    pid_t pid = getpid();
    std::cout<<(pid)<<std::endl;
    std::cout<<getpgid(pid)<<std::endl;
}

输出的都是一样的进程id和进程组id

setpgid 可以将进程加入进程组或者setsid也可以创建一个进程组

setpgid(pid_t pid,pid pgid);

setpgid函数将pid进程的进程组id设置为pgid。如果这两个参数相等,则由pid指定的进程变成进程组组长。如果pid是0,则使用调用者的进程id。如果pgid是0,则用调用者的进程id作为进程组id。

用来创建一个新的进程组

大多数作业控制的shell中会调用这个函数,使父进程设置其子进程的进程组id,并且也使子进程设置他自己的进程组id。这两个调用由一个是冗余的,但是让父进程和子进程都这样做可以保证,在父进程和子进程认为子进程已经进入这个分组之前,这确实已经发生了。如果不这样做,在fork之后,由于父子进程的执行先后顺序不确定,会因为子进程的组员身份取决于哪一个进程限制性而产生竞态条件。

上面设个函数是一个十分有意思的函数我们可以写几个demo来研究一下

#include <unistd.h>
#include <iostream>
#include <wait.h>
#include <unistd.h>

int main()
{
    int status;

    pid_t son_pid = fork();
    pid_t parent_pid = getpid();
    if(son_pid == 0)
    {
        setpgid(0, getpid());

        //son
        printf("son-pid:%d;group id:%d\n",getpid(),getpgrp());

        pid_t _son_pid = fork();
        if(_son_pid == 0)
        {
            printf("son son group id:%d\n",getpgrp());
            while(1)
            {

            }
        }else if(_son_pid > 0)
        {
            waitpid(_son_pid, &status, 0 );
        }
    }else if(son_pid > 0)
    {
        sleep(3);
        printf("parent-pid:%d;group id:%d\n",parent_pid,getpgrp());
        //parent
        waitpid(son_pid, &status, 0 );
    }

}

我们使用ps aux 来观察这个进程组的id /home/zhanglei/ourc/test/cmake-build-debug/test son-pid:11207;group id:11207 son son group id:11207 parent-pid:11206;group id:11206

setpgid函数有很多限制:

·pid参数必须指定为调用setpgid函数的进程或其子进程,不能随意修改不相关进程的进程组ID,如果违反这条规则,则返回-1,并置errno为ESRCH。
·pid参数可以指定调用进程的子进程,但是子进程如果已经执行了exec函数,则不能修改子进程的进程组ID。如果违反这条规则,则返回-1,并置errno为EACCESS。
·在进程组间移动,调用进程,pid指定的进程及目标进程组必须在同一个会话之内。这个比较好理解,不加入公司(会话),就无法加入公司下属的部门(进程组),否则就是部门要造反的节奏。如果违反这条规则,则返回-1,并置errno为EPERM。
·pid指定的进程,不能是会话首进程。如果违反这条规则,则返回-1,并置errno为EPERM

我用这个demo看到了这个程序是正确的,子进程设置进程组的id为他自己这样就变成了两个进程组了。

先到这里继续向下读。

9.5会话

会话是一个或者多个进程组的集合

进程调用setsid函数建立一个新的会话。

#include <unistd.h>
pid_t setsid

如果调用的不是进程组的组长,那么这个函数会创建一个会话。具体还会有下面三个事情发生。

1)这个进程会变成会话的首进程。此时该进程是这个会话中的唯一进程。

2)这个进程会变为新进程组的组长进程。

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

如果调用的是进程组的组长则会失败。一般会fork父进程之后把父进程杀死,子进程获取了一个全新的进程id,这就保证了这个进程不会是进程组的组长。

getsid(pid_t pid)

如果pid是0,getsid返回调用进程的会话首进程的进程组id。如果调用的pid不属于调用者所在的会话,那么这个进程就不能得到会话首进程的进程组id。

猜你喜欢

转载自blog.csdn.net/qq_32783703/article/details/107238355