Linux练级宝典->任务管理和守护进程
任务管理
进程组概念
每个进程除了进程ID以外,还有一个进程组,进程组就是一个或多个进程的集合
同一个进程组,代表着他们是共同作业的,可以接收同一个终端的各种信号,进程组也有其唯一的进程组号。还有一个组长进程,组长进程ID == 进程组ID。组长进程可以创建一个进程组,创建进程,然后终止。
attention:进程组只要还有进程就会存在,和组长进程无关,组长会易主。
作业概念
Shell分前后控制的不是进程而是作业(Job)或者叫进程组(Process Group)。
一个前台作业可以由很多进程组成,后台作业同理,Shell可以运行一个前台任务和多个后台任务。这叫作业控制。
作业和进程组的区别:
如果作业中的某个进程又创建了一个子进程,这个子进程不属于作业。一旦作业运行结束,Shell就先把自己拉到前台,如果原来的这个子进程还存在没被终止,此时这个子进程自动变为后台进程组。
会话概念
会话是一个或多个进程组的集合
一个会话有一个控制终端,通常的两种终端情况:1.终端设备(终端登录)2.伪终端设备(网络登录)。建立和控制终端连接的会话首进程称为控制进程。一个会话中有一个前台任务组和多个后台任务组和控制进程。
下列代码我们生成了5个死循环文件:
#include<stdio.h>
int main(){
while(1);
return 0;
}
我们用ps axj指令查看生成的程序。
上面的[2] 1666625是进程组中的某一个进程的pid。
R表示后台程序 R+表示前台程序
上图我们使用信号(ctrl + c)把前台进程删除了。1 2 在后台并没有被删除。
相关操作
前台进程&后台进程
运行某个程序是使用 ./ 的方式,这时的程序在前台运行,如果在后面加上 & 就变成了后台进程。
前台进程状态后面有 + 号,后台进程没有 + 号。
jobs fg bg指令
jobs指令:可以查看当前会话中有哪些作业
fg(foreground)指令:可以将后台进程提到前台运行,如果该作业在后台运行则直接放到前台运行,如果该作业是停止状态,则会给这个作业的每个进程提到前台运行并发送SIGCONT信号使他们唤醒。
上图可以发现我们的1号任务被取出了,在前台运行。
如果想将一个前台进程放到后台,可以使用ctrl + z 指令,进程就会变为停止状态。
bg(background)指令:bg指令可以让停止的作业,转而在后台继续进行。本质就是给进程组的每一个进程发送了SIGCONT信号。
ps指令查看指定选项
ps -o指令,可以查看指定的信息。
-o指令指定只看当前会话的进程。
SESS:session 就是会话的意思,同一会话的进程会话ID相同
守护进程
守护进程也称为精灵进程(Daemon),是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件
守护进程的查看
可以用ps axj指令查看系统中的进程
TPGID为-1的进程全部都是没有控制终端的进程,都是守护进程。
COMMAND中用[ ] 括起来的表示内核进程,这些进程在内核中创建,没有用户空间代码,所以这些进程找不到程序文件名和命令行,通常以k为开头表示Kernel内核。
守护进程的创建
守护进程的创建如下:
1.设置文件掩码为0.
2.fork后终止父进程,子进程会创建新会话。
3.忽略SIGCHLD信号。
4.再次fork,终止父进程,保持子进程不是会话首进程,保证了后续不会和其他终端关联。
5.更改工作目录为根目录。
6.将标准输入,标准输出,标准错误都重定向到/dev/null。
说明:
1.文件掩码设置为0,创建出来的文件权限就和我们预期的一样
2.调用setsid创建新会话,让当前进程自成会话,并且此时会与bash脱离关系(创建守护进程的核心)。
3.调用setsid创建新会话时,要求调用进程不能是组长进程,但是我们在命令行上同时启动多个进程协同完成某个任务,其中一个创建好的进程就会成为组长进程,所以我们需要创建子进程,子进程调用后创建新会话后,让父进程退出即可
4.守护进程不能和用户交互,所以就没必要打开某个终端了,打开终端是会话首进程进行的,所以为了防止守护进程有打开终端的操作,所以我们再次fork创建子进程,此时的首进程去执行后续的代码,它不是会话首进程也就没有办法打开终端了(不是必须的操作,但是是一种防御性编程)。
5.一般将守护进程的工作目录放到根目录下,这样就可以使用绝对路径的方式打开文件(不是必须得)。
6.守护进程因为不能和用户交互也就是守护进程和终端没关系了,所以我们把守护进程的fd 0 1 2也就是标准输入输出错误,都放到一个垃圾站里 /dev/null,这是一个字符文件,用于屏蔽丢弃输入输出的信息(不是必须的)。
测试代码如下:
#include <fcntl.h>
#include <signal.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
//1.设置文件掩码为0
umask(0);
//2.fork后终止父进程,子进程创建新会话
if(fork()>0){
exit(0);
}
setsid();
//3.忽略SIGCHLD信号
signal(SIGCHLD,SIG_IGN);
//4.再次fock,终止父进程,保持子进程不是会话首进程,不会和终端关联
if(fork()>0){
exit(0);
}
//5.更改工作目录为根目录
chdir("/");
//6.将标准输入输出错误重定向到/dev/null中
close(0);
int fd = open("/dev/null",O_RDONLY);
dup2(fd,2);
dup2(fd,1);
while(1);
return 0;
}
结果我们发现该进程的TPGID为-1,TTY显示的为问号,表示这个进程已经与终端失去关联了。
同时PID和PGID不同说明不是会话首进程也不是组长进程,所以不会与其他终端关联。
我们进程的SID和-bash进程的SID是不同的,说明二者也不是同一个会话了。
通过 ls /proc/进程id -al命令,可以查看这个守护进程的工作目录改为了根目录
通过 ls /proc/进程id/fd -al命令 可以查看该进程的标准输入输出,重定向的位置
调用daemon函数创建守护进程
函数原型:
int daemon(int nochdir, int noclose);
参数说明:
nochdir如果为0,则把守护进程的工作目录改为根目录,否则不做处理
noclose如果为0,则将守护进程的标准输入输出错误进行重定向,否则不做处理
测试代码:
注意:有些编译器需要添加 #define _DEFAULT_SOURCE 才能使用
#define _DEFAULT_SOURCE
#include <unistd.h>
int main(){
daemon(0,0);
while(1);
return 0;
}
可以发现这样创建出来和我们自己的没有什么差距,差距就是daemon 依然是组长进程。可能会打开终端。