IPC進程間通信學習筆記[1]

進程間通信的各種模式

  • Pipe
  • Message Queue
  • Shared Memory

管道

還記得咱們最初學 Linux 命令的時候,有下面這樣一行命令:

ps -ef | grep 關(guān)鍵字 | awk '{print $2}' | xargs kill -9

這里面的豎線“|”就是一個管道溺职。它會將前一個命令的輸出隆嗅,作為后一個命令的輸入霍弹。從管道的這個名稱可以看出來谒撼,管道是一種單向傳輸數(shù)據(jù)的機制,它其實是一段緩存熄捍,里面的數(shù)據(jù)只能從一端寫入烛恤,從另一端讀出。如果想互相通信余耽,我們需要創(chuàng)建兩個管道才行棒动。管道分為兩種類型,“|” 表示的管道稱為匿名管道宾添,意思就是這個類型的管道沒有名字船惨,用完了就銷毀了。就像上面那個命令里面的一樣缕陕,豎線代表的管道隨著命令的執(zhí)行自動創(chuàng)建粱锐、自動銷毀。用戶甚至都不知道自己在用管道這種技術(shù)扛邑,就已經(jīng)解決了問題怜浅。所以這也是面試題里面經(jīng)常會問的,到時候千萬別說這是豎線蔬崩,而要回答背后的機制恶座,管道。
另外一種類型是命名管道沥阳。這個類型的管道需要通過 mkfifo 命令顯式地創(chuàng)建跨琳。

mkfifo hellohello

就是這個管道的名稱。管道以文件的形式存在桐罕,這也符合 Linux 里面一切皆文件的原則脉让。這個時候桂敛,我們 ls 一下,可以看到溅潜,這個文件的類型是 p术唬,就是 pipe 的意思。

ls -lprw-r--r-- 1 root root 0 May 21 23:29 hello

接下來滚澜,我們可以往管道里面寫入東西粗仓。例如,寫入一個字符串设捐。

echo "hello world" > hello

這個時候借浊,管道里面的內(nèi)容沒有被讀出,這個命令就是停在這里的挡育,這說明當一個項目組要把它的輸出交接給另一個項目組做輸入巴碗,當沒有交接完畢的時候,前一個項目組是不能撒手不管的即寒。這個時候橡淆,我們就需要重新連接一個終端。在終端中母赵,用下面的命令讀取管道里面的內(nèi)容:

cat < hello hello world

一方面逸爵,我們能夠看到,管道里面的內(nèi)容被讀取出來凹嘲,打印到了終端上师倔;另一方面,echo 那個命令正常退出了周蹭,也即交接完畢趋艘,前一個項目組就完成了使命,可以解散了凶朗。

消息隊列

和管道將信息一股腦兒地從一個進程瓷胧,倒給另一個進程不同,消息隊列有點兒像郵件棚愤,發(fā)送數(shù)據(jù)時搓萧,會分成一個一個獨立的數(shù)據(jù)單元,也就是消息體宛畦,每個消息體都是固定大小的存儲塊瘸洛,在字節(jié)流上不連續(xù)。

消息結(jié)構(gòu)的定義

struct msg_buffer { long mtype; char mtext[1024];};

接下來次和,我們需要創(chuàng)建一個消息隊列反肋,使用 msgget 函數(shù)。這個函數(shù)需要有一個參數(shù) key斯够,這是消息隊列的唯一標識囚玫,應該是唯一的喧锦。如何保持唯一性呢读规?這個還是和文件關(guān)聯(lián)抓督。我們可以指定一個文件,ftok 會根據(jù)這個文件的 inode束亏,生成一個近乎唯一的 key铃在。只要在這個消息隊列的生命周期內(nèi),這個文件不要被刪除就可以了碍遍。只要不刪除定铜,無論什么時刻,再調(diào)用 ftok怕敬,也會得到同樣的 key揣炕。這種 key 的使用方式在這一章會經(jīng)常遇到,這是因為它們都屬于 System V IPC 進程間通信機制體系中东跪。


#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>


int main() {
  int messagequeueid;
  key_t key;


  if((key = ftok("/root/messagequeue/messagequeuekey", 1024)) < 0)
  {
      perror("ftok error");
      exit(1);
  }


  printf("Message Queue key: %d.\n", key);


  if ((messagequeueid = msgget(key, IPC_CREAT|0777)) == -1)
  {
      perror("msgget error");
      exit(1);
  }


  printf("Message queue id: %d.\n", messagequeueid);
}

ipcs -q就能看到上面我們創(chuàng)建的消息隊列對象畸陡。


------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x00288297 0          root       777        0            0 

發(fā)送消息主要調(diào)用 msgsnd 函數(shù)。第一個參數(shù)是 message queue 的 id虽填,第二個參數(shù)是消息的結(jié)構(gòu)體丁恭,第三個參數(shù)是消息的長度,最后一個參數(shù)是 flag斋日。這里 IPC_NOWAIT 表示發(fā)送的時候不阻塞牲览,直接返回。

#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <getopt.h>
#include <string.h>


struct msg_buffer {
    long mtype;
    char mtext[1024];
};


int main(int argc, char *argv[]) {
  int next_option;
  const char* const short_options = "i:t:m:";
  const struct option long_options[] = {
    { "id", 1, NULL, 'i'},
    { "type", 1, NULL, 't'},
    { "message", 1, NULL, 'm'},
    { NULL, 0, NULL, 0 }
  };

  int messagequeueid = -1;
  struct msg_buffer buffer;
  buffer.mtype = -1;
  int len = -1;
  char * message = NULL;
  do {
    next_option = getopt_long (argc, argv, short_options, long_options, NULL);
    switch (next_option)
    {
      case 'i':
        messagequeueid = atoi(optarg);
        break;
      case 't':
        buffer.mtype = atol(optarg);
        break;
      case 'm':
        message = optarg;
        len = strlen(message) + 1;
        if (len > 1024) {
          perror("message too long.");
          exit(1);
        }
        memcpy(buffer.mtext, message, len);
        break;
      default:
        break;
    }
  }while(next_option != -1);


  if(messagequeueid != -1 && buffer.mtype != -1 && len != -1 && message != NULL){
    if(msgsnd(messagequeueid, &buffer, len, IPC_NOWAIT) == -1){
      perror("fail to send message.");
      exit(1);
    }
  } else {
    perror("arguments error");
  }

  return 0;
}

收消息主要調(diào)用 msgrcv 函數(shù)恶守,第一個參數(shù)是 message queue 的 id第献,第二個參數(shù)是消息的結(jié)構(gòu)體,第三個參數(shù)是可接受的最大長度兔港,第四個參數(shù)是消息類型, 最后一個參數(shù)是 flag庸毫,這里 IPC_NOWAIT 表示接收的時候不阻塞,直接返回押框。


#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <getopt.h>
#include <string.h>


struct msg_buffer {
    long mtype;
    char mtext[1024];
};


int main(int argc, char *argv[]) {
  int next_option;
  const char* const short_options = "i:t:";
  const struct option long_options[] = {
    { "id", 1, NULL, 'i'},
    { "type", 1, NULL, 't'},
    { NULL, 0, NULL, 0 }
  };

  int messagequeueid = -1;
  struct msg_buffer buffer;
  long type = -1;
  do {
    next_option = getopt_long (argc, argv, short_options, long_options, NULL);
    switch (next_option)
    {
      case 'i':
        messagequeueid = atoi(optarg);
        break;
      case 't':
        type = atol(optarg);
        break;
      default:
        break;
    }
  }while(next_option != -1);


  if(messagequeueid != -1 && type != -1){
    if(msgrcv(messagequeueid, &buffer, 1024, type, IPC_NOWAIT) == -1){
      perror("fail to recv message.");
      exit(1);
    }
    printf("received message type : %d, text: %s.", buffer.mtype, buffer.mtext);
  } else {
    perror("arguments error");
  }

  return 0;
}

接下來岔绸,我們可以編譯并運行這個發(fā)送程序∠鹕。可以看到盒揉,如果有消息,可以正確地讀到消息兑徘;如果沒有刚盈,則返回沒有消息。

# ./recv -i 0 -t 123
received message type : 123, text: hello world.
# ./recv -i 0 -t 123
fail to recv message.: No message of desired type

共享內(nèi)存

有時候挂脑,項目組之間的溝通需要特別緊密藕漱,而且要分享一些比較大的數(shù)據(jù)欲侮。如果使用郵件,就發(fā)現(xiàn)肋联,一方面郵件的來去不及時威蕉;另外一方面,附件大小也有限制橄仍,所以韧涨,這個時候,我們經(jīng)常采取的方式就是侮繁,把兩個項目組在需要合作的期間虑粥,拉到一個會議室進行合作開發(fā),這樣大家可以直接交流文檔呀宪哩,架構(gòu)圖呀娩贷,直接在白板上畫或者直接扔給對方,就可以直接看到锁孟”蜃妫可以看出來,共享會議室這種模型罗岖,類似進程間通信的共享內(nèi)存模型涧至。前面咱們講內(nèi)存管理的時候,知道每個進程都有自己獨立的虛擬內(nèi)存空間桑包,不同的進程的虛擬內(nèi)存空間映射到不同的物理內(nèi)存中去南蓬。這個進程訪問 A 地址和另一個進程訪問 A 地址,其實訪問的是不同的物理內(nèi)存地址哑了,對于數(shù)據(jù)的增刪查改互不影響赘方。但是,咱們是不是可以變通一下弱左,拿出一塊虛擬地址空間來窄陡,映射到相同的物理內(nèi)存中。這樣這個進程寫入的東西拆火,另外一個進程馬上就能看到了跳夭,都不需要拷貝來拷貝去,傳來傳去们镜。共享內(nèi)存也是 System V IPC 進程間通信機制體系中的币叹,所以從它使用流程可以看到熟悉的面孔。

int shmget(key_t key, size_t size, int flag);

創(chuàng)建完畢之后模狭,我們可以通過 ipcs 命令查看這個共享內(nèi)存颈抚。

#ipcs ----shmems
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status           
0x0000162e 65594      root       666        27         0  

接下來,如果一個進程想要訪問這一段共享內(nèi)存嚼鹉,需要將這個內(nèi)存加載到自己的虛擬地址空間的某個位置贩汉,通過 shmat 函數(shù)驱富,就是 attach 的意思。其中 addr 就是要指定 attach 到這個地方匹舞。但是這個地址的設(shè)定難度比較大褐鸥,除非對于內(nèi)存布局非常熟悉,否則可能會 attach 到一個非法地址策菜。所以晶疼,通常的做法是將 addr 設(shè)為 NULL酒贬,讓內(nèi)核選一個合適的地址又憨。返回值就是真正被 attach 的地方。

void *shmat(int shm_id, const void *addr, int flag);

如果共享內(nèi)存使用完畢锭吨,可以通過 shmdt 解除綁定蠢莺,然后通過 shmctl,將 cmd 設(shè)置為 IPC_RMID零如,從而刪除這個共享內(nèi)存對象躏将。

int shmdt(void *addr); int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

shm_server.c

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>

#define SHMSZ     27

main()
{
    char c;
    int shmid;
    key_t key;
    char *shm, *s;

    /*
     * We'll name our shared memory segment
     * "5678".
     */
    key = 5678;

    /*
     * Create the segment.
     */
    if ((shmid = shmget(key, SHMSZ, IPC_CREAT | 0666)) < 0) {
        perror("shmget");
        exit(1);
    }

    /*
     * Now we attach the segment to our data space.
     */
    if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) {
        perror("shmat");
        exit(1);
    }

    /*
     * Now put some things into the memory for the
     * other process to read.
     */
    s = shm;

    for (c = 'a'; c <= 'z'; c++)
        *s++ = c;
    *s = NULL;

    /*
     * Finally, we wait until the other process
     * changes the first character of our memory
     * to '*', indicating that it has read what
     * we put there.
     */
    while (*shm != '*')
        sleep(1);

    exit(0);
}

shm_client.c

/*
 * shm-client - client program to demonstrate shared memory.
 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>

#define SHMSZ     27

main()
{
    int shmid;
    key_t key;
    char *shm, *s;

    /*
     * We need to get the segment named
     * "5678", created by the server.
     */
    key = 5678;

    /*
     * Locate the segment.
     */
    if ((shmid = shmget(key, SHMSZ, 0666)) < 0) {
        perror("shmget");
        exit(1);
    }

    /*
     * Now we attach the segment to our data space.
     */
    if ((shm = shmat(shmid, NULL, 0)) == (char *) -1) {
        perror("shmat");
        exit(1);
    }

    /*
     * Now read what the server put in the memory.
     */
    for (s = shm; *s != NULL; s++)
        putchar(*s);
    putchar('\n');

    /*
     * Finally, change the first character of the
     * segment to '*', indicating we have read
     * the segment.
     */
    *shm = '*';

    exit(0);
}

test:

./shm_server
./shm_client 
abcdefghijklmnopqrstuvwxyz
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市考蕾,隨后出現(xiàn)的幾起案子祸憋,更是在濱河造成了極大的恐慌,老刑警劉巖肖卧,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚯窥,死亡現(xiàn)場離奇詭異,居然都是意外死亡塞帐,警方通過查閱死者的電腦和手機拦赠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葵姥,“玉大人荷鼠,你說我怎么就攤上這事±菩遥” “怎么了允乐?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長削咆。 經(jīng)常有香客問我牍疏,道長,這世上最難降的妖魔是什么态辛? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任麸澜,我火速辦了婚禮,結(jié)果婚禮上奏黑,老公的妹妹穿的比我還像新娘炊邦。我一直安慰自己编矾,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布馁害。 她就那樣靜靜地躺著窄俏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪碘菜。 梳的紋絲不亂的頭發(fā)上凹蜈,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音忍啸,去河邊找鬼仰坦。 笑死,一個胖子當著我的面吹牛计雌,可吹牛的內(nèi)容都是我干的悄晃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼凿滤,長吁一口氣:“原來是場噩夢啊……” “哼妈橄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起翁脆,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤眷蚓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后反番,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沙热,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年恬口,在試婚紗的時候發(fā)現(xiàn)自己被綠了校读。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡祖能,死狀恐怖歉秫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情养铸,我是刑警寧澤雁芙,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站钞螟,受9級特大地震影響兔甘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鳞滨,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一洞焙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦澡匪、人聲如沸熔任。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疑苔。三九已至,卻和暖如春甸鸟,著一層夾襖步出監(jiān)牢的瞬間惦费,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工抢韭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留薪贫,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓篮绰,卻偏偏與公主長得像后雷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子吠各,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容