1. 程式人生 > 其它 >c++ fork 程序時 共享記憶體_Linux筆記(16)| 程序同步機制——管道和IPC

c++ fork 程序時 共享記憶體_Linux筆記(16)| 程序同步機制——管道和IPC

技術標籤:c++ fork 程序時 共享記憶體fork子程序shmget共享資料到父程序linux 管道linux管道

今天要分享的是Linux程序的同步機制,包括管道和IPC。之前學習的訊號也有控制程序同步的作用,但是訊號僅僅傳輸很少的資訊,而且系統開銷大,所以這裡再介紹幾種其他的程序同步機制。在之前的一篇文章中有提到相關內容,但是當時沒有詳細展開,可以回顧一下:Linux筆記(10)| 程序概述。 一、管道(無名管道) 一般說管道都是指無名 管道(也稱匿名管道),有名管道(也稱命名管道)不會直接說管道,肯定會加上字首。管道是Linux下最常見的程序間的通訊方式之一,它是在兩個程序之間實現一個數據流通的通道。它有以下特點:1、管道一般是半雙工的,資料只能向一個方向流動。2、通常只能在父子程序或者兄弟程序之間使用。3、管道是一種特殊的檔案,並且只存在於記憶體當中……接下來說一下管道的使用:建立管道
int pipe(int pipefd[2]);
輸入引數是一個數組,實際上這個輸入引數是當做輸出來用的,呼叫這個函式,成功的話數組裡就會儲存兩個檔案描述符,並且返回0,失敗返回-1.其中檔案描述符fd[0]是讀端,fd[1]是寫端,這是固定不變的。剛剛我們也說了,建立管道肯定是為了父子程序或者兄弟程序之間通訊的,單獨在一個程序裡面使用管道毫無意義。所以父程序建立好管道之後,再呼叫fork函式建立子程序,子程序就會繼承那個管道,於是父子間可以約定誰來讀,誰來寫。因為是半雙工通訊,所以只能一個讀,一個寫,如果想要兩端都能讀寫,那就要建立兩個管道。比如父程序讀,子程序寫,那麼父程序可以先close(fd[1]),然後read(fd[0]),子程序先close(fd[0]),然後write(fd[1]).讀寫的時候只要把他當做普通的檔案就行了,和普通的檔案描述符的讀寫一樣,但是有一點不一樣的是普通檔案讀完了資料還在,而管道讀完之後資料就沒了。 二、有名管道管道只能在有親緣關係的程序之間實現通訊,但是有名管道可以在任何兩個程序之間實現通訊。有名管道嚴格遵循先進先出的規則,不支援lseek函式等檔案定位操作。首先建立一個有名管道:
int mkfifo(const char*pathname,mode_t mode);
pathname引數是一個普通的路徑名,也就是建立後有名管道檔案的名字,mode引數是檔案的操作許可權,呼叫成功返回0,呼叫失敗返回-1.接下來就可以使用open或者fopen函式開啟剛剛建立的有名管道檔案,對其進行讀寫操作了。 三、System VIPC機制IPC機制由訊息佇列、訊號量以及共享記憶體三種具體實現方法組成。首先要了解兩個概念, 識別符號關鍵字。每一個IPC結構(訊息佇列或者訊號量或者共享記憶體)都有一個識別符號,這是一個非負整數,每建立一個IPC結構,相應的識別符號就會加1,這個識別符號在相同的結構中是唯一的,也就是說,如果“666”是某個訊息佇列的識別符號,那麼肯定不會有第二個訊息佇列的識別符號也是“666”。但是需要注意的是,可能某個共享記憶體的識別符號也是“666”。 於是,還得用一個東西來區分,那就是關鍵字了 。所以,根據關鍵字和識別符號可以唯一確定一個IPC結構。 IPC的關鍵字一般可以使用IPC_PRIVATE,也可以使用ftok函式獲得,他們有一些區別,後面會提到。 ftok函式的使用:
key_tftok(constchar*pathname,int proj_id);
ftok函式是用於將一個路徑和專案ID轉換為關鍵字,第一個引數必須是一個存在的、可以訪問的檔案路徑名,第二個引數 專案ID只有低八位有效。 在建立一個IPC物件的時候,他們有一些共同的特點: 我們先來看一下IPC物件建立函式: 1、建立訊息佇列
intmsgget(key_tkey,intmsgflg);
2、建立訊號量
int semget(key_t key,int nsems,int semflg);
3、建立共享記憶體
int shmget(key_t ,size_t size,int shmflg);
可以發現,他們都有一個關鍵字key,都有一個flag引數。在使用上,也有一些共同的特點:當key使用IPC_PRIVATE時,作業系統保證建立一個唯一的IPC物件,此時flag引數僅決定物件的存取許可權。當key使用ftok函式得到的關鍵字時,flag引數不僅決定物件的存取許可權,還和建立方式有關,具體就是:設定flag引數的IPC_CREAT位,但不設定IPC_EXCL位,如果不存在指定key的IPC物件,就建立,如果存在,就返回該物件。同時設定 IPC_C REAT位和 IPC_ EXCL位,如果物件不存在就建立,如果已經存在,則返回錯誤。這和檔案操作函式open是類似的。接下來介紹一下各個IPC物件涉及到的API函式。由於具體的細節比較多,這裡就不一一羅列了,可以使用man手冊來檢視具體細節。1、訊息佇列建立:用來建立或者開啟一個訊息佇列
int msgget(key_t key,int msgflg);
控制:其中cmd命令引數可以獲取或者設定buf裡的內容,也可以刪除這個訊息佇列(看具體的引數設定)
intmsgctl(intmsqid,int cmd,struct msqid_ds *buf);
傳送:向訊息佇列裡傳送訊息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
接收:從訊息佇列接受訊息
ssize_tmsgrcv(intmsqid,void*msgp,size_tmsgsz,longmsgtyp,intmsgflg);
2、訊號量建立: 用來建立或者開啟一個訊號量
int semget(key_t key, int nsems, int semflg);
操作:可以申請或者釋放訊號量。
int semop(int semid, struct sembuf *sops, size_t nsops);
控制:可以設定或返回訊號量的值,可以刪除訊號量
int semctl(int semid, int semnum, int cmd, ...);
3、共享記憶體建立:用來建立或者開啟一個共享記憶體。
int shmget(key_t key, size_t size, int shmflg);
連線:將共享記憶體附加到程序裡的地址空間(有點像對映)
void *shmat(int shmid, const void *shmaddr, int shmflg);
脫離:將共享記憶體和程序地址空間脫離,但不刪除共享記憶體本身
void *shmat(int shmid, const void *shmaddr, int shmflg);
設定屬性:可以設定或者返回buf裡的內容,也可以刪除共享記憶體。
 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
接下來寫一段程式碼,需求是:建立一個共享記憶體,子程序寫入資料,父程序讀出資料,同時使用訊號量來對讀寫進行保護
#include #include #include #include #include #include #include #include #define  SHMSIZE  128int main(int argc,char* argv[]){  if(argc !=2){    printf("please input param\n");    exit(0);  }  key_t semkey,shmkey;  int semid,shmid,pid;  char* shmp;  struct sembuf locksembuf={0,-1,SEM_UNDO};  struct sembuf unlocksembuf={0,1,SEM_UNDO|IPC_NOWAIT};  struct shmid_ds shmdsbuff;  semkey=ftok(argv[1],0);  if(semkey==-1){    printf("semkey ftok failed\n");    exit(0);  }  shmkey=ftok(argv[1],1);  if(semkey==-1){    printf("shmkey ftok failed\n");    exit(0);  }  semid=semget(semkey,1,IPC_CREAT | 0666);        //建立訊號量  if(semid==-1){    perror("semget");    exit(0);  }  shmid=shmget(shmkey,SHMSIZE,0666 | IPC_CREAT );      //建立共享記憶體  if(shmid==-1)      {    perror("shmget");    exit(0);  }  printf("creat share memory success\n");  shmp=shmat(shmid,NULL,0);                             //連線共享記憶體(對映地址)  if((long)shmp==-1)  {     perror("shmat failed\n");    exit(0);  }  pid=fork();  if(pid<0)  {    perror("fork failed\n");    exit(0);  }    else if(pid==0)                              //子程序  {    printf("creat child process success\n");    if(semctl(semid,0,SETVAL,1)<0)             //初始化訊號量為1    {      perror("semctl");      exit(0);    }    if(semop(semid,&locksembuf,1)==-1)          //申請資源    {      perror("semop");      exit(0);    }    sleep(4);    strcpy(shmp,"hello world\n");    if(shmdt((void*)shmp)<0)        //使共享記憶體脫離程序地址空間    {      perror("shmdt");      exit(0);    }    if(semop(semid,&unlocksembuf,1)<0)      //解鎖臨界資源    {      perror("child process unlock semop");      exit(0);    }  }  else                                      //父程序  {    printf("This is parent process\n");    sleep(1);    int ret=semop(semid,&locksembuf,1);    if(ret<0)    {      perror("parent process semop lock");      exit(0);    }    if(shmctl(shmid,IPC_STAT,&shmdsbuff)<0)      //父程序獲取共享記憶體的資訊    {      perror("shmctl");      exit(0);    }        else{      printf("get shm success\n");      printf("Shared Memory Infomation:\n");      printf("\tCreator PID:%d\n",shmdsbuff.shm_cpid);      printf("\tSize(bytes):%ld\n",shmdsbuff.shm_segsz);      printf("\tLast Operator PID :%d\n",shmdsbuff.shm_lpid);      printf("Receive message:%s\n",(char*)shmp);    }    if(shmdt((void*)shmp)<0)    {      perror("parent process shmdt");      exit(0);    }    ret=semop(semid,&unlocksembuf,1);    if(ret<0)    {      perror("parent process semop");      exit(0);    }    if(shmctl(shmid,IPC_RMID,NULL)<0)    {      perror("shmctl RMID");      exit(0);    }    if(semctl(semid,0,IPC_RMID,NULL)<0)    {      perror("semctl RMID");      exit(0);    }  }  return 0;}
接下來編譯執行
gcc sem.c./a.outa.txt//注意,當前目錄下需要有一個a.txt,其他檔案也行
輸出:

bc36c9c1b9df8aedaabb1c27eb8d5711.png

以上就是今天的內容。

61eadc36a43b5639b89c13e75b55ab12.gif