让你的进程24小时在Linux上运行

xshell原理

可能我们都在使用xshell时,都会遇到一些问题,就是你在xshell运行了你的服务器。可是你把xshell页面一关,你的服务器就自动关闭了,这是为什么呢??

本质是因为我们的xshell在登陆服务器时,会创建一个会话,而在这个会话中,只能允许一个进程在前台运行,多个进程在后台运行。而会话退出时会话的内容也会跟着退出,因为会话的内容都是以bash为父进程创建的。我们在命令行输入执行的进程,都是以bash为父进程创建的。

我们还要明白一个进程组的概念,我们可以分别执行 sleep 10000 | sleep 20000 | sleep 30000 &sleep 40000 | sleep 50000 | sleep 60000 & 命令,会建立6个睡眠的进程,而&的意思是在后台中运行。然后我们输入jobs,可以发现有2个进程组。

我们在查看进程,会发现有2个进程组,且进程组id和进程id相同的进程即为进程组组长。

而这个2个进程组组长都是由bash创建的。举个例子:

bash 就是大老板,它给了A一大笔钱,让A自己找几个人帮他完成一些任务。随后A就找了自己的兄弟一起给大老板bash干活。然后bash又找到B,给了B一大笔钱,让B自己组个队伍去帮他完成另一个任务。随后B也拉上了自己的兄弟们一起为大老板bash干活。

这里的大老板就是bash,A就是进程组组长,B是另一个进程组的组长。而它们都是为bash干活,而一旦bash退出,那么它们也自然会跟着退出。因为老板都挂了,进程组们肯定原地解散了。

而我们发现当命令行处于前台状态在运行进程时,就无法在使用命令行了。这本质的原因就是因为一个会话最多只允许一个进程在前台运行。

那么我们怎么让A和B不受会话的限制,即使会话退出了也不会影响它们运行呢?

很简单,我们让进程组组长不要替别人干活啦!自己出去单干,那么自己就是老大!让它们自己出去自成终端!自成进程组!自成会话!除非用户自己关闭或者操作系统挂了,那么它们就不在受到会话的影响。即使会话退出,它们依旧可以运行。这种进程我们把它称为守护进程 ,也可以叫做精灵进程 ,本质上还是一个孤儿进程

需要注意的是,想要自己出去单干,那么自己必须当老大。也就是说自己必须成为进程组的组长。

创建守护进程

上面我说过,守护进程的本质还是个孤儿进程,那么就意味着它必定会被操作系统领养。

实现守护进程我们可以分三个重要步骤,若干个小步骤。

1. 让调用的进程忽略掉异常信号

比如SIGPIPE信号,如果现在我要把服务器自身独立成为会话。那么要忽略掉这个信号,避免客户端搞事(在服务器读取时客户端关闭,服务器会收到SIGPIPE信号)。

2. 自成进程组

有一个setsid函数,可以让自己自成进程组,并自身处于新会话内,也就是说脱离了bash的掌控之中了!但是有个条件!那就是调用的进程本身不能是进程组! ,否则会调用失败!只要fork一下,那么fork出来的子进程就绝对部署进程组组长。这时候再把父进程立马退出,让子进程执行setsid。即可让子进程脱离bash的魔掌!

该函数调用成功返回进程 的pid,调用失败返回-1.

C 复制代码
#include <unistd.h>
pid_t setsid(void);

手册对函数的说明:

3 .关闭或重定向进程默认打开的文件

如果我的服务器有大量的输出内容,如果不关闭默认打开的文件的话。那么可能会占用系统的IO资源。而在/dev/null 路径的null文件。是一个"黑洞" ,无论你往里面读还是写。都不会发生什么,可以认为是个垃圾桶,但是在垃圾桶里面还能捡到东西,而你这里面你读不到消息,写进去的消息也没有反应,所以我愿称之为"黑洞"。 我们可以直接关闭标准输入输出错误,但不建议,建议还是重定向到 /dev/null中。

4. 进程的执行路径发生更改(非必须)

我们都知道进程所在的目录路径是会保存在cwd中的,所以程序里 open一个文件如果不用绝对路径,那么自动从当前路径开始找,这是因为当前路径其实已经被进程保存在cwd中了。如果想要修改也是可以的,但是这并不是必须的。

接下来我们用代码来实现一下:

cpp 复制代码
#pragma once
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define DEV "/dev/null"

void daemonSelf(const char* currpath = nullptr)
{
    //1.屏蔽信号
    signal(SIGPIPE,SIG_IGN); 

    //2.自成进程组
    if(fork() > 0 ) exit(0); //父进程秒退
    //子进程自成进程组
    pid_t ret = setsid(); 
    assert(ret != -1); 

    //3.关闭默认打开的文件
    int fd = open(DEV,O_RDWR); 
    if(fd < 0)
    {
        //文件打开失败,那么就关闭标准输入/输出/错误
        close(0);
        close(1);
        close(2);
    }else{
        //替换掉标准输入输出错误
        dup2(fd,0);
        dup2(fd,1);
        dup2(fd,2);
    }

    //4. 路径修改
    if(currpath) chdir(currpath);
    close(fd);
}

那么接下来我们写个程序验证一下。

cpp 复制代码
#include "deamon.hpp"
#include <iostream>
#include <string>
#include <unistd.h>

int main()
{
    daemonSelf(); //独立进程组
    FILE* testTxt = fopen("test.txt","a"); 
    int i = 0;
    while(1)
    {
        std::string msg = "hello" + std::to_string(i);
        fprintf(testTxt,msg.c_str());
        sleep(1);
    }

    return 0 ;
}

我们会发现进程一执行就结束了,可是我们明明写的是死循环啊。

这时候我们关掉vscode终端,打开另一个终端,查看test进程。

我们发现test进程已经脱离bash掌控了,也就意味着我们的程序可以24小时在服务器上运行了!

相关推荐
神奇小汤圆11 分钟前
Unsafe魔法类深度解析:Java底层操作的终极指南
后端
神奇小汤圆1 小时前
浅析二叉树、B树、B+树和MySQL索引底层原理
后端
文艺理科生1 小时前
Nginx 路径映射深度解析:从本地开发到生产交付的底层哲学
前端·后端·架构
千寻girling1 小时前
主管:”人家 Node 框架都用 Nest.js 了 , 你怎么还在用 Express ?“
前端·后端·面试
南极企鹅1 小时前
springBoot项目有几个端口
java·spring boot·后端
Luke君607971 小时前
Spring Flux方法总结
后端
define95271 小时前
高版本 MySQL 驱动的 DNS 陷阱
后端
忧郁的Mr.Li2 小时前
SpringBoot中实现多数据源配置
java·spring boot·后端
暮色妖娆丶2 小时前
SpringBoot 启动流程源码分析 ~ 它其实不复杂
spring boot·后端·spring
Coder_Boy_2 小时前
Deeplearning4j+ Spring Boot 电商用户复购预测案例中相关概念
java·人工智能·spring boot·后端·spring