会话&&守护进程

1.会话

1.概念和特性

进程组,也称之为作业。BSD 于 1980 年前后向 Unix 中增加的一个新特性。代表一个或多个进程的集合。每个

进程都属于一个进程组。在 waitpid 函数和 kill 函数的参数中都曾使用到。操作系统设计的进程组的概念,是为了

简化对多个进程的管理。

当父进程,创建子进程的时候,默认子进程与父进程属于同一进程组。进程组 ID==第一个进程 ID(组长进程)。

所以,组长进程标识:其进程组 ID==其进程 ID

可以使用

1
kill -SIGKILL -进程组 ID(负的)

来将整个进程组内的进程全部杀死。

组长进程可以创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中有一个进程存在,进程组就

存在,与组长进程是否终止无关。

进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组)。

一个进程可以为自己或子进程设置进程组 ID

会话(Session)和进程组(Process Group)

  • 进程组:

    • 进程组是一个或多个进程的集合。进程组中的进程可以通过组 ID 来标识,这个组 ID 是一个正整数。当一个进程被创建时,它会被加入到父进程所在的进程组中。进程组主要用于对一组进程进行信号的批量操作,例如,当向一个进程组发送一个信号(如SIGINT信号)时,该进程组中的所有进程都会收到这个信号。
  • 会话:

    • 会话是一个或多个进程组的集合。一个会话通常有一个会话首进程(Session Leader),会话首进程是创建会话的进程。会话有自己的会话 ID,它也是一个正整数。一个会话可以包含多个进程组,这些进程组之间相互独立,但是在会话的层次结构下又有一定的关联。例如,在一个终端登录会话中,用户启动的所有进程(包括前台进程组和后台进程组)都属于同一个会话。
  • 包含关系:

    • 进程组是会话的组成部分。一个会话包含一个或多个进程组,会话为这些进程组提供了一个更高层次的组织框架。例如,在一个用户登录的会话中,用户可能会启动多个命令,每个命令可能包含一个或多个进程,这些进程构成不同的进程组,而所有这些进程组都属于这个用户登录的会话。

2.创建会话

创建一个会话需要注意以下 6 点注意事项:

1.调用进程不能是进程组组长,该进程变成新会话首进程(session header)

2.该进程成为一个新进程组的组长进程。

3.需有 root 权限 (ubuntu 不需要)

4.新会话丢弃原有的控制终端,该会话没有控制终端(没有终端就无法与用户完成交互)

5.该调用进程是组长进程,则出错返回

6.建立新会话时,先调用 fork, 父进程终止,子进程调用 setsid()

3.getsid和setsid函数

getsid函数

获取进程所属的会话 ID

1
pid_t getsid(pid_t pid); 

成功:返回调用进程的会话 ID;失败:-1,设置 errno

pid 为 0 表示察看当前进程 session ID

ps ajx 命令查看系统中的进程。参数 a 表示不仅列当前用户的进程,也列出所有其他用户的进程,参数 x 表示

不仅列有控制终端的进程,也列出所有无控制终端的进程,参数 j 表示列出与作业控制相关的信息。

组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。

setsid 函数

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

1
pid_t setsid(void); 

成功:返回调用进程的会话 ID;失败:-1,设置 errno

调用了 setsid 函数的进程,既是新的会长,也是新的组长。

进程id,组id,会话id三者都相同

4.代码

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
pid_t pid;

if ((pid = fork()) < 0) {
perror("fork");
exit(1);

} else if (pid == 0) {
//进程id
printf("child process PID is %d\n", getpid());
//进程组id
printf("Group ID of child is %d\n", getpgid(0));
//会话id
printf("Session ID of child is %d\n", getsid(0));

sleep(10);
setsid(); //子进程非组长进程,故其成为新会话首进程,且成为组长进程。该进程组id即为会话进程

printf("Changed:\n");

printf("child process PID is %d\n", getpid());
printf("Group ID of child is %d\n", getpgid(0));
printf("Session ID of child is %d\n", getsid(0));

sleep(20);

exit(0);
}

return 0;
}

image-20241220220802471

2.守护进程

Daemon(精灵)进程,是 Linux 中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以 d 结尾的名字。

Linux 后台的一些系统服务进程,没有控制终端,不能直接和用户交互。不受用户登录、注销的影响,一直在运行着,他们都是守护进程。如:预读入缓输出机制的实现;ftp 服务器;nfs 服务器等。

创建守护进程,最关键的一步是调用 setsid 函数创建一个新的 Session,并成为 Session Leader。

重点:

daemon进程。通常运行与操作系统后台,脱离控制终端。一般不与用户直接交互。周期性的等待某个事件发生或周期性执行某一动作。

不受用户登录注销影响。通常采用以d结尾的命名方式。

3.创建守护进程模型

守护进程创建步骤:

1.创建子进程,父进程退出

所有工作在子进程中进行形式上脱离了控制终端

2.在子进程中创建新会话

setsid()函数

使子进程完全独立出来,脱离控制

3.改变当前目录位置

chdir()函数 传入自己想要改到的目录位置就行

防止占用可卸载的文件系统

一般设置为根目录,也可以换成其它路径

4.重设文件权限掩码

umask()函数

防止继承的文件创建屏蔽字拒绝某些权限

增加守护进程灵活性

5.关闭/重定向文件描述符

继承的打开文件不会用到(因为守护进程不与用户交互),浪费系统资源,无法卸载

还有另外一种做法是将文件描述符0,1,2重定向到/dev/null,不让标准输入输出异常影响守护进程而且系统返回的新的文件描述符还是从3开始的,符合编程习惯

6.开始执行守护进程核心工作守护进程退出处理程序模型

两个函数

chdir函数

  • 函数原型int chdir(const char *path);
  • 功能chdir函数用于改变当前工作目录。它接受一个字符串参数path,这个参数指定了新的工作目录的路径。工作目录是进程在文件系统中进行文件操作(如打开、读取、写入文件等)时的默认目录位置。

参数:

  • const char *path:这是一个指向以空字符结尾的字符串的指针,该字符串包含了新工作目录的绝对路径或相对路径。例如,"/home/user/documents"是绝对路径,"../new_folder"是相对路径(相对于当前工作目录)。

返回值:

  • 成功时,函数返回0
  • 失败时,返回-1,并且会设置errno来指示错误原因。常见的错误原因包括:
    • EACCES:没有权限访问指定的目录。
    • ENOENT:指定的目录不存在。
    • ENOTDIR:路径中的某个部分不是一个目录。

umask函数

  • 函数原型mode_t umask(mode_t mask);
  • 功能umask函数用于设置进程的文件模式创建掩码(file mode creation mask)。文件模式创建掩码决定了新创建文件或目录的默认权限。当进程创建一个新文件或目录时,实际的权限是由open(或creat等用于创建文件的函数)等函数中指定的权限与umask函数设置的掩码进行按位与运算后的结果。
  • 参数:
    • mode_t mask:这是一个位掩码,用于指定要屏蔽的权限位。它通常是一个八进制数,用于表示用户(文件所有者)、组(与文件所有者同组的用户)和其他用户(既不是所有者也不是同组用户)的读(r)、写(w)和执行(x)权限。例如,0022表示屏蔽组用户和其他用户的写权限。
  • 返回值:
    • 函数返回旧的文件模式创建掩码。这可以用于在需要恢复之前的掩码设置时使用。

完整代码:

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
47
48
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.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);

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

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

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

close(STDIN_FILENO); // 关闭文件描述符 0

fd = open("/dev/null", O_RDWR); // fd --> 0
if (fd == -1)
sys_err("open error");

dup2(fd, STDOUT_FILENO); // 重定向 stdout和stderr
dup2(fd, STDERR_FILENO);

while (1); // 模拟 守护进程业务.

return 0;
}

运行:

1
2
./mydeamon
ps aux

image-20241220213958304

可以看到它没有控制终端而是跑到后台运行去了

这个不受用户登录注销影响,即使我注销了换个账号登录也还会有这个的

可以使用kill命令杀死进程

1
kill -9 进程号