• 【Linux】守护进程


    任务管理

    进程组

    进程组是一个或多个进程的集合。每个进程除了有一个进程ID之外,还属于一个进程组。

    每个进程组有一个唯一的进程组ID。每个进程组都有一个组长进程。组长进程ID等于其进程组ID。

    作业

    SHELL终端通过前后台来控制作业和进程组。

    一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,Shell可以运行一个前台作业和任意多个后台作业,这称为作业控制。

    会话

    会话是一个或者多个进程组的集合

    • 一个会话可以有一个控制终端。建立与控制终端连接的会话首进程被称为控制进程。

    • 一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组。

    • 一个会话包括:一个控制进程,一个前台进程和多个后台进程。

    创建四个死循环程序

    image-20221203235748418

    下面将job1和job2放在一个进程组,job3和job4放在一个进程组

    image-20221204000416339

    • TTY:与进程关联的终端(tty)(完整的终端名称)
    • SID:会话组ID

    这些进程组的控制终端相同,它们同属于一个会话。

    任务管理操作

    直接运行某一可执行程序,例如./可执行程序,此时默认将程序放到前台运行,在前台运行的进程的状态后有一个+号,例如s+

    image-20221204001421344

    运行可执行程序时在后面加上&,可以指定将程序放到后台运行,例如./可执行程序 &

    image-20221204001551468

    [1] 7358
    [2] 7460
    
    • 1
    • 2

    每创建一个后台进程组(作业),就会提升一条[ num ] NUM的信息;这里[ num ]是作业的编号。NUM是该作业中某个进程的进程ID。

    相关操作

    job命令

    job命令,可以查看当前会话有那些工作

    image-20221204004045827

    fg命令

    使用fg命令(foreground),可以将某个作业提至前台运行,如果该作业正在后台运行则直接提至前台运行,如果该作业处于停止状态,则给进程组的每个进程发SIGCONT信号使它继续运行并提至前台。

    image-20221204004322628

    job1和job2的状态从S变为了S+。

    需要注意的是:前台进程只能有一个,当一个进程变成前台进程后,bash会自动变为后台进程,此时bash就无法进行命令行解释了。

    image-20221204004652440

    bash相关进程的+都没有了。

    bg命令

    使用bg命令,可以让某个停止的作业在后台继续运行(Running),本质就是给该作业的进程组的每个进程发SIGCONT信号。

    image-20221204005329747

    守护进程

    守护进程也称精灵进程(Daemon),是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。

    Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。

    其他进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但系统服务进程不受用户登录注销的影响,它们一直在运行着,这种进程有一个名称叫守护进程。

    Linux系统中存在大量的守护进程,下面使用ps ajx命令查看。

    image-20221204005744803

    • TPGID为终端的前台进程组。TPGID为-1的就是守护进程

    创建守护进程

    守护进程的创建步骤如下:

    1. 设置文件掩码为0。
    2. 忽略SIGCHLD信号。
    3. fork,终止父进程,保持子进程不是会话首进程,从而保证后续不会再和其他终端相关联。
    4. 将子进程设置为一个独立的会话
    5. 将标准输入、标准输出、标准错误重定向到/dev/null。

    为什么需要将子进程设置为一个单独的会话,而不是父进程?

    一般在一个多进程工作中,父进程即为进程组的组长。而组长管理整个进程组。

    一个守护进程的父进程是init进程,需要单独设为一个会话,不与终端交互;一旦一个进程组的组长被设置为一个守护进程,那么原进程组就没有进程组组长。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    void daemonize()
    {
        umask(0);
        int fd = 0;
        // 1. 忽略SIGPIPE
        signal(SIGPIPE, SIG_IGN);
        signal(SIGCHLD,SIG_IGN);	
        // 2. 更改进程的工作目录
        // chdir();
        // 3. 让自己不要成为进程组组长
        if (fork() > 0){
            exit(1);
        }
        
        // 4. 设置自己是一个独立的会话
        setsid();
        // 5. 重定向0,1,2
        if ((fd = open("/dev/null", O_RDWR)) != -1) // fd == 3
        {
            dup2(fd, STDIN_FILENO);
            dup2(fd, STDOUT_FILENO);
            dup2(fd, STDERR_FILENO);
            // 6. 关闭掉不需要的fd
            if(fd > STDERR_FILENO) close(fd);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    关于/dev/null

    /dev/null可以理解为Linux下的垃圾回收箱。我们可以通过把命令的输出重定向到 /dev/null 来丢弃脚本的全部输出。

    守护进程不能直接和用户交互,所以可以把守护进程的输出都重定到/dev/null文件中。

    daemon函数创建守护进程

    int daemon(int nochdir, int noclose);
    
    • 1

    参数说明:

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

    所谓的项目部署

    项目部署中很重要的一步就是将服务后台化,这里的后台化就是将进程**变成守护进程,**我们以一个TCP服务端为例

    源码地址:

    socket · 影中人/test - 码云 - 开源中国 (gitee.com)

    int main(int argc, char *argv[])
    {
        if (argc != 2 && argc != 3)
        {
            Usage(argv[0]);
            exit(3);
        }
        uint16_t port = atoi(argv[1]);
        std::string ip;
        if (argc == 3)
        {
            ip = argv[2];
        }
        //将进程守护进程化
        daemonize();
        //创建TCP服务器并运行
        Tcpserver svr(port, ip);
        svr.init();
        svr.start();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    image-20221204013602018

    使用daemon守护进程化

    int main(int argc, char *argv[])
    {
        if (argc != 2 && argc != 3)
        {
            Usage(argv[0]);
            exit(3);
        }
        uint16_t port = atoi(argv[1]);
        std::string ip;
        if (argc == 3)
        {
            ip = argv[2];
        }
        //守护进程化
        daemon(0,0);
        //创建服务器和运行
        Tcpserver svr(port, ip);
        svr.init();
        svr.start();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    image-20221204014153066

  • 相关阅读:
    【算法】一类支持向量机OC-SVM(1)
    SQL调优方案
    318. 最大单词长度乘积
    基于Docker部署GeoWebCache(离线地图)
    liteos连接器脚本隐藏的指针问题
    Java基础知识【HashMap和Hashtable区别与红黑树】
    探索 GO 项目依赖包管理与Go Module常规操作
    【重识云原生】第六章容器6.1.7.3节——cgroups数据结构剖析
    Json文件编辑器 ( json-editor-vue3 详细闭坑指南)
    word文档删除了还能找回来吗?这里分享可行方法
  • 原文地址:https://blog.csdn.net/qq_53893431/article/details/128168889