总体情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ngx_master_process_cycle()        //创建子进程等一系列动作
ngx_setproctitle() //设置进程标题
ngx_start_worker_processes() //创建worker子进程
for (i = 0; i < threadnums; i++) //master进程在走这个循环,来创建若干个子进程
ngx_spawn_process(i,"worker process");
pid = fork(); //分叉,从原来的一个master进程(一个叉),分成两个叉(原有的master进程,以及一个新fork()出来的worker进程
//只有子进程这个分叉才会执行ngx_worker_process_cycle()
ngx_worker_process_cycle(inum,pprocname); //子进程分叉
ngx_worker_process_init();
sigemptyset(&set);
sigprocmask(SIG_SETMASK, &set, NULL); //允许接收所有信号
Create(threadnums) //创建线程池中线程
ngx_epoll_init(); //初始化epoll往监听socket上增加监听事件
ngx_setproctitle(pprocname); //重新为子进程设置标题为worker process
for ( ;; ) {//子进程开始在这里不断的死循环
ngx_process_events_and_timers();//处理网络事件和定时器事件
}. ....
StopAll(); //考虑在这里停止线程池;
sigemptyset(&set);
for ( ;; ) {}. //父进程[master进程]会一直在这里循环

父进程

image-20220206201425399

master进程创建work进程的流程图如上所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
//描述:创建worker子进程
void ngx_master_process_cycle()
{
sigset_t set; //信号集

sigemptyset(&set); //清空信号集

//下列这些信号在执行本函数期间不希望收到【考虑到官方nginx中有这些信号,老师就都搬过来了】(保护不希望由信号中断的代码临界区)
//建议fork()子进程时学习这种写法,防止信号的干扰;
sigaddset(&set, SIGCHLD); //子进程状态改变
sigaddset(&set, SIGALRM); //定时器超时
sigaddset(&set, SIGIO); //异步I/O
sigaddset(&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失败,程序流程 也继续往下走

//首先我设置主进程标题---------begin
size_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 for
ngx_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/53255970

sigsuspend(&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;
}

一些细节:

读取配置信息是用的一个单例类

子进程

1

image-20220206202556632

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//描述:worker子进程的功能函数,每个woker子进程,就在这里循环着了(无限循环【处理网络事件和定时器事件以对外提供web服务】)
// 子进程分叉才会走到这里
//inum:进程编号【0开始】
static void ngx_worker_process_cycle(int inum,const char *pprocname)
{
//设置一下变量
ngx_process = NGX_PROCESS_WORKER; //设置进程的类型,是worker进程

//重新为子进程设置进程名,不要与父进程重复------
ngx_worker_process_init(inum);
ngx_setproctitle(pprocname); //设置标题
ngx_log_error_core(NGX_LOG_NOTICE,0,"%s %P 【worker进程】启动并开始运行......!",pprocname,ngx_pid); //设置标题时顺便记录下来进程名,进程id等信息到日志
for(;;)
{
ngx_process_events_and_timers(); //处理网络事件和定时器事件

} //end for(;;)

//如果从这个循环跳出来
g_threadpool.StopAll(); //考虑在这里停止线程池;
g_socket.Shutdown_subproc(); //socket需要释放的东西考虑释放;
return;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//描述:子进程创建时调用本函数进行一些初始化工作
static void ngx_worker_process_init(int inum)
{
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()失败!");
}

//线程池代码,率先创建,至少要比和socket相关的内容优先
CConfig *p_config = CConfig::GetInstance();
int tmpthreadnums = p_config->GetIntDefault("ProcMsgRecvWorkThreadCount",5); //处理接收到的消息的线程池中线程数量
if(g_threadpool.Create(tmpthreadnums) == false) //创建线程池中线程
{
//内存没释放,但是简单粗暴退出;
exit(-2);
}
sleep(1); //再休息1秒;

if(g_socket.Initialize_subproc() == false) //初始化子进程需要具备的一些多线程能力相关的信息
{
//内存没释放,但是简单粗暴退出;
exit(-2);
}

//如下这些代码参照官方nginx里的ngx_event_process_init()函数中的代码
g_socket.ngx_epoll_init(); //初始化epoll相关内容,同时 往监听socket上增加监听事件,从而开始让监听端口履行其职责
//g_socket.ngx_epoll_listenportstart();//往监听socket上增加监听事件,从而开始让监听端口履行其职责【如果不加这行,虽然端口能连上,但不会触发ngx_epoll_process_events()里边的epoll_wait()往下走】


//....将来再扩充代码
//....
return;
}