Nginx: a little source code

Nginx被称为C++程序员必学的源码之一,我觉得名副其实,它的事件机制、内存管理、进程通信都可以说是顶级实践,非常值得学习。
Nginx源码比较多,本文只看几个重要的模块,更详细的内容请参考《深入理解nginx模块开发与架构》,这本书内容很全,本文只能是跟在后面做一点东施效颦的解读了。

Nginx的事件驱动架构

和传统服务器只将事件划分为连接建立和关闭事件不同,Nginx会将事件处理分成很多阶段进行:
image.png
划分方法如下:

  1. 将阻塞进程的方法按照相关的触发事件分解为两个阶段。第一个阶段用于调用非阻塞的方法处理事件,并且注册相应的fd到事件监听器中;第二个阶段用于处理fd的回调事件。
  2. 拆分阻塞事件。将一个大规模的阻塞事件通过网络事件或者定时器事件拆分成多个小事件。
  3. 将阻塞事件转换为定时器事件。将需要检查标识位的阻塞事件转换为定时检查的定时事件。
  4. 如果阻塞方法完全无法继续划分,则必须使用独立的进程执行这个阻塞方法

值得注意的是nginx没有使用多线程,所有的worker和master都是进程实现的。

进程同步

nginx在进程通信方面真的是教科书级别的案例,所有的进程通信方式都用了个遍。
因为一个进程要处理大量请求,所以nginx的处理方式是尽量不让进程休眠,所以自旋等待用的很多。
对于多线程而言,文件锁和信号量是不推荐使用的:文件锁很容易用错并且需要维护计数结构;信号量没有所有权(不等待信号量的线程也可以解锁信号量)。
nginx更多的也只是将其封装给多进程的ngx_shmtx_t互斥锁使用,实现了阻塞/非阻塞和先自旋再等待的功能而已。所以无论如何,文件锁和信号量能不用就不用。

共享内存

共享内存这里的操作有三种:使用mmap分配共享内存、以/dev/zero文件使用mmap映射共享内存、用shmget调用来分配共享内存。这三种先定了了哪个就用哪个。
从这里我们也可以看出这三种系统调用的区别

  • mmap匿名映射最简单也最推荐使用,但是有的posix系统不支持,mmap匿名映射几乎所有的unix系统都会保证返回填充0的页。但是注意这并不意味着malloc就会返回填充0的页了,因为mmap分配的页可能被重用。
  • 以/dev/zero文件使用mmap映射共享内存,这其实也是某些unix系统实现匿名映射的方式。
  • shmget支持最广泛,但是一般不推荐使用。
#if (NGX_HAVE_MAP_ANON)ngx_int_t
ngx_shm_alloc(ngx_shm_t *shm)
{shm->addr = (u_char *) mmap(NULL, shm->size,PROT_READ|PROT_WRITE,MAP_ANON|MAP_SHARED, -1, 0);if (shm->addr == MAP_FAILED) {ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,"mmap(MAP_ANON|MAP_SHARED, %uz) failed", shm->size);return NGX_ERROR;}return NGX_OK;
}void
ngx_shm_free(ngx_shm_t *shm)
{if (munmap((void *) shm->addr, shm->size) == -1) {ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,"munmap(%p, %uz) failed", shm->addr, shm->size);}
}#elif (NGX_HAVE_MAP_DEVZERO)ngx_int_t
ngx_shm_alloc(ngx_shm_t *shm)
{ngx_fd_t  fd;fd = open("/dev/zero", O_RDWR);if (fd == -1) {ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,"open(\"/dev/zero\") failed");return NGX_ERROR;}shm->addr = (u_char *) mmap(NULL, shm->size, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);if (shm->addr == MAP_FAILED) {ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,"mmap(/dev/zero, MAP_SHARED, %uz) failed", shm->size);}if (close(fd) == -1) {ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,"close(\"/dev/zero\") failed");}return (shm->addr == MAP_FAILED) ? NGX_ERROR : NGX_OK;
}void
ngx_shm_free(ngx_shm_t *shm)
{if (munmap((void *) shm->addr, shm->size) == -1) {ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,"munmap(%p, %uz) failed", shm->addr, shm->size);}
}#elif (NGX_HAVE_SYSVSHM)#include <sys/ipc.h>
#include <sys/shm.h>ngx_int_t
ngx_shm_alloc(ngx_shm_t *shm)
{int  id;id = shmget(IPC_PRIVATE, shm->size, (SHM_R|SHM_W|IPC_CREAT));if (id == -1) {ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,"shmget(%uz) failed", shm->size);return NGX_ERROR;}ngx_log_debug1(NGX_LOG_DEBUG_CORE, shm->log, 0, "shmget id: %d", id);shm->addr = shmat(id, NULL, 0);if (shm->addr == (void *) -1) {ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "shmat() failed");}if (shmctl(id, IPC_RMID, NULL) == -1) {ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,"shmctl(IPC_RMID) failed");}return (shm->addr == (void *) -1) ? NGX_ERROR : NGX_OK;
}void
ngx_shm_free(ngx_shm_t *shm)
{if (shmdt(shm->addr) == -1) {ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,"shmdt(%p) failed", shm->addr);}
}#endif

原子操作

对于x86上的原子操作,nginx使用总线锁来实现。
总线锁(见https://www.zhihu.com/column/p/24146167):
image.png

static ngx_inline ngx_atomic_uint_t
ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,ngx_atomic_uint_t set)
{u_char  res;__asm__ volatile (// 多核架构下锁总线NGX_SMP_LOCK// 将lock和old比较,如果相等则设置lock的值为set"    cmpxchgl  %3, %1;   "// 如果相等设置返回值res"    sete      %0;       ": "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "cc", "memory");return res;
}

自旋锁

自旋锁的使用场景在于不想陷入睡眠的nginx进程。如果进程陷入睡眠,那么下一次被唤醒的时间是不可控的(或者长到无法接受),因此,自旋等待是更好的方法。
这里的PAUSE是在许多架构体系中专门为了自旋锁而提供的指令,它会告诉CPU现在处于自旋锁等待状态,通常一些CPU会将自己置于节能状态,降低功耗。
实现如下:


void
ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin)
{
// lock为0表示锁被释放
// lock不为0表示锁被占用
// value表示希望当锁没有被任何进程持有时(lock为0),将lock设置为value则表示持有了锁
// spin表示在smp架构下,没有拿到锁时,等待其他处理器释放锁的时间
#if (NGX_HAVE_ATOMIC_OPS)ngx_uint_t  i, n;for ( ;; ) {if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {return;}if (ngx_ncpu > 1) {for (n = 1; n < spin; n <<= 1) {for (i = 0; i < n; i++) {// 调用PAUSE指令 PAUSE指令是REP NOP(重复空操作)指令的一种特殊形式,其操作码为0xF3 0x90ngx_cpu_pause();}if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {return;}}}ngx_sched_yield();}#else#if (NGX_THREADS)#error ngx_spinlock() or ngx_atomic_cmp_set() are not defined !#endif#endif

channel

nginx channel就是常见的socket通信,这里就不说了。

信号

通过sigaction,nginx会注册一组有回调的信号:


ngx_int_t
ngx_init_signals(ngx_log_t *log)
{ngx_signal_t      *sig;struct sigaction   sa;for (sig = signals; sig->signo != 0; sig++) {ngx_memzero(&sa, 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) {
#if (NGX_VALGRIND)ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,"sigaction(%s) failed, ignored", sig->signame);
#elsengx_log_error(NGX_LOG_EMERG, log, ngx_errno,"sigaction(%s) failed", sig->signame);return NGX_ERROR;
#endif}}return NGX_OK;
}

信号量

信号量的初始化调用int sem_init(sem_t *sem, int pshared, unsigned int value);
其中 pshared标明了这个信号量被进程还是线程使用。
nginx将信号量当作互斥锁使用。

信号量是如何实现互斥锁功能的呢?例如,最初的信号量sem值为0,调用sem_post方法 将会把sem值加1,这个操作不会有任何阻塞;调用sem_wait方法将会把信号量sem的值减1, 如果sem值已经小于或等于0了,则阻塞住当前进程(进程会进入睡眠状态),直到其他进程 将信号量sem的值改变为正数后,这时才能继续通过将sem减1而使得当前进程继续向下执 行。因此,sem_post方法可以实现解锁的功能,而sem_wait方法可以实现加锁的功能。

文件锁

文件锁可以锁住部分文件区域(fcntl)或者整个文件(flock)。
fcntl和flock的区别如下:

  1. flock(2)仅可对整个文件加锁;fcntl(2)可对从单一字节到整个文件范围内的任意区域加锁。
  2. 通过flock(2)设置文件锁不受文件的打开访问模式标志影响;通过fcntl(2)设置文件锁时,锁的类型必须与文件的打开访问模式标志一致,即F_RDLCK/F_WRLCK分别对应于O_RDONLY/O_WRONLY;先后设置读写锁时,访问模式标志应为O_RDWR。
  3. 同一进程可以通过再次调用flock(2)或fcntl(2)的方式,对同一文件的共享锁与独占锁之间进行相互转换;内核保证fcntl(2)的原子性,但不保证flock(2)的原子性。
  4. 通过flock(2)设置的文件锁与系统文件表项相关联,而非进程的文件描述符或文件(inode)自身;通过fcntl(2)设置的文件锁与进程的文件描述符表项与系统的inode表项相关联,而非系统文件表项。

对于文件锁,Nginx封装了3个方法:

  1. ngx_trylock_fd实现了不会阻塞进程、不会使得进程 进入睡眠状态的互斥锁;
  2. ngx_lock_fd提供的互斥锁在锁已经被其他进程拿到时将会导致当前 进程进入睡眠状态,直到顺利拿到这个锁后,当前进程才会被Linux内核重新调度,所以它 是阻塞操作;
  3. ngx_unlock_fd用于释放互斥锁。

ngx_shmtx_t互斥锁

为了防止使用操作系统的信号量和互斥锁时导致当前进程被饿死(单线程架构),所以nginx实现了ngx_shmtx_t,用于多进程之间的同步。
这里使用原子操作、信号量、文件锁配合的方式实现:

typedef struct {
#if (NGX_HAVE_ATOMIC_OPS)// 支持原子操作,使用原子变量ngx_atomic_t  *lock;
#if (NGX_HAVE_POSIX_SEM)// 支持原子操作和信号量// 如果支持信号量,那么在原子自旋等待失败后睡眠ngx_atomic_t  *wait;ngx_uint_t     semaphore;sem_t          sem;
#endif
#else// 不支持原子操作,使用文件锁ngx_fd_t       fd;u_char        *name;
#endifngx_uint_t     spin;
} ngx_shmtx_t;

如果ngx_shmtx_lock方法在运行一段 时间后,如果其他进程始终不放弃锁,那么当前进程将有可能强制性地获得到这把锁。我们看这里的代码。

文件锁
ngx_int_t
ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name)
{if (mtx->name) {if (ngx_strcmp(name, mtx->name) == 0) {mtx->name = name;return NGX_OK;}// name与 mtx->name不一致,销毁之前的文件锁ngx_shmtx_destroy(mtx);}mtx->fd = ngx_open_file(name, NGX_FILE_RDWR, NGX_FILE_CREATE_OR_OPEN,NGX_FILE_DEFAULT_ACCESS);if (mtx->fd == NGX_INVALID_FILE) {ngx_log_error(NGX_LOG_EMERG, ngx_cycle->log, ngx_errno,ngx_open_file_n " \"%s\" failed", name);return NGX_ERROR;}// 只需要inode信息,所以调用unlink删除文件if (ngx_delete_file(name) == NGX_FILE_ERROR) {ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,ngx_delete_file_n " \"%s\" failed", name);}mtx->name = name;return NGX_OK;
}void
ngx_shmtx_destroy(ngx_shmtx_t *mtx)
{if (ngx_close_file(mtx->fd) == NGX_FILE_ERROR) {ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,ngx_close_file_n " \"%s\" failed", mtx->name);}
}ngx_uint_t
ngx_shmtx_trylock(ngx_shmtx_t *mtx)
{ngx_err_t  err;err = ngx_trylock_fd(mtx->fd);if (err == 0) {return 1;}if (err == NGX_EAGAIN) {return 0;}#if __osf__ /* Tru64 UNIX */if (err == NGX_EACCES) {return 0;}#endifngx_log_abort(err, ngx_trylock_fd_n " %s failed", mtx->name);return 0;
}void
ngx_shmtx_lock(ngx_shmtx_t *mtx)
{ngx_err_t  err;err = ngx_lock_fd(mtx->fd);if (err == 0) {return;}ngx_log_abort(err, ngx_lock_fd_n " %s failed", mtx->name);
}void
ngx_shmtx_unlock(ngx_shmtx_t *mtx)
{ngx_err_t  err;err = ngx_unlock_fd(mtx->fd);if (err == 0) {return;}ngx_log_abort(err, ngx_unlock_fd_n " %s failed", mtx->name);
}ngx_uint_t
ngx_shmtx_force_unlock(ngx_shmtx_t *mtx, ngx_pid_t pid)
{return 0;
}#endif
信号量+原子变量

#if (NGX_HAVE_ATOMIC_OPS)static void ngx_shmtx_wakeup(ngx_shmtx_t *mtx);ngx_int_t
ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name)
{mtx->lock = &addr->lock;if (mtx->spin == (ngx_uint_t) -1) {// 不能使用信号量,返回成功return NGX_OK;}mtx->spin = 2048;#if (NGX_HAVE_POSIX_SEM)mtx->wait = &addr->wait;if (sem_init(&mtx->sem, 1, 0) == -1) {ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,"sem_init() failed");} else {mtx->semaphore = 1;}#endifreturn NGX_OK;
}void
ngx_shmtx_destroy(ngx_shmtx_t *mtx)
{
#if (NGX_HAVE_POSIX_SEM)if (mtx->semaphore) {if (sem_destroy(&mtx->sem) == -1) {ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,"sem_destroy() failed");}}#endif
}ngx_uint_t
ngx_shmtx_trylock(ngx_shmtx_t *mtx)
{// 这里操作并非原子,ngx_atomic_cmp_set会保证*mtx->lock == 0 return (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid));
}void
ngx_shmtx_lock(ngx_shmtx_t *mtx)
{ngx_uint_t         i, n;ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx lock");for ( ;; ) {if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {return;}// 多处理器时,等待才有意义if (ngx_ncpu > 1) {for (n = 1; n < mtx->spin; n <<= 1) {for (i = 0; i < n; i++) {ngx_cpu_pause();}if (*mtx->lock == 0&& ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)){return;}}}#if (NGX_HAVE_POSIX_SEM)if (mtx->semaphore) {(void) ngx_atomic_fetch_add(mtx->wait, 1);if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {(void) ngx_atomic_fetch_add(mtx->wait, -1);return;}ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,"shmtx wait %uA", *mtx->wait);while (sem_wait(&mtx->sem) == -1) {ngx_err_t  err;err = ngx_errno;if (err != NGX_EINTR) {ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err,"sem_wait() failed while waiting on shmtx");break;}}ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,"shmtx awoke");continue;}#endifngx_sched_yield();}
}void
ngx_shmtx_unlock(ngx_shmtx_t *mtx)
{if (mtx->spin != (ngx_uint_t) -1) {ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx unlock");}if (ngx_atomic_cmp_set(mtx->lock, ngx_pid, 0)) {ngx_shmtx_wakeup(mtx);}
}ngx_uint_t
ngx_shmtx_force_unlock(ngx_shmtx_t *mtx, ngx_pid_t pid)
{ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,"shmtx forced unlock");if (ngx_atomic_cmp_set(mtx->lock, pid, 0)) {ngx_shmtx_wakeup(mtx);return 1;}return 0;
}static void
ngx_shmtx_wakeup(ngx_shmtx_t *mtx)
{
#if (NGX_HAVE_POSIX_SEM)ngx_atomic_uint_t  wait;if (!mtx->semaphore) {return;}for ( ;; ) {wait = *mtx->wait;if ((ngx_atomic_int_t) wait <= 0) {return;}if (ngx_atomic_cmp_set(mtx->wait, wait, wait - 1)) {break;}}ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,"shmtx wake %uA", wait);if (sem_post(&mtx->sem) == -1) {ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,"sem_post() failed while wake shmtx");}#endif
}

内存池

nginx内存池的设计很特殊,它的生命周期跟随请求或者连接。
这是因为连接往往是短暂的,因此内存池带来了分配内存的便利,又不会因为生命周期过长带来内存泄漏。
它的构成如下:

typedef struct {// 可分配的位置u_char               *last;// 当前内存池的结尾u_char               *end;// 下一个小块内存池ngx_pool_t           *next;// 分配失败的次数,如果超过 4 ngx_pool_s 中的cunrrent指向nextngx_uint_t            failed;
} ngx_pool_data_t;struct ngx_pool_s {// 描述小块内存池ngx_pool_data_t       d;// 大内存池和小内存池的标准size_t                max;// 指向第一个小块内存池ngx_pool_t           *current;ngx_chain_t          *chain;// 大内存池链表ngx_pool_large_t     *large;ngx_pool_cleanup_t   *cleanup;ngx_log_t            *log;
};

流程如下:
image.png
这里很明显有一些内存浪费。
小块内存的大小默认(也是最大大小)是 (ngx_pagesize - 1) 也就是4095,主要是为了防止内存碎片过大。

master和worker的事件循环

worker

image.png

master

image.png


void
ngx_master_process_cycle(ngx_cycle_t *cycle)
{char              *title;u_char            *p;size_t             size;ngx_int_t          i;ngx_uint_t         sigio;sigset_t           set;struct itimerval   itv;ngx_uint_t         live;ngx_msec_t         delay;ngx_core_conf_t   *ccf;sigemptyset(&set);sigaddset(&set, SIGCHLD);sigaddset(&set, SIGALRM);sigaddset(&set, SIGIO);sigaddset(&set, SIGINT);sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,"sigprocmask() failed");}sigemptyset(&set);size = sizeof(master_process);for (i = 0; i < ngx_argc; i++) {size += ngx_strlen(ngx_argv[i]) + 1;}title = ngx_pnalloc(cycle->pool, size);if (title == NULL) {/* fatal */exit(2);}p = ngx_cpymem(title, master_process, sizeof(master_process) - 1);for (i = 0; i < ngx_argc; i++) {*p++ = ' ';p = ngx_cpystrn(p, (u_char *) ngx_argv[i], size);}ngx_setproctitle(title);ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);ngx_start_worker_processes(cycle, ccf->worker_processes,NGX_PROCESS_RESPAWN);ngx_start_cache_manager_processes(cycle, 0);ngx_new_binary = 0;delay = 0;sigio = 0;live = 1;for ( ;; ) {if (delay) {if (ngx_sigalrm) {sigio = 0;delay *= 2;ngx_sigalrm = 0;}ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,"termination cycle: %M", delay);itv.it_interval.tv_sec = 0;itv.it_interval.tv_usec = 0;itv.it_value.tv_sec = delay / 1000;itv.it_value.tv_usec = (delay % 1000 ) * 1000;if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,"setitimer() failed");}}ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "sigsuspend");sigsuspend(&set);ngx_time_update();ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,"wake up, sigio %i", sigio);if (ngx_reap) {// 需要监控所有子进程ngx_reap = 0;ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");// 管理子进程live = ngx_reap_children(cycle);}// 所有子进程都退出或者程序终止if (!live && (ngx_terminate || ngx_quit)) {ngx_master_process_exit(cycle);}// 程序终止但还有子进程没退出if (ngx_terminate) {if (delay == 0) {delay = 50;}if (sigio) {sigio--;continue;}sigio = ccf->worker_processes + 2 /* cache processes */;// 等待,然后发送kill信号if (delay > 1000) {ngx_signal_worker_processes(cycle, SIGKILL);} else {ngx_signal_worker_processes(cycle,ngx_signal_value(NGX_TERMINATE_SIGNAL));}continue;}if (ngx_quit) {ngx_signal_worker_processes(cycle,ngx_signal_value(NGX_SHUTDOWN_SIGNAL));ngx_close_listening_sockets(cycle);continue;}if (ngx_reconfigure) {// 重新初始化ngx_reconfigure = 0;if (ngx_new_binary) {ngx_start_worker_processes(cycle, ccf->worker_processes,NGX_PROCESS_RESPAWN);ngx_start_cache_manager_processes(cycle, 0);ngx_noaccepting = 0;continue;}ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");cycle = ngx_init_cycle(cycle);if (cycle == NULL) {cycle = (ngx_cycle_t *) ngx_cycle;continue;}ngx_cycle = cycle;ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,ngx_core_module);// 初始化新的workerngx_start_worker_processes(cycle, ccf->worker_processes,NGX_PROCESS_JUST_RESPAWN);ngx_start_cache_manager_processes(cycle, 1);/* allow new processes to start */// 挂起一段时间ngx_msleep(100);live = 1;// 结束旧workerngx_signal_worker_processes(cycle,ngx_signal_value(NGX_SHUTDOWN_SIGNAL));}if (ngx_restart) {ngx_restart = 0;// 从旧的worker初始化新的workerngx_start_worker_processes(cycle, ccf->worker_processes,NGX_PROCESS_RESPAWN);ngx_start_cache_manager_processes(cycle, 0);live = 1;}if (ngx_reopen) {ngx_reopen = 0;ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");ngx_reopen_files(cycle, ccf->user);ngx_signal_worker_processes(cycle,ngx_signal_value(NGX_REOPEN_SIGNAL));}if (ngx_change_binary) {ngx_change_binary = 0;ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "changing binary");ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);}if (ngx_noaccept) {ngx_noaccept = 0;ngx_noaccepting = 1;ngx_signal_worker_processes(cycle,ngx_signal_value(NGX_SHUTDOWN_SIGNAL));}}
}

解决过期事件

epoll可能在处理事件的过程中有事件过期了,不能简单的把事件的文件描述符置为-1, 因为中间可能有事件重新打开这个文件描述符,这就导致新的描述符处理了旧的事件。
nginx通过添加标识位instance解决了这个问题。

缓存时间

nginx通过维护一个时间变量表示当前时间,如果设置了timer_resolution,那么就会通过settime系统调用定时更新resolution标识位,在ngx_epoll_process_events时更新缓存时间。

事件循环的优先级

image.png

惊群问题

image.png

slab内存和nginx缓存

slab内存

nginx在共享内存之上引入了slab内存来作为多进程复杂数据结构共享的抽象。
它的内存管理如下:
image.png

  1. 把整块内存按4KB分为许多页,这样,如果每一页只存放一种固定大小的内存块, 由于一页上能够分配的内存块数量是很有限的,所以可以在页首上用bitmap方式,按二进制 位表示页上对应位置的内存块是否在使用中。只是遍历bitmap二进制位去寻找页上的空闲内存块,使得消耗的时间很有限,例如bitmap占用的内存空间小导致CPU缓存命中率高,可以 按32或64位这样的总线长度去寻找空闲位以减少访问次数等。
  2. 基于空间换时间的思想,slab内存分配器会把请求分配的内存大小简化为极为有限的 几种(简化的方法有很多,例如可以按照fibonacci方法进行),而Nginx slab是按2的倍数, 将内存块分为8、16、32、64…字节,当申请的字节数大于8小于等于16时,就会使用16字 节的内存块,以此类推。所以,一种页面若存放的内存块大小为N字节,那么,使用者申请 的内存在N/2+1与N之间时,都将使用这种页面。这样最多会造成一倍内存的浪费,但使得 页种类大大减少了,这会降低碎片的产生,提高内存的利用率
  3. 让有限的几种页面构成链表,且各链表按序保存在数组中,这样一来,用直接寻址 法就可以快速找到。在Nginx slab中,用slots数组来存放链表首页。例如,如果申请的内存大 小为30字节,那么根据最小的内存块为8字节,可以算出从小到大第3种内存块存放的内存大 小为32字节,符合要求,从slots数组中取第3个元素则可以寻找到32字节的页面。
  4. 这些页面中分为空闲页、半满页、全满页。为什么要这么划分呢?因为上述的同种 页面链表不应当包含太多元素,否则分配内存时遍历链表一样非常耗时。所以,全满页应当 脱离链表,分配内存时不应当再访问到它。空闲页应该是超然的,如果这个页面曾经为32字 节的内存块服务,在它又成为空闲页时,下次便可以为128字节的内存块服务。因此,所有 的空闲页会单独构成一个空闲页链表。这里slots数组采用散列表的思想,用快速的直接寻址 方式将半满页展现在使用者面前。
  5. 虽然大部分情况下申请分配的内存块是小于4KB的,但极个别可能会有一些大于 4KB的内存分配请求,拒绝它则太粗暴了。对于此,可以用遍历空闲页链表寻找地址连续的 空闲页来分配,例如需要分配11KB的内存时,则遍历到3个地址连续的空闲页即可。

通过共享内存分配的空间布局如下:
image.png

slab被划分成四种内存块:
image.png
在slab中分配内存的流程如下:
image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/669554.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

6.s081 学习实验记录(五)traps

文章目录 一、RISC-V assembly简介问题 二、Backtrace简介注意实验代码实验结果 三、Alarm简介注意实验代码实验结果 一、RISC-V assembly 简介 git checkout traps&#xff0c;切换到traps分支user/call.c 文件在我们输入 make fs.img 之后会被汇编为 call.asm 文件&#xf…

网络原理TCP/IP(5)

文章目录 IP协议IP协议报头地址管理网段划分特殊的IP地址路由选择以太网认识MAC地址对比理解MAC地址和IP地址DNS&#xff08;域名服务器&#xff09; IP协议 IP协议主要完成的工作是两方面&#xff1a; 地址管理&#xff0c;使用一套地址体系&#xff0c;来描述互联网上每个设…

day20网页基本标签

网页基本标签 标题标签段落标签换行标签水平线标签字体样式标签注释和特殊符号 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>网页基本标签</title> </head> <body> <!--标题…

CTFshow web(php特性 105-108)

web105 <?php /* # -*- coding: utf-8 -*- # Author: Firebasky # Date: 2020-09-16 11:25:09 # Last Modified by: h1xa # Last Modified time: 2020-09-28 22:34:07 */ highlight_file(__FILE__); include(flag.php); error_reporting(0); $error你还想要flag嘛&…

BVH动画绑骨蒙皮并在Unity上展示

文章目录 Blender绑定骨骼Blender蒙皮Blender中导入bvh文件将FBX导入Unity Blender绑定骨骼 先左上角红框进入model模式&#xff0c;选中要绑定的模型&#xff0c;然后进入Edit模式把骨骼和关节对齐。 &#xff08;选中骨骼&#xff0c;G移动&#xff0c;R旋转&#xff09; 为…

如何使用NimExec通过无文件命令执行实现横向移动

关于NimExec NimExec是一款功能强大的无文件远程命令执行工具&#xff0c;该工具专为红队研究人员设计&#xff0c;使用Nim语言开发&#xff0c;基于服务控制管理器远程协议&#xff08;MS-SCMR&#xff09;实现其功能&#xff0c;可以帮助广大研究人员在目标网络系统中实现横…

【算法与数据结构】583、72、LeetCode两个字符串的删除操作+编辑距离

文章目录 一、583、两个字符串的删除操作二、72、编辑距离三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、583、两个字符串的删除操作 思路分析&#xff1a;本题的思路和115、不同的子序列差不多&#xff0c;只是变成…

【Java EE初阶十】多线程进阶二(CAS等)

1. 关于CAS CAS: 全称Compare and swap&#xff0c;字面意思:”比较并交换“&#xff0c;且比较交换的是寄存器和内存&#xff1b; 一个 CAS 涉及到以下操作&#xff1a; 下面通过语法来进一步进项说明&#xff1a; 下面有一个内存M&#xff0c;和两个寄存器A,B; CAS(M,A,B)&am…

synchronized内部工作原理

作者简介&#xff1a; zoro-1&#xff0c;目前大二&#xff0c;正在学习Java&#xff0c;数据结构&#xff0c;javaee等 作者主页&#xff1a; zoro-1的主页 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f496; synchronized内部工作原理 syn…

矿泉水市场调研:预计2029年将达到83亿美元

矿泉水为国民饮水消费升级的方向&#xff0c;估算我国矿泉水市场规模约472亿元&#xff0c;成长性好。我们按照水种将包装水划分为矿泉水、纯净水、天然水及其他&#xff0c;根据多个第三方数据来源数据&#xff0c;我们估算矿泉水2017年瓶装与桶装合计市场销售规模约472亿元&a…

深入理解指针(3)

⽬录 1. 字符指针变量 2. 数组指针变量 3. ⼆维数组传参的本质 4. 函数指针变量 5. 函数指针数组 6. 转移表 1. 字符指针变量 在指针的类型中我们知道有⼀种指针类型为字符指针 char* ; ⼀般使⽤: int main() {char ch w;char *pc &ch;*pc w;return 0; } 还有…

属性“xxxx”在类型“ArrayConstructor”上不存在。是否需要更改目标库? 请尝试将 “lib” 编译器选项更改为“es2015”或更高版本。

使用vscode编写vue&#xff0c;在使用elementUI时&#xff0c;发现代码中的form报错如下&#xff1a; 属性“form”在类型“ArrayConstructor”上不存在。是否需要更改目标库? 请尝试将 “lib” 编译器选项更改为“es2015”或更高版本。 解决方法&#xff1a; 打开jsconfig.…

TOP100-二叉数

1.94. 二叉树的中序遍历 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入&#xf…

第8章 多线程

8.1 线程概述 人们在日常生活中&#xff0c;很多事情都是可以同时进行的。例如&#xff0c;一个人可以一边听音乐&#xff0c;一边打扫房间&#xff0c;可以一边吃饭&#xff0c;一边看电视。在使用计算机时&#xff0c;很多任务也是可以同时进行的。例如&#xff0c;可以一边…

对多面体数据进行裁剪和加盖的功能

开发环境&#xff1a; Windows 11 家庭中文版Microsoft Visual Studio Community 2019VTK-9.3.0.rc0vtk-example demo解决问题&#xff1a;对多面体数据进行裁剪和加盖的功能。 关键点&#xff1a; 创建了一个平面&#xff0c;并将其定位在输入多面体数据的中心位置&#xff…

详解洛谷P1352 没有上司的舞会(树形DP经典例题)

题目 没有上司的舞会 - 洛谷 思路 这是一道非常裸的树形DP&#xff0c;对于初学树形DP的OIer来说&#xff0c;是一道十分良心的题 我们可以设: dp[x][0]表示以x为根的子树,且x不参加舞会的最大快乐值 dp[x][1]表示以x为根的子树&#xff0c;且x参加了舞会的最大快乐值 则有 …

爬虫工作量由小到大的思维转变---<第四十四章 Scrapyd 用gerapy管理多台机器爬虫>

前言: 之前讲过关于如何在gerapy中部署本地爬虫, 爬虫工作量由小到大的思维转变---&#xff1c;第三十四章 Scrapy 的部署scrapydGerapy&#xff1e;_gerapy如何登录-CSDN博客 爬虫工作量由小到大的思维转变---&#xff1c;第三十五章 Scrapy 的scrapydGerapy 部署爬虫项目&…

SpringBoot:配置相关知识点

SpringBoot&#xff1a;多环境配置 配置知识点demo&#xff1a;点击查看LearnSpringBoot02 点击查看更多的SpringBoot教程 一、SpringBootApplication SpringBootApplication 来标注一个主程序类&#xff0c;说明这是一个Spring Boot应用&#xff0c;运行这个类的main方法来…

睿尔曼超轻量仿人机械臂-- RM65-B手眼标定使用说明

一、前言 机器人的视觉系统分为固定场景视觉系统和运动的「手-眼」视觉系统。相机与机器人手臂末端&#xff0c;构成手眼视觉系统。根据相机在机器人安装位置的不同&#xff0c;手眼视觉系统分为Eye-in-Hand系统&#xff08;眼在手上&#xff09;和Eye-to-Hand系统&#xff08…

Javascript | 打印菱形

Javascript打印菱形&#xff0c;在校大学生可以拿来糊弄作业&#xff08;笑&#xff09; var str ; for (var i 1; i < 9; i) {if (i < 5) {for (var k1 1; k1 < 5 - i; k1) {str ;}} else {for (var k2 1; k2 < i - 5; k2) {str ;}}if (i < 5) {for (…