IPC通訊陷阱之六萬五千分之一

 

 

【摘要】在本文中,做者剖析了IPC通訊機制,經過對系統函數源碼分析,指出了其中存在的1/65535概率可能出現的隱患, 並結合實際的案例給出瞭解決方案.在本文中,咱們能夠了解到針對IPC通訊常見問題 。在咱們以後的測試工做中,能夠有選擇針對這些注意事項和易錯點設計測試case,讓bug無處藏身。因爲做者能力有限,文中若是有一些不夠清晰不夠全面的地方,歡迎指正。
【關鍵詞】IPC,共享內存html

1 IPC通訊概述

IPC(InterProcess Communication) 進程間通訊,一般在同一臺主機各個進程間的PIC主要有:管道,FIFO,消息隊列,信號量,以及共享內存,而不一樣主機上各個進程間IPC通訊能夠經過套接字和stream.node

(1)管道(Pipe):管道可用於具備親緣關係進程間的通訊,容許一個進程和另外一個與它有共同祖先的進程之間進行通訊。
(2)命名管道(named pipe):命名管道克服了管道沒有名字的限制,所以,除具備管道所具備的功能外,它還容許無親緣關係進程間的通訊。命名管道在文件系統中有對應的文件名。命名管道經過命令mkfifo或系統調用mkfifo來建立。
(3)信號(Signal):信號是比較複雜的通訊方式,用於通知接受進程有某種事件發生,除了用於進程間通訊外,進程還能夠發送信號給進程自己;linux除了支持Unix早期信號語義函數sigal外,還支持語義符合Posix.1標準的信號函數sigaction(實際上,該函數是基於BSD的,BSD爲了實現可靠信號機制,又可以統一對外接口,用sigaction函數從新實現了signal函數)。
(4)消息(Message)隊列:消息隊列是消息的連接表,包括Posix消息隊列system V消息隊列。有足夠權限的進程能夠向隊列中添加消息,被賦予讀權限的進程則能夠讀走隊列中的消息。消息隊列克服了信號承載信息量少,管道只能承載無格式字節流以及緩衝區大小受限等缺
(5)共享內存:使得多個進程能夠訪問同一塊內存空間,是最快的可用IPC形式。是針對其餘通訊機制運行效率較低而設計的。每每與其它通訊機制,如信號量結合使用,來達到進程間的同步及互斥。
(6)內存映射(mapped memory):內存映射容許任何多個進程間通訊,每個使用該機制的進程經過把一個共享的文件映射到本身的進程地址空間來實現它。
(7)信號量(semaphore):主要做爲進程間以及同一進程不一樣線程之間的同步手段。
(8)套接口(Socket):更爲通常的進程間通訊機制,可用於不一樣機器之間的進程間通訊。起初是由Unix系統的BSD分支開發出來的,但如今通常能夠移植到其它類Unix系統上:Linux和System V的變種都支持套接字。

2 XSI IPC: 消息隊列,信號量以及共享內存linux


每因爲篇幅關係,在這裏着重討論 XSI IPC,消息隊列,信號量以及共享存儲器,他們之間有不少共同之處
一個內核中的IPC結構(消息隊列,信號量和共享存儲器)都用一個非負整數的標識符(identifier)加以引用。例如,爲了對一個消息隊列發 送消息或取消息,只需知道其隊列標識符。當一個IPC結構被建立,之後又被刪除時,與這種結構相關的標識符連續加1,直至達到一個整型數的最大值,而後又 回到0。
標識符是IPC對象的內部名。鍵(key)是IPC對象的外部名。不管什麼時候建立IPC結構(調用msgget,semget或shmget),都應 該指定一個鍵。鍵的數據類型是基本系統數據類型key_t,一般在<sys/types.h>中被定義爲長整型。鍵由內核變成標識符。
客戶進程和服務器進程認同一個路徑名和項目ID(項目ID是0~255之間的字符值),接着調用ftok將這兩個值變成爲一個鍵。而後用該鍵建立一個新的IPC結構或獲得一個的IPC結構。ftok提供的惟一服務就是由一個路徑名和項目ID產生一個鍵。
#include <sys/ipc.h>
key_t ftok(const char *path, int id);
path參數必須引用一個現存文件。當產生鍵時,只使用id參數的低八位。
三個get函數(msgget,semget和shmget)都有兩個相似的參數:一個key和一個整型flag。若是知足如下兩個條件之一,則建立一個新的IPC結構:
1). key是IPC_PRIVATE;
2). key當前未與特定類型的IPC結果相結合,而且flag中指定IPC_CREAT位。
爲訪問現存的隊列(一般是客戶進程),key必須等於建立該隊列時所指定的鍵,而且不該該指定IPC_CREAT。
若是但願建立一個新的IPC結構,並且要確保不是引用具備同一標識符的一個現行IPC結構,則必須在flag中同時指定IPC_CREAT和IPC_EXCL位。這樣作,若是IPC結構已經存在就會出錯,返回EEXIST。
XSI IPC爲每一個IPC結構設置了一個ipc_perm結構。該結構規定了權限和全部者。
struct ipc_perm{
uid_t uid; // owner's effective user id
gid_t gid; // owner's effective group id
uid_t cuid; // creator's effective user id
gid_t cgid; // creator's effective group id
mode_t mode; // access modes
};
在建立IPC結構時,對全部字段都附初值。調用msgctl、semctl或shmctl修改uid、gid和mode字段。爲了改變這些值,調用進程必須是IPC結構的建立者或超級用戶。更改這些字段相似於文件調用chown和chmod。
字段mode的值以下所示的值,可是對於任何IPC結構都不存在執行權限。另外,消息隊列和共享內存使用術語讀(read)和寫(write),而信號量則用術語讀(rend)和更改(alter)。服務器

3 不一樣IPC通訊方式大比拼
XSI IPC的主要問題是:IPC結構是在系統範圍內起做用,沒有訪問計數。例如:若是進程建立了一個消息隊列,在該隊列中放入了幾則消息,而後終止,可是該消 息隊列及其內容並不會被刪除。它們留在系統中直至出現下述狀況:由某個進程調用msgrcv或msgctl讀消息或刪除消息隊列;或某個進程執行 ipcrm命令刪除該消息隊列;或是由正在啓動的系統刪除消息隊列。與管道相比,當最後一個訪問管道的進程終止時,管道就被徹底刪除了。對於FIFO而 言,當最後一個引用FIFO的進程終止時,其名字仍保留在系統中,直至顯示地刪除它,可是留在FIFO中的數據卻在此時所有被刪除了。
XSI IPC的另外一問題:這些IPC結構在文件系統中沒有名字,不得不增長新的命令ipcs和ipcrm。
由於這些XSI IPC不使用文件描述符,因此不能對它們使用多路轉接I/O函數:select和poll。這使得難於一次使用多個IPC結構,以及文件或設備I/O中使 用IPC結構。例如,沒有某種形式的忙-等待循環,就不能使一個服務器進程等待將要放在兩個消息隊列任一個中的消息。oracle

4 1/65553的陷阱
剛纔提到,系統創建IPC通信(如消息隊列、共享內存時)必須指定一個ID值。一般狀況下,該id值經過ftok函數獲得。好比: if (-1 == (conf.shm_key = ftok(conf.chnl_shm_path, 'l')));
看一下ftok函數
* 原型: key_t ftok( char * fname, int id )
* 頭文件: <sys/ipc.h>
* 返回值: 成功則返回key, 出錯則返回(key_t)-1.
* 參數: fname參數必須引用一個現存文件. fname就時你指定的文件名,id是指定的值。
Keys:
1)pathname必定要在系統中存在而且進程可以訪問的
3)proj_id是一個1-255之間的一個整數值,典型的值是一個ASCII值。
一切看上去都是那麼合理。app

在通常的UNIX實現中,是將文件的索引節點號取出,前面加上子序號獲得key_t的返回值。
讓咱們在看下ftok代碼到底如何獲取key的呢,果真很撮ide

// ftok庫實現爲
// key_t ftok(const char* path, int project_id)
// {
// struct stat st;
// if ( lstat(path, &st) < 0 )
// return -1;
// return (key_t)( (st.st_ino & 0xffff) | ((st.st_dev & 0xff) << 16) | ((id & 255) << 24) );
// }
st.st_ino 是inode節點
st.st_dev是文件系統設備號
id是程序指定的0-255值
ftok調用返回的整數IPC鍵由proj_id的低序8位,st_dev成員的低序8位,st_info的低序16位組合而成。函數

可就會出現如下的風險:
當project_id相同時, 及時inode不一樣,可是隻要inode的低序16相同時,就會映射到同一個key中,而若是恰巧這個key中也有IPC訪問權限,那麼這會致使程序可能訪問了本不該該訪問的key,即訪問了本不應通訊的區域獲取錯誤的信息,那麼這種事情發生的機率是多少呢?答案是1/65535如何得來?低16bit最多能夠表示65536個數。因此65537個文件裏面必定是有兩個文件的inode號的最低兩位相同。 請讀者朋友們想想是否是醬紫的.源碼分析

這麼巧的事情真的會發生麼?答案很悲劇的是的,這個世界就是無巧不成書的,這種狀況在複雜的線上系統上會無情的發生的。測試

5 血案實例
背景:
1.線上A程序須要和同時和B,C兩個程序經過兩段不一樣的共享內存進行IPC通訊
2.BC之間沒有關係,但B和A,C和A之間發生關係:B,C須要寫各自的內存,A去讀,從中獲取以便進行進行後續處理;
3.B和C和A的通訊機制徹底一致,區別僅僅在於共享內存指向的路徑不一樣,因此是用的一段代碼不一樣配置項

結果:
11臺機器部署徹底一致的程序,可是隻有一臺機器上觀察共享內存處理邏輯是混亂滴,而且在線下死活是不能復現滴,
緣由:
仔細觀察了B,C的共享內存分別指向路徑:path_B,path_c,沒錯,灰長正常,可是再定睛看他們的inode節點,以下:
31866881 --> 1E64001
32260097 ---> 1EC4001
發現了什麼: 天殺的低16位徹底相同4001,只有高位不一樣,回憶下,返回的整數IPC鍵由proj_id的低序8位,st_dev成員的低序8位,st_info的低序16位組合而成, proj_id(相同代碼,因此指定的id相同),設備號相同的狀況下,inode的低16位又相同,因而B,C同時寫到了一塊共享內存中,瘋掉了,這是 1/65536的機率,真實的中獎了。


6 解決方法的思考
那麼這種陷阱有沒有辦法避免呢?在瞭解了以上的原理後,相信聰明的讀者已經想到了解決方法.
1.最直接的方法:改project_id,可是這樣就須要升級程序,又要從新的迴歸,測試,勞民傷財
2.本身寫一個代替ftok的,保證不衝突的,缺點也同1
3.在共享內存中加一個標識,B,C只認屬於本身的標識,缺點同上
4.最後來個最簡單的吧,諸如共享內存之類的方式,部署後藉助系統命令查看,發現不對,當即重啓撒
經過ipcs查看
$ ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x6c03806a 32769 work 666 33554016 1

nattch字段就表示,鏈接在關聯的共享內存段的進程數。經過這個來判斷下,是否符合預期,像剛剛描述的血案,很容易經過實際創建的共享內存數量和natch字段這個觀察出異常.

7 其餘tips

1.取得ipc信息:
ipcs [-m|-q|-s]
-m 輸出有關共享內存(shared memory)的信息
-q 輸出有關信息隊列(message queue)的信息
-s 輸出有關「遮斷器」(semaphore)的信息
# ipcs -m
IPC status from <running system> as of 2007年04月10日 星期二 18時32分18秒 CST
T ID KEY MODE OWNER GROUP
Shared Memory:
m 0 0x50000d43 --rw-r--r-- root root
m 501 0x1e90c97c --rw-r----- oracle dba
#ipcs |grep oracle|awk '{print $2}
501

2.刪除ipc(清除共享內存信息)
ipcrm -m|-q|-s shm_id
%ipcrm -m 501
for i in `ipcs |grep oracle|awk '{print $2}'`
do
ipcrm -m $i
ipcrm -s $i
done

ps -ef|egrep "ora_|asm_"|grep -v grep |grep -v crs|awk '{print $2}' |xargs kill -9
helgrind死鎖,或者由於線程問題致使valgrind崩潰的狀況。
還有不少其餘的經驗,在你們的使用過程當中將會繼續發現的,咱們也會持續更新這個列表,讓你們有所參考。


8 結束語
在Linux環境中進行IPC測試是一件頗有挑戰性的事情,由於不少問題每每不容易設計case進行覆蓋,每每只能通原理層面分析和經驗積累來發現隱患;從這個角度而言,筆者給咱們提供了一些經驗, 幫助咱們更快更好地發現問題;而另外一方面,因爲IPC也是程序中的易錯點,因此咱們QA須要對這方面使用得更加熟練、瞭解得更加透徹,才能更好地發現隱藏在代碼和實現細節中的問題。歡迎同窗們就文章中的內容與我進一步交流,謝謝!

(做者:qijiayin、liuchao)
 

相關文章
相關標籤/搜索