進程間通訊(一)

1.進程是一種單執行流,每一個進程都私有/獨佔一份系統資源,代碼段共享可是數據段不共享,子進程寫數據時會觸發寫實拷貝,從而保證資源獨享。總的來講 每一個進程各自有不一樣的用戶地址空間,任何一個進程的全局變量在另外一個進程中都看不到 。
 因此進程之間要交換數據必須經過內核,在內核中開闢一塊緩衝區,進程1把數據從用戶空間 拷到內核緩衝區,進程2再從內核緩衝區把數據讀走,內核提供的這種機制稱爲進程間通訊 如圖所示:
 

 

進程間通訊(IPC,InterProcess Communication)的方式:
1.管道:匿名管道、命名管道
2. XSI IPC:消息隊列、信號量、共享內存
XSI IPC,依託標識符和鍵來實現的,如同管道靠文件描述符來實現同樣。
XSI IPC使用通常步驟:
1)IPC對象進程內部用標識符identifier,進程外部標識用key
2)首先用semget,shmget,msgget等函數根據key建立或獲取IPC對象的identifier
3)而後根據identifier用semctrl,shmctrl等控制函數作某些修改,這步可選
4)最後用各個IPC特有操做函數如semop,shmat等函數操做)
 

 

 
 
 
匿名管道(pipe)
調⽤用pipe函數時在內核中開闢一塊緩衝區(稱爲管道)用於通訊,它有一個讀端一個寫端,而後 經過filedes參數傳出給用戶程序兩個文件描述符,filedes[0]指向管道的讀端,filedes[1]指向 管道 的寫端(很好記,就像0是標準輸入1是標準輸出同樣)。因此管道在用戶程序看起來就像一個打開的文件,經過read(filedes[0]);或者write(filedes[1]);向這個文件讀寫數據實際上是在讀寫內核緩衝區。pipe函數調用成功返回0,調用失敗返回-1。

 

 

1. 父進程調用pipe開闢管道,獲得兩個文件描述符指向管道的兩端。 
2. 父進程調用fork建立子進程,那麼子進程也有兩個文件描述符指向同一管道。 
3. 父進程關閉管道讀端,子進程關閉管道寫端。父進程能夠往管道里寫,子進程能夠從管道里讀,管道是用環形隊列實現的,數據從寫端流入從讀端流出,這樣就實現了進程間通訊
 
 
int Test1()         //Test Creat IPC Fun "pipe()"
{
           int _pipe[2];   //two file describe sign
           int ret = pipe(_pipe);
           if (ret == -1)
          {
                   perror( "create pipe error" );
                    return 1;
          }
          pid_t id = fork();
           if (id < 0)
          {
                   perror( "create fork error" );
                    return 1;
          }
           else if (id == 0)         //child only write,father only read
          { //child
                   close(_pipe[0]);
                    int i=0;
                    char * msg = "hello world" ;
                    while (i<5)
                   {
                             write(_pipe[1],msg,strlen(msg));
                             sleep(1);
                             ++i;
                   }
          }
           else
          { //father
                   close(_pipe[1]);
                    int i=0;
                    char buf[1024];
                    while (i<5)
                   {
                             read(_pipe[0],buf, sizeof (buf)-1);
                             printf( "%s\n" ,buf);
                             ++i;
                   }
          }
           return 0;
}
 
使用管道有一些限制:
兩個進程經過一個管道只能實現單向通訊。好比上面的例子,父進程寫子進程讀,若是有時候也須要子進程寫父進程讀,就必須另開一個管道。(緣由: 管道是一種半雙工方式,即對於進程來講,要麼只能讀管道,要麼只能寫管道。不容許對管道又讀又寫)<半雙工數據傳輸容許數據在兩個方向上傳輸,可是,在某一時刻,只容許數據在一個方向上傳輸>
 
管道的讀寫端經過打開的文件描述符來傳遞,所以要通訊的兩個進程必須從它們的公共祖先 那⾥裏繼承管道文件描述符。
父進程fork兩次,把文件描述符傳給兩個子進程,而後兩個子進程之間通訊, 總之須要經過fork傳遞文件描述符使兩個進程都能訪問同一管道,它們才能通訊。也就是說,管道通訊是須要進程之間有血緣關係。
 
⽤用管道須要注意如下4種特殊狀況(假設都是阻塞I/O操做,沒有設置O_NONBLOCK標誌):  
1. 若是全部指向管道寫端的文件描述符都關閉了(管道寫端的引⽤用計數等於0),而仍然有進程 從管道的讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會返回0,就像 讀到文件 末尾同樣。
2. 若是有指向管道寫端的文件描述符沒關閉(管道寫端的引用計數⼤大於0),而持有管道寫端的進程也沒有向管道中寫數據,這時有進程從管道讀端讀數據,那麼管道中剩餘的數據都被讀取後,再次read會阻塞,直到管道中有數據可讀了纔讀取數據並返回 
3. 若是全部指向管道讀端的文件描述符都關閉了(管道讀端的引用計數等於0),這時有進程向管道的寫端write,那麼該進程會收到信號SIGPIPE,一般會致使進程異常終止。
4. 若是有指向管道讀端的文件描述符沒關閉(管道讀端的引⽤用計數⼤大於0),而持有管道讀端的進程也沒有從管道中讀數據,這時有進程向管道寫端寫數據,那麼在管道被寫滿時再次write會阻塞,直到管道中有空位置了才寫入數據並返回。
 
 
 
 
命名管道(FIFO) 文件系統中的路徑名是全局的,各進程均可以訪問,所以能夠⽤用文件系統中的路徑名來標識一 個IPC通道。  命名管道也被稱爲FIFO文件,它是一種特殊類型的文件,它在文件系統中以文件名的形式存在,可是它的行爲卻和以前所講的沒有名字的管道(匿名管道)相似。
因爲Linux中全部的事物均可被視爲文件,因此對命名管道的使用也就變得與文件操做很是的統一,也使它的使用很是方便,同時咱們也能夠像日常的文件名同樣在命令中使用。 建立命名管道 咱們可使用兩下函數之一來建立一個命名管道,他們的原型以下:
#include <sys/types.h>  
#include <sys/stat.h>   
int mkfifo(const char *filename, mode_t mode);  
int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0);  
這兩個函數都能建立一個FIFO文件,注意是建立一個真實存在於文件系統中的文件, filename指定了文件名,而mode則指定了文件的讀寫權限。
mknod是比較老的函數,而使 用mkfifo函數更加簡單和規範,因此建議在可能的狀況下,儘可能使⽤用mkfifo而不是mknod。
 
 
//client.c
int main()
{
           int fp = open(_PATH_,O_RDONLY);
           if (fp < 0)
          {
                   perror( "open file fail" );
                    return -1;
          }
           char buf[_SIZE_];
          memset(buf, '\0' ,sizeof (buf));
           while (1)
          {
                    int ret = read(fp,buf,sizeof (buf)-1);
                    if (ret <= 0)
                   {
                             perror( "read fail of read over" );
                              return -1;
                   }
                   printf( "%s\n" ,buf);
                    if (strncmp(buf,"quit" ,4) == 0)
                              break ;
          }
          close(fp);
           return 0;
}
 
 
 
//server.c
int main()
{
           int ret = mkfifo(_PATH_,0644);
           if (ret == -1)
          {
                   perror( "mkfifo" );
                    return -1;
          }
 
           int fp = open(_PATH_,O_WRONLY);
           if (fp == -1)
          {
                   perror( "open file" );
                    return -2;
          }
           char buf[_SIZE_];
          memset(buf, '\0' ,sizeof (buf));
           while (1)
          {
                   scanf( "%s" ,buf);
                    int ret = write(fp,buf,strlen(buf)+1);
           //       sleep(5);
                    if (ret <= 0)
                   {
                             perror( "write error" );
                              return -3;
                   }
                    if (strncmp(buf,"quit" ,4) == 0)
                              break ;
          }
          close(fp);
           return 0;
}
相關文章
相關標籤/搜索