文章目录

  • 1、信号处理函数
  • 2、创建worker线程
  • 3、sigsuspend函数说明
  • 4、write函数思考

1、信号处理函数

1、初始化信号的函数,用于注册信号处理程序
2、信号处理函数

初始化信号函数,遍历结构体数组,然后给结构体数组中的每个成员注册信号处理函数,并将信号集全部设置为空,表示可以接受所有信号

//一个信号有关的结构 ngx_signal_t
typedef struct
{int           signo;       //信号对应的数字编号 ,每个信号都有对应的#define ,大家已经学过了 const  char   *signame;    //信号对应的中文名字 ,比如SIGHUP //信号处理函数,这个函数由我们自己来提供,但是它的参数和返回值是固定的【操作系统就这样要求】,大家写的时候就先这么写,也不用思考这么多;void  (*handler)(int signo, siginfo_t *siginfo, void *ucontext); //函数指针,   siginfo_t:系统定义的结构
} ngx_signal_t;//声明一个信号处理函数
static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext); //static表示该函数只在当前文件内可见
static void ngx_process_get_status(void);                                      //获取子进程的结束状态,防止单独kill子进程时子进程变成僵尸进程//数组 ,定义本系统处理的各种信号,我们取一小部分nginx中的信号,并没有全部搬移到这里,日后若有需要根据具体情况再增加
//在实际商业代码中,你能想到的要处理的信号,都弄进来
ngx_signal_t  signals[] = {// signo      signame             handler{ SIGHUP,    "SIGHUP",           ngx_signal_handler },        //终端断开信号,对于守护进程常用于reload重载配置文件通知--标识1{ SIGINT,    "SIGINT",           ngx_signal_handler },        //标识2   { SIGTERM,   "SIGTERM",          ngx_signal_handler },        //标识15{ SIGCHLD,   "SIGCHLD",          ngx_signal_handler },        //子进程退出时,父进程会收到这个信号--标识17{ SIGQUIT,   "SIGQUIT",          ngx_signal_handler },        //标识3{ SIGIO,     "SIGIO",            ngx_signal_handler },        //指示一个异步I/O事件【通用异步I/O信号】{ SIGSYS,    "SIGSYS, SIG_IGN",  NULL               },        //我们想忽略这个信号,SIGSYS表示收到了一个无效系统调用,如果我们不忽略,进程会被操作系统杀死,--标识31//所以我们把handler设置为NULL,代表 我要求忽略这个信号,请求操作系统不要执行缺省的该信号处理动作(杀掉我)//...日后根据需要再继续增加{ 0,         NULL,               NULL               }         //信号对应的数字至少是1,所以可以用0作为一个特殊标记
};//初始化信号的函数,用于注册信号处理程序
//返回值:0成功  ,-1失败
int ngx_init_signals()
{ngx_signal_t      *sig;  //指向自定义结构数组的指针 struct sigaction   sa;   //sigaction:系统定义的跟信号有关的一个结构,我们后续调用系统的sigaction()函数时要用到这个同名的结构for (sig = signals; sig->signo != 0; sig++)  //将signo ==0作为一个标记,因为信号的编号都不为0;{        //我们注意,现在要把一堆信息往 变量sa对应的结构里弄 ......memset(&sa,0,sizeof(struct sigaction));if (sig->handler)  //如果信号处理函数不为空,这当然表示我要定义自己的信号处理函数{sa.sa_sigaction = sig->handler;  //sa_sigaction:指定信号处理程序(函数),注意sa_sigaction也是函数指针,是这个系统定义的结构sigaction中的一个成员(函数指针成员);sa.sa_flags = SA_SIGINFO;        //sa_flags:int型,指定信号的一些选项,设置了该标记(SA_SIGINFO),就表示信号附带的参数可以被传递到信号处理函数中//说白了就是你要想让sa.sa_sigaction指定的信号处理程序(函数)生效,你就把sa_flags设定为SA_SIGINFO}else{sa.sa_handler = SIG_IGN; //sa_handler:这个标记SIG_IGN给到sa_handler成员,表示忽略信号的处理程序,否则操作系统的缺省信号处理程序很可能把这个进程杀掉;//其实sa_handler和sa_sigaction都是一个函数指针用来表示信号处理程序。只不过这两个函数指针他们参数不一样, sa_sigaction带的参数多,信息量大,//而sa_handler带的参数少,信息量少;如果你想用sa_sigaction,那么你就需要把sa_flags设置为SA_SIGINFO;                                       } //end ifsigemptyset(&sa.sa_mask);   //比如咱们处理某个信号比如SIGUSR1信号时不希望收到SIGUSR2信号,那咱们就可以用诸如sigaddset(&sa.sa_mask,SIGUSR2);这样的语句针对信号为SIGUSR1时做处理,这个sigaddset三章五节讲过;//这里.sa_mask是个信号集(描述信号的集合),用于表示要阻塞的信号,sigemptyset()这个函数咱们在第三章第五节讲过:把信号集中的所有信号清0,本意就是不准备阻塞任何信号;//设置信号处理动作(信号处理函数),说白了这里就是让这个信号来了后调用我的处理程序,有个老的同类函数叫signal,不过signal这个函数被认为是不可靠信号语义,不建议使用,大家统一用sigactionif (sigaction(sig->signo, &sa, NULL) == -1) //参数1:要操作的信号//参数2:主要就是那个信号处理函数以及执行信号处理函数时候要屏蔽的信号等等内容//参数3:返回以往的对信号的处理方式【跟sigprocmask()函数边的第三个参数是的】,跟参数2同一个类型,我们这里不需要这个东西,所以直接设置为NULL;{   ngx_log_error_core(NGX_LOG_EMERG,errno,"sigaction(%s) failed",sig->signame); //显示到日志文件中去的 return -1; //有失败就直接返回}   else{            //ngx_log_error_core(NGX_LOG_EMERG,errno,"sigaction(%s) succed!",sig->signame);     //成功不用写日志 //ngx_log_stderr(0,"sigaction(%s) succed!",sig->signame); //直接往屏幕上打印看看 ,不需要时可以去掉}} //end forreturn 0; //成功
}

2、信号处理函数

siginfo:这个系统定义的结构中包含了信号产生原因的有关信息
static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext)
1、通过ngx_process全局量判断,是父进程还是子,然后进入对应的流程中处理
2、siginfo:这个系统定义的结构中包含了信号产生原因的有关信息进行日志打印
3、防止子进程退出,变成僵尸进程,需要处理SIGCHLD信号,需要调用waitpid函数,使用无阻塞调用,通过补获函数返回值进行打印日志

//信号处理函数
//siginfo:这个系统定义的结构中包含了信号产生原因的有关信息
static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext)
{    //printf("来信号了\n");    ngx_signal_t    *sig;    //自定义结构char            *action; //一个字符串,用于记录一个动作字符串以往日志文件中写for (sig = signals; sig->signo != 0; sig++) //遍历信号数组    {         //找到对应信号,即可处理if (sig->signo == signo) { break;}} //end foraction = (char *)"";  //目前还没有什么动作;if(ngx_process == NGX_PROCESS_MASTER)      //master进程,管理进程,处理的信号一般会比较多 {//master进程的往这里走switch (signo){case SIGCHLD:  //一般子进程退出会收到该信号ngx_reap = 1;  //标记子进程状态变化,日后master主进程的for(;;)循环中可能会用到这个变量【比如重新产生一个子进程】break;//.....其他信号处理以后待增加default:break;} //end switch}else if(ngx_process == NGX_PROCESS_WORKER) //worker进程,具体干活的进程,处理的信号相对比较少{//worker进程的往这里走//......以后再增加//....}else{//非master非worker进程,先啥也不干//do nothing} //end if(ngx_process == NGX_PROCESS_MASTER)//这里记录一些日志信息//siginfo这个if(siginfo && siginfo->si_pid)  //si_pid = sending process ID【发送该信号的进程id】{ngx_log_error_core(NGX_LOG_NOTICE,0,"signal %d (%s) received from %P%s", signo, sig->signame, siginfo->si_pid, action); }else{ngx_log_error_core(NGX_LOG_NOTICE,0,"signal %d (%s) received %s",signo, sig->signame, action);//没有发送该信号的进程id,所以不显示发送该信号的进程id}//.......其他需要扩展的将来再处理;//子进程状态有变化,通常是意外退出【既然官方是在这里处理,我们也学习官方在这里处理】if (signo == SIGCHLD) {ngx_process_get_status(); //获取子进程的结束状态} //end ifreturn;
}//获取子进程的结束状态,防止单独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;
}

2、创建worker线程

主进程流程函数:现将下列信号添加到信号集中屏蔽这些信号,获取主进程名字长度+argv参数长度加进来,然后设置进去,写一个日志,然后通过读取配置文件获取需要创建的子进程数量,然后调用创建子进程函数,创建完之后,回到主进程,将信号集设置为空,然后进入主进程循环

//变量声明
static u_char  master_process[] = "master process";//描述:创建worker子进程
void ngx_master_process_cycle()
{    sigset_t set;        //信号集sigemptyset(&set);   //清空信号集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"{strcat(title,g_os_argv[i]);}//end forngx_setproctitle(title); //设置标题ngx_log_error_core(NGX_LOG_NOTICE,0,"%s %P 【master进程】启动并开始运行......!",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);// sigsuspend(const sigset_t *mask))用于在接收到某个信号之前, 临时用mask替换进程的信号掩码, 并暂停进程执行,直到收到信号为止。// sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR。//sigsuspend是一个原子操作,包含4个步骤://a)根据给定的参数设置新的mask 并 阻塞当前进程【因为是个空集,所以不阻塞任何信号】//b)此时,一旦收到信号,便恢复原先的信号屏蔽【我们原来调用sigprocmask()的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;
}

循环创建子进程:fork函数如果创建失败就写日志,进入子进程流程,第一步先将所有信号集初始化为0,然后设置进去,然后设置进程名字,写一条日志表示创建成功

static void ngx_start_worker_processes(int threadnums)
{int i;for (i = 0; i < threadnums; i++)  //master进程在走这个循环,来创建若干个子进程{ngx_spawn_process(i,"worker process");} //end forreturn;
}static int ngx_spawn_process(int inum,const char *pprocname)
{pid_t  pid;pid = fork(); //fork()系统调用产生子进程switch (pid)  //pid判断父子进程,分支处理{  case -1: //产生子进程失败ngx_log_error_core(NGX_LOG_ALERT,errno,"ngx_spawn_process()fork()产生子进程num=%d,procname=\"%s\"失败!",inum,pprocname);return -1;case 0:  //子进程分支ngx_parent = ngx_pid;              //因为是子进程了,所有原来的pid变成了父pidngx_pid = getpid();                //重新获取pid,即本子进程的pidngx_worker_process_cycle(inum,pprocname);    //我希望所有worker子进程,在这个函数里不断循环着不出来,也就是说,子进程流程不往下边走;break;default: //这个应该是父进程分支,直接break;,流程往switch之后走            break;}//end switch//父进程分支会走到这里,子进程流程不往下边走-------------------------//若有需要,以后再扩展增加其他代码......return pid;
}static void ngx_worker_process_cycle(int inum,const char *pprocname)
{sigset_t  set;      //信号集sigemptyset(&set);  //清空信号集if (sigprocmask(SIG_SETMASK, &set, NULL) == -1)  //原来是屏蔽那10个信号【防止fork()期间收到信号导致混乱】,现在不再屏蔽任何信号【接收任何信号】{ngx_log_error_core(NGX_LOG_ALERT,errno,"ngx_worker_process_init()中sigprocmask()失败!");}ngx_setproctitle(pprocname); //设置标题   ngx_log_error_core(NGX_LOG_NOTICE,0,"%s %P 【worker进程】启动并开始运行......!",pprocname,ngx_pid); //设置标题时顺便记录下来进程名,进程id等信息到日志for(;;){}
}

3、sigsuspend函数说明

sigsuspend是一个原子操作,包含4个步骤:
a)根据给定的参数设置新的mask 并 阻塞当前进程【因为是个空集,所以不阻塞任何信号】
b)此时,一旦收到信号,便恢复原先的信号屏蔽【我们原来调用sigprocmask()的mask在上边设置的,阻塞了多达10个信号,从而保证我下边的执行流程不会再次被其他信号截断】
c)调用该信号对应的信号处理函数
d)信号处理函数返回后,sigsuspend返回,使程序流程继续往下走
阻塞在这里等待一个信号,此时进程是挂起的,不占用CPU时间,只有收到信号才会被唤醒,就会返回,他是把多个操作组合在一起,是一个原子操作,不可分割的,是同一时刻执行的,

4、write函数思考

读个进程同时写一个文件,比如5个进程同时往日志文件中写,会不会造成混乱?
通过查看,确定是有序的,因为父子进程是亲缘关系,是会共享表项,write是原子操作,不会被打断,如果不是父子进程关系进程,可能出现数据混乱

write函数返回时,用户缓存区已经将数据放到内核缓存区了,但是无法确定写入磁盘,因为write调用速度极快,可能没有时间完成该项工作,实际写磁盘。

断电导致write数据丢失解决:

1、open是加上O_DIRECT,表示跳过内核缓存区,直接访问磁盘,用posix_memalign来分配一块特殊的内存
2、open文件时,加上O_SYNC选项,同步选项,把数据同步到磁盘,只针对write函数,使每次write操作等待物理IO操作完成
3、缓存同步:尽量保证缓存数据和写到磁盘上的数据一致,
sync(void)将所有修改过的缓冲区排入写队列,然后返回,并不等待实际写磁盘操作结束,数据是否写入磁盘并没有保证。
fsync(int fd)将文件描述符,对应的文件缓存区立即写入磁盘,并等待磁盘写入结束。
fdatasync(int fd)类似fsync,只影响fsync但是只影响文件数据内容,而非fsync不一样,他会修改文件属性
例如:
write(4k) 写了1000次直到将所有写入
fsync最后调用一次

1.3 信号处理函数,创建worker进程相关推荐

  1. nginx源码分析--master和worker进程模型

    一.Nginx整体架构 正常执行中的nginx会有多个进程,最基本的有master process(监控进程,也叫做主进程)和woker process(工作进程),还可能有cache相关进程. 一个 ...

  2. php worker是什么意思,php-fpm中worker进程执行流程是什么

    看php-fpm源码worker进程创建部分,有一点没明白. php-fpm初始启动时,执行流程如下 main -> fpm_run -> fpm_children_create_init ...

  3. Linux系统【一】CPU+MMU+fork函数创建进程

    切板中的内容输出到文件### 进程相关概念 程序:编译好的二进制文件,在磁盘上,不占用系统资源(不包括磁盘).(剧本) 进程:占用系统资源,是程序的一次运行.(戏剧) 一个程序可以产生多个进程,一个进 ...

  4. 2信号处理之:信号产生原因,进程处理信号行为,信号集处理函数,PCB的信号集,sigprocmask()和sigpending(),信号捕捉设定,sigaction,C标准库信号处理函数,可重入函数,

     1信号产生原因 2.进程处理信号行为 manpage里信号3中处理方式: SIG_IGN SIG_DFL                                            默 ...

  5. Linux 如何创建进程函数与查看进程

    进程创建函数 进程就是pcb,意味着创建一个进程,就是创建一个pcb pid_t fork(void) –通过复制调用进程(父进程)创建一个新的进程(子进程) 创建一个新的pcb,然后从父进程pcb中 ...

  6. 理解进程、通过调用 fork 函数创建进程

    文章目录 1.理解进程 1.1 CPU核的个数与进程数 1.2 进程 ID 2.通过调用 fork 函数创建进程 1.理解进程 进程(Process),其定义如下:"占用内存空间的正在运行的 ...

  7. 函数简介篇——进程创建函数:system()

    说明:   本文章旨在总结备份.方便以后查询,由于是个人总结,如有不对,欢迎指正:另外,内容大部分来自网络.书籍.和各类手册,如若侵权请告知,马上删帖致歉.   QQ 群 号:513683159 [相 ...

  8. Linux 多线程应用中编写安全的信号处理函数

    2019独角兽企业重金招聘Python工程师标准>>> Linux 多线程应用中编写安全的信号处理函数 在 开发多线程应用时,开发人员一般都会考虑线程安全,会使用 pthread_m ...

  9. c++ sleep函数_Linux 多线程应用中如何编写安全的信号处理函数

    关于代码的可重入性,设计开发人员一般只考虑到线程安全,异步信号处理函数的安全却往往被忽略.本文首先介绍如何编写安全的异步信号处理函数:然后举例说明在多线程应用中如何构建模型让异步信号在指定的线程中以同 ...

最新文章

  1. 免费学python的网站-学数据分析Python必备的8个免费学习网站
  2. 使用DirectX截屏
  3. cam db num
  4. 使用PHP处理Kafka消息
  5. java spring getbean_spring依赖注入中获取JavaBean
  6. win7 网络打印机 未授予用户在此计算机上的请求登录类型,Win7提示未授予用户在此计算机上的请求登录类型...
  7. anaconda python36 tensorflow virtualenv
  8. Atitit uke签名规范 与防伪鉴别 attilax总结
  9. vue导出自定义的excel表格
  10. HTML5教程7-实战:调色板
  11. Hulu:视频广告系统中的算法实践
  12. 计算机专业考研 数学分析,中国农业大学2018年计算机考研816数学分析考试大纲...
  13. 2021-2027全球与中国BFSI中的聊天机器人市场现状及未来发展趋势
  14. 排球分组循环交叉编排_第一届“黄河金三角杯”全国学生排球邀请赛竞赛规程...
  15. 严蔚敏《数据结构》——导航
  16. npm run serve 报错:Error: error:0308010C:digital envelope routines::unsupported
  17. MySQL连接查询——外连接
  18. 如何在Excel中使用数据透视表计算百分比变化
  19. Apollo record文件格式
  20. 相遇3000亿美金之巅,阿里腾讯战力与血值几何?

热门文章

  1. 给深度学习计算机视觉方向求职者的建议
  2. 2022 新版本c++安装opencv库的简单操作教程
  3. TFT 屏幕的使用——ESP32学习笔记(番外)
  4. 恶意融资与上市公司的股权结构研究
  5. 优化算法(二)遗传算法及python实现
  6. FBI网站被黑致数据泄露?官方称这根本是个骗局
  7. Python 提取图片中的GPS信息
  8. JDK的下载,安装与配置(Win10安装方法)
  9. 音乐播放器的设计与实现 功能要求:设计一款基于HTML5音频技术的音乐播放器,要求实现音乐的播放、暂停、音量大小调节、上一首和下一首切换,运行效果如图所示
  10. 我的世界服务器背景音乐修改,我的世界怎么自定义背景音乐教程攻略