会话是一组进程组(任务)的集合。会话中的所有进程都具有相同的会话标识符。会话首进程(session leader)是指创建会话的进程,其进程 ID 会成为会话 ID(SID)
- 会话中的每个进程组称为一个「作业」。
- 会话可以有一个进程组称为会话的「前台作业」,其它进程组为「后台作业」
会话都会与某个控制终端相关。
- 当我们打开多个终端窗口时,实际上就创建了多个终端会话。每个会话都会有自己的前台工作和后台工作。这样,我们就为进程增加了管理和运行的层次。
- 在没有图形化界面的时代,会话允许用户通过shell进行多层次的进程发起和管理。比如说,我可以通过shell发起多个后台工作,而此时标准输入输出并不被占据,我依然可以继续其它的工作。
- 如今,图形化界面可以帮助我们解决这一需求,但工作组和会话机制依然在Linux的许多地方应用。
另外:
- 会话在被创建出来的时候是没有控制终端的,控制终端建立于会话首进程初次打开终端设备之时。
- 对于由交互式shell所创建的会话,这恰恰是用户的登录终端,一个终端最多只能成为一个会话的控制终端
- 一个会话可以有一个控制终端,当控制终端有输入和输出时都会传递给前台进程组,比如Ctrl + Z。会话的意义在于能将多个作业通过一个终端控制,一个前台操作,其它后台运行。
- 当会话首进程打开了一个控制终端之后它同时也成为了该终端的控制进程。在发生终端断开之后,内核会向控制进程发送一个 SIGHUP 信号来通知这一事件的发生
- 如果一个进程拥有一个控制终端,那么打开特殊文件/dev/tty就能够获取该终端的文件描述符。这对于一个程序在标准输入和输出被重定向之后需要确保在与控制终端进行通信是很有用的。如果进程没有控制终端,那么在打开/dev/tty 时会报出 ENXIO 的错误
使用会话最多的是支持任务控制的shell,由shell创建的所有进程组与shell自身隶属于同一会话,shell是此会话首进程。支持任务控制的shell提供如下命令:列出所有任务,向任务发送信号,以及在前后台任务之间来回切换
- 在任一时点,会话中总有一个前台进程组(前台任务),可以从终端中读取输入,向终端发送输出。
- 如果用户在控制终端中输入了“中断”(通常是 Control-C)或“挂起”字符(通常是 Control-Z),那么终端驱动程序会发送信号以终止或挂起(亦即停止)前台进程组。
- 一个会话可以拥有任意数量的后台进程组(后台任务),由以“&”字符结尾的行命令来创建。
控制终端会被由 fork()创建的子进程继承并且在 exec()调用中得到保持
API
1、会话是一组进程组集合。一个进程的会话成员关系是由其会话ID来定义的来定义的,会话ID是一个数字。新进程会继承其父进程的会话ID。getsid()会返回pid执行的进程的会话ID。
#include <unistd.h>
//返回值:成功返回会话首进程的进程组ID;失败返回-1
pid_t getsid(pid_t pid);
如果pid参数值为0,会返回调用进程的会话ID
1、一个进程通过调用setsid函数来建立一个新的会话。
#include <unistd.h>
pid_t setsid(void);
//返回值:成功返回进程组ID;出错返回-1
调用此函数分为以下两种情况
①如果调用进程不是一个进程组组长,则此函数创建一个新会话:
- 此进程变成该新会话的会话首进程(session leader,会话首进程是创建该会话的进程)。此时,该进程是该会话中的唯一进程
- 此进程成为一个新进程组的组长进程。调用进程的进程组 ID和会话 ID 会被设置成该进程的进程 ID
- 调用进程没有控制终端。所有之前到控制终端的连接都会被断开
②如果调用此函数的进程是一个进程组组长,那么 setsid()调用会报出 EPERM 错误。
- 为了保证不处于这种情况,通常先调用fork,然后使其父进程终止,而子进程则继续。因为子进程继承了父进程的进程组ID, 而其进程ID则是新分配的,两者不可能相等,所以这就保证了子进程不是一个进程组的组长
组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程…
实践
会话ID
例如:下面的一个会话中,有3个进程组
通常是由shell的管道线将几个进程编成一组的,则上图的安排可能是由下列形式的shell命令形成的
shell中的前台后台进程
一个命令可以通过在末尾加上&方式让它在后台运行:
$ ping localhost > log &
[1] 4345 # 括号中的1表示工作号,而10141为PGID
$ ps -o pid,pgid,ppid,sid,tty,comm
PID PGID PPID SID TT COMMAND
3772 3772 3764 3772 pts/0 bash
4345 4345 3772 3772 pts/0 ping
4355 4355 3772 3772 pts/0 ps
信号可以通过kill
$kill -SIGTERM -4345
或者
$kill -SIGTERM %1
的方式来发送给工作组。上面的两个命令,一个是发送给PGID(通过在PGID前面加-来表示是一个PGID而不是PID),一个是发送给工作1(%1),两者等价。
一个工作可以通过$fg从后台工作变为前台工作:
$cat > log &
$fg %1
当我们运行第一个命令后,由于工作在后台,我们无法对命令进行输入,直到我们将工作带入前台,才能向cat命令输入。在输入完成后,按下CTRL+D来通知shell输入结束。
下面演示了使用 setsid()来创建一个新会话。为了检查该进程已经不再拥有控制终端了,这个程序尝试打开一个特殊文件/dev/tty
#if ! defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 500
#define _XOPEN_SOURCE 500
#endif
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if (fork() != 0) /* Exit if parent, or on error */
_exit(EXIT_SUCCESS);
if (setsid() == -1){
perror("setsid");
_exit(EXIT_FAILURE);
}
printf("PID=%ld, PGID=%ld, SID=%ld\n", (long) getpid(),
(long) getpgrp(), (long) getsid(0));
/* Following should fail, since we don't have a controlling terminal */
if (open("/dev/tty", O_RDWR) == -1){
perror("open /dev/tty");
_exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
从输出中可以看出,进程成功地将其自身迁移至了新会话中的一个新进程组中。由于这个会话没有控制终端,因此open()调用会失败。