1 概念
进程组,也称之为作业。 BSD 于 1980 年前后向 Unix 中增加的一个新特性。代表一个或多个进程的集合。每个进程都属于一个进程组。在 waitpid 函数和 kill 函数的参数中都曾使用到。操作系统设计的进程组的概念,是为了简化对多个进程的管理。
当父进程,创建子进程的时候,默认子进程与父进程属于同一进程组。
进程组 ID==第一个进程 ID(组长进程)。所以,组长进程标识:其进程组 ID==其进程 ID 。
可以使用 kill -SIGKILL -进程组 ID(负的)来将整个进程组内的进程全部杀死 。
组长进程可以创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关。
进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组) 。
一个进程可以为自己或子进程设置进程组 ID 。
1.1 会话
会话:多个进程组的集合。
创建一个会话需要注意以下 6 点注意事项:
1. 调用进程不能是进程组组长,该进程变成新会话首进程(session header)
2. 该进程成为一个新进程组的组长进程。
3. 需有 root 权限 (ubuntu 不需要)
4. 新会话丢弃原有的控制终端,该会话没有控制终端
5. 该调用进程是组长进程,则出错返回
6. 建立新会话时,先调用 fork, 父进程终止,子进程调用 setsid()
getsid函数
获取进程所属的会话 ID
pid_t getsid(pid_t pid);
// 成功:返回调用进程的会话 ID;失败: -1,设置 errno
// pid 为 0 表示察看当前进程 session ID
ps ajx 命令查看系统中的进程。
参数 a 表示不仅列当前用户的进程,也列出所有其他用户的进程;
参数 x 表示不仅列有控制终端的进程,也列出所有无控制终端的进程;
参数 j 表示列出与作业控制相关的信息。
setsid函数
创建一个会话,并以自己的 ID 设置进程组 ID,同时也是新会话的 ID。
pid_t setsid(void);
// 成功:返回调用进程的会话 ID;失败: -1,设置 errno
三合一:gid、sid、pid
测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
pid_t pid;
if ((pid = fork()) < 0)
{
perror("fork error\n");
exit(1);
}
else if (pid == 0)
{
printf("child process PID = %d\n", getpid()); // 进程id
printf("Group ID of child GID = %d\n", getpgid(0)); // 进程组id
printf("session ID of child SID = %d\n", getsid(0)); // 会话id
sleep(10);
setsid(); // 子进程不是组长进程,可以成为新会话首进程,且成为组长进程,该进程组长id即为会话
printf("Change:\n");
printf("child process PID = %d\n", getpid()); // 进程id
printf("Group ID of child GID = %d\n", getpgid(0)); // 进程组id
printf("session ID of child SID = %d\n", getsid(0)); // 会话id
sleep(20);
exit(0);
}
return 0;
}
执行
2 守护进程
2.1 什么是守护进程
1 在linux系统中,我们会发现在系统启动的时候有很多的进程就已经开始跑了,也称为服务,这也是我们所说的守护进程。
2 守护进程(daemon)是生存期长的一种进程,没有控制终端。
3 它们常常在系统引导装入时启动,仅在系统关闭时才终止。
4 UNIX系统有很多守护进程,守护进程程序的名称通常以字母“d”结尾:例如,syslogd 就是指管理系统日志的守护进程
5 通过ps进程查看器 ps -efj 的输出实例,内核守护进程的名字出现在方括号中,大致输出如下:
守护进程:
daemon进程。通常运行于操作系统后台,脱离控制终端。一般不与用户直接交互。周期性的等待某个事件发生或周期性执行某一动作。
不受用户登录注销影响。通常采用以d结尾的命名方式。
创建守护进程,最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader。
2.2 守护进程创建步骤
1. fork子进程,让父进程终止。
2. 子进程调用 setsid() 创建新会话
3. 通常根据需要,改变工作目录位置 chdir(), 防止目录被卸载。(U盘)
4. 通常根据需要,重设umask文件权限掩码,影响新文件的创建权限。 022 -- 755 0345 ---
432 r---wx-w- 422
5. 通常根据需要,关闭/重定向(0/1/2) dev/null 文件描述符
6. 守护进程 业务逻辑。while()
2.3 守护进程代码实现(重点)
主要是理解一些概念。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
int main(int argc, char *argv[])
{
pid_t pid; // 进程
int res; // 返回值
int fd; // 文件描述符
pid = fork();
if (pid > 0)
{ // 父进程
exit(1);
}
else
{ // 子进程
pid = setsid();
if (pid == -1)
{
perror("setsid error\n");
exit(1);
}
res = chdir("/home/winter/linuxStudy"); // 改变工作目录位置
if (res == -1)
{
perror("chdir error\n");
exit(1);
}
umask(0022); // 改变文件访问权限掩码
close(STDIN_FILENO); // 关闭文件描述符0
fd = open("/dev/null", O_RDWR); // 读写权限打开 fd就是0
if (fd == -1)
{
perror("open error\n");
exit(1);
}
dup2(fd, STDOUT_FILENO); // 重定向,将STDOUT_FILENO指向fd
dup2(fd, STDERR_FILENO);
// 模拟守护进程业务
while (1)
;
}
return 0;
}
执行
这个daemon进程就不会受到用户登录注销影响。要想终止,就必须用kill命令。