在 Github 上找了个现成的 VS 项目,直接就编起来了。运行时,因为 conf 的缺失,解决了一下。
打断点的时候,遇到了些问题,比如一开始断点是有效的,但访问一个页面的时候,比如访问一个不存在的页面(404)时,应该是要执行一个代码的,但断点一直没打上。后来猜想是 Process 的问题,并且似乎停止调试的时候,并没有真正停止运行。
利用在 Linux 上的经验,找了个 command 来找 bind 了一个端口的程序:
Get-Process -Id (Get-NetTCPConnection -LocalPort 8080).OwningProcess
然后找了个 kill process 的命令:
taskkill /PID 13488 /F
然后再解决调试的问题,在 visual studio 里 attach 一下就好了。
入口
调试环境 OK 后,找了一个 use case 作为切入点,来看基本运行的堆栈。
use case 就是在代码里搜 404,然后打个断点。然后访问一个不存在的页面。
发现 worker thread 的入口是 ngx_worker_thread。
// ngx_process_cycle.c
static ngx_thread_value_t __stdcall ngx_worker_thread(void *data);
继续看之前,先看看 __stdcall 是啥意思。搜了下,没看懂。
跟踪了一下,这个是怎么起来的:
// nginx.c
int ngx_cdecl main(int argc, char *const *argv) {
// end of the function
ngx_master_process_cycle(cycle);
}
// ngx_process_cycle.c
void ngx_master_process_cycle(ngx_cycle_t *cycle) {
// some setup code
// use ngx_process to check if the current process is a worker or not.
if (ngx_process == NGX_PROCESS_WORKER) {
// this is the place to start ngx_worker_thread
ngx_worker_process_cycle(cycle, ngx_master_process_event_name);
// more setup
// create an event. It is used for communication between worker
ngx_master_process_event = CreateEvent(NULL, 1, 0, ngx_master_process_event_name);
// Setup other events
// Setup a mutex for cache manager
// It seems that it is the places to start the other processes.
if (ngx_start_worker_processes(cycle, NGX_PROCESS_RESPAWN) == 0) {
exit(2);
}
// handle processes logic
for ( ; ; ) {
// an inifinite loop
}
}
// mevn is the event name
static void ngx_worker_process_cycle(ngx_cycle_t *cycle, char *mevn) {
// setup some internal events
// Open Event with mevn
mev = OpenEvent(EVENT_MODIFY_STATE, 0, mevn);
SetEvent(mev);
// Get the mutex. The mutex is created by the main process. Use name to get for the current process.
ngx_cache_manager_mutex = OpenMutex(SYNCHRONIZE, 0, ngx_cache_manager_mutex_name);
}
ngx_cache_manager_event = CreateEvent(NULL, 1, 0, NULL);
// Create 3 threads for different purposes
if (ngx_create_thread(&wtid, ngx_worker_thread, NULL, log) != 0) goto failed;
if (ngx_create_thread(&cmtid, ngx_cache_manager_thread, NULL, log) != 0) goto failed;
if (ngx_create_thread(&cltid, ngx_cache_loader_thread, NULL, log) != 0) goto failed;
发现到处都是 ngx_time_update() 的调用。似乎是一个时间缓存的机制,见:https://blog.csdn.net/fengmo_q/article/details/6302354
后来找到一篇很好的源码分析博客。基本看完,IOCP 的部分跳过了,准备先看看 IOCP 的基础先。
https://www.twblogs.net/a/5b87b6962b71775d1cd8a35a
一些小知识点,Windows 里有个 API 叫 SetEnvironmentVariable. 这里的环境变量是针对 Process 的,而不是系统的。另外,由这个 Process 创建的子进程也会继承这个环境变量。一个子进程启动的时候,也是从 main 开始执行的(参数可以由主进程决定)。所以可以利用这个环境变量来判断当前进程是否子进程,执行不同的行为。
这样看下来,还是很有收获的。主要是关于在 C 里,怎么写多进程,多线程代码:
- 需要有全局变量,并且在程序启动时,做好初始化,程序结束前,都一直有效。这样对这个上下文的访问本身就是安全的。
- 全局变量 (nginx 里的 cycle) 是一个 struct。变量本身会被多个线程访问,但都是只读,真正操作的是变量里的 member。member 有两种保证安全的方式:
- 可以由主线程来写,但必须在读线程访问前初始化好,之后不再改变。跟全局变量本身的初始化类似。
- 只有一个线程读写。
- C 没有闭包,没有 event loop,没有 OOP,所以线程模型跟之前的认知是不一样的:
- 在之前的模型里,我们倾向于 class 优先于线程。意思是,尽量将线程这个概念做到透明,每个模块管理自己的线程,让自己大部分逻辑跑在自己管理的线程当中。在对外的接口实现中,做到线程安全。这样的设计会让接口变成异步。
- 在 Nginx 中,线程模型是比较严谨地设计的。在 worker 进程起来的时候,就把主要的线程初始化好,生命周期也是阻塞式控制的。线程的 main body 里会轮询在这个线程中操作的 manager 的函数。所以这种模型,更像是线程持有 class。