微信公眾號:鄭爾多斯
關注可瞭解更多的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;
9} ngx_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, -1, 0);
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_alloc
和ngx_shm_free
的確是對mmap
和munmap
分別進行了封裝。而且使用了MAP_ANON
,是在記憶體中開闢了一塊空間用於共享記憶體,而不是將硬碟中的檔案對映。
nginx如何使用共享記憶體
首先,我們得知道:預設情況下,通過fork
派生的子程式並不與其父程式共享記憶體區。但master
與worker
程式是父子程式啊,這該怎麼辦呢?如何讓master
程式與worker
程式共享記憶體區呢?
解決方法就在於mmap
的flags
引數。master
程式在呼叫fork
之前先指定flags
為MAP_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_ANON
或MAP_ANONYMOUS
,表示不從檔案中對映,只從記憶體中開闢一塊連續的線性地址空間出來作為共享記憶體。因此,這種情況下fd
和offset
引數就沒意義,分別置-1
和0
即可。
為從某一程式的地址空間中刪除一個對映關係,呼叫munmap
。
1int munmap(void *addr, size_t len);
複製程式碼
成功:0;出錯:-1
喜歡本文的朋友們,歡迎長按下圖關注訂閱號鄭爾多斯,更多精彩內容第一時間送達