linux(2)- 共享内存的实现
目錄
- 問題
- 環境
- 問題分析及思路
- 程序文件說明和執行
- init.c
- sharedm-v2.c
- 相關截圖
問題
(1)X、Y兩個進程相互配合實現對輸入文件中數據的處理,并將處理結果寫入輸出文件。
(2)X進程負責分塊讀取輸入文件,并將輸入數據利用共享內存傳輸給Y進程。
(3)Y進程負責將讀入的數據(假定皆為文本數據)全部處理成大寫,然后寫入輸出文件。
(4)為提高并行效率,X、Y兩個進程之間創建2個共享內存區A、B。X讀入數據到A區,然后用LInux信號或信號量機制通知Y進程進行處理;在Y進程處理A區數據時,X繼續讀入數據到B區;B區數據被填滿之后,X進程通知Y進程處理,自己再繼續向A區讀入數據。如此循環直至全部數據處理完畢。
環境
??????Ubuntu20.04 64位虛擬機
問題分析及思路
??????上述問題中的核心在于以下幾點:1,c語言讀寫文件;2,構建兩個進程之間的共享內存;3,linux信號機制;4,實現兩個進程互斥
??????接下來一步步實現即可。
??????按照剛才的思路,我們可以從簡單的開始著手。
??????首先,比如c語言讀寫文件,這個比較簡單,無非就是打開文件,對文件操作,關閉文件這一套。然后,關于建立兩個進程,可以采用 fork()來實現或者編寫兩個獨立的程序文件,在這里我們采用 fork()來實現。
??????這樣,一個大體的框架就有了,我們采用Y進程作為父進程,用X進程作為子進程(X進程作為讀進程,它的工作一定比寫進程結束的早,所以將X進程作為子進程,一旦其工作結束,父進程采用執行完畢之后采用wait()將其回收,在邏輯也符合結束的先后順序)。
??????然后我們休息一下,洗把臉再繼續。
??????那如何在兩個進程之間創建共享內存呢?相關的函數為 shmget、shmat、shmdt和shmctl(shm 的意思是 shared-memory )。包含的頭文件為 <sys/ipc.h> 和<sys/shm.h>。
??????shmget函數的原型為 int shmget(key_t key, size_t size, int shmflg) ,它用來創建一個共享內存對象并返回一個共享內存標識符。下面僅當創建一個新的共享內存時進行相關參數的說明:參數 key 為0(IPC_PRIVATE),表示創建一個新的共享內存對象; size 表示新建共享內存的大小,單位為字節;shmflg 是標識符,簡單的可以認為對共享內存的讀寫權限,新建對象時設置其為 IPC_CREAT | 讀寫權限。創建成功時返回標識符ID,失敗返回-1。
(補充:linux中的權限說明。Linux 系統中常采用三位十進制數表示權限,并且會在前面加上0表示采用十進制形式。
如0755, 0644,對應的形式為ABCD,其中A- 0, 表示十進制,B-用戶,C-組用戶,D-其他用戶。
不同的權限分別用數字0-7來表示:
0 (no excute, no write, no read)
1 excute (no write, no read)
2 write
3 write, excute
4 read
5 read, excute
6 read, write
7 read, write, excute
0755->即用戶具有讀/寫/執行權限,組用戶和其它用戶具有讀執行權限;
0644->即用戶具有讀寫權限,組用戶和其它用戶具有只讀權限;
0600->僅擁有者具有文件的讀取和寫入權限
)
??????shmat函數的原型為 void *shmat(int shmid, const void *shmaddr, int shmflg),功能為:連接共享內存標識符為shmid的共享內存,連接成功后把共享內存區對象映射到調用進程的地址空間,隨后可像本地空間一樣訪問。參數shmid為shmget函數的返回值,參數shmaddr指定共享內存出現在進程內存地址的什么位置,直接指定為NULL讓內核自己決定一個合適的地址位置,參數shmflg表示進程對連接的共享內存的讀寫權限,可以采用三位十進制數來表示,函數執行成功:返回附加好的共享內存地址,出錯返回-1。
??????shmdt函數的原型為 int shmdt(const void *shmaddr),功能為:與shmat函數相反,是用來斷開與共享內存附加點的地址,禁止本進程訪問此片共享內存。參數shmaddr:連接的共享內存的起始地址,即shmat函數的返回值。函數執行成功:返回0,出錯返回-1。注意:這個函數僅僅是將本進程與共享內存的連接斷開,并非刪除共享內存。
??????shmctl函數的原型為 int shmctl(int shmid, int cmd, struct shmid_ds *buf),功能為:進行對共享內存的控制。參數shmid是共享內存標識符(shmget函數的返回值),參數cmd為IPC_RMID時表示刪除這片共享內存,參數buf是共享內存管理結構體,函數執行成功:返回0,出錯返回-1。
??????共享內存的各個函數示例如下:它創建一個大小為64B的新的共享內存對象并返回該共享內存的標識符,保存在變量 memoryID 中。
#include<sys/types.h> #include<sys/ipc.h> #include<sys/shm.h> #define SIZE 64memoryID=shmget(IPC_PRIVATE,SIZE,0600|IPC_CREAT); //創建一個大小為64B的新的共享內存對象并返回該共享內存的標識符,保存在變量 memoryID 中。僅對當前用戶具有讀寫權限。char* memoryAddr=(char*)shmat(memoryID,NULL,0200); //將標識符為memoryID的共享內存映射到本進程的地址空間中,本進程對其僅具有寫權限。返回共享內存的起始地址memoryAddr。shmdt((void*)memoryAddr); //斷開本進程與起始地址為memoryAddr的共享內存的連接。shmctl(memoryID,IPC_RMID,NULL); //刪除標識符為memoryID的共享內存。??????linux的信號機制 (更多的可以參考博客https://blog.csdn.net/qq_37653144/article/details/81942026)
??????在Linux中,可以通過signal和sigaction函數注冊信號并指定接收到該信號時需要完成的動作,對于已經有自己的功能動作的信號而言其注冊就是用一個用戶自己定義的動作去替換Linux內核預定義的動作。
??????signal函數可以為一個特定的信號(除了無法捕獲的SIGKILL和SIGSTOP信號)注冊相應的處理函數。其包含在頭文件#include <signal.h>中,原型為 void (*signal(int signum, void (*handler)(int)))(int);參數signum表示所注冊函數針對的信號名稱,參數handler通常是指向調用函數的函數指針,即所謂的信號處理函數。通俗的來講:進程A采用signal函數為信號a(參數signum)注冊了一個處理函數haha(參數handler),那么在進程A收到信號a的時候就會執行haha處理函數。
??????一般我們常采用sigaction函數,它在完成信號注冊工作的同時提供了更多的功能選擇。函數原型為 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)。參數signum指定要處理的信號,參數act和oldact都是指向信號動作結構的指針(即信號處理函數)。函數執行成功:返回0,出錯返回-1。
??????結構體struct sigaction {
??????void (*sa_handler)(int);
??????void (*sa_sigaction)(int, siginfo_t *, void *);
??????sigset_t sa_mask;
??????int sa_flags;
??????void (*sa_restorer)(void);
??????};
??????信號處理函數可以采用void (*sa_handler)(int)或void (*sa_sigaction)(int, siginfo_t *, void *)。到底采用哪個要看sa_flags中是否設置了SA_SIGINFO位,如果設置了就采用void (*sa_sigaction)(int, siginfo_t *, void *),此時可以向處理函數發送附加信息;默認情況下采用void (*sa_handler)(int),此時只能向處理函數發送信號的數值。當設置sa_flags為0時,sa_handler此參數和signal()的參數handler相同,代表新的信號處理函數,其他參數暫時可忽略。
??????kill函數,它給指定進程發送指定信號,原型為 int kill(pid_t pid, int sig);參數pid為接受參數sig信號的進程。函數執行成功:返回0,出錯返回-1。
??????關于信號機制的示例如下:
#include<signal.h> #include<sys/types.h>void sig_usr(int signum);//信號處理函數 handlerstruct sigaction sa_usr; sa_usr.sa_flags=0; sa_usr.sa_handler=sig_usr;//信號處理函數sig_usr與sigaction結構體關聯起來 if(sigaction(SIGUSR1,&sa_usr,NULL))//為信號SIGUSR1注冊信號處理函數sig_usr。printf("Error in sigaction().SIGUSR1\n"); //至此,一旦本進程收到信號SIGUSR1,就會執行處理函數sig_usrkill(pid,SIGUSR1);//對進程id為pid的進程發送信號SIGUSR1??????進程互斥機制的實現
??????進程互斥可以采用信號量來實現,這是一種優秀的方式,但是實現起來稍有些復雜,所以我們采用簡單的循環判定的方法來實現互斥。為什么要實現互斥?原因在于對同一塊內存任意一個時刻只能有一個進程去訪問它,即寫滿了才能讀,讀完了才能寫。
??????在此采用幾個標記來表示每個進程對每塊內存的可用狀態,分別為變量X1,X2,Y1 和 Y2。其中X1和X2表示X進程對兩塊內存的可用狀態(是否可讀),Y1和Y2表示Y進程對兩塊內存的可用狀態(是否可寫)。初始時將X1和X2均置為1,表示X進程可讀,一旦內存1被X進程讀入數據,設置X1為0,當讀入完畢時,發送信號給Y進程,相應的處理函數將Y1置為1,從而Y進程從該內存中轉換數據并寫入到輸出文件。重復上述流程,直至所有數據轉換傳輸結束。
程序文件說明和執行
??????兩個c文件,分別為init.c 文件和sharedm-v2.c,將二者放在同一目錄下面,先編譯并執行 init.c 文件,會得到一個 helloIn.txt 輸入文件。后編譯并執行 sharedm-v2.c 文件,生成 helloOut.txt 目標文件。
??????輸入文件大小通過init.c文件可以修改,當前大小為26*100字節,兩塊共享內存均為64字節。
init.c
#include<stdio.h>int main() {FILE* fp=fopen("helloIn.txt","w");char temp;for(int i=0;i<100;i++){temp='a';for(int j=0;j<26;j++){fputc(temp,fp);temp++;}}fclose(fp);return 0; }sharedm-v2.c
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/shm.h> #include<string.h> #include<signal.h> #include<wait.h> #define SIZE 64 #define C 10000 //延時功能volatile int X1=1; volatile int X2=1; //初始時兩塊內存1和2對X內存均可用,均設為1。 volatile int Y1=0; volatile int Y2=0; //初始時兩塊內存1和2對X內存均不可用,均設為0。void sig_usrX(int signum);//信號處理函數 void sig_usrY(int signum);//信號處理函數void X_Process(FILE* fp,volatile int* flag,int memoryID,volatile int* X,int pid,int sig);//X進程寫內存操作 void Y_Process(FILE* fp,volatile int* flag,int memoryID,volatile int* Y,int ppid,int sig);//Y進程讀內存操作int main() {pid_t pid;int memoryID1,memoryID2;//共享內存標識符char* memoryAddr=NULL;memoryID1=shmget(IPC_PRIVATE,SIZE,0600|IPC_CREAT);//讀寫權限memoryID2=shmget(IPC_PRIVATE,SIZE,0600|IPC_CREAT);//讀寫權限if(memoryID1<0||memoryID2<0)printf("Get memory with error.");if((pid=fork())<0)//出錯返回printf("Fork error!\n");else if(pid>0)//父進程作為Y進程{printf("parentID:%d\n",getpid());FILE* fp=fopen("helloOut.txt","w");if(!fp){printf("Fail at open the file\n");return 0;}struct sigaction sa_usr;sa_usr.sa_flags=0;sa_usr.sa_handler=sig_usrY;//信號處理函數if(sigaction(SIGUSR1,&sa_usr,NULL))printf("Error in sigaction().SIGUSR1\n");if(sigaction(SIGUSR2,&sa_usr,NULL))printf("Error in sigaction().SIGUSR2\n");volatile int flag=1;//退出時處理標志while(flag){if(Y1==1)Y_Process(fp,&flag,memoryID1,&Y1,pid,SIGUSR1);if(Y2==1)Y_Process(fp,&flag,memoryID2,&Y2,pid,SIGUSR2);}fclose(fp); if(shmctl(memoryID1,IPC_RMID,NULL)==0)printf("Release the sharememory1.\n");//由Y進程來釋放掉共享內存,因為它結束的比較晚if(shmctl(memoryID2,IPC_RMID,NULL)==0)printf("Release the sharememory2.\n");printf("Y process will quit.\n");}else//子進程作為X進程{int ppid=getppid();printf("childID:%d\n",getpid());FILE* fp=fopen("helloIn.txt","r");if(!fp){printf("Fail ot open the file\n");return 0;}struct sigaction sa_usr;sa_usr.sa_flags=0;sa_usr.sa_handler=sig_usrX;//信號處理函數if(sigaction(SIGUSR1,&sa_usr,NULL))printf("Error in sigaction().SIGUSR1\n");if(sigaction(SIGUSR2,&sa_usr,NULL))printf("Error in sigaction().SIGUSR2\n");volatile int flag=1;//退出時處理標志while(flag){if(X1==1)X_Process(fp,&flag,memoryID1,&X1,ppid,SIGUSR1);else if(X2==1)X_Process(fp,&flag,memoryID2,&X2,ppid,SIGUSR2);}fclose(fp); printf("X process will quit.\n");}return 0; }void sig_usrX(int signum) {if(signum==SIGUSR1)X1=1;else if(signum==SIGUSR2)X2=1;return; } void sig_usrY(int signum) {if(signum==SIGUSR1)Y1=1;else if(signum==SIGUSR2)Y2=1;return; } void X_Process(FILE* fp,volatile int* flag,int memoryID,volatile int* X,int pid,int sig) {*X=0;char* memoryAddr=(char*)shmat(memoryID,NULL,0200);//連接共享內存(寫)char* memoryAddrX=memoryAddr;int count=0;char temp;while(1){if(count<SIZE){temp=fgetc(fp);if(temp==EOF)break;int index=0;while(index<C)index++;*(memoryAddrX++)=temp;count++;}else{if(shmdt((void*)memoryAddr))//斷開與共享內存的連接printf("Error in shmdt().");kill(pid,sig);break;}}if(temp==EOF){*flag=0;*memoryAddrX='\0';//'\0'作為文件尾在共享內存中的結束標志if(count!=0)kill(pid,sig);//最后一塊未滿的共享內存讓Y進程來處理。if(shmdt((void*)memoryAddr))//斷開與共享內存的連接printf("Error in shmdt().");printf("Last time for shmdt.X\n");}return; } void Y_Process(FILE* fp,volatile int* flag,int memoryID,volatile int* Y,int ppid,int sig) {*Y=0;char* memoryAddr=(char*)shmat(memoryID,NULL,0400);//連接共享內存(讀) 0400//if(memoryAddr==-1)//printf("Error in shmat(),Y\n");char* memoryAddrY=memoryAddr;int count=0;char temp;while(1){ if(count<SIZE){temp=*memoryAddrY;if(temp=='\0')break; //X進程會設置內存結尾為'\0'int index=0;while(index<C)index++;temp-=32;//轉換小寫字母為大寫字母fputc(temp,fp);memoryAddrY++;count++;}else{if(shmdt((void*)memoryAddr))//斷開與共享內存的連接printf("Error in shmdt().");kill(ppid,sig);break;}}if(temp == '\0'){*flag=0;if(shmdt((void*)memoryAddr))//斷開與共享內存的連接printf("Error in shmdt().");printf("Last time for shmdt.Y\n");}return; }相關截圖
??????程序執行截圖:
??????查看輸入輸出文件內容:
總結
以上是生活随笔為你收集整理的linux(2)- 共享内存的实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux(1)- 简单的 shell
- 下一篇: linux(4)-Ptrace 系统调用