您的位置:首页 > 理论基础 > 数据结构算法

Nginx内存管理及数据结构浅析–共享内存的实现

2015-11-02 16:08 911 查看
nginx是多进程模型,在许多场景我们可能需要跨进程共享数据,考虑到这个可能性,nginx本身也提供了共享内存这方面的接口。

ngx_int_t ngx_shm_alloc(ngx_shm_t *shm);

这个是nginx中关于分配共享内存最底层的接口了,它实际上就是直接调用mmap进行共享内存的分配的。对于需要多次分配和释放共享内存的场景来说,直接用这个接口实际是无法忍受效率上的问题,或者有人想到可以直接分配一块大内存自己来进行分配释放的管理,但其实你不用自己做,nginx已经做好这部分的工作了,相关的接口是:

ngx_shm_zone_t *ngx_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name, size_t size, void *tag)

通过这个接口我们可以分配一大块内存,再借助ngx_slab_pool_t这个结构体来管理共享内存。

那nginx又是如何来组织共享内存的呢?

在ngx_cycle_s结构里有一个成员,即ngx_cycle_s->shared_memory,它的类型是ngx_list_t,用来登记所有分配的共享内存,它装的节点类型是ngx_shm_zone_t,一个节点对应一块分配的共享内存,以下是它的源码:

12345678910struct ngx_shm_zone_s {//指向自定义数据结构,一般用来初始化时使用,可能指向本地地址void *data;//真正的共享内存ngx_shm_t shm;//初始化函数ngx_shm_zone_init_pt init;//标记void *tag;};
我们再看看ngx_shared_memory_add这个函数的实现,它首先检查要分配的内存是否存在,存在则直接返回,否则创建一个新的再返回。
1. 两个相同名字的共享内存大小要一样。
2. 两个相同名字的共享内存tag要一样。
3. 如果当前共享内存已经存在,则不需要再次添加。会返回同一个共享内存
4. 如果此共享内存不存在,则添加一个新的ngx_shm_zone_t
添加完后会返回ngx_shm_zone_t,我们需要设置init函数和data数据

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

ngx_shm_zone_t
*

ngx_shared_memory_add(ngx_conf_t
*cf,
ngx_str_t
*name,
size_t
size,
void
*tag)

{

ngx_uint_t i;

ngx_shm_zone_t
*shm_zone;

ngx_list_part_t *part;

part
=
&cf->cycle->shared_memory.part;

shm_zone
=
part->elts;

//先查找所有已经存在的共享内存,看看要创建的共享内存是否存在于这里面,如果存在的话就直接返回,否则

//创建一个新的共享内存结构体再返回

for
(i
=
0;
/*
void */
;
i++)
{

if
(i
>=
part->nelts)
{

if
(part->next
==
NULL)
{

break;

}

part
=
part->next;

shm_zone
=
part->elts;

i
=
0;

}

if
(name->len
!=
shm_zone[i].shm.name.len)
{

continue;

}

if
(ngx_strncmp(name->data,
shm_zone[i].shm.name.data,
name->len)

!=
0)

{

continue;

}

if
(size
&&
size
!=
shm_zone[i].shm.size)
{

ngx_conf_log_error(NGX_LOG_EMERG,
cf,
0,

"the
size %uz of shared memory zone \"%V\" "

"conflicts
with already declared size %uz",

size,
&shm_zone[i].shm.name,
shm_zone[i].shm.size);

return
NULL;

}

if
(tag
!=
shm_zone[i].tag)
{

ngx_conf_log_error(NGX_LOG_EMERG,
cf,
0,

"the
shared memory zone \"%V\" is "

"already
declared for a different use",

&shm_zone[i].shm.name);

return
NULL;

}

//此共享内存已经存在,直接返回

return
&shm_zone[i];

}

//插入一个新的共享内存结构体结点

shm_zone
=
ngx_list_push(&cf->cycle->shared_memory);

if
(shm_zone
==
NULL)
{

return
NULL;

}

shm_zone->data
=
NULL;

shm_zone->shm.log
=
cf->cycle->log;

shm_zone->shm.size
=
size;

shm_zone->shm.name
=
*name;

shm_zone->shm.exists
=
0;

shm_zone->init
=
NULL;

shm_zone->tag
=
tag;

return
shm_zone;

}

看到这里,会不会有一个疑问,ngx_shared_memory_add也就是找到或者插入一个ngx_shm_zone_t节点而已啊,但是并没有真正分配共享内存的操作啊?我刚开始也是百思不得其解,直到看到ngx_init_cycle这个函数后,才豁然开朗。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596ngx_cycle_t *ngx_init_cycle(ngx_cycle_t *old_cycle){。。。 /* create shared memory */ part = &cycle->shared_memory.part; shm_zone = part->elts; for (i = 0; /* void */ ; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; shm_zone = part->elts; i = 0; } if (shm_zone[i].shm.size == 0) { ngx_log_error(NGX_LOG_EMERG, log, 0, "zero size shared memory zone \"%V\"", &shm_zone[i].shm.name); goto failed; }//注意这里,没有设置init函数的共享内存是被视为不使用的 if (shm_zone[i].init == NULL) { /* unused shared zone */ continue; } shm_zone[i].shm.log = cycle->log;//这里主要是考虑reload操作 opart = &old_cycle->shared_memory.part; oshm_zone = opart->elts; for (n = 0; /* void */ ; n++) { if (n >= opart->nelts) { if (opart->next == NULL) { break; } opart = opart->next; oshm_zone = opart->elts; n = 0; } if (shm_zone[i].shm.name.len != oshm_zone[n].shm.name.len) { continue; } if (ngx_strncmp(shm_zone[i].shm.name.data, oshm_zone[n].shm.name.data, shm_zone[i].shm.name.len) != 0) { continue; }//如果新的共享内存和旧的共享内存大小一样,就无需再次分配了,直接指过去即可 if (shm_zone[i].shm.size == oshm_zone[n].shm.size) { shm_zone[i].shm.addr = oshm_zone[n].shm.addr;//这里还需要重新初始化一下,因为可能会有对本地内存的操作 if (shm_zone[i].init(&shm_zone[i], oshm_zone[n].data) != NGX_OK) { goto failed; } goto shm_zone_found; }//如果老的共享内存和新的要分配的共享内存大小不一致,那就只能释放掉了 ngx_shm_free(&oshm_zone[n].shm); break; }//真正分配共享内存的调用 if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) { goto failed; }//初始化共享内存,为用slab来管理内存池做准备 if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) { goto failed; }//调用设置的init函数 if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) { goto failed; } shm_zone_found: continue; }。。。}
通过代码我们可以看到,对于每一个共享内存,都会先调用ngx_shm_alloc进行共享内存的分配,然后使用ngx_init_zone_pool对共享内存进行初始化,最后就调用我们设置的init函数了。
ngx_init_zone_pool主要的目的就是让我们可以通过slab来分配、释放内存(具体的用法这里暂不提及,以后有空可以专门写一篇文章来说)。下面我们来看看ngx_init_zone_pool的实现吧:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

static
ngx_int_t

ngx_init_zone_pool(ngx_cycle_t
*cycle,
ngx_shm_zone_t
*zn)

{

u_char
*file;

ngx_slab_pool_t *sp;

//通过slab来管理内存

sp
=
(ngx_slab_pool_t
*)
zn->shm.addr;

if
(zn->shm.exists)
{

if
(sp
==
sp->addr)
{

return
NGX_OK;

}

ngx_log_error(NGX_LOG_EMERG,
cycle->log,
0,

"shared
zone \"%V\" has no equal addresses: %p vs %p",

&zn->shm.name,
sp->addr,
sp);

return
NGX_ERROR;

}

//初始化slab分配器

sp->end
=
zn->shm.addr
+
zn->shm.size;

sp->min_shift
=
3;

sp->addr
=
zn->shm.addr;

#if
(NGX_HAVE_ATOMIC_OPS)

file
=
NULL;

#else

file
=
ngx_pnalloc(cycle->pool,
cycle->lock_file.len
+
zn->shm.name.len);

if
(file
==
NULL)
{

return
NGX_ERROR;

}

(void)
ngx_sprintf(file,
"%V%V%Z",
&cycle->lock_file,
&zn->shm.name);

#endif

//创建共享内存的锁

if
(ngx_shmtx_create(&sp->mutex,
(void
*)
&sp->lock,
file)
!=
NGX_OK)
{

return
NGX_ERROR;

}

ngx_slab_init(sp);

return
NGX_OK;

}

在调用这个接口进行初始化后,以后我们在这块共享内存上进行分配和释放都是通过slab来完成的了,例如有ngx_slab_alloc和

ngx_slab_free。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: