目录

1、守护进程

守护进程的概念

进程组和会话

2、守护进程化的方式

TCP网络程序(守护进程化)

TCP网络程序(守护进程化)gitee地址

daemon创建守护进程

nohup命令


1、守护进程

守护进程的概念

守护进程也叫做精灵进程,是运行在后台的一种特殊进程他独立于控制终端并且可以周期性的执行某种任务或者等待处理某些发生的事件。

  • 守护进程是非常有用的进程,在Linux当中大多数服务器用的就是守护进程比如Web服务器httpd等,同时守护进程完成很多系统的任务。当Linux系统启动的时候,会启动很多系统服务,这些进程服务是没有终端的也就是说你把终端关闭了这些系统服务是不会停止的,他们一直运行着他们有一个名字就叫做守护进程。

一般以服务器的方式工作,对外提供服务的服务器,都是以守护进程(精灵进程)的方式在服务器中工作的,一旦启动之后,除非用户主动关闭,否则,一直会在运行。


进程组和会话

进程组的相关概念:

  • 进程除了有进程的PID之外还有一个进程组,进程组是由一个进程或者多个进程组成。通常他们与同一作业相关联可以收到同一终端的信号
  • 每个进程组有唯一的进程组ID,每个进程组有一个进程组组长。如何判断一个进程是不是这个进程组的组长了,通常进程组ID等于进程ID那么这个进程就是对应进程组的组长。

会话的相关概念:

  • 会话是有一个或者多个进程组组成的集合
  • 一个会话可以有一个终端,建立与控制终端连接的会话首进程被成为控制进程,一个会话的几个进程组可以分为前台进程和后台进程,而这些进程组的控制终端相同也就是sesion id是一样的当用户使用ctr +c 产生SIGINT信号时内核会发送信号给相应前台进程组的所有进程如果我运行一个程序我们想要把他放到后台运行我们可以在可执行程序的后面加一个& 举个列子:./test & 如果我们想要把他提到前台进程我们可以使用fg

我们使用如下监控脚本先观察一个现象:

[xzy@ecs-333953 date37]$ ps axj | head -1 && ps axj | grep sshd

上述第一行以 -D 结尾的就是服务器(守护进程)。它的PPID是1。下面来介绍上述选项的意义:

  • COMMAND:启动的进程命令名称
  • TIME:进程启动的时长
  • UID:是谁启动的
  • STAT:状态
  • TPGID:当前进程组和终端的关系(如果是-1,则没有任何关系)
  • TTY:代表哪一个终端
  • SID:当前进程的会话ID
  • PGID:当前进程所属的进程组
  • PID:当前进程自己的ID
  • PPID:当前进程的父进程的ID

如下,我把三个进程放到后台运行,并使用如下监控脚本观察现象:

[root@xzy-centos ~]# ps ajx | head -1 && ps axj | grep sleep

  • 上述我创建了三个进程,可以确定的是这三个进程的PPID都是一样的,因为父进程都是Bash。上述三个进程是属于同一个进程组(PGID)的,且会发现启动的第一个进程是进程组的组长

上述三个进程的会话ID(SID)为6821,即bash。我们通过下面的监控脚本观察:

看如下的图:

  • 一旦我们登陆linux,linux会我们创建一个会话。会话内部由多个进程组构成。登陆后会给我们加载bash,所以其内部必须有一个前台进程组(任何时刻,只能有一个前台进程组)。0个或多个后台进程组。

再使用如下监控脚本观察上述的会话6821:

[root@xzy-centos ~]# ps axj | head -1 && ps ajx | grep 6821

图中可以看出,bash自己就是一个进程,自己就是进程组的组长,也是会话中的老大(会话前台进程组)。 所以一开始创建的三个进程,它们的会话SID均为6821,即bash:

  • 后续如果我们再自己启动新进程 && 启动进程组,它依旧属于bash自己的会话。

我们使用jobs命令查看系统中的任务:

先前这三个进程是放到后台运行的。我们使用fg 1命令把此任务提到前台:

  • 当把后台进程提到前台后,会发现我shell命令用不起来了。因为我们只能有一个前提进程组。当我们把刚才的后台进程提到前台,那么我bash命令行解释器会自动退到后台进程组 。那么自然就没有办法接受你的输入了。

综上:

  • 我们在命令行中启动一个进程,现在就可以叫做在会话中启动一个进程组,来完成某种任务。
  • 所有会话内的进程fork创建子进程,一般而言依旧属于当前会话。

像平时当我们觉得windows卡顿的时候,我们可能会重新注销一下。注销就是让用户退出登陆后再重新登陆,那么此时就相当于给你新建一个会话。卡顿是因为你本次登陆过程中启动了很多任务,且都属于同一个会话,注销本质就是把你内部会话的所有进程组删掉。

注意:

  • 在登录的状态时,新起了一个网络服务器,创建好之后,再派生的子进程也属于当前会话,所以我们就不能让这个网络服务器属于这个会话内容,要不然它会受到用户的登录和注销的影响。
  • 所以当我们有个网络服务的时候,应该脱离这个会话,让它独立的在计算机里自成进程组,自成新会话。这样在两个用户同时登录的时候,形成的两个会话是独立的,在操作各自的bash不会互相影响。
  • 像这种自成进程组,自成新会话,而且周而复始进行的进程称为守护进程(精灵进程)。

2、守护进程化的方式

我们这里有三种方式让自己的进程守护进程化:

  • 自己写daemon函数,推荐使用这种方式(下面的TCP网络程序中的daemon函数就是自己模拟实现的)
  • 用系统的daemon函数
  • nohup命令

TCP网络程序(守护进程化)

我们上篇博文的TCP网络程序是在前台进行的,但是实际上服务器并不是在前台运行的,而是在后台进行的。所以现在对TCP网络程序的代码进行修改,使其守护进程化。让服务器在后台运行。我们创建daemon.hpp文件完成守护进程的主要逻辑。代码逻辑如下:

  • 调用signal函数忽略SIGPIPE信号
  • 更改进程的工作目录(选做)
  • fork子进程,exit退出父进程。让自己不要成为进程组组长。从而保证后续不会再和其他终端相关联。
  • 调用setsid函数设置自己是一个独立的会话
  • 将标准输入、标准输出、标准错误重定向到/dev/null。

生产守护进程需要调用setsid函数,注意点如下:

  • 调用setsid创建新会话的目的,是让当前进程自成会话,与当前bash脱离关系(创建守护进程的核心)。
  • 调用setsid创建新会话时,要求调用进程不能是进程组组长,但是当我们在命令行上启动多个进程协同完成某种任务时,其中第一个被创建出来的进程就是组长进程,因此我们需要fork创建子进程,让子进程调用setsid创建新会话并继续执行后续代码,而父进程我们直接让其退出即可。此时子进程就不是组长进程了,而是独立会话的守护进程。
  • 当服务端给客户端写入时,但是客户端突然关掉了,那就是向一个不存在的文件描述符写入,此时服务端会收到SIGPIPE信号而自动终止
  • 当前进程有自己的工作目录,有时候守护进程想要更改自己的工作目录,一般会将守护进程的工作目录设置为根目录,便于让守护进程以绝对路径的形式访问某种资源。我们可以使用chdir更改进程的工作目录,不过此操作不强求。
  • 守护进程不能直接和用户交互,也就是说守护进程已经与终端去关联了,因此一般我们会将守护进程的标准输入、标准输出、标准错误都重定向到/dev/null,/dev/null是一个字符文件(设备),类似于Linux下的一个“文件黑洞” or “垃圾桶”,通常用于屏蔽/丢弃输入输出信息。(该操作不是必须的)

dameon.hpp文件代码如下:

#pragma once
#include <cstdio>
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>void daemonize()
{int fd = 0;// 1、1、忽略SIGPIPEsignal(SIGPIPE, SIG_IGN);// 2、更改进程的工作目录//  chdir();// 3、让自己不要成为进程组组长if (fork() > 0)exit(0);// 4、设置自己是一个独立的会话setsid();// 5、重定向0,1,2if ((fd == open("/dev/null", O_RDWR) != -1)) // fd == 3{dup2(fd, STDIN_FILENO);dup2(fd, STDOUT_FILENO);dup2(fd, STDERR_FILENO);// 6、关闭掉不需要的fdif (fd > STDERR_FILENO)close(fd);}
}

上述我们将标准输入、标准输出、标准错误重定向到/dev/null,那么我运行后打印的日志也就不见了。解决办法如下:我们在log.hpp打印日志文件那,用宏定义一个serverTcp.log文件。在log.hpp文件内部定义一个Log类,在此类内定义一个logFd变量,内部实现一个enable函数,enable处理过程如下:

  • 利用open函数打开此文件,返回到logFd
  • 利用dup2把标准输入,标准输出重定向到logFd文件
#pragma once
#include <cstdio>
#include <cstdarg>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctime>#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3const char *log_level[] = {"DEBUG", "NOTICE", "WARINING", "FATAL"};#define LOGFILE "serverTcp.log"
class Log
{
public:Log(): logFd(-1){}void enable(){umask(0);logFd = open(LOGFILE, O_WRONLY | O_CREAT | O_APPEND, 0666);assert(logFd != -1);dup2(logFd, 1);dup2(logFd, 2);}~Log(){if (logFd != -1){fsync(logFd);close(logFd);}}private:int logFd;
};
// 打印日志函数
void logMessage(int level, const char *format, ...) // logMessage(DEBUG, "%d", 10);
{// 确保level等级是有效的assert(level >= DEBUG);assert(level <= FATAL);// 获取当前的使用者(环境变量)char *name = getenv("USER");char logInfo[1024];// 定义一个va_list类型的指针表示可变参数列表类型va_list ap;// 初始化此指针变量va_start(ap, format);// 将可变参数格式化输出到一个字符数组logInfo里vsnprintf(logInfo, sizeof(logInfo) - 1, format, ap);// 置空此指针变量va_end(ap);// umask(0);// int fd = open(LOGFILE, O_WRONLY | O_CREAT | O_APPEND, 0666);// assert(fd >= 0);FILE *out = (level == FATAL) ? stderr : stdout;// 日志等级,打印日志的时间,打印日志的用户名fprintf(out, "%s | %u | %s | %s\n",log_level[level],(unsigned int)time(nullptr),name == nullptr ? "unknow" : name,logInfo);fflush(out);        // 将C缓冲区的数据刷新到OSfsync(fileno(out)); // 将OS中的数据尽快刷盘
}

我们只需要在服务端的main函数命令行参数信息处理后调用此daemon函数即可:

测试结果:

  • 现在我们运行服务端,通过下面的监控脚本辅助观察信息:
[xzy@ecs-333953 tcp]$ ps axj | head -1 && ps axj | grep serverTcp
[xzy@ecs-333953 tcp]$ ps axj | head -1 && ps axj | grep sshd

运行代码,用ps命令查看该进程,会发现该进程的TPGID为-1,TTY显示的是,也就意味着该进程已经与终端去关联了。此外PID、PGID、SID均是一样的,可以看出已经是守护进程了:

现在就相当于把代码部署到了Linux中,现在运行客户端,能够正常与服务端通信。即使我们把电脑关掉了,此服务端也是一直在运行的。除非我们kill -9杀掉此进程。


TCP网络程序(守护进程化)gitee地址

最终版本的TCP网络程序(守护进程化)总代码的gitee地址如下:

  • TCP网络程序(守护进程化)源码汇总

daemon创建守护进程

实际当我们创建守护进程时可以直接调用daemon接口进行创建,daemon函数的函数原型如下:

#include <unistd.h>
int daemon(int nochdir, int noclose);

参数说明:

  • 如果参数nochdir为0,则将守护进程的工作目录该为根目录,否则不做处理。
  • 如果参数noclose为0,则将守护进程的标准输入、标准输出以及标准错误重定向到/dev/null,否则不做处理。

调用示例:

#include <unistd.h>int main()
{daemon(0, 0);while (1);return 0;
}
  • 调用daemon函数创建的守护进程与我们原生创建的守护进程差距不大,唯一区别就是daemon函数创建出来的守护进程,既是组长进程也是会话首进程。
  • 也就是说系统实现的daemon函数没有防止守护进程打开终端,因此我们实现的反而比系统更加完善。


nohup命令

来看我如下test.cc文件下的代码:

#include <iostream>
#include <unistd.h>
#include <cstdio>
int main()
{while (true){std::cout << "hello world" << std::endl;sleep(1);}return 0;
}

我们用此小程序来充当网络服务器,当我们正常编译运行后,此程序默认是在前台进行的:

我们可以使用如下的指令将其变成后台进程:

nphup ./a.out &

此时会发现我nohup.out文件的大小在不断增大,使用如下命令帮助我们观察现象:

[xzy@ecs-333953 hello]$ ps axj | head -1 && ps ajx | grep a.out
[xzy@ecs-333953 hello]$ ps ajx | grep 15318

通过测试可以看出nohup进程当前正在运行,且PID和PGID是一样的,自成进程组,它所属的会话是15318(属于bash),此进程依旧是在本会话内部,并非是守护进程,但是已经很接近了。使用nohup命令可以让此进程不受用户退出和登陆的影响,已经是后台进程。其实也算是守护进程了。如下我们退出linux,重新登陆,再次执行刚才的ps命令:

< Linux > 守护进程相关推荐

  1. Linux守护进程实现

    Linux守护进程 redis版: void daemonize(void) {int fd;if (fork() != 0) exit(0); /* parent exits */setsid(); ...

  2. Linux 命令详解(六)Linux 守护进程的启动方法

    Linux 守护进程的启动方法 http://www.ruanyifeng.com/blog/2016/02/linux-daemon.html

  3. .NET跨平台实践:.NetCore、.Net5/6 Linux守护进程设计

    几年前,我写过两篇关于用C#开发Linux守护进程的技术文章,分别是<.NET跨平台实践:用C#开发Linux守护进程.NET跨平台实践:再谈用C#开发Linux守护进程 - 完整篇 这就是本文 ...

  4. .NET跨平台实践:再谈用C#开发Linux守护进程 — 完整篇

    Linux守护进程是Linux的后台服务进程,相当于Windows服务,对于为Linux开发服务程序的朋友来说,Linux守护进程相关技术是必不可少的,因为这个技术不仅仅是为了开发守护进程,还可以拓展 ...

  5. .NET跨平台实践:用C#开发Linux守护进程

    Linux守护进程(Daemon)是Linux的后台服务进程,它脱离了与控制终端的关联,直接由Linux init进程管理其生命周期,即使你关闭了控制台,daemon也能在后台正常工作. 一句话,为L ...

  6. 深入理解Linux守护进程

    深入理解Linux守护进程Linux服务器在启动时需要启动很多系统服务,它们向本地和网络用户提供了Linux的系统功能接口,直接面向应用程序和用户.提供这些服务的程序是由运行在后台的守护进程(daem ...

  7. Linux守护进程的创建(结合nginx框架)

    Linux守护进程的创建(结合nginx框架) 先介绍几个相关函数: int dup2(arg1,arg2):参数一指向的内容赋给参数二,shi的参数二也能访问参数一所指向的内容,并返回新的描述符 i ...

  8. linux+守护进程+php,【转载】Linux 守护进程的编程方法

    [转载]Linux 守护进程的编程方法 原文见: http://www.linuxdevelop.org/tingxx/show.php?table=c&id=3 Linux 守护进程的编程方 ...

  9. linux 守护进程_网络工程师之linux守护进程

    Linux守护进程就是通常所说的DEAMON进程,linux后台服务多种多样,每一个服务都运行一个对应的程序,这些后台程序对应的进程就是守护进程.系统中可以看到很多如DHCPD和HTTPD之类的进程, ...

  10. 【Linux】Linux 守护进程的启动方法

    转载:Linux 守护进程的启动方法 "守护进程"(daemon)就是一直在后台运行的进程(daemon). 本文介绍如何将一个 Web 应用,启动为守护进程. 一.问题的由来 W ...

最新文章

  1. 西安交通大学2019计算机复试方案,西安交通大学2019年招收硕士研究生复试体检通知...
  2. pythond的执行原理_python基础——继承实现的原理
  3. 【原】让H5页面适配移动设备全家 - 设计师篇 - PPT
  4. 程序员计算手机分辨率比例
  5. 计算机的键盘如何保养,知识每天涨一点:快捷键2 键盘键位知识 电脑小保养
  6. oracle函数trunc的使用
  7. ★ Linked List Cycle II -- LeetCode
  8. ExtJS6 Grid的日期编辑栏位处理
  9. System center 2012 R2 实战三、windows server 2012R2安装sharepoint2010及排错
  10. SCCM 2012 R2部署,SCCM配置(五)
  11. PyG快速安装(一键脚本,2021.7.14简单有效)
  12. 鸿蒙:这个备胎不太冷
  13. python-画3D图
  14. 论文解读:SegNeXt: Rethinking Convolutional Attention Design for Semantic Segmentation
  15. 基于JavaScript的HTML用户注册界面以及简单应用
  16. iis网站访问默认到html文件,mvc vs iis默认页面
  17. 重新发明轮子--麦肯锡方法
  18. rk3188--8.android camera驱动分析
  19. 论文笔记:Deep Learning [nature review by Lecun, Bengio, Hinton]
  20. 水波纹(water ripple)

热门文章

  1. oracle通过DBlink连接神通数据库方法教程
  2. CCPC 1010 YJJ's Salesman
  3. 计算机在课堂教学中的应用,计算机技术在课堂教学中的应用
  4. (课堂作业)spring-boot集成shiro的步骤及代码解析
  5. python的dev包怎么安装_python-dev如何安装 sudo apt-get install python-dev ?
  6. 一键修复wpcap.dll文件丢失或出错
  7. JS导入Excel实战
  8. Python制作微信自动回复机器人,打游戏时自动回复女朋友消息
  9. HyperLynx仿真(一)LineSim简单介绍
  10. 面包店利用拼团模式面包免费送-月销售30万 !