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