目录
信号功能实战
- 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():系统调用。
- 有一句话:所有系统调用都是原子性的