目录
任务管理
jobs,fg,bg
进程组概念
任务概念
守护进程
守护进程的概念
守护进程的查看
守护进程的创建
编辑模拟实现daemon函数
任务管理
每当有一个用户登录Linux时,系统就会创建一个会话(session)
任何进程都可以被设置为前台进程,只要前台进程被干掉系统会自动的将bash放到前台变成前台进程
我下面写了一个死循环,创建一个文件 test.cc 测试它被干掉之后bash进程是否会自动顶替它
可以看到在a.out运行期间不管我们输入多少次指令都是没有响应的,原因就是此时的前台进程已经不是bash了,当我们使用ctrl + c将a.out终止之后就可以看到,我们输入的ls指令受到了响应,原因就是a.out执行结束之后,前台进程被bash自动顶上了
jobs,fg,bg
将当前任务变成前台任务
当我们以后台进程的方式运行a.out时,我们发送的指令,bash是会给我们进行响应的,那我们如果想将a.out变成前台进程应该怎么做呢?
fg + 任务号
任务号:
为了方便演示我们将a.out打印的值追加重定向到log.txt中
同样的,我们创建三个文件 test.cc,test1.cc,test2.cc 让它生成可执行程序
然后将这三个启动的进程追加重定向到log.txt中
我们可以使用 jobs 指令来查看当前会话中的所有后台进程
后台进程的杀死方式
1.可以使用kill直接杀掉进程
2.提到前程,再使用键盘组合键杀死
暂停前台进程 ctrl + z 组合键
当我们使用ctrl + z将前台进程暂停之后,系统会将bash提到前台,让bash充当前台进程
此时我们向键盘文件中输入 ls pwd 指令是没有响应的
当我们使用ctrl+z将该进程暂停之后,bash自动充当前台进程,这时我们输入指令就会得到响应
恢复已经暂停的进程 bg指令
使用方式:bg + 任务号
怎么区分前台进程和后台进程?
后台进程无法从标准输入获取数据,所以谁拥有键盘文件,谁就是前台进程
进程组概念
每个进程除了有一个进程ID之外,是属于一个进程组,进程组是一个或多个进程的集合
通常它们与同任务相关联,可以接收来自同一终端的各种信号。每个进程组有一个唯一的进程组ID。每个进程组都有一个组长进程。组长进程的标识是,其进程组ID等于其进程ID。组长进程可以创建一个进程组,创建该组中的进程,然后终止
注意:进程组中,只要有进程存在则该进程组就存在,这与其组长进程是否终止无关
PGID:进程组ID
SID:用来标识系统中的某一个会话(session)
TTY:该用户使用的终端编号
注意:前台进程的状态后面有一个 + 号,后台进程则没有 + 号
任务概念
Shell分前后台来控制的不是进程而是任务或者进程组
一个前台任务可以由多个进程组成,一个后台任务也可以由多个进程组成,Shell可以运行一个前台作业和任意多个后台任务,这称为作业控制
作业和进程组的区别:
如果作业中的某个进程有创建出了子进程,则子进程不属于任务,一旦任务运行结束,Shell就把自己提到前台,如果原来前台进程还存在,也就是这个被创建的子进程还没有终止,那么它将自动变为后台进程组
守护进程
守护进程的概念
此时,我们打开三个后台进程,我们看看用户退出时这三个进程是否会收到影响
用户退出之后,启动的进程也随之受到了影响
怎么让这些进程不受到用户登录和退出的影响呢?我们可以将这些进程守护进程化
怎么让进程守护进程化
守护进程也称精灵进程(Daemom),是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件
守护进程是一种很有用的进程,Linux的大多数服务器就是用守护进程实现的,比如Internet服务器inetd,Web服务器httpd等。同时守护进程完成许多系统任务,比如作业规划进程crond等。
Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。其他进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但系统服务进程不受用户登录注销的影响,它们一直在运行着,这种进程有一个名称叫守护进程(Daemon)。
守护进程的查看
我们可以使用ps ajx指令查看系统中的进程:
- 参数a表示不仅列出当前用户的进程,也列出所有其他用户的进程。
- 参数x表示不仅列出有控制终端的进程,也列出所有无控制终端的进程。
- 参数j表示列出与作业控制相关的信息。
凡是TPGID一栏写着-1的都是没有控制终端的进行,也就是守护进程
除此之外,在COMMAND一列用[ ]括起来的名字表示内核线程,这些线程在内核里创建,没有用户空间代码,因此没有程序文件名和命令行,通常采用以k开头的名字,表示Kernel。
个别说明:
- udevd负责维护/dev目录下的设备文件。
- acpid负责电源管理。
- syslogd负责维护/var/log下的日志文件。
可以看出,守护进程通常采用以d结尾的名字,表示Daemon。
守护进程的创建
1.忽略其他异常信号,以免收到信号影响到守护进程(SIGCLD,SIGPIPE,SIGSTOP)
2.将自己变成独立的会话
3.更改当前调用进程的工作目录
4.将标准输入,标准输出,标准错误重定向到 dev/null 中
相关说明:
1.调用setsid创建新会话的目的,是让当前进程自称会话与当前的bash脱离关系(创建守护进程的核心)。
2.调用setsid创建新会话时,要求调用进程不能是进程组组长,但是当我们在命令行上启动多个进程协同完成某种任务时,其中第一个被创建出来的进程就是组长进程,因此我们需要fork创建子进程,让子进程调用setsid创建新会话并继续执行后续代码,而父进程直接退出即可
3.我们一般将守护进程的工作目录设置为根目录,便于让守护进程以绝对路径的方式访问某种资源
4.守护进程不能直接和用户交互,也就是说守护进程已经与终端去关联了,因此一般我们会将守护进程的标准输入,标准输出,标准错误都重定向到 /dev/null 中, /dev/null是一个字符文件(设备),通常用于屏蔽、丢弃输入输出信息
#include <iostream>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string>const std::string filename = "/dev/null";//守护进程化
int main()
{// 1.屏蔽信号signal(SIGCLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);signal(SIGSTOP, SIG_IGN);// 2.fork子进程,子进程执行setsid(),创建守护进程,父进程退出if (fork() > 0)exit(0);setsid();// 3.将守护进程的工作路径更改为根目录chdir("/");// 由于守护进程没有终端,不能和用户进程交互,所以我们需要将标准输入,标准输出,标准错误重定向到 /dev/null下int fd = open(filename.c_str(), O_RDWR, 000);dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);return 0;
}
可以看到当前进程已经和终端去关联了
通过 ls /proc/进程id -al 命令我们可以看到该进程的工作目录已经成功修改为了根目录
通过 ls /proc/进程id/fd -al 命令,我们可以看到该进程的标准输入,标准输出,标准错误,重定向到了 /dev/null 中
调用daemom创建守护进程
实际当我们创建守护进程时可以直接调用daemom接口进行创建,daemom函数的函数原型如下:
int daemon(int nochdir, int noclose);
参数说明:
- 如果参数nochdir为0,则将守护进程的工作目录该为根目录,否则不做处理。
- 如果参数noclose为0,则将守护进程的标准输入、标准输出以及标准错误重定向到
/dev/null
,否则不做处理。
#include <iostream>
#include <unistd.h>
#include <string>const std::string filename = "/dev/null";// 守护进程化
int main()
{daemon(0, 0);// 死循环,守护进程是不退的while (true);return 0;
}
调用daemon函数创建的守护进程与我们原生创建的守护进程差距不大

模拟实现daemon函数
有了上述创建守护进程的代码,要模拟实现daemon函数就很容易了,我们只需要设置两个参数nochdir和noclose,当所给nochdir为0时,我们将守护进程的工作目录该为根目录,当所给noclose为0时,我们则将守护进程的标准输入、标准输出以及标准错误重定向到/dev/null即可。
#include <iostream>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string>const std::string filename = "/dev/null";// 守护进程化
void Daemom(const std::string &Chdir)
{// 1.屏蔽信号signal(SIGCLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);signal(SIGSTOP, SIG_IGN);// 2.fork子进程,子进程执行setsid(),创建守护进程,父进程退出if (fork() > 0)exit(0);setsid();// 3.将守护进程的工作路径更改为根目录if(!Chdir.empty()) chdir("/");// 由于守护进程没有终端,不能和用户进程交互,所以我们需要将标准输入,标准输出,标准错误重定向到 /dev/null下int fd = open(filename.c_str(), O_RDWR, 000);if (fd > 0){dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);}// daemon(0, 0);// 死循环,守护进程是不退的while (true);return;
}