进程组和会话
这一个课程的笔记
概念和特性
进程组
也称之为作业。BSD于1980年前后向Unix中增加的一个新特性。代表一个或多个进程的集合。每个进程都属于一个进程组。在waitpid函数和kill函数的参数中都曾使用到。操作系统设计的进程组的概念,是为了简化对多个进程的管理。
一个进程组是一组相关的进程,它们共享同一个终端或控制台,并且可以通过进程组ID(PGID)来标识。一个进程可以创建一个新的进程组,并将其子进程分配给该组。进程组的一个常见用途是在Shell中将一组进程一起启动或停止。
当父进程,创建子进程的时候(使用fork),默认子进程与父进程属于同一进程组。进程组ID第一个进程ID(组长进程)。所以,组长进程标识:其进程组ID其进程ID
可以使用kill -SIGKILL -进程组ID(负的)来将整个进程组内的进程全部杀死。
使用命令
cat | cat | cat | wc -l
可以使用
kill - 9 -3991
杀死这一个组这一个使用正的3991也可以, 这是因为这一个管道关闭, 读不到数据, 子进程结束
组长进程可以创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关。
进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组)。
一个进程可以为自己或子进程设置进程组ID
会话
Linux session(会话) - sparkdev - 博客园 (cnblogs.com)
见的 Linux session 一般是指 shell session。Shell session 是终端中当前的状态,在终端中只能有一个 session。当我们打开一个新的终端时,总会创建一个新的 shell session。
Session 中的每个进程组被称为一个 job,有一个 job 会成为 session 的前台 job(foreground),其它的 job 则是后台 job(background)。每个 session 连接一个控制终端(control terminal),控制终端中的输入被发送给前台 job,从前台 job 产生的输出也被发送到控制终端上。同时由控制终端产生的信号,比如 ctrl + z 等都会传递给前台 job。
一般情况下 session 和终端是一对一的关系,当我们打开多个终端窗口时,实际上就创建了多个 session。
Session 的意义在于多个工作(job)在一个终端中运行,其中的一个为前台 job,它直接接收该终端的输入并把结果输出到该终端。其它的 job 则在后台运行。
会话是多个进程组的集合,
这时候会出现一个问题, 如果你希望一个进程一直在后台执行, 但是因为这一个进程的父进程(或者父进程的父进程等)是shell终端, 一旦这一个终端关闭, 会导致这一个终端里面的所有一个组里面的进程退出
解决方法: 守护进程
创建会话
创建一个会话需要注意以下6点注意事项:
- 调用进程不能是进程组组长,该进程变成新会话首进程(session header)
- 该进程成为一个新进程组的组长进程。
- 需有root权限 (ubuntu不需要)
- 新会话丢弃原有的控制终端,该会话没有控制终端
- 该调用进程是组长进程,则出错返回
- 建立新会话时,先调用fork, 父进程终止,子进程调用setsid
getsid获取当前的sid
获取进程所属的会话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
调用了setsid函数的进程,既是新的会长,也是新的组长。
练习:fork一个子进程,并使其创建一个新会话。查看进程组ID、会话ID前后变化
守护进程daemon
Daemon(精灵)进程,是Linux中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字。
Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互。不受用户登录、注销的影响,一直在运行着,他们都是守护进程。如:预读入缓输出机制的实现;ftp服务器;nfs服务器等。
创建守护进程,最关键的一步是调用setsid函数创建一个新的Session,并成为Session Leader。
创建守护进程模型
- 创建子进程,父进程退出所有工作在子进程中进行形式上脱离了控制终端
- 在子进程中创建新会话
setsid()函数
使子进程完全独立出来,脱离控制
- 改变当前目录为根目录
chdir()函数
防止占用可卸载的文件系统
也可以换成其它路径
如果这一个进程处于的目录是一个U盘, 这一个U盘被拔出来会导致这一个程序出问题, 改为根目录这一个程序就不会出错
- 重设文件权限掩码
umask()函数
防止继承的文件创建屏蔽字拒绝某些权限
增加守护进程灵活性
- 关闭文件描述符(主要是标准输入输出)
继承的打开文件不会用到,浪费系统资源,无法卸载
开始执行守护进程核心工作守护进程退出处理程序模型
一般可以把这三个文件重定向到/dev/null里面,用于符合一般的编程习惯
示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(void){
pid_t pid;
pid = fork(); //1.创建子进程
int ret;
if(pid > 0){
exit(0);
}else{
pid = setsid(); //2.创建新的会话
if(pid == -1){
perror("setid error");
exit(0);
}
ret = chdir("/home/jiao");//3.改变文件的目录
if(ret < 0){
perror("chdir error");
exit(0);
}
umask(0022);//4.改变文件的掩码
close(STDIN_FILENO);
int fd;
fd = open("/dev/null", O_RDWR);//5.重定向文件的输出
if(fd == -1){
perror("open error");
exit(0);
}
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
while(1);
}
return 0;
}
这一个进程不会随着shell的关闭结束, 可以使用kill命令进行杀死