1、终端登录
本地和远程登录都经内核中的终端设备驱动程序。
(1)本地终端登录
终端设备文件中,每个终端设备都有一行,每一行说明设备名和传到getty程序的参数。
系统自举时,init进程(ID=1)使系统进入多用户模式,读取终端设备文件,对每一个允许登录的终端设备调用一次fork,生成的子进程exec getty程序(以空环境运行)。此时所有进程的实际用户ID和有效用户ID都是0(因为init进程具有root权限,都具有root权限)。
getty对终端设备调用open函数,以读写方式打开终端。如果是调制解调器则直到用户拨号调制解调器,线路接通再open。打开后文件描述符0、1、2被设置到该设备。
getty输出“login:”,用户键入用户名后,如下方式调用login程序。
login得到用户名,调用getpwnam得到相应用户的口令文件登陆项。然后调用getpass显示“Psaaword:”,接着读取用户口令,调用crypt加密用户键入的口令,并与用户在阴影口令文件登陆项的pw_passwd字段进行比较。如果几次输入口令无效,进程退出,父进程init再次调用fork执行getty。
execle("/bin/login","login","-p","username",(char *)0,envp);
如果用户正确登陆,login完成如下工作
(1)更改当前目录为用户其实目录(chdir)
(2)使当前用户成为该终端的所有者(chown)
(3)改变终端设备为该用户读和写
(4)调用setgid和initgroups设置进程组ID
(5)login得到的信息来初始化环境:起始目录(HOME)、shell(SHELL)、用户名(USER和LOGNAME)、一个系统默认路径(PATH)
(6)login进程更改为登录用户的用户ID(setuid),调用该用户的登录shell
//符号“-”表示该shell作为登录shell调用
execl("/bin/sh","-sh",(char *)0);
现在shell读取启动文件,GUN Bourne-again shell是 .bash_profile、.bash_login或 .profile。这些启动文件通常改变某些环境变量并增加很多环境变量。执行完启动文件后,用户得到shell提示符,并能键入命令。
(2)网络登录
所有登录都经内核的网络接口驱动程序(如以太网驱动程序),等待一个网络连接请求的到达,而不是像上面一样一个进程等待一个可能的登录。
系统使用伪终端的软件驱动程序,仿真串型终端的运行行为,并将终端操作映射为网络操作。
init调用一个shell执行脚本/etc/rc,此脚本启动一个守护进程inetd(Linux有些版本使用扩展的因特网服务守护进程xinetd),shell脚本终止则inetd的父进程变为init。inetd进程等待TCP/IP连接请求到达主机。当一个请求到达,执行一次fork生成子进程exec其他程序。
在另一个主机上或同一主机上启动TELNET客户进程来登录。
//打开一个到hostname主机的TCP连接
telnet hostname
hostname主机启动TELNET的服务进程。然后,客户进程和服务进程使用TELNET应用协议通过TCP连接交换数据。
telnetd进程(这里是服务器端,客户端也通过telnet进行网络连接)打开一个伪终端备,fork为两个进程。子进程使文件描述符0、1、2与伪终端相连。子进程再exec执行login程序,父进程负责通过通过网络进行通信。如果登录正确则和上面一样。Mac OS X上执行网络登录的更好办法是使用ssh。
2、进程组
同一进程组中的各进程接收来自同一终端的各种信号。一个进程组有一个组长进程,组长进程的ID就是进程组ID。一个进程只能为自己和它的子进程设置进程组ID。
//返回进程组ID
pid_t getpgrp(void);
//将pid进程的进程组ID设为pgid
int setpgid(pid_t pid, pid_t pgid);
3、会话
会话session是一个或多个进程的集合。
//如果当前进程不是一个进程组组长,进程调用下面函数建立一个新会话,否则返回错误。
//为了避免这个情况,通常父进程调用fork后终止,子进程继承父进程的进程组ID,而且其进程ID和父进程ID不同,从而保证子进程不是一个进程组组长。
pid_t setsid(void);
//返回会话首进程的进程组ID
pid_t getsid(pid_t pid);
4、控制终端
建立与控制终端连接的会话首进程为控制进程。一个会话的几个进程组为一个前台进程组,几个为后台进程组。任何时候键入中断键,都会把中断信号发送给前台进程组的所有进程。
5、
//得到前台进程组ID,它与在fd上打开的终端相关联(得到)
pid_t tcgetpgrp(int fd);
//设置进程组pgrpid为前台进程组,fd必须引用该会话的控制终端
int tcsetpgrp(int fd, pid_t pgrpid);
6、作业控制
一个终端上允许启动多个作业(进程组)。作业控制用于处理后台程序想要使用中断,即从终端输入输出的情况。他比较好的处理一个终端在多个进程之间转换。父进程是shell作为前台作业执行时终止,子进程变为后台进程组。
7、会话实现
//会话结构session
s_count 会话中进程组数
s_leader 指向会话中首个进程
s_ttyvp 指向控制终端vnode
s_ttyp 指向控制终端tty
s_sid 会话ID
//每个终端和伪终端在内核中分配一个tty结构
t_session 指向将此终端作为控制终端的会话结构
t_pgrp 指向前台进程组pgrp结构
t_terminos 包含与终端相关的信息
t_winsize 包含终端窗口当前大小的winsize
//pgrp结构包含特定进程组信息
pg_id 进程组ID
pg_sessions 指向此进程组所属会话session结构
pg_members 指向进程组中进程结构的p_pglist,p_pglist是双向链表,它把进程组中所有进程串联起来
//proc结构包含每个进程的信息
p_pglist 双向链表,它把进程组中所有进程串联起来
p_pid 进程ID
p_pptr 指向父进程proc结构
p_pgrp 指向本进程所属组