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

news/2024/7/26 10:24:53/文章来源:https://blog.csdn.net/hunandede/article/details/137165056

初始化信号

使用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/Osigaddset(&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失败,程序流程 也继续往下走//首先我设置主进程标题---------beginsize_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 forngx_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/53255970sigsuspend(&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 forreturn;
}

将守护进程加进去

    //(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.luyixian.cn/news_show_1033681.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【SpringBoot整合系列】SpirngBoot整合EasyExcel

目录 背景需求发展 EasyExcel官网介绍优势常用注解 SpringBoot整合EaxyExcel1.引入依赖2.实体类定义实体类代码示例注解解释 3.自定义转换器转换器代码示例涉及的枚举类型 4.Excel工具类5.简单导出接口SQL 6.简单导入接口SQL 7.复杂的导出&#xff08;合并行、合并列&#xff0…

C++刷题篇——04找等值元素

一、题目 二、解题思路 1、分割后放进二维数组 2、使用map&#xff0c;key为数值&#xff0c;value为其坐标 3、遍历二维数组元素&#xff0c;再在map中找该元素对应的value值&#xff08;二维数组形式&#xff09;&#xff0c;倘若value.size为1&#xff0c;那直接返回-1&…

每天五分钟计算机视觉:使用神经网络完成人脸的特征点检测

本文重点 我们上一节课程中学习了如何利用神经网络对图片中的对象进行定位,也就是通过输出四个参数值bx、by、bℎ和bw给出图片中对象的边界框。 本节课程我们学习特征点的检测,神经网络可以通过输出图片中对象的特征点的(x,y)坐标来实现对目标特征的识别,我们看几个例子。…

uniapp-打包IOS的APP流程

打包前所需配置 在manifest文件内配置 1. APP图标 2. 启动界面 有三种启动界面配置 第一种是 HBuilderX 官方给的通用启动界面&#xff0c;页面单一&#xff0c;屏幕中间就一个圆框图标 第二种是自定义的启动图&#xff0c;无法通过AppStore的审核 第三种是自定义storyboard启动…

蚁剑流量分析

蚁剑流量分析 在靶机上面上传一个一句话木马&#xff0c;并使用蚁剑连接&#xff0c;进行抓包, 一句话木马内容 <?php eval($_POST[1]); defalut编码器 在使用蚁剑连接的时候使用default编码器 连接之后进行的操作行为是查看当前目录(/var/www/html)下的文件&#xff0…

MySQL-主从复制与读写分离

一、MySQL主从复制&#xff1a; 1、主从复制的作用&#xff1a; 主从复制&#xff1a;主设备通过二进制日志传输到从设备&#xff0c;从设备通过二进制日志和主同步数据。 作用&#xff1a;负载均衡读操作&#xff0c;备份(实时备份&#xff0c;不能替换手动的备份)&#xf…

(QT5142、OpenCV452、SeetaFace2 )facetest

服务端 .pro QT core gui network sql#window平台opencv&#xff0c;seetaface环境 win32{ LIBS C:\opencv452\x64\mingw\lib\libopencv* LIBS C:\SeetaFace\lib\libSeeta* INCLUDEPATH C:\opencv452\include INCLUDEPATH C:\opencv452\include\opencv2 INCLUDEPATH …

技术分享 | 如何写好测试用例?

对于软件测试工程师来说&#xff0c;设计测试用例和提交缺陷报告是最基本的职业技能。是非常重要的部分。一个好的测试用例能够指示测试人员如何对软件进行测试。在这篇文章中&#xff0c;我们将介绍测试用例设计常用的几种方法&#xff0c;以及如何编写高效的测试用例。 ## 一…

Nginx配置导致请求成环的问题

前期介绍 在一台主机上部署LAMP&#xff0c;之后使用Nginx实现反向代理&#xff0c;并且实现动静分离。 apache的访问端口为80&#xff0c;Nginx&#xff0c;访问端口为8001端口。 首先可以实现反向代理。 [rootlnmp-247 ~]# cat /usr/local/nginx/conf/nginx.conf worker_…

FPGA之组合逻辑与时序逻辑

数字逻辑电路根据逻辑功能的不同&#xff0c;可以分成两大类&#xff1a;组合逻辑电路和时序逻辑电路&#xff0c;这两种电路结构是FPGA编程常用到的&#xff0c;掌握这两种电路结构是学习FPGA的基本要求。 1.组合逻辑电路 组合逻辑电路概念&#xff1a;任意时刻的输出仅仅取决…

k8s笔记28--快速在ubuntu上基于二进制和源码安装containerd

k8s笔记28--快速在ubuntu上基于二进制和源码安装containerd 介绍containerd 安装方法二进制文件安装源码构建安装 注意事项说明 介绍 Containerd是一个工业标准的容器运行时&#xff0c;它强调简单、健壮和可移植性。它可作为Linux和Windows的守护进程&#xff0c;能管理主机系…

十四.PyEcharts基础学习

目录 1-PyEcharts介绍 优点&#xff1a; 安装: 官方文档&#xff1a; 2-PyEcharts快速入门 2.1 第一个图表绘制 2.2 链式调用 2.3 opeions配置项 2.4 渲染图片文件 2.5 使用主题 3-PyEcharts配置项 3.1 初始化配置项InitOpts InitOpts 3.2 全局配置项set_global_o…

Python爬虫逆向:揭秘网站反爬虫技术与应对策略

目录 前言 一、网站反爬虫技术概述 1. User-Agent检测 2. IP限制 3. 图像验证码 4. 动态渲染 5. 反爬虫算法 二、Python爬虫逆向的基本原理 三、应对网站反爬虫技术的代码示例 1. User-Agent检测&#xff1a; 2. IP代理&#xff1a; 3. 验证码识别&#xff1a; 4.…

如何将本地仓库放到远程仓库中

在我们仓库创建好之后&#xff0c;我们复制好ssh 接着我们需要使用git remote add<shortname><url>这个命令 shortname就是我们远程仓库的别名 接着使用git remote -v这个命令查看一下目前远程仓库的别名和地址 原本还有一个指令git branch -M main 指定分支的名…

vitess执行计划缓存 测试

打开执行计划器缓存&#xff1a; sysbench /usr/local/share/sysbench/oltp_write_only.lua --mysql-host127.0.0.1 --mysql-port15306 --mysql-userroot --mysql-password --mysql-dbcustomer --report-interval10 100s sysbench /usr/local/share/sysbench/oltp_read_only.l…

QML嵌套页面的实现学习记录

StackView是一个QML组件&#xff0c;用于管理和显示多个页面。它提供了向前和向后导航的功能&#xff0c;可以在堆栈中推入新页面&#xff0c;并在不需要时将页面弹出。 ApplicationWindow {id:rootvisible: truewidth: 340height: 480title: qsTr("Stack")// 抽屉:…

格雷希尔G10系列L150A和L200A气动快速连接器,在新能源汽车线束线缆剥线后的气密性测试密封方案

线束线缆在很多用电环境都有使用&#xff0c;比如说新能源汽车&#xff0c;从电池包放电开始&#xff0c;高低压、通讯都开始进行工作&#xff0c;线束在连接的地方需要具有较高的气密性和稳定性&#xff0c;才能保证车辆在不同环境下能够正常的运行。 线束在组装铜鼻子前需要剥…

vue+elementUI搭建动态表头的表格

前提&#xff1a;以下代码是vue2项目结合elementUi完成的 数据结构 后端传来的数据是两个list&#xff0c;一个表头的list&#xff0c;一个表格内容的list // 表头 headTableAtts: [{ columnLabel: 姓名, columnName: name },{ columnLabel: 年龄, columnName: age },{ colu…

基于Arduino IDE 野火ESP8266模块 文件系统LittleFS 的开发

一、文件系统LittleFS的介绍 LittleFS是一个为微控制器设计的轻量级、可靠且高性能的文件系统。它专为嵌入式设备打造&#xff0c;拥有占用空间小、对硬件要求低的特点&#xff0c;同时保证在断电情况下数据的完整性和稳定性。 1.设计与特点 LittleFS的设计旨在提供嵌入式系统所…

vue基础教程(6)——构建项目级登录页

同学们可以私信我加入学习群&#xff01; 正文开始 前言一、创建首页二、登录页代码讲解三、对应的vue知识点&#xff1a;四、附件-各文件代码总结 前言 前面我们已经把vue自带的页面删除&#xff0c;也搭建了最简单的router路由&#xff0c;下面就可以真正开发我们自己的项目…