阿里云oss做网站,深圳地址大全,域名服务器作用,信息发布网站模板下载初始化信号 使用neg_init_signals();
在nginx.cxx中的位置如下 //(3)一些必须事先准备好的资源#xff0c;先初始化ngx_log_init(); //日志初始化(创建/打开日志文件)#xff0c;这个需要配置项#xff0c;所以必须放配置文件载入的后边#xff1b;//(4)一些初…初始化信号 使用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 默认传递0sa_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 processstrcat(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 one0; //抄自官方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 的时候可能断电了那么写入到 内核缓存的数据可能没有真正的写到磁盘中。