相关函数列表
- //返回调用进程的进程组ID
- #include <unistd.h>
- pid_t getpgrp(void);
- pid_t getpgid(pid_t pid);
- //getpgid(0) 等于 getpgrp()
- //创建一个新会话
- #include <unistd.h>
- pid_t setsid(void);
- pid_t getsid(get_t pid); //返回会话首进程的进程组ID
- //对于setsid()函数,如果调用此函数的进程不是一个进程组的组长,则此函数创建一个新会话
- //1.该进程变成新会话的会话首进程(session leader会话首进程是创建该会话的进程),此时,该进程
- // 是新会话中唯一进程
- //2.该进程成为一个新进程组的组长进程。新进程组ID是该调用进程的进程ID
- //3.该进程没有控制终端,如果在调用setsid之前该进程有一个控制终端,那么这种联系也被切断
- //根据FD获取哪个进程组是前台进程组
- #include <unistd.h>
- pid_t tcgetpgrp(int fd);
- int tcsetpgrp(int fd, pid_t pgrpid);
- //给出控制TTY的文件描述符,下面函数可以获得会话首进程的进程组ID
- #include <unistd.h>
- pid_t tcgetsid(int fd);
终端登录
系统管理者创建/etc/ttys文件,之后init进程读取这个文件,然后fork新进程执行exec调用getty程序
getty对终端设备调用open函数,以读写方式将终端打开,一旦设备打开,则文件描述符就被设置到该设备,然后getty输出“login”之类的信息。getty会根据波特率适当的更改终端的速度。
当用户输入用户名后,getty工作就完成了,然后它调用login程序
- execle("/bin/login", "login" "-p", username, (char*)0, envp);
最初的init进程有超级用户权限,所以它fork后的进程都有超级用户权限
login能处理多项工作,得到用户名调用getpwnam取得相应的用户口令,然后调用getpass提示"password",用户输入口令后,调用crypt将用户口令加密,并与阴影文件的pw_passwd字段比较,如果出错则login调用exit表示登陆失败,父进程init了解到子进程情况后再次调用fork又执行getty,重复上述过程
如果用户登陆正常login就将完成如下工作:
1)将当前工作目录更改为用户的起始目录(chdir)
2)调用chown更改该终端的所有权,使登陆用户成为它的所有者
3)将对该终端设备的访问权限改变为“用户读和写”
4)调用setgid以及initgroups设置进程的组ID
5)用login得到所有信息初始化环境:起始目录(HOME),shell(SHELL),用户名(USER和LOGNAME)以及一个
系统默认路径(PATH)
6)login进程更改为登陆用户的用户ID(setuid)并调用该用户的登陆shell,其方式类似于
- execl("/bin/sh", "-sh", (char*)0);
网络登陆
init进程调用一个shell,使其执行shell脚本/etc/rc,由此shell脚本启动一个守护进程inetd。一旦shell脚本终止,inetd的父进程就变成init。inetd等待TCP连接达到主机,然后执行一次fork,生成子进程exec执行适当的程序。
当telnet请求达到后,inetd会fork一个进程,然后执行telnetd程序(exec调用),之后分成两个进程,父进程继续处理连接的通讯,子进程执行login程序,父进程和子进程通过伪终端相连接。
执行过程:
1.init调用fork和exec启动inetd
2.inetd接收到客户端的telnet连接,然后fork新进程
3.inetd继续接受客户来的连接,新进程调用exec启动telnetd处理用户逻辑
会话
进程组是一个或多个进程的集合,会话是一个或多个进程组的集合
比如proc1 | proc2 & | proc3 | proc4 | proc5
会话和进程组还有一些其他特性
1.一个会话可以有一个控制终端(controlling terminal),这通常是终端设备(在终端登录)或伪终端设备(网络)
2..建立与控制终端连接的会话首进程被称为控制进程(controlling process)
3.一个会话中的几个进程组可被分成一个前台进程组(foreground process group)以及一个或多个后台进程
组(background process group)
4.如果一个会话有一个控制终端,则它有一个前台进程组,其他进程组为后台进程组
5.无论何时键入终端的中断键,都会将终端信号发送至前台进程组的所有进程
6.如果终端接口检测到调制解调器(或网络)已断开连接,则将挂断信号发送至控制进程(会话首进程)
作业控制
允许在一个终端上启动多个作业(进程组),它控制一个作业可以访问该终端以及哪些作业在后台运行。要求以下三种形式的支持:
1.支持作业控制的shell
2.内核中的终端驱动程序必须支持作业控制
3.内核必须提供对某些作业控制信号的支持
- //这里启动了两个作业
- pr *.c | lpr &
- make all &
- //在后台运行,之后又将作业1号变为前台运行
- cat > temp.foo &
- fg %1
- stty tostop //禁止作业输出至控制终端
shell执行程序
- //执行下面这段shell
- ps -o pid,ppid,pgid,sid,tpgid,comm | cat | cat | tail | more | head
- //结果
- PID PPID PGID SID TPGID COMMAND
- 26383 26381 26383 26383 26514 bash
- 26456 26383 26450 26383 26514 more
- 26514 26383 26514 26383 26514 ps
- 26515 26383 26514 26383 26514 cat
- 26516 26383 26514 26383 26514 cat
- 26517 26383 26514 26383 26514 tail
- 26518 26383 26514 26383 26514 more
- 26519 26383 26514 26383 26514 head
从结果中可以看到,所有的命令都父进程ID都是bash,也就是bash去启动了那些命令
而ps,cat,cat,tail,more,head这些命令是在一个进程组中的。同时上面所有的进程都是在一个会话中
Bourne shell执行管道ps | cat1 | cat2时的进程
这里执行的sh后,又会fork一个新进程,然后这个新的sh进程再去fork一些子进程,fork的这些子进程都是执行
sh命令,这些sh命令又调用exec函数执行不同的shell命令,比如ps,cat这样的
管道中最后一个shell命令是由最初的sh(PID为202)调用exec执行的
当然这里不同的sh执行会有不同的效果,而且创建的进程顺序也会不同
linux上的bash shell对于所有的执行的管道命令都是bash的子进程
通过pstree命令执行
- ps -o pid,ppid,gid,sid,comm | cat | more | head | pstree
- //结果
- init-+-bash
- |-6*[mingetty]
- |-sshd---sshd-+-bash
- | `-bash-+-cat
- | |-head
- | |-more
- | |-ps
- | `-pstree
- `-udevd
可以看到init进程for了sshd(也就是终端),然后sshd又fork了bash,
bash调用exec再fork了cat,head,more,ps等命令,生成相应的进程
孤儿进程组
一个其父进程已经终止的进程称为孤儿进程(orphan process),这种进程由init进程收养,而整个进程组也有可能成为孤儿。
POSIX.1将孤儿进程组(orphaned process group)定义为:
该组中每个成员的父进程要么是该组的一个成员,要么不是该组所属会话的成员。
对孤儿进程组的另一种描述可以使:
一个进程组不是孤儿进程组的条件是---该组中有一个进程,其父进程在属于同一会话的另一个组中。如果进程组
不是孤儿进程组,name在属于同一会话另一个组中的父进程就有机会重新启动该组中停止的进程。在这里,进程组中每一个进程的父进程都属于另一个会话。所以此进程组是孤儿进程组。
FreeBSD实现
会话和进程组的FreeBSD实现
session结构体
1)s_count是该会话中的进程组数。当此计数器减至0时,则可释放此结构
2)s_leader是指向会话首进程proc结构体的指针
3)s_ttyvp是指向控制终端vnode结构的指针
4)s_ttyp是指向控制终端tty结构的指针
5)s_sid是会话id
tty结构体
1)t_session指向将此终端作为控制终端的session结构(注意,tty结构指向session结构,session结构也指向
tty)。终端在市区载波信号时使用此指针将挂起信息发送给会话首进程
2)t_pgrp是指向前台进程组的pgrp结构。终端驱动程序用此字段将信号发送给前台进程组。
3)t_termios是包含所有这些特殊字符和与该终端有关信息(如波特率,回显打开或关闭)的结构
4)t_winsize是包含终端窗口当前大小的winsize型结构。当终端窗口大小改变时,信号SIGWINCH被发送至前台
进程组。
pgrp结构体(包含一个特定进程组的信息)
1)pg_id是进程组ID
2)pg_session指向此进程组所属会话的session结构
3)pg_members是指向此进程组proc结构表的指针,该proc结构代表进程组的成员
proc结构体
1)p_pid包含进程ID
2)p_pptr是指向父进程proc结构的指针
3)p_pglist是一个结构,其中包含两个指针,分别指向进程组中上一个和下一个进程
最后还有一个vnode结构,在打开控制终端设备时分配此结构体,进程对/dev/tty的所有访问都通过vnode。