进程关系

1.终端,控制台,控制终端的概念

1.1控制台和控制台

终端和控制台都不是个人电脑的概念,而是多人共用的小型中型大型计算机上的概念。

一台主机,连很多终端,终端为主机提供了人机接口,每个人都通过终端使用主机的资源.。

终端有字符哑终端和图形终端两种.

控制台是另一种人机接口,,不通过终端与主机相连,,而是通过显示卡-显示器和键盘接口

别与主机相连, 这是人控制主机的第一人机接口。

回到个人计算机上,个人计算机只有控制台,没有终端. 当然愿意的话,,可以在串口上连一

两台字符哑终端.。但是linux偏要按POSIX标准把个人计算机当成小型机来用,那么就在控制

台上通过getty软件虚拟了六个字符哑终端(或者叫控制台终端tty1-tty6,数量可以在

/etc/inittab里自己调)和一个图型终端。在虚拟图形终端中又可以通过软件(如rxvt)再虚拟

无限多个虚拟字符哑终端pts/0....)。记住,这全是虚拟的,用起来一样,但实际上并不是。

所以在个人计算机上,只有一个实际的控制台,没有终端,所有终端都是在控制台上用软件模拟的。
要把个人计算机当主机再通过串口或网卡外连真正的物理终端也可以,但由于真正的物理终端

并不比个人计算机本身便宜,一般没有人这么做。

1.2切换控制台

如同其他UNIX类系统,Linux本身也是基于命令行的。试试“Ctrl”+“Alt”+“Fx”。这就是控制台算是Linux的本来面目。至于使用方法,除了多出登录注销外,其它操作和我们在linux图形界面

(X—window)下的终端操作是一样的,在X-Window出问题或不运行X-Window的时候,操作

主要在这里完成。

  Linux在控制台下提供了不止一个(字符哑)终端,支持多用户同时登录,包括在本机同时登录。

控制台“Alt”+“Fx”能够切换到第x个(字符哑)终端。如果需要从X-Window里跳到第(字符哑)终端,

需要“Ctrl”+“Alt”+“Fx”。一般情况下如果要从控制台返回Xwindow可用““Alt”+7”来返回到Xwind的

图形界面。(Linux发行版提供7个虚拟屏幕,1~6号是控制台终端((字符哑)终端),第7个

上面跑X-Window。)

1.3控制终端(/dev/tty)

这是个在应用程序中的一个概念,前台进程有个控制终端,就对应这个。

不过它并不指任何物理意义上的终端,其实/dev/tty会映射到当前的设备(通过tty命令可以看到),

比如你如果在控制台界面下(即字符界面下)那么dev/tty就是映射到dev/tty1-6之间的一个(取决于你当

前的控制台号),但是如果你现在是在图形界面(Xwindows),那么你会发现现在的/dev/tty映射到

的是/dev/pts的伪终端上。比如你可以输入命令 #tty 那么将显示当前映射终端如:/dev/tty1或者/dev/pts/0等。

///////////////////////////////////////////////在第四条之后buchong

(1)一个会话可以有一个控制终端,这通常是终端设备(在现在的个人电脑上,通常是虚拟的tty1-tty6,然后图形界面虚拟的),或者伪终端设备,建立与控制终端连接的首进程叫做控制进程,不是前台进程组

(2)一个会话中的进程组可以被分成前台进程组(必须该会话有一个控制终端)和后台进程组(除前台进程组之外的都是后台进程组)

(3)如果键入delte或者是ctrl+c则(中断信号),ctrl+\(终端退出键)被送往前台进程组,并且当终端接口检测到调制解调器断开了连接,则将挂断信号发送到控制进程


我们一般不需要担心控制终端,一般控制终端在登陆的时候自己进行建立

POSIX.1分配控制终端的机制

介绍,比如说在linux3.2.0中,如果在第一次调用open的时候,前提是没有O_NOCITY的时候,就将此open的控制终端分配给此会话

这是从system V派生的系统

下面的命令是相同的

有的时候不管是否标准输入,标准输出是否重定向,程序都需要与控制终端进行交互(如果重定向了,标准输入输出就不一定指向的是控制终端,因此在这里就需要万无一失准确的打开控制终端)

2终端登陆

2.1终端登陆::::(这里是终端登陆,并不是我们平常的个人电脑直接登陆,但是大体相同)

(1)系统管理者创建一个通常名为/etc/ttys的文件(这个文件本来就有),其中,每个终端设备有一行,每一行说

明设备名和传到getty程序的参数,这些参数说明了终端的波特率等。

(2)当系统自举时,内核创建进程 ID 1,也就是init进程。init进程使系统进入多用户状态。init

读文件/etc/ttys,对每一个允许登录的终端设备, init调用一次fork(以空环境执行getty程序),它所生成的子进程则

执行程序getty。        

(3) getty对终端设备调用open函数,以读、写方式将终端打开。如果设备是调制解调器,则open可能会在设备

驱动程序中滞留,直到用户拨号调制解调器,并且线路被接通。一旦设备被打开,则文件描述符 0、1、2就被

设置到该设备。然后getty输出“login:”之类的信息,并等待用户键入用户名。如果终端支持多种速度,则

getty可以测试特殊字符以便适当地更改终端速度 (波特率)(综上来看getty主要是用来打开终端,分配文件标识

符,并且最后调用login登陆程序)

(4)当用户键入了用户名后,getty就完成了。然后它以类似于下列的方式调用login程序:

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

(在gettytab文件中可能会有一些选择项使其调用其他程序,但系统默认是login程序)。init以一个空环境调用getty

。getty以终端名(例如TERM=foo, 其中终端foo的类型取自gettytab文件-->绿色的都放在envp中)和在gettytab中

的环境字符串为 login创建一个环境( envp参数)。-p标志通知login保留传给它的环境,也可将其他环境字符串加

到该环境中,但是不要替换它。图 9 - 2显示了login刚被调用后这些进程的状态。

(5)login 能处理多项工作。因为它得到了用户名,所以能调用 getpwnam 取得相应用户的口令文件登录项。然后

调用 getpass(3)以显示提示“ Password:”接着读用户键入的口令(自然,禁止回送用户键入的口令)。它调用 

crypt(3)将用户键入的口令加密,并与该用户口令文件中登录项的pw_passwd字段相比较。如果用户几次键入

口令都无效,则 login 以参数1调用 exit 表示登录过程失败。父进程(init)了解到子进程的终止情况后,将再次

调用fork其后又跟随着执行getty,对此终端重复上述过程。如果用户正确登录,login就将当前工作目录更改

为该用户的起始目录 (chdir)。它也调用chown改变该终端的所有权,使该用户成为所有者和组所有者。将对该

终端设备的存取许可权改变成:用户读、写和组写。调用setgid及initgroups设置进程的组ID。然后用login所得

到的所有信息初始化环境:起始目录 (HOME )、shell ( SHELL)、用户名(USER和LOGNAME),以及一

个系统默认路径 (PATH)。最后login进程改变为登录用户的用户 I D (setuid )并调用该用户的登录shell,其方式类似于:execl("/bin/sh", "-sh", (char *) 0);(login主要是用来在getty得到用户名之后,进行登陆

argv[0]的第一个字符-是一个标志,表示该shell被调用为登录shell.shell可以查看此字符,并相应地修改其起动过程。

最后一步setuid改变一下3个用户ID:实际用户ID,有效用户ID,和保存的用户ID!!(登陆成功sh用来启动一个shell,叫做登陆shell

3网络登陆

3.1网络登陆与终端登陆的区别

终端登录中 ,init知道哪些终端设备可用来进行登录,并为每个设备生成一个getty进程。但是,对网络登录则情况

有所不同,所有登录都经由内核的网络界面驱动程序(例如:以太网驱动程序),事先并不知道将会有多少这样的登

录。不是使一个进程等待每一个可能的登录,而是必须等待一个网络连接请求的到达。在4.3+BSD中,有一个称

为 i n e t d的进程(有时称为Internet superserver),它等待大多数网络连接

3.2网络登陆

(1)作为系统起动的一部分,init调用一个shell,使其执行shell脚本etc/rc。由此shell脚本起动一个精灵进程 inted。一

旦此shell脚本终止,inted的父进程就变成init。inted等待TCP/IP连接请求到达主机,而当一个连接请求到达时,它

执行一次fork,然后该子进程执行适当的程序。(在这里执行了一次fork)

(2)假定到达了一个对于TELNET服务器的TCP连接请求.TELNET是使用TCP协议的远程登录应用程序。在另

一个主机 (它通过某种形式的网络,连接到服务器主机上 )上的用户,或在同一个主机上的一个用户起动 

TELNET客户进程( c l i e n t )起动登录过程:

                                                telnet hostname

该客户进程打开一个到名为hostname的主机的TCP连接,在hostname主机上起动的程序被称为TELNET服务器

。然后,客户进程和服务器进程之间使用TELNET应用协议通过TCP连接交换数据。所发生的是起动客户进程的

用户现在登录到了服务器进程所在的主机。(自然,用户需要在服务器进程主机上有一个有效的账号)。

图 9 - 4显示了在执行 TELNET服务器进程 (称为telnetd )中所涉及的进程序列。然后telnetd进程打开一个

伪终端设备,并用fork生成一个子进程。父进程处理通过网络连接的通信,子进程则执行login程序。父、子

进程通过伪终端相连接。在调用exec之前,子进程使其文件描述符 0 , 1 , 2与伪终端相连。如果登录正确

, login就执行9.2节中所述的同样步骤 — 更改当前工作目录为起始目录,设置登录用户的组ID和用户ID,

以及登录用户的初始环境。然后login用exec将其自身替换为登录用户的登录shell。图9 - 5显示了到达这一点

时的进程安排。

需要理解的重点是:当通过终端(见图 9 - 3)或网络(见图 9 - 5)登录时,我们得到一个登录shell,其标准输入、输出

和标准出错连接到一个终端设备或者伪终端设备上。在下一节中我们会了解到这一登录shell是一个PISXO.1对

话期的开始,而此终端或伪终端则是会话期的控制终端。


4进程组

每一个进程都有一个进程组ID,进程组是一个或多个进程的集合,每一个进程组都有一个进程组长,进程组组长的pid等于组id,进程组组长可以创建一个进程组,只要本进程组有一个进程存在,这个进程组就是存在的

4.1取得进程组ID

#include<unistd.h>

pid_t getpgrp(void);

得到调用进程的进程组ID,如果成功则返回进程组ID,出错,返回-1

pid_t getpgid(pid_t pid);

得到进程为pid的进程组ID,如果pid是0,则得到调用进程的进程组id

4.2设置进程组ID

#include <unistd.h>

int setpgid(pid_t pid,pid_t pgid);将pid的进程组ID设置成pgid

(1)如果pid不等于pgid:将指定pid的进程组的组ID设置成pgid

(2)pid=pgid:由pid指定的进程变成组长进程

(3)pid=0:使用调用者的pid,将其进程组的id设置成pgid

(4)pgid=0:则pid指定的进程ID,变成组长进程

一个进程只能给自己或者子进程设置组ID,并且在子进程调用了exec之后,就不能再进行进程组的设置

5会话

会话的定义,就是一个或者多个进程组

5.1创建一个新的会话

pid_t setsid(void);

创建一个新的会话

注意点

(1)应该保证调用此函数的进程不是进程组的组长,为了保证不是进程组长ID,通常进行fork,然后终止其父进程,因为进程组是继承的,所以不可能与新的ID重合

(2)该进程编程新会话的会话首进程

(3)该进程的ID成为第一个进程组的ID,即新进程组的ID是该进程ID,第一个进程的ID也是会话ID

(4)该进程事没有控制终端的,如果有,也进行切断

pid_t tcgetpgrp(int fd);

函数返回前台进程组的ID,这个进程组ID与fd相关连

int tcsetpgrp(int fd,pid_t pgrpid);

从fd找到前台进程组ID,将前台进程组ID设置成pgrpid,就这样!!

pid_t getsid(void);

得到会话首进程的进程组ID!!!

一个会话可以没有控制终端,没有控制终端证明着没有与终端进行交互的功能

pid_t tcgetsid(int fd);

给出控制TTY的文件描述符,通过此函数就能得到会话首进程的进程组ID

6作业控制

有作业控制特性可以在终端上连续启动很多个作业(多个进程组)但是要具备作业控制必须

(1)支持作业控制的shell

(2)内核中的终端驱动程序必须支持作业控制(这体现子在当用户输入一个退出,中断的时候,驱动程序应该接受)

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

列如在setsid的时候已经建立了会话,在登陆shell指向终端驱动的时候,其实在这里建立了前台进程组(即确定了前台进程组ID),在作业控制的时候,后台进程组写终端的时候会产生SIGTTOU信号,而终端产生的信号应该送往前台进程组,setpgid是设置进程组ID,这样就可以把那些作业设置成前台进程组,完美的解决了!!!!这是在有作业控制的时候的一些过程

4:shell执行程序

在没有作业控制的shell上执行

则不管是前台进程还是后台进程,进程组ID都是不变的,下面是后台进程组,发现并没有变化,进程组ID

如果,在后台进程组中试图读控制终端(再有作业控制的时候,会产生开心好sigtin),在没有作业控制的时候,没有重定向标准输入,则将标准输入重定向到dev/null

如果通过打开/dev/tty的时候,则情况发生改变,比如说

crypt < salaries | lpr&

这个命令,crypt是通过打开tty进行的读写,这样子是无效的

这张图解释了为什么会出现这种情况,如上图,因为当执行ps的时候,其实cat1,cat2并没有exec好 所以都是sh

5孤儿进程组

问题的提出:考虑一个进程,当他fork了一个子进程的时候,子进程停止,然后父进程终止,子进程应该如何继续,子进程是否知道了他目前是一个孤儿进程?

5.1定义:该组中的每个成员要么是该组的一个成员,要么不是该组所属会话的成员

5.2解释如图所示的程序编程了孤儿进程组

当进行fork的时候,首先让父进程进行睡眠,然后等待子进程运行,子进程停止自己,父进程由于睡眠时间已到,所以exit进行退出,此时父进程退出发出sighup信号,子进程收到之后进行pr_ids信息打印,此时由于父进程已死,所以子进程的父进程编程init,此时子进程所属的进程组变成一个孤儿进程组,由于向标准输入读一个数据,此时本进程变成了一个后台进程,后台进程在进程组是孤儿进程租的时候读标准输入,此时read应该返回一个errno,并且进行打印





posted @ 2016-09-12 09:12  SmileLion_LY  阅读(816)  评论(0编辑  收藏  举报