1. 程式人生 > >總結下linux下的IPC使用原理及注意事項

總結下linux下的IPC使用原理及注意事項

首先說明一下mmap函式用途:

採用共享記憶體通訊的一個顯而易見的好處是效率高,因為程序可以直接讀寫記憶體,而不需
要任何資料的拷貝
1、將一個普通檔案對映到記憶體中,通常在需要對檔案進行頻繁讀寫時使用,這樣用記憶體讀
   寫取代I/O讀寫,以獲得較高的效能;
2、將特殊檔案進行匿名記憶體對映,可以為關聯程序提供共享記憶體空間;
適用於具有親緣關係的程序之間。由於父子程序特殊的親緣關係,在父程序中先呼叫mmap
(),然後呼叫 fork()。那麼在呼叫fork()
之後,子程序繼承父程序匿名對映後的地址空間,同樣也繼承mmap()
返回的地址,這樣,父子程序就可以通過對映區域進行通訊了。
3、為無關聯的程序提供共享記憶體空間,一般也是將一個普通檔案對映到記憶體中。


普通檔案fd用於mmap引數:

1、最終被對映檔案的內容的長度不會超過檔案本身的初始大小,即對映不能改變檔案的大小
2、記憶體的保護是以頁為基本單位的,即使被對映檔案只有一個位元組大小,核心也會為對映分
   配一個頁面大小的記憶體,用於程序間通訊的有效地址空間大小不會超過檔案大小及一個頁
   面大小的和
3、檔案一旦被對映後,呼叫mmap()的程序對返回地址的訪問是對某一記憶體區域的訪問,暫時
   脫離了磁碟上檔案的影響,只有在呼叫了munmap()後或者msync()時,才把記憶體中的相應
   內容寫回磁碟檔案,所寫內容仍然不能超過檔案的大小。


A、ftok使用說明
key_t ftok( char * fname, int id )  fname就時你指定的檔名,id是子序號。


在一般的UNIX實現中,是將檔案的索引節點號取出,前面加上子序號得到key_t的返回值。
如指定檔案的索引節點號為65538,換算成16進製為0x010002,而你指定的ID值為38,換
算成16進製為0x26,則最後的key_t返回值為0x26010002。


當刪除重建檔案後,索引節點號由作業系統根據當時檔案系統的使用情況分配,因此與原來
不同,所以得到的索引節點號也不同。


如果要確保key_t值不變,要目確保ftok的檔案不被刪除,要麼不用ftok,指定一個固定的key_t值


注意:
ftok將一個已存在的路徑名和一個整數識別符號轉換成一個key_t值。 


ftok會組合三個值來產生key:
1、pathname所在的檔案系統的資訊。
2、該檔案在本檔案系統內的索引節點號。
3、id的低序8位。 
 
key_t
的生成是以一個已存在的檔案作為輸入,並不是簡單的字串雜湊函式,必須真正存在某
個檔案,才能將其位置傳入ftok。


B、檔案空洞是怎麼回事

off_t lseek(int filedes, off_t offset, int whence);
引數 offset 的含義取決於引數 whence:
當前檔案偏移量(current file offset),以下簡稱為 cfo,非負整數
    1. 如果 whence 是 SEEK_SET,檔案偏移量將被設定為 offset。
    2. 如果 whence 是 SEEK_CUR,檔案偏移量將被設定為 cfo 加上 offset,
       offset 可以為正也可以為負。
    3. 如果 whence 是 SEEK_END,檔案偏移量將被設定為檔案長度加上 offset,
       offset 可以為正也可以為負。


注意: 對於普通檔案(regular file),cfo 是一個非負整數。但對於特殊裝置,cfo 
有可能是負數。因此,我們不能簡單地測試 lseek 的返回值是否小於 0 來判斷 lseek 
成功與否,而應該測試 lseek 的返回值是否等於 -1 來判斷 lseek 成功與否。


if (lseek(fd, 16384, SEEK_SET) == -1)
{
    printf("lseek error\n");
    return -1;
}


測試結果表明,lseek並不能extend檔案大小,需要write一下0的資料
/**
  * 參看前面man手冊中的說明,mmap()不能用於擴充套件檔案長度。所以這裡必須事
  * 先擴大目標檔案長度,準備一個空架子等待後面讀寫使用:
  * 如果 offset 比檔案的當前長度更大,下一個寫操作就會把檔案“撐大(extend)”。
  * 這就是所謂的在檔案裡創造“空洞(hole)”。沒有被實際寫入檔案的所有位元組
  * 由重複的 0 表示。空洞是否佔用硬碟空間是由檔案系統(file system)決定的
  */ 
int tmpData = 0x00;
lseek(fd,maxSize-4,SEEK_SET);
write(fd,(const void*)&tmpData,sizeof(ui32)); /* 寫入一個int型資料長度 */
或者
write(fd, "\0", 1); /* 在檔案最後新增一個空字元 */

也可以使用ftruncate(改變檔案大小)進行設定大小。

即對於普通檔案的對映前,首稱必須將fd的檔案擴充套件到與你所對映空間的同樣大小,這
樣子後面對映後才可使用,否過會越界使用而出現宕機


high memory |-------| A
            |       |
            |       |
            |       | memory mapped partion of file 
            |       |
            |       |
low memory  |-------| B


file mapped |-----------------|
            |offfset|   len   |
            |-----------------|
                    A'       B'
以上fd[A--B]對映到[A'--B']範圍內,操作[A-B]與操作下面file mapped一樣                    


C、copy_to_user與mmap的工作原理
copy_to_user
在每次拷貝時需要檢測指標的合法性,也就是使用者空間的指標所指向的地址的確是一段
該程序本身的地址,而不是指向了不屬於它的地方,而且每次都會拷貝一次資料,頻繁
訪問記憶體,由於虛擬地址連續,實體地址不一定會連續,從而造成CPU的CACHE頻繁失效,
從而使速度降低


mmap優點:
僅在第一次使用時為程序建立頁表,也就是將一段實體地址對映到一段虛擬地址上,以後
操作時不再檢測其地址的合法性(合法性交由CPU頁保護異常來做),另一方面是核心下
直接操作mmap地址,可以不用頻繁拷貝,也就是說在核心下直接可用指標向該地址操作,
而不再在核心中專門開一個緩衝區,然後將緩衝區中的資料拷貝一次進來,mmap一般是
將一段連續的實體地址對映成一段虛擬地址,當然,也可以將每段連續,但各段不連續
的實體地址對映成一段連續的虛擬地址,無論如何,其實體地址在每段之中是連續的,
這樣一來,就不會造成CPU的CACHE頻繁失效,從而大大節約時間

總結:
mmap 地址影射 包括 
A、記憶體實體地址 -- 虛擬地址
B、檔案裝置--虛擬地址 

目的是通過 虛擬地址訪問 目標地址 ,目標地址 包括 實體地址 檔案裝置等