As for shared memory, different processes may allocate memory from it at the same time, so it needs a lock when allocate and free shared memory, nginx provides ngx_shmtx_t which is a combination of spin lock and semaphore, in that lock, first try spin lock, if can’t get the lock, use semaphore to block process.
/* This part always from shared memory, it is on shared memory */ typedefstruct { ngx_atomic_t lock; #if (NGX_HAVE_POSIX_SEM) ngx_atomic_t wait; #endif } ngx_shmtx_sh_t;
/* shmtx is the lock for shared memory * within atomic(spin lock) if not get the lock, process spins a while, then yield the CPU by way(semaphore) * blocked until resource is available, when wake up, check resource again to see if can get it */
typedefstruct { ngx_atomic_t *lock; /* save the process id who holds the lock(can access the shared memory) */ #if (NGX_HAVE_POSIX_SEM) ngx_atomic_t *wait; /* count how many process blocked on this lock */
/* you can see lock and wait are pointers, because they always point to ngx_shmtx_sh_t * which is allocated from the shared memory, hence every process can see lock and wait */ ngx_uint_t semaphore; /* flag that indicates semaphore is enabled or not */ sem_t sem; /* posix semaphore used by sem_init/sem_wait/sem_post */ #endif /* spin count if we can't get the lock * as it's unit hence spin = -1 means spin for ever if call ngx_shmtx_lock() * mostly used for accessing critical area within short period that another process only spin very shortly!!! * * Actually: nginx does not spin for ever even if spin = -1, as with spin = -1, nginx does NOT call ngx_shmtx_lock * but ngx_shmtx_trylock()!!! */ ngx_uint_t spin; } ngx_shmtx_t;
typedefstruct { ngx_shmtx_sh_t lock; /* spin lock used by ngx_shmtx_t */ ngx_shmtx_t mutex; } ngx_slab_pool_t;
void ngx_shmtx_lock(ngx_shmtx_t *mtx) { ngx_uint_t i, n; for ( ;; ) {
if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) { /* get the lock and save the process id */ return; }
if (ngx_ncpu > 1) { /* only need to spin if multicore * if there is only one core, no need to spin, spin here, the lock can't free at all which is hold by other process, spin waste time * because during the spin, you still can't the lock as the hold process is not running!!! */ for (n = 1; n < mtx->spin; n <<= 1) {
for (i = 0; i < n; i++) { /* use assemble directive to pause cpu */ ngx_cpu_pause(); }
/* spin means cpu pauses a while then check if i can get the lock * if NO, pause a while again, then check...repeated */ if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) { return; } } }
#if (NGX_HAVE_POSIX_SEM) /* goes here means after spin a while, we still can't get the lock * so block my self by semaphore */ if (mtx->semaphore) { /* increase process counter who are waiting on the semaphore */ (void) ngx_atomic_fetch_add(mtx->wait, 1);
/* before call sem_wait to block the process, here let's try one more time * to see if the lock is freed by another process */ if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) { (void) ngx_atomic_fetch_add(mtx->wait, -1); return; }
while (sem_wait(&mtx->sem) == -1) { /* block here */ 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; } }
/* as I'm awoke, decrease the count of waiting processes? * Yes! but not here as we already did this in wake process when call ngx_shmtx_wakeup */ ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx awoke");
/* after wake up, start next loop(try to get lock) */ continue; }
#endif /* it goes here only when semaphore is unavailable * yield just call usleep(1) after one spin loop is over to block the process */ ngx_sched_yield(); } }