Apache: No space left on device: Couldn't create accept lock
服务器的Apache进程突然无法启动了,在错误日志中,有如下信息:
[Mon Feb 13 14:54:10 2017] [emerg] (28)No space left on device: Couldn't create accept lock (/var/logs/accept.lock.8173) (5)
[Mon Feb 13 14:55:02 2017] [emerg] (28)No space left on device: Couldn't create accept lock (/var/logs/accept.lock.8823) (5)
[Mon Feb 13 14:56:01 2017] [emerg] (28)No space left on device: Couldn't create accept lock (/var/logs/accept.lock.9113) (5)
[Mon Feb 13 14:57:01 2017] [emerg] (28)No space left on device: Couldn't create accept lock (/var/logs/accept.lock.9765) (5)
看了一下磁盘,空间并没有被占满,于是搜索了一下,找到了办法。
使用 ipcs -s
查看一下当前的系统信号量占用情况:
[root@phpruntime ~]# ipcs -s
------ Semaphore Arrays --------
key semid owner perms nsems
0x00000000 0 root 600 1
0x00000000 32769 root 600 1
0x00000000 688130 nobody 600 1
0x00000000 720899 nobody 600 1
0x00000000 753668 nobody 600 1
0x00000000 786437 nobody 600 1
0x00000000 819206 nobody 600 1
0x00000000 851975 nobody 600 1
0x7a03096d 587137032 root 600 13
0x00000000 1114121 nobody 600 1
0x00000000 1212426 nobody 600 1
0x00000000 1146891 nobody 600 1
0x00000000 1081356 nobody 600 1
0x00000000 1179661 nobody 600 1
0x00000000 1245198 nobody 600 1
0x00000000 586940431 nobody 600 1
0x00000000 586973200 nobody 600 1
...
其中nobody用户占用的信号量总数非常多,超过了100个,而我们的Apache也是运行在nobody下的,应该是信号量没有正确释放导致的,手动释放一下:
for i in `ipcs -s|awk '/nobody/ {print $2}'`; do (ipcrm -s $i); done
释放结束后,Apache便可以正常启动了。
具体到Semaphore,也就是信号量,有一个内核参数可以修改:
[root@yq138.phpruntime ~]# cat /proc/sys/kernel/sem
250 32000 32 128
上面的4个数字分别代表SEMMSL, SEMMNS, SEMOPM, SEMMNI这4个属性。
- SEMMSL:用于控制每个信号集的最大信号数量。(defines the maximum number of semaphores per semaphore set.)
- SEMMNS:用于控制整个 Linux 系统中信号(不是信号集)的最大数。(defines the total number of semaphores (not semaphore sets) for the entire Linux system)
- SEMOPM:用于控制每次semop系统调用最大可以调用的信号数量 。(defines the maximum number of semaphore operations that can be performed per semop(2) system call (semaphore call))
- SEMMNI:用于控制整个 Linux 系统中信号集的最大数量。(defines the maximum number of semaphore sets for the entire Linux system.)
可以通过调整这4个数值来解决上面问题,但是是治标不治本的,因为问题发生的原因不是信号量资源不够用,而是因为没有正确释放。这里顺便看了看httpd的代码:
在prefork模式中,需要创建一个fork的锁,调用的是apr_proc_mutex_create这个函数。
int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s)
{
int index;
int remaining_children_to_start;
apr_status_t rv;
ap_log_pid(pconf, ap_pid_fname);
first_server_limit = server_limit;
if (changed_limit_at_restart) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
"WARNING: Attempt to change ServerLimit "
"ignored during restart");
changed_limit_at_restart = 0;
}
/* Initialize cross-process accept lock */
ap_lock_fname = apr_psprintf(_pconf, "%s.%" APR_PID_T_FMT,
ap_server_root_relative(_pconf, ap_lock_fname),
ap_my_pid);
// 调用apr_proc_mutex_create创建accept_mutex
rv = apr_proc_mutex_create(&accept_mutex, ap_lock_fname,
ap_accept_lock_mech, _pconf);
if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s,
"Couldn't create accept lock (%s) (%d)",
ap_lock_fname, ap_accept_lock_mech);
mpm_state = AP_MPMQ_STOPPING;
return 1;
}
// 省略....
}
调用时,传入了一个参数 ap_accept_lock_mech
,这个参数可以通过配置文件的 AcceptMutex
这个配置项进行配置,默认如果不配置的话,会使用 APR_LOCK_DEFAULT
这个默认方式。
apr_proc_mutex_create
是apr库的一部分,apr库在不同平台有不同的实现,我们就看针对unix系统的实现。
static apr_status_t proc_mutex_create(apr_proc_mutex_t *new_mutex, apr_lockmech_e mech, const char *fname)
{
apr_status_t rv;
// 根据mech选择合适的实现
if ((rv = proc_mutex_choose_method(new_mutex, mech)) != APR_SUCCESS) {
return rv;
}
new_mutex->meth = new_mutex->inter_meth;
// 调用对应的实现
if ((rv = new_mutex->meth->create(new_mutex, fname)) != APR_SUCCESS) {
return rv;
}
return APR_SUCCESS;
}
// apr_proc_mutex_create 实现
APR_DECLARE(apr_status_t) apr_proc_mutex_create(apr_proc_mutex_t **mutex,
const char *fname,
apr_lockmech_e mech,
apr_pool_t *pool)
{
apr_proc_mutex_t *new_mutex;
apr_status_t rv;
new_mutex = apr_pcalloc(pool, sizeof(apr_proc_mutex_t));
new_mutex->pool = pool;
// 调用上面的proc_mutex_create
if ((rv = proc_mutex_create(new_mutex, mech, fname)) != APR_SUCCESS)
return rv;
*mutex = new_mutex;
return APR_SUCCESS;
}
在 proc_mutex_choose_method
选择对应实现的时候,会根据传入的mech
参数进行选择,当参数为APR_LOCK_DEFAULT
时:
static apr_status_t proc_mutex_choose_method(apr_proc_mutex_t *new_mutex, apr_lockmech_e mech)
{
// 如果指定了某个确定的选项,则直接使用对应的实现。
switch (mech) {
case APR_LOCK_FCNTL:
#if APR_HAS_FCNTL_SERIALIZE
new_mutex->inter_meth = &mutex_fcntl_methods;
#else
return APR_ENOTIMPL;
#endif
break;
case APR_LOCK_FLOCK:
#if APR_HAS_FLOCK_SERIALIZE
new_mutex->inter_meth = &mutex_flock_methods;
#else
return APR_ENOTIMPL;
#endif
break;
case APR_LOCK_SYSVSEM:
#if APR_HAS_SYSVSEM_SERIALIZE
new_mutex->inter_meth = &mutex_sysv_methods;
#else
return APR_ENOTIMPL;
#endif
break;
case APR_LOCK_POSIXSEM:
#if APR_HAS_POSIXSEM_SERIALIZE
new_mutex->inter_meth = &mutex_posixsem_methods;
#else
return APR_ENOTIMPL;
#endif
break;
case APR_LOCK_PROC_PTHREAD:
#if APR_HAS_PROC_PTHREAD_SERIALIZE
new_mutex->inter_meth = &mutex_proc_pthread_methods;
#else
return APR_ENOTIMPL;
#endif
break;
// 默认选择,和编译环境相关,当前环境中选择的是&mutex_sysv_methods。
case APR_LOCK_DEFAULT:
#if APR_USE_FLOCK_SERIALIZE
new_mutex->inter_meth = &mutex_flock_methods;
#elif APR_USE_SYSVSEM_SERIALIZE
new_mutex->inter_meth = &mutex_sysv_methods;
#elif APR_USE_FCNTL_SERIALIZE
new_mutex->inter_meth = &mutex_fcntl_methods;
#elif APR_USE_PROC_PTHREAD_SERIALIZE
new_mutex->inter_meth = &mutex_proc_pthread_methods;
#elif APR_USE_POSIXSEM_SERIALIZE
new_mutex->inter_meth = &mutex_posixsem_methods;
#else
return APR_ENOTIMPL;
#endif
break;
default:
return APR_ENOTIMPL;
}
return APR_SUCCESS;
}
系统选择了sysv的实现,所以create的具体实现是:
static apr_status_t proc_mutex_sysv_create(apr_proc_mutex_t *new_mutex,
const char *fname)
{
union semun ick;
apr_status_t rv;
new_mutex->interproc = apr_palloc(new_mutex->pool, sizeof(*new_mutex->interproc));
// 调用semget获得信号
new_mutex->interproc->filedes = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
if (new_mutex->interproc->filedes < 0) {
rv = errno;
proc_mutex_sysv_cleanup(new_mutex);
return rv;
}
ick.val = 1;
if (semctl(new_mutex->interproc->filedes, 0, SETVAL, ick) < 0) {
rv = errno;
proc_mutex_sysv_cleanup(new_mutex);
return rv;
}
new_mutex->curr_locked = 0;
// 注册清理函数
apr_pool_cleanup_register(new_mutex->pool,
(void *)new_mutex, apr_proc_mutex_cleanup,
apr_pool_cleanup_null);
return APR_SUCCESS;
}
所以实际的情况是,Apache在启动的时候申请了信号集,但是并没有正常的在退出的时候执行清理,导致了信号集的堆积,当超过了系统的上限,就会导致申请失败,Apache无法启动。
而我们的系统在Apache相关的扩展或者依赖有更新时,会使用非常暴力的 kill -9
强制让Apache退出的方式以便加快更新速度,由于是 kill -9
,程序直接就退出了,没有执行清理操作,
所以才会导致没释放的信号集越来越多,最终导致出现问题。
解决方法也比较简单,不使用 kill -9
的方式杀死Apache进程,让进程自然退出就好了。