APR分析-共享内存篇
APR分析-共享內存篇
共享內存是一種重要的IPC方式。在項目中多次用到共享內存,只是用而并未深入研究。這次趁研究APR代碼的機會復習了共享內存的相關資料。
APR共享內存封裝的源代碼的位置在$(APR_HOME)/shmem目錄下,本篇blog著重分析unix子目錄下的shm.c文件內容,其相應頭文件為$(APR_HOME)/include/apr_shm.h。
一、共享內存簡單小結
共享內存是最快的IPC方式,因為一旦這樣的共享內存段映射到各個進程的地址空間,這些進程間通過共享內存的數據傳遞就不需要內核的幫忙了。Stevens的解釋是“各進程不是通過執行任何進入內核的系統調用來傳遞數據,顯然內核的責任僅僅是建立各進程地址空間與共享內存的映射,當然像處理頁面故障這一類的底層活還是要做的”。相比之下,管道和消息隊列交換數據時都需要內核來中轉數據,速度就相對較慢。
Unix"歷史悠久",所以在歷史上不同版本的Unix提供了不同的支持共享內存的方式,我想這也是Stevens在《Unix網絡編程第2卷》中花費三章來講解共享內存的原因吧。你也不妨先看看shm.c中的代碼,代碼用條件宏分割不同Share Memory的實現。
二、APR共享內存封裝
APR提供多種創建共享內存的方式,其中最主要的就是apr_shm_create接口,其偽碼如下:
apr_shm_create {if (要創建匿名shm) { #if APR_USE_SHMEM_MMAP_ZERO || APR_USE_SHMEM_MMAP_ANON #if APR_USE_SHMEM_MMAP_ZEROxxxx ----------(1) #elif APR_USE_SHMEM_MMAP_ANONxxxx ----------(2) #endif #endif /* APR_USE_SHMEM_MMAP_ZERO || APR_USE_SHMEM_MMAP_ANON */ #if APR_USE_SHMEM_SHMGET_ANONxxxx ----------(3) #endif} else { /*創建有名shm*/ #if APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM #if APR_USE_SHMEM_MMAP_TMPxxxx ----------(4) #endif #if APR_USE_SHMEM_MMAP_SHMxxxx ----------(5) #endif #endif /* APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM */ #if APR_USE_SHMEM_SHMGETxxxx ----------(6) #endif} }apr_shm_create函數代碼很長,之所以這樣是因為其支持多種創建Share Memory的方式,在上面的偽代碼中公用條件宏分隔了6種方式,這6中方式將在下面分析。可以看出shmem主要分為"匿名的"和"有名的",其中"有名的"都是通過filename來標識(或通過ftok轉換filename而得到的shmid來標識).
其中不同版本Unix創建匿名shmem的做法如下:
(1) SVR4通過映射"/dev/zero"設備文件來獲取匿名共享內存,其代碼一般為:
fd = open("/dev/zero", ..); ptr = mmap(..., MAP_SHARED, fd, ...);(2) 4.4 BSD提供更加簡單的方式來支持匿名共享內存(注意標志參數MAP_XX)
ptr = mmap(..., MAP_SHARED | MAP_ANON, -1, ...);(3) System V匿名共享內存區的做法如下:
shmid = shmget(IPC_PRIVATE, ...); ptr = shmat(shmid, ...);匿名共享內存一般都用于有親緣關系的進程間的數據通訊。由父進程創建共享內存,子進程自動繼承下來。由于是匿名,沒有親緣關系的進程是不能動態連接到該共享內存區的。
不同版本Unix創建有名shmem的做法如下:
(4)由于是有名的shmem,所以與匿名不同的地方在于用filename替代"/dev/zero"做映射。
fd = open(filename, ...); apr_file_trunc(...); ptr = mmap(..., MAP_SHARED, fd, ...);(5) Posix共享內存的做法
fd = shm_open(filename, ...); apr_file_trunc(...); ptr = mmap(..., MAP_SHARED, fd, ...);值得注意的一點就是通過shm_open映射的共享內存可以供無親緣關系的進程共享。apr_file_trunc用于重新設定共享內存對象長度。
(6) System V有名共享內存區的做法如下:
shmkey = ftok(filename, 1);
shmid = shmget(shmkey, ...); //相當于open orshm_open
ptr = shmat(shmid, ...); //相當于mmap
有名共享內存一般都與一個文件相關,該文件映射到共享內存段,而不同的進程(包括無親緣關系的進程)則都映射到該文件以達到目的。在APR中通過apr_shm_attach可以動態將調用進程連接到已存在的共享內存上,前提是你必須知道該共享內存區的標識,在APR中一律用filename做標識。
三、總結
內核架起了多個進程間
共享數據的紐帶--共享內存。通過上面的敘述你會發現共享內存的創建其實并不困難,真正困難的是共享內存的管理[注1],在正規的軟件公司像內存/共享內存管理這樣的重要底層功能都是封裝成庫形式的,當然內存管理的內容不是這篇blog重點涉及的內容。
四、參考資料:
1、《Unix網絡編程第2卷》
2、《Unix環境高級編程》
[注1] SIGSEGV和SIGBUS
涉及共享內存的管理就不能不提到訪問共享內存對象。談到訪問共享內存對象就要留神“SIGSEGV和SIGBUS”這兩個信號。
系統分配內存頁來承載內存映射區,由于內存頁大小是固定的,所以存在多余的頁空間空閑,比如待映射文件大小為5000 bytes,內存映射區大小也為5000
bytes。而一個內存頁大小4096,系統勢必要分配兩頁來承載,這時空閑的有效空間為從5000-8191,如果進程訪問這段地址空間也不會發生錯誤。但是要超出8191,就會收到SIGSEGV信號,導致程序停止。關于SIGBUS信號的來歷,這里也舉例說明:若待映射文件大小為5000
bytes,我們在mmap時指定內存映射區size = 15000 > 5000,這時內核真正的共享區承載體大小只有8192(能包容映射文件大小即可),此時在[0,8191]內訪問均沒問題,但在[8192,14999]之間會得到SIGBUS信號;超出15000訪問時會觸發SIGSEGV信號。
?
總結
以上是生活随笔為你收集整理的APR分析-共享内存篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一次“内存泄露”引发的血案
- 下一篇: 一次共享内存引起的线上事故分析