1. 程式人生 > 其它 >LInux多程序開發II

LInux多程序開發II

1.程序間通訊。
  • 程序是獨立的資源分配單元,不同程序之間的資源是獨立的,沒有關聯,不能在一個程序中直接訪問另一個程序的資源。
  • 程序之間可以進行資訊的互動和狀態的傳遞,稱為程序間通訊(IPC:Inter Processes Communication)。
  • 程序間通訊:資料傳輸、通知時間、資源共享、程序控制。
2.程序通訊方式。
  • 同一主機程序通訊。匿名管道、有名管道、訊號、訊息佇列、共享記憶體、訊號量。
  • 不同主機(網路)通訊。Socket。
3.匿名管道。
  • Unix系統程序間通訊。
  • 管道是在核心記憶體中維護的緩衝區,不同的作業系統大小不同。
  • 管道擁有檔案的特質:讀寫操作。匿名管道沒有檔案實體;有名管道有檔案實體,但不儲存資料。管道的兩端可以分別用兩個檔案描述符表示。可以用操作檔案的方式對管道進行操作。
  • 一個管道是一個位元組流,使用管道不存在訊息或訊息邊界的概念,從管道讀取資料的程序可以讀取任意大小的資料塊,而不管寫入程序寫入管道的資料塊大小是多少。
  • 通過管道傳遞的資料是順序的,從管道讀取出來的位元組的順序和被寫入管道的順序完全一致。
  • 管道的資料傳遞方向是單向的,一端用於寫入,一端用於讀取,管道是半雙工的。
  • 從管道讀資料是一次性操作,資料一旦被讀走就被從管道中拋棄並釋放空間可以寫入更多的資料。在管道中無法使用lseek()來隨機訪問資料。
  • 匿名管道只能在具有公共祖先的程序之間使用。子程序通過fork()創建出來與父程序共享檔案描述符,因此可以根據相同的檔案描述符進行通訊。
  • 實現:迴圈陣列。由兩個檔案描述符標誌分別讀寫位置。
4.實現匿名管道。
#include <unistd.h>
int pipe(int pipedfd[2]);
    - 作用:建立一個匿名管道,用來程序間通訊。
    - pipefd[2]: 傳出引數。pipefd[0],對應管道的讀端;pipefd[1],對應管道的寫端。
    - 返回值:成功返回0,失敗返回1。
 
注意:
1.匿名管道只用於具有關係的程序之間的通訊。
2.管道預設是阻塞的,如果管道中沒有資料,read阻塞;如果管道滿了,write阻塞。
 
# 檢視管道緩衝大小命令
$ ulimit -a
 
# 檢視管道緩衝大小函式
#include <unistd.h>
long fpathconf(int fd, int name);
long size = fpathcong(pipefd[0], _PC_PIPE_BUF);
注意:實現管道通訊,一般只會有一個方向的通訊,從程序a傳資料到程序b,或者從程序b傳資料到程序a。因為如果程序a,b互相傳資料的話,可能會造成後果:a寫入管道的資料又被a自己讀去了,b寫入管道的資料也可能被b程序讀去了。所以在實現管道時,往管道寫入資料的程序會關閉讀端close(pipefd[0]);而往管道讀資料的程序會關閉管道寫端close(pipefd[1])。 5.管道的讀寫特點:使用管道時,有以下幾種特殊情況(假設都是阻塞IO操作)
  • 所有指向管道寫端的檔案描述符都關閉了(管道寫端引用計數為0),有程序從管道的讀端讀取資料,那麼管道中剩餘的資料被讀取之後,再次read會返回0,就像讀到檔案末尾一樣。
  • 如果有指向管道寫端的檔案描述符沒有關閉(管道寫端引用計數大於0),而持有管道寫端的程序沒有往管道中寫資料,這個時候若有程序從管道中讀取資料,那麼管道中剩餘的資料被讀取之後,再次read會阻塞,直到管道中有資料可以讀了才讀取資料並返回。
  • 所有指向管道讀端的檔案描述符都關閉了(管道讀端引用計數為0),這個時候有程序往管道中寫資料,那麼該程序會受到一個訊號SIGPIPE,通常會導致程序終止。
  • 如果有指向管道讀端的檔案描述符沒有關閉(管道讀端引用技術大於0),而持有管道讀端的程序也沒有從管道讀資料,這時有程序向管道寫資料,那麼管道在被寫滿的時候再次write阻塞,直到管道中有空位置能再次寫入資料並返回。
總結: 讀管道: 1. 管道中有資料,read返回實際讀到的位元組數。 2.管道中無資料:a.寫端全關閉,read返回0;b. 寫端沒有完全關閉,read阻塞等待。 寫管道: 1.管道讀端全部關閉,程序異常終止(程序受到SIGPIPE訊號)。 2.管道讀端沒有全部關閉:a.管道已滿,write阻塞;b.管道沒有滿,write寫入資料,並返回實際寫入的位元組數。 6.設定管道非阻塞。
int flags = fcntl(fd[0], F_GETFL);       # 獲取原來的flag
flags |= O_NONBLOCK;                     # 修改flag的值
fcntl(fd[0], F_SETFL, flags);            # 設定新的flag
7.有名管道(FIFO)。
  • 有名管道提供了一個路徑名與之關聯(即:有一個實體檔案),以FIFO的檔案形式存在與檔案系統中。其開啟方式與開啟普通檔案一樣,因此通過訪問該路徑即可通過FIFO通訊。
  • 與匿名管道的區別:1. FIFO有檔案實體,但是其內容存放於記憶體中;2.當使用FIFO的程序退出後,FIFO檔案繼續儲存在檔案系統中之後可以繼續使用;3.FIFO有名字,不相關的程序可以通過開啟有名管道進行通訊。
8.實現有名管道。
# 建立管道命令
mkfifo 名字
 
# 通過函式建立,建立FIFO之後檔案IO函式(open、read、write、close)都可用於FIFO
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
    - pathname: 管道名稱的路徑
    - mode: 檔案的許可權
    - 返回值: 成功返回0,失敗返回-1並設定errno
注意: 1.一個為開啟管道的只讀程序會阻塞,直到另一個只寫程序開啟管道; 2.一個為開啟管道的只寫程序會阻塞,直到另一個只讀程序開啟管道; 讀管道: 管道中有資料:read返回實際讀到的位元組數; 管道中無資料:a.管道寫端被全部關閉,read返回0;b.寫端沒有全部被關閉,read阻塞等待 寫管道: 管道讀端被全部關閉,程序異常終止(收到一個SIGPIPE訊號); 管道讀端沒有全部關閉:a.管道已滿,write阻塞;b.管道沒滿,write寫入資料,並返回實際寫入的位元組數 9.記憶體對映。將磁碟檔案的資料對映到記憶體中,程式可以通過修改記憶體就能修改磁碟檔案。
  • 同一個檔案可以對映到不同程序的虛擬記憶體空間,因此某個程序修改記憶體後,系統會同步檔案的修改,則另一個將記憶體對映到檔案的程序即可收到訊息。
  • 可以實現程序間通訊:1.有關係的程序(父子程序);2.沒有關係的程序間通訊。
  • 記憶體對映區通訊是非阻塞的。
#include <sys/mman.h>
void mmap(coid *addr, size_t length, int prot, int flags, int fd, off_t offset);
    - 作用:將一個檔案或者裝置的資料對映到記憶體中
    - addr: NULL,由核心指定
    - length: 要對映的資料的長度,這個值不能為0,建議使用檔案的長度,使用stat或者lseek獲取檔案的長度
    - prot: 對申請的記憶體對映區的操作許可權,PROT_EXEC 可執行許可權;PROT_READ 讀許可權;PROT_WRITE 寫許可權;PROT_NONE 沒有許可權
    - flags: MAP_SHARED 對映區的資料會自動和磁碟檔案進行同步,程序間通訊,必須設定這個選項;MAP_PRIVATE 不同步,記憶體對映區的資料改變了,對原檔案不會修改,會建立一個新的檔案(copy on write)
    - fd: 需要對映的檔案的檔案描述符,通過open()得到,檔案大小要>0
    - offset: 偏移量,4k的整數倍,0表示不偏移
    - 返回值:成功返回建立的記憶體的首地址,失敗返回MAP_FAILED ((void *) -1)
 
int munmap(void *addr, size_t length);
    - 作用:釋放記憶體對映
    - addr: 要釋放的記憶體地址
    - length: 要釋放的記憶體的大小,和mmap引數的length一樣

  

10.使用記憶體對映實現程序間通訊:
  • 有關係的程序(父子程序)
- 還沒有子程序的時候 -通過唯一的父程序,先建立記憶體對映區 -有了記憶體對映區後,建立子程序 - 父子程序共享建立的記憶體對映區
  • 沒有關係的程序間通訊
-準備一個大小不是0的磁碟檔案 -程序1通過磁碟檔案建立記憶體對映區 -得到一個操作這塊記憶體的指標 -程序2通過磁碟檔案建立記憶體對映區 -得到一個操作這塊記憶體的指標 -使用記憶體對映區通訊 11.記憶體對映的注意事項。
  • 如果對mmap的返回值(ptr)做++操作(ptr++),munmap是否能成功?
void *ptr = mmap(...);
ptr++;                    // 可以操作
munmap(ptr, len);         // 錯誤,應該儲存使用之前的地址
  • 如果open時O_RDONLY,mmap時prot引數指定PROT_READ|PROT_WRITE會怎樣?
錯誤,返回MAP_FAILD
open()函式的許可權要大於等於prot的許可權。
e.g.: 
open()讀寫,prot可以讀寫/只讀/只寫
open()只讀,prot只能只讀
  • 如果檔案偏移量是1000會怎樣?
返回錯誤MAP_FAILED,偏移量只能是4k的整數倍
  • mmap什麼情況下會呼叫失敗?
1. 第二個該引數length = 0
2. 第三個引數 prot 只有寫許可權
3. 第三個引數有 PROT_READ | PROT_WRITE 許可權,而第五個引數fd通過open開啟時的許可權是O_RDONLY / O_WRONLY(記憶體許可權大於檔案許可權)
  • 可以open的時候O_CREAT一個新檔案來建立對映區嗎?
可以,但是建立的檔案大小為0不行,需要對新檔案進行擴充套件( lseek() / truncate() )
  • mmap後關閉檔案描述符,對mmap對映有沒有影響?
int fd = open("xxx");
mmap(,,,,fd,0);
close(fd);
對映區還存在,建立營社群的fd被關閉,沒有影響。
  • 對ptr越界操作會怎麼樣?
void *ptr = mmap(NULL,100,,,,);
越界操作操作的是非法記憶體,造成段錯誤
12.匿名對映:不需要檔案實體進行一個記憶體對映。只能用於父子程序間通訊。
mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARD | MAP_ANONYMOUS, -1, 0);
檔案描述符 fd = -1
prot 要指定為 PROT_ANONYMOUS