nginx共享記憶體分析

鄭爾多斯發表於2019-02-11

微信公眾號:鄭爾多斯
關注可瞭解更多的Nginx知識。任何問題或建議,請公眾號留言;
關注公眾號,有趣有內涵的文章第一時間送達!

共享記憶體

共享記憶體是linux下最基本的程式間通訊方式。它通過mmap或者shmget系統呼叫在記憶體中建立一塊連續的線性地址空間,使用munmap或者shmdt系統呼叫可以釋放這塊記憶體。使用共享記憶體的好處:當多個程式使用同一塊共享記憶體時,在任何一個程式中修改了共享記憶體中的內容,其他程式通過訪問這段共享記憶體都能夠得到修改後的內容。

資料結構

nginx使用到的資料結構如下:

1 typedef struct {
2    u_char      *addr;    /* 共享記憶體的起始地址 */
3    size_t       size;    /* 共享記憶體的長度 */
4    ngx_str_t    name;    /* 共享記憶體的名字  */
5    ngx_log_t   *log;     /* 記錄日誌的物件 */
6
7/* unsigned  exists:1;  */ /*共享記憶體是否已經分配過,1:已經分配 */
8    ngx_uint_t   exists;   
9ngx_shm_t;
複製程式碼

共享記憶體的API

nginx操作共享記憶體的API有兩個,如下:

1ngx_int_t ngx_shm_alloc(ngx_shm_t *shm);    /* 分配新的共享記憶體 */
2void ngx_shm_free(ngx_shm_t *shm);    /*釋放已經存在的共享記憶體 */
複製程式碼

實現方式

上面是nginx中操作共享記憶體的兩個api。我們應該使用上述兩個api對共享記憶體進行操作。
為了可移植性,nginx使用了三種方式來實現上述的兩個api,三種方式分別如下:
1、不對映檔案使用mmap分配共享記憶體
2、以 /dev/zero 檔案使用mmap對映共享記憶體。
3、用shmget呼叫來分配共享記憶體

原始碼分析

原始碼很簡單,我們對mmap實現方式進行簡單的分析

 1#if (NGX_HAVE_MAP_ANON)
2
3ngx_int_t
4ngx_shm_alloc(ngx_shm_t *shm)
5{
6    /* MAP_ANON:不使用檔案對映方式,因此fd,offset無用,相當於在記憶體開闢一塊空間用於共享,由master建立 */
7    shm->addr = (u_char *) mmap(NULL, shm->size,
8                                PROT_READ|PROT_WRITE,
9                                MAP_ANON|MAP_SHARED, -10);
10
11    if (shm->addr == MAP_FAILED) {
12        ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
13                      "mmap(MAP_ANON|MAP_SHARED, %uz) failed", shm->size);
14        return NGX_ERROR;
15    }
16
17    return NGX_OK;
18}
19
20
21void
22ngx_shm_free(ngx_shm_t *shm)
23
{
24    if (munmap((void *) shm->addr, shm->size) == -1) {
25        ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno,
26                      "munmap(%p, %uz) failed", shm->addr, shm->size);
27    }
28}
29
30#elif (NGX_HAVE_MAP_DEVZERO)
複製程式碼

可以看到ngx_shm_allocngx_shm_free的確是對mmapmunmap分別進行了封裝。而且使用了MAP_ANON,是在記憶體中開闢了一塊空間用於共享記憶體,而不是將硬碟中的檔案對映。

nginx如何使用共享記憶體

首先,我們得知道:預設情況下,通過fork派生的子程式並不與其父程式共享記憶體區。但masterworker程式是父子程式啊,這該怎麼辦呢?如何讓master程式與worker程式共享記憶體區呢?
解決方法就在於mmapflags引數。master程式在呼叫fork之前先指定flagsMAP_SHARED來呼叫mmap,此時,POSIX是保證父程式中的記憶體對映關係是存留到子程式中的,父程式對共享記憶體所做的修改子程式能看到,反過來一樣。所以流程是:master程式在記憶體中以MAP_SHARED方式開闢一塊共享記憶體,並對映到自己程式地址空間中的共享記憶體區,然後master呼叫fork,派生子程式,子程式在自己的地址空間內也會繼承這塊共享記憶體區。這樣問題便解決了。

mmap/munmap 函式

1void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
複製程式碼

返回值:成功:被對映區的起始地址;出錯:MAP_FAILED
addr: 指定的fd描述符應被對映到程式地址空間的起始地址,一般為NULL,意思就是讓核心自己去選擇起始地址
len: 對映到程式地址空間的位元組數
prot:對這塊共享記憶體中的資料,我們可以處理的方式,如下:

prot 說明
PROT_READ 資料可讀
PROT_WRITE 資料可寫
PROT_EXEC 資料可執行
PROT_NONE 資料不可訪問

flags:變動共享記憶體區中的資料這一行為是共享的還是私有的,即對所有程式可見,還是隻對該程式可見。如下:

flags 說明
MAP_SHARED 變動是共享的
MAP_PRIVATE 變動是私有的
MAP_FIXED 準確的解釋addr引數

fd:被對映的檔案描述符
offset:被對映區域在檔案中的起始位置。
具體的見下圖:

對映區域
對映區域

需要注意的是:nginx的共享記憶體不是對映檔案中的內容。當flags引數中MAP_ANONMAP_ANONYMOUS,表示不從檔案中對映,只從記憶體中開闢一塊連續的線性地址空間出來作為共享記憶體。因此,這種情況下fdoffset引數就沒意義,分別置-10即可。

為從某一程式的地址空間中刪除一個對映關係,呼叫munmap

1int  munmap(void *addr, size_t len);
複製程式碼

成功:0;出錯:-1


喜歡本文的朋友們,歡迎長按下圖關注訂閱號鄭爾多斯,更多精彩內容第一時間送達

鄭爾多斯
鄭爾多斯

相關文章