本章Gitee仓库:守护进程
1. 前台 & 后台进程
在Linux系统登陆的时候,它会给我们形成一次会话,会话在系统里面会创建一个bash
进程,这个bash
就会给用户提供命令行服务。
**在一个会话里面,只能存在一个前台进程,但可以有多个后台进程。**键盘信号只能发给前台进程。
当我们直接运行程序的时候,系统就会自动把bash
调到后台,然后把我们的程序调到前台,所以我们程序执行的时候,输入一些指令就没有翻译,当我们终止我们的进程的时候,此时没有前台进程,所以操作系统会将bash
又重新调回前台。
如果加上&
选项,就是让程序以后台进程的形式运行,此时输入一些指令就可以执行
不管是前台还是后台进程,都可以向显示器输出内容,这就会影响到我们输入一些指令,所以我们可以采取重定向的方式,让后台进程将内容输出要一个文本里面。
所以显示器并不是区分前台和后台的一个重要指标,而是谁拥有标准输入(键盘文件),谁就是前台
启动一个后台进程时,会显示一个后台任务号
这个后面跟的一串数字就是进程的pid
如果要查看所有的后台进程,采用指令jobs
:
我们可以直接用kill
命令杀掉这个后台进程,当然也可以用fg 后台任务号
将这个后台进程调到前台来,然后采用ctrl + c
终止
如果又想将这个任务放回后台,ctrl + z
当这个进程被暂停了,那么bash
就又会被调到前台,但此时这个进程在后台是处于暂停状态的,采用指令bg 后台进程任务号
,可重新将这个进程启动起来
2. Linux进程之间的关系
这里单独创建了一个后台进程和连续创建了三个后台进程:
这里单独创建的进程,自成一组;而用管道建立起的进程,它们几个是同一个组,组长是第一个创建的
任务和进程组的关系:
任务是具体要完成的事情,而这个事情是有谁来完成,是可以指定的,可以一个进程独立完成,也可以一个进程组协同完成
所以这里可以稍微纠正一下前面的内容,一般叫做前台任务和后台任务
这里的会话ID和这些进程的父进程ID是一样的,这个就是ID就是bash
的pid
当关闭当前会话时,这些后台进程就会被系统回收或者是直接被杀掉,也就是说,这些后台进程是受当前会话影响的
在
Windows
中,我们的注销,就可以理解为关闭当前会话,这里会杀掉我们打开的进程:
如果想让进程不受会话影响,我们就需要将这个进程守护进程化。
3. 守护进程
如果将一个进程(组)自称会话,也就是不需要和键盘显示器进行关联,这个就叫做守护进程
#include <unistd.h>
pid_t setsid(void);
这里要形成的新会话不能说这个进程组的组长,但如果这个进程组只有一个进程,那么就需要创建子进程了。
所以守护进程的本质,也是孤儿进程
守护进程之后,按理说也不应该占用标准输入输出及错误,如果直接将标准输入输出错位关闭,这就需要对原来的代码进行改动。
在系统中存在一个/dev/null
:
它就相当于一个垃圾桶一样,向这里面写的内容全部会自动丢弃
大部分情况下都是将日志写入文件而不是向显示屏输出,因为这样方便排查信息
守护进程代码Daemon.hpp
:
#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
const std::string nullfile = "/dev/null";
void Daemon(const std::string &cwd = "")
{
//忽略常见异常信号
signal(SIGCLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGSTOP, SIG_IGN);
//核心 变成独立会话
if(fork() > 0) exit(0);
setsid();
//更改工作目录
if(!cwd.empty()) chdir(cwd.c_str());
//重定向标准输入 输出 错位 >> /dev/null
int fd = open(nullfile.c_str(), O_RDWR);
if(fd > 0)
{
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
}
以之前写的翻译服务器为例,要想守护进程化,可以在main
函数初始化之后加上这个功能:
int main(int argc, char *argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = std::stoi(argv[1]);
std::unique_ptr<TcpServer> tcpSvr(new TcpServer(port));
tcpSvr->Init();
Daemon();
tcpSvr->Start();
return 0;
}
也可也直接在启动的时候守护进程:
void Start()
{
Daemon();
signal(SIGPIPE, SIG_IGN);
threadPool<Task>::GetInstance()->Start();
//signal(SIGCHLD, SIG_IGN); //直接忽略进程等待 V2
log(Info, "server is running...");
while(true)
{
//...
}
我们能远程登录
linux
,这是因为系统里面会默认起一个服务,我们每次登录就会发起一个会话:守护进程名字一般以
d
结尾
服务端以日志形式记录:
在系统当中有一个daemon
函数,这样就不用我们自己写了(但一般情况下都是自己写):
3号手册,是C的库函数