154 Linux C++ 通讯架构实战9 ,信号功能添加,信号使用sa_sigaction 回调,子进程添加,文件IO详谈,守护进程添加

news2024/11/25 22:49:32

初始化信号

使用neg_init_signals();

在nginx.cxx中的位置如下

    //(3)一些必须事先准备好的资源,先初始化
    ngx_log_init();             //日志初始化(创建/打开日志文件),这个需要配置项,所以必须放配置文件载入的后边;
    
    //(4)一些初始化函数,准备放这里        
    if(ngx_init_signals() != 0) //信号初始化
    {
        exitcode = 1;
        goto lblexit;
    }    

neg_init_signals()核心代码分析

该方法是在ngx_signal.cxx中实现

这里先要复习一下sigaction函数的调用

sigaction函数
修改信号处理动作(通常在Linux用其来注册一个信号的捕捉函数)

    int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);  成功:0;失败:-1,设置errno

参数:

act:传入参数,新的处理方式。

oldact:传出参数,旧的处理方式。 【signal.c】

struct sigaction结构体
    struct sigaction {

        void     (*sa_handler)(int);

        void     (*sa_sigaction)(int, siginfo_t *, void *);

        sigset_t   sa_mask;

        int       sa_flags;

        void     (*sa_restorer)(void);

    };

sa_restorer:该元素是过时的,不应该使用,POSIX.1标准将不指定该元素。(弃用)

sa_sigaction:当sa_flags被指定为SA_SIGINFO标志时,使用该信号处理程序。(很少使用)  

重点掌握:

① sa_handler:指定信号捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN表忽略 或 SIG_DFL表执行默认动作

② sa_mask: 调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。

设置这个sa_mask 的意义在于:假设我们处理 SIGINT信号的函数需要的时间比较长,这时候又来了一个 SIGINT信号,应该怎么办呢?是接着处理第一次收到的SIGINT信号的逻辑,还是又从开始处理这个新的SIGINT信号呢?因此设置了这个sa_mask,当处理信号SIGINT的函数阶段,让当前sa_mask信号屏蔽集替换原先PCB中的mask,那么理论上应该是让这个sa_mask = sa_mask | 当前信号,但是实际上看很多代码中并没有这么做,而是将sa_mask直接清0(设置方法为:sigemptyset(&act.sa_mask) ;),这是因为sa_flags这个参数的原因,因为当sa_flags设置为0的时候,默认就会屏蔽当前处理的信号。记住:sa_mask 默认传递0,sa_flags也是0的情况下,只会 屏蔽当前处理的信号;

如果我们想在当前函数处理的时候,屏蔽其他信号,则还是需要设置 sa_mask的值。一般设置方法为 

sigemptyset(&act.sa_mask) ;

sigaddset(&act.sa_mask,想要屏蔽的信号);

这样当想屏蔽的信号再发送过来的时候,不会对想要屏蔽的信号有反馈。



③ sa_flags:通常设置为0,表使用默认属性。这个默认属性是说:在处理该信号的过程中,如果有该信号再一次发过来,默认屏蔽,

信号捕捉特性
进程正常运行时,默认PCB中有一个信号屏蔽字,假定为☆,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不由☆来指定。而是用sa_mask来指定。调用完信号处理函数,再恢复为☆。
XXX信号捕捉函数执行期间,XXX信号自动被屏蔽。
阻塞的常规信号不支持排队,产生多次只记录一次。(后32个实时信号支持排队)

代码分析:

注意,这里老师用的是sa_sigaction回调函数,而不是前面学过的

        void     (*sa_handler)(int);  ---- >前面学过的

        void     (*sa_sigaction)(int, siginfo_t *, void *) ;  -------->老师用的是这个,那么这两个有啥区别吗?

区别是sa_sigaction能带更多的信息到 回调函数。

实际上:sa_sigaction 和 sa_handler 使用的是同一块内存空间,所以只能设置其中的一个。

sa_handler信号处理程序,不接受额外数据,可以为SIG_DFL的默认动作。

如果SA_SIGINFO是在sa_flags中指定的,说明了信号处理程序带有附加信息,那么sa_sigaction需要指定信号处理函数的信号。这个函数接收信号作为它的值第一个参数,一个指向siginfo_t的指针作为第二个参数,一个指向ucontext_t的指针(转换为void *)作为第三个参数。

代码执行流程如下:

sigsuspend() 函数:

//阻塞在这里,等待一个信号,此时进程是挂起的,不占用cpu时间,只有收到信号才会被唤醒(返回);//此时master进程完全靠信号驱动干活    

该函数内部做的事情如下:

假设代码是     sigemptyset(&set); //信号屏蔽字为空,表示不屏蔽任何信号

        //a)根据给定的参数设置新的mask 并 阻塞当前进程【因为是个空集,所以不阻塞任何信号】

        //b)此时,一旦收到信号,便恢复原先的信号屏蔽【我们原来的mask在上边设置的,阻塞了多达10个信号,从而保证我下边的执行流程不会再次被其他信号截断】

        //c)调用该信号对应的信号处理函数

        //d)信号处理函数返回后,sigsuspend返回,使程序流程继续往下走

参考书中的说明,也就是说,代码中&set相当于 下面的&mask,那么&prev是什么呢?是当前进程的信号掩码。

sigsuspend用于在接收到某个信号之前,临时用mask替换进程的信号掩码,并暂停进程执行,直到收到信号为止。

屏蔽信号字 sigprocmask的使用注意点。

参考这个老师的视频

18.信号-sigaction_哔哩哔哩_bilibili

子进程添加

//描述:创建worker子进程
void ngx_master_process_cycle()
{    
    sigset_t set;        //信号集

    sigemptyset(&set);   //清空信号集

    //下列这些信号在执行本函数期间不希望收到【考虑到官方nginx中有这些信号,老师就都搬过来了】(保护不希望由信号中断的代码临界区)
    //建议fork()子进程时学习这种写法,防止信号的干扰;
    sigaddset(&set, SIGCHLD);     //子进程状态改变
    sigaddset(&set, SIGALRM);     //定时器超时
    sigaddset(&set, SIGIO);       //异步I/O
    sigaddset(&set, SIGINT);      //终端中断符
    sigaddset(&set, SIGHUP);      //连接断开
    sigaddset(&set, SIGUSR1);     //用户定义信号
    sigaddset(&set, SIGUSR2);     //用户定义信号
    sigaddset(&set, SIGWINCH);    //终端窗口大小改变
    sigaddset(&set, SIGTERM);     //终止
    sigaddset(&set, SIGQUIT);     //终端退出符
    //.........可以根据开发的实际需要往其中添加其他要屏蔽的信号......
    
    //设置,此时无法接受的信号;阻塞期间,你发过来的上述信号,多个会被合并为一个,暂存着,等你放开信号屏蔽后才能收到这些信号。。。
    //sigprocmask()在第三章第五节详细讲解过
    if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) //第一个参数用了SIG_BLOCK表明设置 进程 新的信号屏蔽字 为 “当前信号屏蔽字 和 第二个参数指向的信号集的并集
    {        
        ngx_log_error_core(NGX_LOG_ALERT,errno,"ngx_master_process_cycle()中sigprocmask()失败!");
    }
    //即便sigprocmask失败,程序流程 也继续往下走

    //首先我设置主进程标题---------begin
    size_t size;
    int    i;
    size = sizeof(master_process);  //注意我这里用的是sizeof,所以字符串末尾的\0是被计算进来了的
    size += g_argvneedmem;          //argv参数长度加进来    
    if(size < 1000) //长度小于这个,我才设置标题
    {
        char title[1000] = {0};
        strcpy(title,(const char *)master_process); //"master process"
        strcat(title," ");  //跟一个空格分开一些,清晰    //"master process "
        for (i = 0; i < g_os_argc; i++)         //"master process ./nginx aa bb cc"
        {
            strcat(title,g_os_argv[i]);
        }//end for
        ngx_setproctitle(title); //设置标题
        ngx_log_error_core(NGX_LOG_NOTICE,0,"%s %P 启动并开始运行......!",title,ngx_pid); //设置标题时顺便记录下来进程名,进程id等信息到日志
    }    
    //首先我设置主进程标题---------end
        
    //从配置文件中读取要创建的worker进程数量
    CConfig *p_config = CConfig::GetInstance(); //单例类
    int workprocess = p_config->GetIntDefault("WorkerProcesses",1); //从配置文件中得到要创建的worker进程数量
    ngx_start_worker_processes(workprocess);  //这里要创建worker子进程

    //创建子进程后,父进程的执行流程会返回到这里,子进程不会走进来    
    sigemptyset(&set); //信号屏蔽字为空,表示不屏蔽任何信号
    
    for ( ;; ) 
    {

    //    usleep(100000);
        //ngx_log_error_core(0,0,"haha--这是父进程,pid为%P",ngx_pid);

        //a)根据给定的参数设置新的mask 并 阻塞当前进程【因为是个空集,所以不阻塞任何信号】
        //b)此时,一旦收到信号,便恢复原先的信号屏蔽【我们原来的mask在上边设置的,阻塞了多达10个信号,从而保证我下边的执行流程不会再次被其他信号截断】
        //c)调用该信号对应的信号处理函数
        //d)信号处理函数返回后,sigsuspend返回,使程序流程继续往下走
        //printf("for进来了!\n"); //发现,如果print不加\n,无法及时显示到屏幕上,是行缓存问题,以往没注意;可参考https://blog.csdn.net/qq_26093511/article/details/53255970

        sigsuspend(&set); //阻塞在这里,等待一个信号,此时进程是挂起的,不占用cpu时间,只有收到信号才会被唤醒(返回);
                         //此时master进程完全靠信号驱动干活    

//        printf("执行到sigsuspend()下边来了\n");
        
        //printf("master进程休息1秒\n");      
        //ngx_log_stderr(0,"haha--这是父进程,pid为%P",ngx_pid); 
        sleep(1); //休息1秒        
        //以后扩充.......

    }// end for(;;)
    return;
}

子进程死亡的信号处理流程

//获取子进程的结束状态,防止单独kill子进程时子进程变成僵尸进程
static void ngx_process_get_status(void)
{
    pid_t            pid;
    int              status;
    int              err;
    int              one=0; //抄自官方nginx,应该是标记信号正常处理过一次

    //当你杀死一个子进程时,父进程会收到这个SIGCHLD信号。
    for ( ;; ) 
    {
        //waitpid,有人也用wait,但老师要求大家掌握和使用waitpid即可;这个waitpid说白了获取子进程的终止状态,这样,子进程就不会成为僵尸进程了;
        //第一次waitpid返回一个> 0值,表示成功,后边显示 2019/01/14 21:43:38 [alert] 3375: pid = 3377 exited on signal 9【SIGKILL】
        //第二次再循环回来,再次调用waitpid会返回一个0,表示子进程还没结束,然后这里有return来退出;
        pid = waitpid(-1, &status, WNOHANG); //第一个参数为-1,表示等待任何子进程,
                                              //第二个参数:保存子进程的状态信息(大家如果想详细了解,可以百度一下)。
                                               //第三个参数:提供额外选项,WNOHANG表示不要阻塞,让这个waitpid()立即返回        

        if(pid == 0) //子进程没结束,会立即返回这个数字,但这里应该不是这个数字【因为一般是子进程退出时会执行到这个函数】
        {
            return;
        } //end if(pid == 0)
        //-------------------------------
        if(pid == -1)//这表示这个waitpid调用有错误,有错误也理解返回出去,我们管不了这么多
        {
            //这里处理代码抄自官方nginx,主要目的是打印一些日志。考虑到这些代码也许比较成熟,所以,就基本保持原样照抄吧;
            err = errno;
            if(err == EINTR)           //调用被某个信号中断
            {
                continue;
            }

            if(err == ECHILD  && one)  //没有子进程
            {
                return;
            }

            if (err == ECHILD)         //没有子进程
            {
                ngx_log_error_core(NGX_LOG_INFO,err,"waitpid() failed!");
                return;
            }
            ngx_log_error_core(NGX_LOG_ALERT,err,"waitpid() failed!");
            return;
        }  //end if(pid == -1)
        //-------------------------------
        //走到这里,表示  成功【返回进程id】 ,这里根据官方写法,打印一些日志来记录子进程的退出
        one = 1;  //标记waitpid()返回了正常的返回值
        if(WTERMSIG(status))  //获取使子进程终止的信号编号
        {
            ngx_log_error_core(NGX_LOG_ALERT,0,"pid = %P exited on signal %d!",pid,WTERMSIG(status)); //获取使子进程终止的信号编号
        }
        else
        {
            ngx_log_error_core(NGX_LOG_NOTICE,0,"pid = %P exited with code %d!",pid,WEXITSTATUS(status)); //WEXITSTATUS()获取子进程传递给exit或者_exit参数的低八位
        }
    } //end for
    return;
}

将守护进程加进去

    //(6)创建守护进程
    if(p_config->GetIntDefault("Daemon",0) == 1) //读配置文件,拿到配置文件中是否按守护进程方式启动的选项
    {
        //1:按守护进程方式运行
        int cdaemonresult = ngx_daemon();
        if(cdaemonresult == -1) //fork()失败
        {
            exitcode = 1;    //标记失败
            goto lblexit;
        }
        if(cdaemonresult == 1)
        {
            //这是原始的父进程
            freeresource();   //只有进程退出了才goto到 lblexit,用于提醒用户进程退出了
                              //而我现在这个情况属于正常fork()守护进程后的正常退出,不应该跑到lblexit()去执行,因为那里有一条打印语句标记整个进程的退出,这里不该限制该条打印语句;
            exitcode = 0;
            return exitcode;  //整个进程直接在这里退出
        }
        //走到这里,成功创建了守护进程并且这里已经是fork()出来的进程,现在这个进程做了master进程
        g_daemonized = 1;    //守护进程标记,标记是否启用了守护进程模式,0:未启用,1:启用了
    }

文件IO 详谈

write 函数的思考,我们在代码中同时去写日志文件,5个进程同时写,不会有混乱吗?

从测试结果来看,是没有混乱的。

这是因为父进程和子进程 是亲缘关系,会共享文件表项。因此不会发生问题。

如果我们write 的时候,可能断电了,那么写入到 内核缓存的数据,可能没有真正的写到磁盘中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1563458.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Appium无线自动化实用教程

文章目录 简介核心特点工作原理使用Appium进行自动化测试的一般步骤 环境设置安装和启动Appium Server使用Node.js和npm安装Appium Server&#xff1a;启动Appium Server:命令行启动使用Appium Desktop安装和启动Appium Server&#xff1a;使用代码启动appium server 编写测试代…

矩阵的组合

重构 将原始矩阵,重构形状 例如,下图中,将AB重构为一个新的矩阵,CD重构为一个新的矩阵,EF重构为一个新的矩阵,GH重构为一个新的矩阵。 MATLAB实现 clc; clearvars; close all;X1=1:64; X1=reshape(X1,8,8)

基于YOLOv8/YOLOv5的人脸表情识别系统(附完整资源+PySide6界面+训练代码)

本篇博客呈现了一种基于深度学习的人脸表情识别系统&#xff0c;并详细展示了其实现代码。系统采纳了领先的YOLOv8算法&#xff0c;并与YOLOv7、YOLOv6、YOLOv5等早期版本进行了比较&#xff0c;展示了其在图像、视频、实时视频流及批量文件中识别人脸表情的高准确度。文章深入…

PostgreSql 01 安装,创建用户,创建数据库,创建关系

01.安装postgresql postgreSql 是在linux上 去官网找按照的方式 选择好postgreSql 的版本&#xff0c;已经Linux的发行版本 拿到命令&#xff0c;直接扔到Linux中运行即可 # 下载PGSQL的rpm包 sudo yum install -y https://download.postgresql.org/pub/repos/yum/reporpm…

【核心复现】同时考虑考虑孤岛与重构的配电网故障恢复运行策略

目录 主要内容 内容详情 1.问题引出 2.可控负荷 3.网络拓扑约束 4.算法流程 结果一览 1.原文结果 2.程序运行结果 下载链接 主要内容 该模型复现文章《同时考虑考虑孤岛与重构的配电网故障恢复运行策略》&#xff0c;以IEEE33配电网为分析对象&#xff0c;…

Flutter应用在苹果商店上架前的准备工作与注意事项

引言 &#x1f680; Flutter作为一种跨平台的移动应用程序开发框架&#xff0c;为开发者提供了便利&#xff0c;使他们能够通过单一的代码库构建出高性能、高保真度的应用程序&#xff0c;同时支持Android和iOS两个平台。然而&#xff0c;完成Flutter应用程序的开发只是第一步…

智能电表如何在新能源发电领域发挥作用

智能电表具备自动计量计费和数据传输功能&#xff0c;这使其成为配用电管理的核心设备。在新能源发电领域&#xff0c;智能电表能够实时监测和计量新能源发电设备的发电量&#xff0c;确保电能的质量和安全。同时&#xff0c;通过数据传输功能&#xff0c;智能电表可以将新能源…

[云呐]固定资产盘点报告哪个部门写

固定资产盘点报告通常由哪个部门来完成和签发呢?总体来说,固定资产盘点报告主要由资产管理部门或核算部门具体组织拟定并与财务部门共同签发。个别重大报告还需要上级领导或委员会研讨通过。  资产管理部门:  资产管理部门是直接负责公司固定资产管理工作的核心部门,它主导…

网络钓鱼升级 Darcula如何窃取用户信息

近日&#xff0c;网络安全领域一种名为 “Darcula” 的网络钓鱼欺诈&#xff08;PhaaS&#xff09;悄然兴起。这种新型钓鱼方式不同于传统的手段&#xff0c;它巧妙地利用了谷歌信息和 iMessage 的富通信服务&#xff08;RCS&#xff09;&#xff0c;成为了网络犯罪分子的新手段…

分享一种快速移植OpenHarmony Linux内核的方法

移植概述 本文面向希望将 OpenHarmony 移植到三方芯片平台硬件的开发者&#xff0c;介绍一种借助三方芯片平台自带 Linux 内核的现有能力&#xff0c;快速移植 OpenHarmony 到三方芯片平台的方法。 移植到三方芯片平台的整体思路 内核态层和用户态层 为了更好的解释整个内核…

一、单层感知器(MCP人工神经元模型)

目录 前言 由来 介绍 算法原理 假设前提 一、前向传递过程&#xff08;算法训练好后的使用过程&#xff09; 补充&#xff1a;sign函数 二、训练方法&#xff08;求解权重w、偏转量b&#xff09; 写出损失函数 误分点选取 损失函数求导 梯度下降自确定w、b 算法…

救砖刷机------MTK芯片类线刷 救砖 常规线刷不开机 MTK分区表修复【二】

救砖刷机------MTK芯片类线刷 救砖 免授权的一些操作步骤与工具解析【一】 在上期的博文中解析了mtk刷机平台的一些基本选项说明和授权刷写的一些常识。这期将着重说明下mtk机型分区表损坏如何解决。 众所周知。高通分区表在高通线刷包中显而易见,在常规9008刷写中你会正常刷…

主流公链 - Fantom

Fantom&#xff1a;高性能的区块链协议 Fantom是一种开创性的区块链协议&#xff0c;旨在革新去中心化应用和数字金融领域 技术特点 共识机制 Lachesis协议&#xff1a;Fantom使用了Lachesis协议作为其共识算法。Lachesis是一种 异步拜占庭容错&#xff08;ABFT&#xff09;共…

基于springboot+vue+Mysql的火车订票管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

TikTok账号封号原因是什么?全面攻略

多人在使用TikTok的过程中都会遇到一些问题&#xff0c;比如为什么TikTok没有浏览量&#xff1f;事实上&#xff0c;这很可能是因为你的账号已被禁止。但为什么它会被封呢&#xff1f;你怎样才能解决它&#xff1f; 一、TikTok账号为什么被封&#xff1f; 1、什么是 TikTok 影…

掼蛋技巧秘籍

1、开始出牌不走牌一定是在理顺子。 2、起手出顺&#xff0c;后面一定还有两三把顺子。 3、上中场就出同花顺就一定还有大炸。 4、双贡顺子、对子是优势&#xff0c;绝对不能放。 5、单贡返牌&#xff0c;给对友单张小牌&#xff08;5以下&#xff09;&#xff0c;给对手大…

vue3中播放flv流视频,以及组件封装超全

实现以上功能的播放&#xff0c;只需要传入一个流的地址即可&#xff0c;当然组件也只有简单的实时播放功能 下面直接上组件 里面的flvjs通过npm i flv.js直接下载 <template><div class"player" style"position: relative;"><p style&…

站群CMS系统

站群CMS系统是一种用于批量建立和管理网站的内容管理系统&#xff0c;它能够帮助用户快速创建大量的网站&#xff0c;并实现对这些网站的集中管理。以下是三个在使用广泛的站群CMS系统&#xff0c;它们各具特色&#xff0c;可以满足不同用户的需求。 1. Z-BlogPHP Z-BlogPHP是…

云计算HCIE学员分享丨授人以鱼不如授人以渔

我还记得大一时&#xff0c;我对是否要报名云计算IE有着很多犹豫。当时&#xff0c;我对这个领域充满了好奇&#xff0c;但同时也担心自己是否有能力应对这样的挑战。正是在这个时候&#xff0c;我有幸认识了誉天培训机构的老师们。 我第一次接触誉天&#xff0c;就被那里的师资…

数据结构二叉树顺序存储——堆

堆 1.堆的概念2.堆的实现 &#xff08;建小堆为例&#xff09;2.1 初始化和销毁2.2 判空2.3 获得堆顶元素和堆的大小2.4 插入2.5 删除 3.堆的构建&#xff08;建小堆为例&#xff09; 1.堆的概念 将若干数据或元素按照完全二叉树的存储方式顺序存储到一个一维数组中&#xff0…