(C++通讯架构学习笔记):信号,子进程实战,文件IO详谈

目录

信号功能实战

nginx中创建worker子进程

日志输出重要信息谈

write()函数思考

掉电导致write()的数据丢失破解法

信号功能实战

  • signal():注册信号处理程序的函数
  • 实际开发中,不用signal(),而要用sigaction()

【signal/ngx_signal.cxx】

#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>    
#include <errno.h>     

#include "ngx_macro.h"
#include "ngx_func.h" 

typedef struct 
{
    int           signo;       
    const  char   *signame;       
    void  (*handler)(int signo, siginfo_t *siginfo, void *ucontext); 
} ngx_signal_t;


static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext); 

ngx_signal_t  signals[] = {    
    { SIGHUP,    "SIGHUP",           ngx_signal_handler },        
    { SIGINT,    "SIGINT",           ngx_signal_handler },        
	{ SIGTERM,   "SIGTERM",          ngx_signal_handler },        
    { SIGCHLD,   "SIGCHLD",          ngx_signal_handler },        
    { SIGQUIT,   "SIGQUIT",          ngx_signal_handler },        
    { SIGIO,     "SIGIO",            ngx_signal_handler },        
    { SIGSYS,    "SIGSYS, SIG_IGN",  NULL               },        
                                                                     
    { 0,         NULL,               NULL               }         
};

int ngx_init_signals()
{
    ngx_signal_t      *sig;  
    struct sigaction   sa;   

    for (sig = signals; sig->signo != 0; sig++)  
    {        
        
        memset(&sa,0,sizeof(struct sigaction));

        if (sig->handler)  
        {
            sa.sa_sigaction = sig->handler;  
            sa.sa_flags = SA_SIGINFO;                                                       
        }
        else
        {
            sa.sa_handler = SIG_IGN; 
                                                                           
        } 

        sigemptyset(&sa.sa_mask);   
                                          
        if (sigaction(sig->signo, &sa, NULL) == -1)                                                             
        {   
            ngx_log_error_core(NGX_LOG_EMERG,errno,"sigaction(%s) failed",sig->signame); 
            return -1; 
        }	
        else
        {                        
            ngx_log_stderr(0,"sigaction(%s) succed!",sig->signame); 
        }
    } 
    return 0; 
}

static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext)
{
    printf("来信号了\n");
}
  • 主函数中调用

nginx中创建worker子进程

  • 官方nginx ,一个master进程,创建了多个worker子进程。
  • 查看nginx进程信息,一般显示为
master process ./nginx 
worker process

【演示】

  • 配置文件中设置

  • main函数中进行调用

【pro/ngx_process_cycle.cxx】

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>   
#include <errno.h>    
#include <unistd.h>

#include "ngx_func.h"
#include "ngx_macro.h"
#include "ngx_c_conf.h"

static void ngx_start_worker_processes(int threadnums);
static int ngx_spawn_process(int threadnums,const char *pprocname);
static void ngx_worker_process_cycle(int inum,const char *pprocname);
static void ngx_worker_process_init(int inum);

static u_char  master_process[] = "master process";

void ngx_master_process_cycle()
{    
    sigset_t set;        

    sigemptyset(&set);   

    sigaddset(&set, SIGCHLD);     
    sigaddset(&set, SIGALRM);     
    sigaddset(&set, SIGIO);       
    sigaddset(&set, SIGINT);      
    sigaddset(&set, SIGHUP);      
    sigaddset(&set, SIGUSR1);     
    sigaddset(&set, SIGUSR2);     
    sigaddset(&set, SIGWINCH);    
    sigaddset(&set, SIGTERM);     
    sigaddset(&set, SIGQUIT);     
    
    if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) 
    {        
        ngx_log_error_core(NGX_LOG_ALERT,errno,"ngx_master_process_cycle()中sigprocmask()失败!");
    }
   
    size_t size;
    int    i;
    size = sizeof(master_process);  
    size += g_argvneedmem;          
    if(size < 1000) 
    {
        char title[1000] = {0};
        strcpy(title,(const char *)master_process); 
        strcat(title," ");  
        for (i = 0; i < g_os_argc; i++)         
        {
            strcat(title,g_os_argv[i]);
        }
        ngx_setproctitle(title); 
    }
   
    CConfig *p_config = CConfig::GetInstance(); 
    int workprocess = p_config->GetIntDefault("WorkerProcesses",1); 
    ngx_start_worker_processes(workprocess);  
  
    sigemptyset(&set); 
    
    for ( ;; ) 
    {
        sigsuspend(&set);
        ngx_log_error_core(0,0,"haha--这是父进程,pid为%P",ngx_pid);
    }
   
    return;
}

static void ngx_start_worker_processes(int threadnums)
{
    int i;
    for (i = 0; i < threadnums; i++)  
    {
        ngx_spawn_process(i,"worker process");
    } 
    return;
}

static int ngx_spawn_process(int inum,const char *pprocname)
{
    pid_t  pid;

    pid = fork(); 
    switch (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;              
        ngx_pid = getpid();                
        ngx_worker_process_cycle(inum,pprocname);    
        break;

    default: 
        break;
    } 
    return pid;
}

static void ngx_worker_process_cycle(int inum,const char *pprocname) 
{    
    ngx_worker_process_init(inum);
    ngx_setproctitle(pprocname); 
 
    for(;;)
    {  
        ngx_log_error_core(0,0,"good--这是子进程,编号为%d,pid为%P!",inum,ngx_pid);
    } 
    return;
}

static void ngx_worker_process_init(int inum)
{
    sigset_t  set;      

    sigemptyset(&set);  
    if (sigprocmask(SIG_SETMASK, &set, NULL) == -1)  
    {
        ngx_log_error_core(NGX_LOG_ALERT,errno,"ngx_worker_process_init()中sigprocmask()失败!");
    }
    
    return;
}
  • 调用关系
(i)ngx_master_process_cycle()        //创建子进程等一系列动作
(i)    ngx_setproctitle()            //设置进程标题    
(i)    ngx_start_worker_processes()  //创建worker子进程   
(i)        for (i = 0; i < threadnums; i++)   //master进程在走这个循环,来创建若干个子进程
(i)            ngx_spawn_process(i,"worker process");
(i)                pid = fork(); 	//分叉,从原来的一个master进程(一个叉),分成两个叉(原有的master进程,以及一个新fork()出来的worker进程
(i)                //只有子进程这个分叉才会执行ngx_worker_process_cycle()
(i)                ngx_worker_process_cycle(inum,pprocname);  //子进程分叉
(i)                    ngx_worker_process_init();
(i)                        sigemptyset(&set);  
(i)                        sigprocmask(SIG_SETMASK, &set, NULL); //允许接收所有信号
(i)                        ngx_setproctitle(pprocname); //重新为子进程设置标题为worker process
(i)                        for ( ;; ) {}. ....          //子进程开始在这里不断的死循环
(i)    sigemptyset(&set); 
(i)    for ( ;; ) {}.                //父进程[master进程]会一直在这里循环
  • 查看进程信息

  • 补充知识
kill -9 -1344   ,用负号 -组id,可以杀死一组进程

【sigsuspend()函数(原子操作)】

  • a)根据给定的参数设置新的mask 并 阻塞当前进程【因为是个空集,所以不阻塞任何信号】
  • b)此时,一旦收到信号,便恢复原先的信号屏蔽【我们原来的mask在上边设置的,阻塞了多达10个信号,从而保证我下边的执行流程不会再次被其他信号截断】
  • c)调用该信号对应的信号处理函数
  • d)信号处理函数返回后,sigsuspend返回,使程序流程继续往下走
  • master是管理进程,只需要用信号驱动即可;worker进程是真正干活的,不用这个函数。

日志输出重要信息谈

【换行回车进一步示意】

  • \r:回车符,把打印输出信息的为止定位到本行开头
  • \n:换行符,把输出为止移动到下一行 
  • 一般把光标移动到下一行的开头,\r\n
    • a)比如windows下,每行结尾 \r\n
    • b)类Unix,每行结尾就只有\n 
    • c)Mac系统,每行结尾只有\r
  • 结论:统一用\n就行了

【printf()函数不加\n无法及时输出的解释】

  • printf末尾不加\n就无法及时的将信息显示到屏幕 ,这是因为 行缓存[ windows上一般没有,类Unix上才有 ]
  • 需要输出的数据不直接显示到终端,而是首先缓存到某个地方,当遇到行刷新表指或者该缓存已满的情况下,才会把缓存的数据显示到终端设备。
  • ANSI C中定义\n认为是行刷新标记,所以,printf函数没有带\n是不会自动刷新输出流,直至行缓存被填满才显示到屏幕。
  • 所以用printf的时候,注意末尾要用\n
  • 或者:fflush(stdout);

  • 或者使用setvbuf(stdout,NULL,_IONBF,0);

write()函数思考

  • 多个进程同时去写一个文件,比如5个进程同时往日志文件中写,会不会造成日志文件混乱?
    • 经过测试,多个进程同时写 一个日志文件,我们看到输出结果并不混乱,是有序的,我们的日志代码应对多进程往日志文件中写时没有问题。
  • 参考:《Unix环境高级编程 第三版》第三章:文件I/O里边的3.10-3.12,涉及到了文件共享、原子操作以及函数dup,dup2的讲解。
    • 第八章:进程控制里的8.3,涉及到了fork()函数。
  • 多个进程写一个文件,可能会出现数据覆盖,混乱等情况。
  • ngx_log.fd = open((const char *)plogname,O_WRONLY|O_APPEND|O_CREAT,0644)。 
    • O_APPEND这个标记能够保证多个进程操作同一个文件时不会相互覆盖。
  • 内核wirte()写入时是原子操作
  • 父进程fork()子进程是亲缘关系,是会共享文件表项
  • 关于write()写的安全问题,是否数据成功被写到磁盘
    • write()调用返回时,内核已经将应用程序缓冲区所提供的数据放到了内核缓冲区,但是无法保证数据已经写出到其预定的目的地【磁盘 】
    • 因为write()调用速度极快,可能没有时间完成该项目的工作【实际写磁盘】,所以这个wirte()调用不等价于数据在内核缓冲区和磁盘之间的数据交换。
    • 打开文件使用了 O_APPEND,多个进程写日志用write()来写。

掉电导致write()的数据丢失破解法

【直接I/O:直接访问物理磁盘】

  • O_DIRECT:绕过内核缓冲区,用posix_memalign

【open文件时用O_SYNC选项】

  • 同步选项【把数据直接同步到磁盘】,只针对write函数有效使每次write()操作等待物理I/O操作的完成
  • 具体说,就是将写入内核缓冲区的数据立即写入磁盘,将掉电等问题造成的损失减到最小。
  • 每次写磁盘数据,务必要大块大块写,一般都512-4k 4k的写;不要每次只写几个字节,否则会被抽死。

【缓存同步:尽量保证缓存数据和写道磁盘上的数据一致】

  • sync(void):将所有修改过的块缓冲区排入写队列;然后返回,并不等待实际写磁盘操作结束,数据是否写入磁盘并没有保证。
  • fsync(int fd):将fd对应的文件的块缓冲区立即写入磁盘,并等待实际写磁盘操作结束返回。
  • fdatasync(int fd):类似于fsync,但只影响文件的数据部分。而fsync不一样,fsync除数据外,还会同步更新文件属性。
     
  • 例:写入4M文件
    • 每次write(4k),1000次之后,一直到把这个write完整[假设整个文件4M]。
    • fsync(fd) ,1次fsync [ 多次write,每次write建议都4k,然后调用一次fsync(),这才是用fsync()的正确用法 ]

【标准IO库】

  • fwrite和write有啥区别:
    • fwrite()是标准I/O库一般在stdio.h文件。
    • write():系统调用。

  • 有一句话:所有系统调用都是原子性的

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值