Colaghost's Blog

自己的世界.

Nginx内存管理及数据结构浅析–内存池

| Comments

基本上所有的高性能服务器都会涉及到内存池这一块,nginx也不例外。nginx的内存池实现相对比较简洁精巧,看起来比较容易理解。

以下是nginx内存池的示意图,这是根据自己的理解画的,有什么理解错误的地方欢迎大家拍砖。

nginx的内存池主要涉及它的创建、小内存分配、大内存分配和资源清理。

一、内存池的创建

这里主要涉及到两个数据结构,分别是ngx_pool_s和ngx_pool_data_t。ngx_pool_s维护了内存池的的头部信息,而ngx_pool_data_t维护了内存池的数据部分的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//该结构用来维护内存池的数据部分,供用户分配内存使用
typedef struct {
  u_char               *last;//当前内存分配结束的位置
  u_char               *end;//内存池结束位置
  ngx_pool_t           *next;//指向下一个内存池
  ngx_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;//分配大内存使用,即请求分配的内存大小超过max
  ngx_pool_cleanup_t   *cleanup;//用来指向内存池释放时同时释放的资源
  ngx_log_t            *log;
};

有了上面两个结构体,实际上已经可以分配一个内存池了,创建一个内存池的入口函数是ngx_pool_t ngx_create_pool(size_t size, ngx_log_t log),切记size需要大于sizeof(ngx_pool_s),大于部分为此内存池供分配的内存。返回的ngx_pool_t指针指向整个内存池链表的头结点,头结点会维护整个内存池链接的头部信息,下次如果往内存池链表里面添加ngx_pool_s结点时只会维护顶部的ngx_pool_data_t部分的信息,即只维护此内存池结点的数据部分的信息。 关于整个内存池链接的结构可以参考下上图,pool指向内存池的头部结点,红色部分为可分配用户使用的内存块。由上图可以看出来,pool->d.last指向从内存池分配新内存的起始地址,pool->d.end指向这块内存池的结束位置,所有分配内存的地址都不能超过pool->d.end。 当进行内存分配操作时,首先判断要分配的size大小是否超过max,是的话就直接跟系统分配一块大内存,跟malloc一样,挂在pool->large下;否则就直接在pool->current指向的内存池里分配,并相应地移动pool->current->d.last的位置。

二、分配小内存(size<max)

小内存分配涉及到的接口函数主要有:

1
2
3
4
void *ngx_palloc(ngx_pool_t *pool, size_t size);
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);

头三个函数都是进行内存分配的,参数为要从中分配内存的内存池链表的头结点和要分配的内存大小。ngx_palloc和ngx_pnalloc都是从内存池中分配内存,区别就是ngx_palloc取得的内存是对齐的,而ngx_pnalloc则没有;ngx_pcalloc是调用ngx_palloc分配内存的,并将分配的内存都置0.ngx_pmemalign主要用来对指针进行对齐,但是nginx似乎没有使用这个,而是用到了另外定义的一个宏,下面会谈到。 这里由于篇幅问题只进行ngx_palloc函数的分析,其它两个大同小异。

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
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
     u_char      *m;
       ngx_pool_t  *p;
                  
       if (size <= pool->max) {
                              
           p = pool->current;
                                              
           do {
               m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);
                                                                              
               if ((size_t) (p->d.end - m) >= size) {
                               p->d.last = m + size;
                              
                               return m;

                       }
                                                                                          
                       p = p->d.next;
               } while (p);
                                                              
               return ngx_palloc_block(pool, size);
       }
                          
       return ngx_palloc_large(pool, size);
}

这里分配的是小内存,进行的是size<=pool->max部分的逻辑。我们可以看到nginx会从pool->current指向的内存池结点开始遍历,首先用ngx_align_ptr这个宏对last指针进行内存对齐,再判断当前内存池结点的数据部分剩余的内存是否够分配,如果够的话则移动last指针size大小的值,然后返回分配的内存的起始地址;否则移动到下一个内存池结点再进行同样的判断。 这里有一种可能性,即内存池链表里可能所有节点的数据部分的剩余内存都不够分配了,这时候就需要在内存池链表里插入一个新的内存池(ngx_pool_t)结点,这是由ngx_palloc_block函数完成的。

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
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
      size_t       psize;
      ngx_pool_t  *p, *new, *current;
                       
      psize = (size_t) (pool->d.end - (u_char *) pool);
                           
      m = ngx_alloc(psize, pool->log);
      if (m == NULL) {
          return NULL;
      }
                                   
      new = (ngx_pool_t *) m;
                                   
      new->d.end = m + psize;
      new->d.next = NULL;
      new->d.failed = 0;
                                                   
      m += sizeof(ngx_pool_data_t);
      m = ngx_align_ptr(m, NGX_ALIGNMENT);
      new->d.last = m + size;
               
      current = pool->current;
      
      for (p = current; p->d.next; p = p->d.next) {
       if (p->d.failed++ > 4) {
               current = p->d.next;
       }
      }
      
      p->d.next = new;
      
      pool->current = current ? current : new;
      
      return m;
}

ngx_palloc_block函数会往内存池链表里插入一个新的内存池结点,并返回请求分配的内存的起始地址。 注意在这时nginx会对p->current结点后的所有内存池结点的数据部分维护的failed的值加1,并当failed的值超过4时,则将p->current的值指向下一个内存池结点,这也就意味着内存池链表里每一个结点的可分配内存不一定会被分配完。 这里再提下ngx_align_ptr这个宏,NGX_ALIGNMENT取值32或者64(sizeof(unsigned long)),这个宏会在这两个平台里对指针进行对齐,下面看看这个宏的定义。

1
2
#define ngx_align_ptr(p, a)               \    
      (u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

uintptr_t可以将指针类型转换为整数,由于这段代码跨平台,所以用uintptr_t更安全,因为有可能为4或者8. 从二进制来看,为32位平台时,进行指针对齐需要保证指针的值的最低两位为0,即为4的倍数。当最低两位有任意一位不为零时,则需要加上((uintptr_t) a – 1)) (这时候a取值为4)产生进位,再通过与~((uintptr_t) a – 1)(最低四位是为1100)做且运算,抹平指针最低两位,这时候对齐完成。 这段代码并不复杂,但是涉及的方面比较多,效率也很高,比较有意思。

三、分配大块内存

大块内存的分配相对来说逻辑上比较简单,这时候并不是从内存池里分配,而是直接跟系统申请(相当于直接用malloc分配),再将这块内存挂到内存池头部的large字段里。由于内存池主要是用来解决小内存的频繁分配问题,这里大内存直接向系统申请是可以忍受的。大块内存的组织结构可以参考上图。 注意每块大内存都有一个数据结构进行维护的:

1
2
3
4
struct ngx_pool_large_s {
  ngx_pool_large_t     *next;//指向下一块大内存
  void                 *alloc;//指向分配的大块内存
};

这个数据结构是在内存池里进行分配的,因为这部分信息占用的字节比较少。由于大块内存很多时候可能需要及时被释放,所以nginx提供了ngx_int_t ngx_pfree(ngx_pool_t pool, void p)函数进行释放。其中p就是指向大块内存的地址。ngx_pfree只会释放分配的大块内存,但数据结构部分并不会被释放,会留下来供下次分配大内存使用。

四、资源回收

可以参考上图的cleanup部分,会发现所有需要被释放的资源会形成一个循环链表。每个需要释放的资源都会有一个头部结构:

1
2
3
4
5
struct ngx_pool_cleanup_s {
  ngx_pool_cleanup_pt   handler;//释放资源的函数指针
  void                 *data;//待释放的资源
  ngx_pool_cleanup_t   *next;//指向下一个待释放的资源头部
};

这里可以看到,当挂载一个待释放的资源时,需要注册一个释放函数。这就意味着这里不单单可以进行内存的释放,相应的文件标识符等也可以在这里进行释放,只要注册相应的释放资源函数调用即可。

五、内存回收

一路看来,貌似nginx并没有提供释放内存的接口(除了大内存块),难道nginx的内存都是只申请不释放的,这样不行啊,内存再多也只有吃完的时候啊。但显然我的担心是多余了,nginx并不会这样,它针对特定的场景,如每一个request都会建一个内存池,当request被完成时内存池分配的内存也会一次性被回收,这样就保证了内存有分配也有释放的时候了。当然具体还有很多的场景,由于理解深度还不够这里暂不涉及。

Unix心传:无名师与MCSE能者(伪文言翻译)(转载)

| Comments

无名师与MCSE能者

一通熟Windows之能者见无名师,曰:“闻师深修Unix之道,吾以密巧相述,相益,可乎?”

无名师曰:“求智,善矣;然Unix之道并无甚密巧。”

能者惑:“人曰师乃当世Unix大道之贤者,通晓其中之变法,如吾之于Windows;吾乃MCSE,有世所罕之证书,吾铭记各注册表项功能于心,更可细述任一Windows API,乃至微软所未曾详述者。师,所以为师,为智,皆揭而知他人之不知也。”

无名师曰:“怠矣,本无秘,何以揭?”

能者嗔:“若无密巧,何以修乃成师之业?”

无名师曰:“求智者,以他人之不知为智,犹如求光明者,拥烛笼火,以为珍贵,乃终被灼也。”

听此,能者顿觉灵光。

Master Foo and the MCSE

Once, a famous Windows system administrator came to Master Foo and asked him for instruction: “I have heard that you are a powerful Unix wizard. Let us trade secrets, that we may both gain thereby.”

Master Foo said: “It is good that you seek wisdom. But in the Way of Unix, there are no secrets.”

The administrator looked puzzled at this. “But it is said that you are a great Unix guru who knows all the innermost mysteries. As do I in Windows; I am an MCSE, and I have many other certifications of knowledge not common in the world. I know even the most obscure registry entries by heart. I can tell you everything about the Windows API, yes, even secrets those of Redmond have half-forgotten. What is the arcane lore that gives you your power?”

Master Foo said: “I have none. Nothing is hidden, nothing is revealed.”

Growing angry, the administrator said “Very well, if you hold no secrets, then tell me: what do I have to know to become as powerful in the Unix way as you?”

Master Foo said: “A man who mistakes secrets for knowledge is like a man who, seeking light, hugs a candle so closely that he smothers it and burns his hand.”

Upon hearing this, the administrator was enlightened.

转载源:http://apt-blog.net/master-foo-and-the-mcse

Ubuntu下自动切换声音输出设备

| Comments

我有一个usb耳机,在ubuntu下驱动也正常,不过有一个蛋疼的地方就是每次插进usb耳机都不会自动切换到usb耳机对应的声音输出设备,次次都要手动去选择对应的,于是想着能不能写一个脚本来实现。
不过貌似关于这方面的中文资料不多,google了很久都没有什么结果,倒是找出一个freebsd上能实现的,就是sysctl,不过貌似ubuntu下实现不了。后来在stackoverflow上问老外才得知用pacmd可以列出各种可用的声音输出设备还有设置默认输出设备等操作神马的。
弄了一下,果真OK了,写随意写了一个脚本来测试了,这个脚本假设系统使用pulseaudio的。
原理也不难,就是检测所有的声音设备,找出当前默认的声音设备,然后把声音设备切换到下一个,重新运行脚本就可以把声音设备切换回原来默认的了。

(change_out_device.sh) download
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
#!/bin/bash

declare -i count=`pacmd list-sinks | grep -c index:[[:space:]][[:digit:]]`
declare -i active=`pacmd list-sinks | sed -n -e 's/\*[[:space:]]index:[[:space:]]\([[:digit:]]\)/\1/p'`
declare -i major=$count-1
declare -i next=0

if [ $active -ne $major ] ; then
next=active+1
fi
 
pacmd "set-default-sink ${next}"

for app in $(pacmd list-sink-inputs | sed -n -e 's/index:[[:space:]]\([[:digit:]]\)/\1/p');
do
pacmd "move-sink-input $app $next"
done
 
declare -i ndx=0
pacmd list-sinks | sed -n -e 's/device.description[[:space:]]=[[:space:]]"\(.*\)"/\1/p' | while read line;
do
if [ $next -eq $ndx ] ; then
notify-send -i notification-audio-volume-high "声音输出切换到" "$line"
exit
fi
ndx+=1
done;

脚本里在切换后会做notify-send的提示,假设找不到命令的可以执行sudo apt-get install libnotify-bin安装一下,但不安装也没有什么关系,只是一个提示而已。
为了方便可以将脚本复制到/usr/bin目录下,然后在“键盘快捷键”里面添加多一个快捷键,命令就是脚本名了,最后设定好想要的快捷键,以后就可以直接切换了.