Linux会话和守护进程
2022-02-25 # 学习笔记 # Linux

Linux会话与守护进程

会话(Session)

会话:多个进程组的集合。

概念:

一个session对应着一个控制终端, 我们常见的 Linux session 一般是指 shell session。Shell session 是终端中当前的状态,在终端中只能有一个 session。当我们打开一个新的终端时,总会创建一个新的 shell session。

就进程间的关系来说,session 由一个或多个进程组组成。一般情况下,来自单个登录的所有进程都属于同一个 session。

Session 中的每个进程组被称为一个 job,有一个 job 会成为 session 的前台 job(foreground),其它的 job 则是后台 job(background)。

前台job主要负责通过控制终端与用户进行交互,而后台job主要负责周期性的执行某项任务,例如输出内容,监听某个端口。

每个 session 连接一个控制终端(control terminal),控制终端中的输入被发送给前台 job,从前台 job 产生的输出也被发送到控制终端上。同时由控制终端产生的信号,比如 ctrl + z 等都会传递给前台 job。

一般情况下 session 和终端是一对一的关系,当我们打开多个终端窗口时,实际上就创建了多个 session。

意义:

Session 的意义在于:多个工作(job)在一个终端中运行,其中的一个为前台 job,它直接接收该终端的输入并把结果输出到该终端。其它的 job 则在后台运行。有效的实现了对进程组的分组管理。就如同进程组的存在可以方便向一组特定的进程发送信号,会话也可以很好的实现这种分组的功能。进程组和会话的发明是为了更好的实现工作控制。

操作:

getsid 函数:

获取当前进程的会话 id

1
pid_t getsid(pid_t pid) 

成功返回调用进程会话 ID,失败返回-1。

setsid 函数:

创建一个会话,并以自己的 ID 设置进程组 ID,同时也是新会话的 ID

1
pid_t setsid(void) 

成功返回调用进程的会话 ID,失败返回-1。

创建注意事项:

  1. 调用进程不能是进程组组长,该进程变成新会话首进程
  2. 该进程成为一个新进程组的组长进程
  3. 需要 root 权限(ubuntu 不需要)
  4. 新会话丢弃原有的控制终端,该会话没有控制终端
  5. 该调用进程是组长进程,则出错返回
  6. 建立新会话时,先调用 fork,父进程终止,子进程调用 setsid

补充:一个进程组的id为进程组长的id,如果是父进程fork出了子进程。那么父进程即为这一进程组的组长进程。因为创建会话的调用进程不能为组长进程。因此需要用子进程创建新的会话。

为什么进程组组长不能创建会话:

进程组ID是进程组组长的PID.会话ID是会话首进程的PID.成功调用setsid()之后,进程组ID,会话ID和PID应该相同.

但是,对于进程组负责人,进程组ID已等于PID.如果能够调用setsid(),则它的进程组ID保持不变,因此:

>进程组组长属于新会话;

>其他进程组成员属于旧会话.

因此,在这种情况下,我们有一个进程组,其成员属于不同的会话. POSIX希望禁止这种情况。

为什么禁止:

控制终端会将信号发送给前台进程组。

只有当进程组里面的所有进程共享同一控制终端的时候,发送的信号才对其有意义.

所以规定,来自同一会话的所有进程共享同一控制终端,来自不同会话的进程不能共享同一控制终端

因此,如果我们要求进程组的所有成员共享同一控制终端,则我们还应要求它们成为同一会话的成员.

守护进程

概念

守护进程(daemon 进程)通常运行于操作系统后台,脱离控制终端。一般不与用户直接交互。周期性的等待某个事件发生或周期性执行某一动作。 不受用户登录注销影响。通常采用以 d 结尾的命名方式,例如httpd等。

创建:

守护进程创建步骤:

  1. fork 子进程,让父进程终止。
  2. 子进程调用 setsid() 创建新会话
  3. 通常根据需要,改变工作目录位置 chdir(), 防止目录被卸载。
  4. 通常根据需要,重设 umask 文件权限掩码,影响新文件的创建权限。
  5. 通常根据需要,关闭/重定向 文件描述符
  6. 执行守护进程的业务逻辑

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>

void sys_err(const char* str)
{
perror(str);
exit(1);
}

int main(int argc, char* argv[])
{
pid_t pid;
int ret, fd;

//创建子进程并结束父进程
pid = fork();
if (pid > 0) exit(0);
if (pid == -1) sys_err("fork error");

//创建新会话
pid = setsid();
if (pid == -1) sys_err("setsid error");

//改变工作目录
ret = chdir("/home/shiji/projects");
if (ret == -1) sys_err("chdir error");

//改变文件访问权限掩码
umask(0022);

//关闭标准输入文件描述符
close(STDIN_FILENO);

//将标准输入和标准错误文件描述符重定向至空洞文件
fd = open("/dev/null", O_RDWR);
if (fd == -1) sys_err("open error");

dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);

//模拟守护进程任务
while (1);
}

运行效果:

用ps ajx查看,结果如下: