Table of Contents
我们经常纠结这种代码:
some_small_struct *ptr=(some_small_struct *) malloc(sizeof(some_small_struct)); ptr->some_member= ...;
overcommit参考 man malloc:
http://linux.die.net/man/3/malloc By default, Linux follows an optimistic memory allocation strategy. This means that when malloc() returns non-NULL there is no guarantee that the memory really is available. This is a really bad bug. In case it turns out that the system is out of memory, one or more processes will be killed by the infamous OOM killer.
但是这时候malloc也是可能返回NULL的, 比如 address space is full.
我们的期望分成4级:
没内存时, 程序依然能够正常工作, 比如http服务器能拒绝掉部分请求而保证另一部分请求正常.
没内存时, 优雅退出, 防止: - 因为文件未关闭造成数据丢失 (如果我们写了文件, 未关闭, 操作系统会保证sync到磁盘么 -- 应该是不能保证) - socket未关闭导致对端长等待. - 比如是一个文件编辑器, 需要保存用户的工作先.
没内存时, 通过ASSERT显示core掉
很显然, 1是最好的情况, 是一个有尊严的程序员所期望的, 4是一定不能发生的. 2,3是需要权衡的.
3 是基线, 做到3可能保证出错时知道原因.
其它:
我们看看一些有名程序的做法
多数地方都做到1:
HP_INFO *heap_open_from_share(HP_SHARE *share, int mode) { HP_INFO *info; DBUG_ENTER("heap_open_from_share"); if (!(info= (HP_INFO*) my_malloc((uint) sizeof(HP_INFO) + 2 * share->max_key_length, MYF(MY_ZEROFILL)))) { DBUG_RETURN(0); }
有的地方只能做到4:
storage/innobase/handler/ha_innodb.cc:
field_lengths = (ulint*) my_malloc(sizeof(ulint) * n_fields, MYF(MY_FAE)); namebuf = (char*) my_malloc((uint) len + 2, MYF(0)); memcpy(namebuf, ptr, len); innobase_rename_table( ... norm_to = (char*) my_malloc(strlen(to) + 64, MYF(0)); norm_from = (char*) my_malloc(strlen(from) + 64, MYF(0));
storage/myisam/myisampack.c:
static my_bool open_isam_files(PACK_MRG_INFO *mrg, char **names, uint count) { mrg->file=(MI_INFO**) my_malloc(sizeof(MI_INFO*)*count,MYF(MY_FAE));
做到3:
void aofRewriteBufferAppend(unsigned char *s, unsigned long len) { block = zmalloc(sizeof(*block)); block->free = AOF_RW_BUF_BLOCK_SIZE;
不过在zmalloc里面做到了assert:
static void zmalloc_default_oom(size_t size) { fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n", size); fflush(stderr); abort(); } static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom; void *zmalloc(size_t size) { void *ptr = malloc(size+PREFIX_SIZE); if (!ptr) zmalloc_oom_handler(size);
但是在cli等不重要代码里面做到4:
static void cliInitHelp() { tmp.argv = malloc(sizeof(sds)); tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]);
alloc 里面打日志, 不是ASSERT (不过ngx_log_error里面应该还会malloc):
void * ngx_alloc(size_t size, ngx_log_t *log) { void *p; p = malloc(size); if (p == NULL) { ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "malloc(%uz) failed", size); } ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size); return p; }
使用时检查(90%的地方都有检查):
env = ngx_palloc(cycle->pool, (n + 1) * sizeof(char *)); if (env == NULL) { return NULL; } overflow_list = ngx_alloc(sizeof(struct pollfd) * rtscf->overflow_events, cycle->log); if (overflow_list == NULL) { return NGX_ERROR; }
也有不检查:
ngx_exec_new_binary(ngx_cycle_t *cycle, char *const *argv) var = ngx_alloc(sizeof(NGINX_VAR) + cycle->listening.nelts * (NGX_INT32_LEN + 1) + 2, cycle->log); p = ngx_cpymem(var, NGINX_VAR "=", sizeof(NGINX_VAR));
ASSERT
和nginx一样:
void * _nc_alloc(size_t size, const char *name, int line) { void *p; ASSERT(size != 0); p = malloc(size); if (p == NULL) { log_error("malloc(%zu) failed @ %s:%d", size, name, line); } else { log_verb("malloc(%zu) at %p @ %s:%d", size, p, name, line); } return p; }
nginx 和mysql, redis 做的差不多, 尽量检查.