日志是网络服务器程序在后台以守护进程的形式运行时,处理情况的描述被打印到了日志文件里面,方便维护人员查看。
1.前台进程与后台进程
左边会话输入命令 sleep 10000 & 代表进程后台运行,右边会话输入命令 sleep 20000可以看到命令行解释器直接卡住了。
STAT S+就是前台进程的意思,STAT S就是后台进程。可以看到后台运行的进程并不影响命令行解释器bash进程的运行,而前台进程sleep 20000运行的时候,bash进程无法运行了。所以能得出结论,一个会话中只能有一个前台进程。
2. 会话与终端
PPID:父进程ID
PID:进程ID
PGID:进程组ID
SID:会话
TTY:终端
会话是一个进程组(任务)的集合。在xshell模拟的Linux终端上,一个会话在被打开的时候就会启动一个bash进程,bash进程是用于对用户输入的命令进行分析解释并返回结果到该会话的终端文件TTY中的。那么bash进程自然就是该会话的第一个进程。可以看到上图红色标记,COMMAND-sleep 10000与COMMAND - -bash的SID都是2917,而bash的PID和PGID也都是2917。这是什么意思呢?一个会话中第一个进程的PID就会作为SID,同理一个进程组中第一个进程PID就是进程组的PGID,一个进程组至少有一个进程。
可以看到我们把hello world重定向到了/dev/pts/4这个文件下,结果在右边的会话中显示出来了。这是为什么呢?在/dev/pts/路径下存在着各个会话的终端文件,在A会话中输入命令,bash进程对命令进行解释后返回的结果就默认输出到A会话的终端文件。
3.前台任务与后台任务切换
jobs 查看当前会话进程组
可以看到每个进程组(任务)前面都有[1] [2]这样的任务编号。
fg 后台任务切换成前台任务
ctrl+z 暂停前台任务
bg 在后台运行任务
4.为什么要创建守护进程?
创建一个会话就会可以创建新的前后台任务,那么销毁会话可能就会影响会话内的所有任务。所以一般网络服务器为了不受到用户的登录注销的影响都是以守护进程的方式运行。
守护进程就是为了让任务不受所在会话的注销的影响,为该进程可以创建一个新的会话。
cpp
#include <unistd.h>
pid_t setsid(void);
setsid() 创建一个新会话,如果调用进程不是进程组组长。调用进程是新会话的领导者,也是新进程组的组长,并且没有控制终端。 这调用进程的进程组 PGID 和会话 SID 被设置为 调用进程的PID。
进程pcb里面是会包含当前进程的工作路径的,这也就是我们在open一个文件的时候,如果没有就创建,文件就会在当前路径下的原因。
/dev/null是一个"黑洞"文件,不能从中读取任何数据也不能向其写入任何数据。
5.如何创建守护进程?
cpp
#pragma once
// 1. setsid();
// 2. setsid(), 调用进程,不能是组长!我们怎么保证自己不是组长呢?
// 3. 守护进程a. 忽略异常信号 b. 0,1,2要做特殊处理 c. 进程的工作路径可能要更改 /
#include <signal.h>
#include <unistd.h>
#include <cstdlib>
#include "log.hpp"
#include "err.hpp"
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void Daemon()
{
// 忽略信号
signal(SIGCHLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
// 让子进程去setsid
if (fork() > 0)
exit(0);
// 为当前进程组(任务)创建新的会话;
pid_t ret = setsid();
if ((int)ret == -1)
{
logMessage(Fatal, "setsid error,code:%d ,string: %s\n", errno, strerror(errno));
exit(SETSID_ERR);
}
// 更改工作路径到根目录
// chdir("/");
//0,1,2做特殊处理
int fd = open("/dev/null",O_RDWR);//可读可写方式打开黑洞文件
if(fd<0)
{
logMessage(Fatal, "open error,code:%d ,string: %s\n", errno, strerror(errno));
exit(OPEN_ERR);
}
dup2(fd,0);
dup2(fd,1);
dup2(fd,2);
close(fd);
}
由于进程组组长长不能单起一个会话的原则,所以创建子进程作为守护进程,守护进程是一个孤儿进程。
其实也可以直接用Unix标准库函数daemon函数创建守护进程:
NAME
daemon - run in the background
SYNOPSIS
#include <unistd.h>
int daemon(int nochdir, int noclose);
当nochdir为0时,daemon将更改进程的工作路径为根目录。
当noclose为0时,daemon将进城的STDIN, STDOUT, STDERR都重定向到/dev/null。一般情况下,这个参数都是设为0的。