1. 程式人生 > >【作業系統】程序之間的通訊機制

【作業系統】程序之間的通訊機制

一、Linux程序間通訊:管道、訊息佇列、共享記憶體;

       程序是一個獨立的資源分配單位,不同程序之間的資源是相互獨立的,沒有關聯,不能在一個程序中直接訪問另一個程序中的資源。

       所以程序需要一些手段來進行程序間資料傳遞、同步與非同步的機制。

====================================================================================

① 管道

          ② 訊息佇列

  ③ 共享記憶體

=============================================================================

二、編寫程式實現匿名管道、FIFO、共享記憶體、訊息佇列的通訊示例

① 匿名管道

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

int main(){
	pid_t pid;
	char buf[128];//傳送的訊息
	int fds_f[2];//子程序聯絡父程序管道 fds_f[0]讀 fds_f[1]寫
	int fds_c[2];//父程序聯絡子程序管道 fds_c[0]讀 fds_c[1]寫
	pipe(fds_f);//給fds_f陣列建立管道
	pipe(fds_c);//給fds_c陣列建立管道
	pid = fork();//生成子程序
	if(pid){//父程序
		printf("1. 這裡是父程序\n");
		strcpy(buf,"來自父程序的資訊。");
		write(fds_c[1],buf,sizeof(buf));//父程序向子程序傳送訊息
		read(fds_f[0],buf,sizeof(buf));//接收來自子程序的訊息
		printf("1. 父程序收到訊息:%s\n",buf);
	}
	else if(pid==0){//子程序
		sleep(1);//休眠1秒
		printf("2. 這裡是子程序\n");
		strcpy(buf,"來自子程序的資訊。");
		write(fds_f[1],buf,sizeof(buf));//子程序向父程序傳送訊息
		read(fds_c[0],buf,sizeof(buf));//接收來自父程序的訊息
		printf("2. 子程序收到訊息:%s\n",buf);
	}
}

1編號代表父程序,2編號代表子程序。子程序休眠1秒。所以父程序傳送訊息給子程序,子程序立刻收到父程序資訊,輸出後,傳送訊息給父程序。

====================================================================================

②  FIFO
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
int main(){
	pid_t pid;
	char buffer[80];//傳輸的資訊
	int fd;//操作編號
	char *FIFO = "fifo1";//FIFO有名管道檔名稱
	mkfifo(FIFO,0666);//建立有名管道檔案 
	pid = fork();
	if(pid){//父程序
		printf("1. 父程序。\n");
		//將“來自父程序的資訊”複製給buffer
		strcpy(buffer,"來自父程序的資訊。");
		fd = open(FIFO,O_WRONLY);//開啟管道檔案,只寫 
		write(fd,buffer,sizeof(buffer));//往管道檔案中寫入資訊
		close(fd);//關閉管道檔案

		fd = open(FIFO,O_RDONLY);//開啟管道檔案,只讀
		sleep(1);//沉睡一秒鐘
		read(fd,buffer,sizeof(buffer));//讀取管道檔案的資訊
		printf("1. 父程序收到資訊 :%s\n",buffer);
		close(fd);//關閉管道檔案
	}
	else if(pid==0){//子程序
		printf("2. 子程序。\n");
		fd = open(FIFO,O_RDONLY);//開啟管道檔案,只讀
		read(fd,buffer,80);//讀取管道檔案的資訊
		printf("2. 子程序收到資訊:%s\n",buffer);
		close(fd);//關閉管道檔案

		fd = open(FIFO,O_WRONLY);//開啟管道檔案,只寫
		//將“來自子程序的資訊”複製給buffer
		strcpy(buffer,"來自子程序的資訊。");
		write(fd,buffer,sizeof(buffer));//往管道檔案中寫入資訊
		close(fd);//關閉管道檔案
	}
}

====================================================================================

③ 共享記憶體

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

#define BUFSZ 4096

int main(void){
	int shm_id = shmget(IPC_PRIVATE,BUFSZ,0666);//分配共享記憶體
	//判斷分配是否成功,如果成功,則編號大於等於0
	if(shm_id < 0){
		perror("shm_id");
		exit(1);
	}
	printf("共享記憶體編號:%d\n",shm_id);
	char* shm_buf;
	//獲取該共享記憶體編號上的地址
	if((shm_buf = shmat(shm_id,0,0))<(char*)0){
		perror("shm_id");
		exit(1);
	}

	pid_t pid;
	pid = fork();
	if(pid){//父程序
		//父程序向子程序傳送訊息。
		sprintf(shm_buf,"來自父程序的訊息~!");
		sleep(2);
		//父程序接受來自子程序的訊息。
		printf("父程序接受訊息:%s\n",shm_buf);
		//解除對映
		if(shmdt(shm_buf)<0){
			perror("shmdt");
			exit(1);
		}
		printf("父程序\n");
		sleep(1);
	}
	else if(pid==0){//子程序
		//子程序接受來自父程序的訊息。
		printf("子程序接受訊息:%s\n",shm_buf);
		sleep(1);
		//子程序向父程序傳送訊息。
		sprintf(shm_buf,"來自子程序的訊息~!");
		//解除對映
		if(shmdt(shm_buf)<0){
			perror("shmdt");
			exit(1);
		}
		printf("子程序\n");
		char s[8];
		char str[20] = {"ipcrm -m "};
		//將int轉成char陣列
		sprintf(s,"%d",shm_id);
		//連線str和s。
		strcat(str,s);
		sleep(1);
		printf("%s\n",str);
		//執行命令,刪除該共享記憶體區		
		system(str);
	}
}

首先建立共享記憶體,獲得共享記憶體的編號 1867793

然後fork()生成子程序。

首先父程序通過共享記憶體傳送訊息“來自父程序的訊息~!”給子程序。

其次子程序通過共享記憶體傳送訊息“來自子程序的訊息~!”給父程序。

輸出“子程序”,“父程序”表示對應程序的任務結束。

ipcrm –m 1867793 撤銷指定編號的共享記憶體。


====================================================================================

④ 訊息佇列

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define MAX_SEND_SIZE 80

struct mymsgbuf{
	/**
	 * mtype的值。
	 * 如果為0,則接受該佇列中的第一條資訊。
	 * 如果小於0,則接受小於該值的絕對值的訊息型別。
	 * 如果大於0,接受指定型別的訊息,即該值訊息。
	 */
	long mtype;//訊息型別
	char mtext[MAX_SEND_SIZE];//訊息文字
};

int type = 1;

int main(){
	int key = ftok(".",'m');//將一個檔案路徑名轉成key值
	//建立一個訊息佇列
	int msgqueue_id = msgget(key,IPC_CREAT|0660);
	printf("訊息佇列編號:%d\n",msgqueue_id);//輸出訊息佇列編號
	struct mymsgbuf qbuf;
	pid_t pid = fork();
	if(pid){//父程序
		qbuf.mtype = type;//為訊息型別賦值
		strcpy(qbuf.mtext , "來自父程序的訊息...");//為訊息文字賦值
		//父程序向訊息佇列傳送訊息
		msgsnd(msgqueue_id,(struct msgbuf *)&qbuf,
							strlen(qbuf.mtext)+1,0);
		sleep(2);

		//父程序從訊息佇列中獲取資訊,即獲取子程序傳送給訊息佇列的資訊。
		msgrcv(msgqueue_id,(struct msgbuf *)&qbuf,
				MAX_SEND_SIZE,type,0);
		printf("父程序接收訊息:%s\n",qbuf.mtext);
		sleep(1);
	}
	else if(pid == 0){//子程序
		sleep(1);
		//從訊息佇列中讀取訊息。
		msgrcv(msgqueue_id,(struct msgbuf *)&qbuf,
							MAX_SEND_SIZE,type,0);
		//子程序輸出從父程序獲得的訊息。
		printf("子程序接收訊息:%s\n",qbuf.mtext);

		qbuf.mtype = type;
		strcpy(qbuf.mtext , "來自子程序的訊息...");
		//子程序向訊息佇列傳送訊息
		msgsnd(msgqueue_id,(struct msgbuf *)&qbuf
				,MAX_SEND_SIZE,0);
		sleep(1);

		//撤銷郵箱
		msgctl(msgqueue_id,IPC_RMID,0);
	}
	return 0;
}


建立一個訊息佇列,然後不撤銷的話會在MessageQueues裡面出現一個訊息佇列。


這次呼叫了撤銷訊息佇列的函式msgctl(msgqueue_id,IPC_RMID,0);所以ipcs–q查不到該訊息佇列的資訊。

通過訊息佇列,子程序接收資訊“來自父程序的訊息…”,父程序接收訊息“來自子程序的訊息…”。

=============================================================================

三、 設計以下程式碼:

      1. 程序A建立子程序B, 子程序B建立孫程序C,AB間用管道1通訊,BC間用管道2通訊,實現程序A將一個訊息字串傳送到C程序,並有C程序打印出來以驗證通訊成功。要求:管道2只對BC可見,即對A是不可見的。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
int main(){
	//程序A
	pid_t pid;
	char buf[128];//訊息的字元陣列
	//管道1:程序A 和 程序B
	int fds_A_B[2];
	pipe(fds_A_B);
	//程序A生成子程序B,A和B都有管道fds_A_B
	pid = fork();
	if(pid){
		printf("1. 這裡是程序A。\n");
		//給字元陣列賦值 
		strcpy(buf,"來自程序A的訊息~!\n");
		printf("1. 程序A傳送訊息給程序B。\n");
		//程序A通過管道1與程序B通訊
		write(fds_A_B[1],buf,sizeof(buf));
		sleep(2);
	}
	else if(pid==0){
		//程序B
		printf("2. 這裡是程序B。\n");
		//建立管道2:程序B 和 程序C通訊。管道2只有B和C可見
		int fds_B_C[2];
		pipe(fds_B_C);
		//程序B建立子程序C
		pid = fork();
		if(pid){
			printf("2. 程序B接收來自程序A的資訊。\n");
			//程序B 通過管道1 從 父程序A那裡接收資訊。
			read(fds_A_B[0],buf,sizeof(buf));
			printf("2. 程序B轉遞訊息給程序C...\n");
			//程序B 通過管道2 向 子程序C 傳遞 父程序A的訊息。
			write(fds_B_C[1],buf,sizeof(buf));
			sleep(2);
		}
		else if(pid==0){
			sleep(2);
			//程序C
			printf("3. 這裡是程序C。\n");
			printf("3. 程序C接收來自程序B的資訊...\n");
			//程序C 通過管道2 從 父程序B那裡接收資訊。
			read(fds_B_C[0],buf,sizeof(buf));
			printf("3. 程序C接收資訊:%s\n",buf);
		}
	}
}

程序A建立管道fds_A_B,生成子程序B,通過管道fds_A_B傳送訊息給程序B。

程序B建立管道fds_B_C,生成子程序C,通過管道fds_B_C傳遞訊息給程序C。

fds_A_B在程序A,B,C都可見,fds_B_C在程序A中不可見。

===================================================================================

      2.   A程序建立一個訊息佇列(郵箱);B程序向郵箱按順序傳送三條型別分別為111/222/333的訊息,內容不作限定;C程序按333/111/222的順序讀取訊息;D程序刪除該訊息佇列(郵箱)。

一、 A程序

#include <stdio.h>
#include <string.h>
#include <sys/msg.h>
int main(){
	printf("這裡是A程序。\n");
	int key = ftok(".",'m');
	printf("建立訊息佇列。\n");
	//建立訊息佇列,獲得訊息佇列編號
	int msgqueue_id = msgget(key,IPC_CREAT | 0660);
	printf("訊息佇列編號:%d\n",msgqueue_id);
	//執行程序B,將訊息佇列編號傳給程序B。
	char s[8];
	char str[] = {"./msgB.out "};
	sprintf(s,"%d",msgqueue_id);
	strcat(str,s);
	printf("%s\n", str);
	system("ipcs -q");
	system(str);
}

二、 B程序

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MAX_SEND_SIZE 4
struct mymsgbuf{
	/**
	 * mtype的值。
	 * 如果為0,則接受該佇列中的第一條資訊。
	 * 如果小於0,則接受小於該值的絕對值的訊息型別。
	 * 如果大於0,接受指定型別的訊息,即該值訊息。
	 */
	long mtype;//訊息型別
	char mtext[MAX_SEND_SIZE];//訊息文字
};

int main(int argc,char* argv[]){
	if(argc!=2){
		printf("只允許一個引數");
		return 0;
	}
	printf("這裡是程序B。\n");
	//從傳引數中獲取訊息佇列編號
	int msgqueue_id = atol(argv[1]);
	struct mymsgbuf qbuf;
	printf("向訊息佇列中寫入資訊。\n");
	int i = -1;
	//寫入資訊
	for(;++i<3;){
		qbuf.mtype = i + 1;//訊息編號
		//訊息內容,分別是"111","222","333"
		qbuf.mtext[0] 
	  = qbuf.mtext[1]
	  = qbuf.mtext[2]
	  = (char)(48+i+1);
		qbuf.mtext[3] = '\0';
		printf("%s\n",qbuf.mtext);
		//將訊息文字傳送給訊息佇列中。
		msgsnd(msgqueue_id,(struct msgbuf*)&qbuf,
				strlen(qbuf.mtext)+1,0);
	}
	//將訊息佇列編號傳給程序C
	char str[] = {"./msgC.out "};
	strcat(str,argv[1]);
	printf("%s\n",str);
	system("ipcs -q");
	system(str);
	return 0;
}
三、C程序
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define MAX_SEND_SIZE 4

struct mymsgbuf{
	/**
	 * mtype的值。
	 * 如果為0,則接受該佇列中的第一條資訊。
	 * 如果小於0,則接受小於該值的絕對值的訊息型別。
	 * 如果大於0,接受指定型別的訊息,即該值訊息。
	 */
	long mtype;//訊息型別
	char mtext[MAX_SEND_SIZE];//訊息文字
};

int main(int argc,char* argv[]){
	if(argc!=2){
		printf("只允許一個引數。\n");
		return 0;
	}
	printf("這裡是程序C。\n");
	struct mymsgbuf qbuf;
	//獲取訊息佇列編號
	int msgqueue_id = atol(argv[1]);
	int i = -1;
	int que[] = {3,1,2};//獲取訊息的順序
	printf("讀取訊息佇列中的資訊。\n");
	for(;++i<3;){
		//從訊息佇列裡按編號獲取資訊
		msgrcv(msgqueue_id,(struct msgbuf *)&qbuf,
				MAX_SEND_SIZE,que[i],0);
		printf("%s\n",qbuf.mtext);
	}
	//將訊息佇列編號傳給程序D
	char str[] = {"./msgD.out "};
	strcat(str,argv[1]);
	printf("%s\n",str);
	system("ipcs -q");
	system(str);
}
四、D程序
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main(int argc,char* argv[]){
	if(argc!=2){
		printf("只允許一個引數\n");
		return 0;
	}
	printf("這裡是程序D。\n");
	//獲取訊息佇列編號
	int msgqueue_id = atol(argv[1]);
	printf("刪除訊息佇列:%d\n",msgqueue_id);
	//刪除訊息佇列
	msgctl(msgqueue_id,IPC_RMID,0);
	system("ipcs -q");
}

1.          首先A程序建立訊息佇列,然後將訊息佇列的編號傳給B程序msgB.out。system(“ipcs –q”)後發現Message Queues中多了一個訊息佇列。

2.          B程序獲取訊息佇列的編號後,往該訊息佇列寫入三個資訊“111”,“222”,“333”。

此時發現Message Queues中該訊息佇列的used-bytes為12,messages為3。說明有三個資訊,用了12個位元組。

之所以有12個位元組是因為“111”,“222”,“333”後面都有個‘\0’,所以總共有4 * 3 = 12個字元。

3.          C程序讀取該訊息佇列的三個資訊,按照3、1、2的順序讀取訊息。

讀取完訊息以後發現,訊息佇列的三個訊息消失了。說明訊息佇列中的訊息只允許讀取一次。

4.          D程序刪除掉該訊息佇列。Message Queues沒有該訊息隊列了。

=============================================================================

   3

       a.  一個程式(程序)從客戶端讀入按鍵資訊,一次將“一整行”按鍵資訊儲存到一個共享儲存的緩衝區內並等待讀取程序將資料讀走,不斷重複上面的操作;

       b.  另一個程式(程序)生成兩個程序/執行緒,用於顯示緩衝區內的資訊,這兩個執行緒併發讀取緩衝區資訊後將緩衝區清空(一個執行緒的兩次顯示操作之間可以加入適當的時延以便於觀察)。

  c.  在兩個獨立的終端視窗上分別執行上述兩個程式,展示其同步與通訊功能,要求一次只有一個任務在操作緩衝區。

① 讀取一整行按鍵資訊的程式(readStr):

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUFSZ 4096
int main(){
	//建立共享記憶體,獲得共享記憶體ID
	int shm_id = shmget(IPC_PRIVATE,BUFSZ,0666);
	if(shm_id < 0){
		perror("shm_id");
		exit(1);
	}
	//獲得共享記憶體地址
	char* shm_buf;
	if((shm_buf = shmat(shm_id,0,0))<(char*)0){
		perror("shm_id");
		exit(1);
	}
	char ch[128];//輸入的資訊儲存在該char陣列中
	//建立訊號量
	sem_t* sem = sem_open("semname",O_CREAT,0644,1);
	char c;
	int i;
	//輸出共享記憶體地址。
	printf("%d\n",shm_id);
	//開啟訊號量
	sem = sem_open("semname",0);
	while(1){
		i = -1;
		//讀入一整行按鍵資訊
		while((c=getchar())!='\n'){
			ch[++i] = c;
		}
		ch[++i] = '\0';
		//將按鍵資訊傳送到共享記憶體
		sprintf(shm_buf,ch);
		//傳送訊號量
		sem_post(sem);
	}
	return 0;
}
② 第一個程序,讀取緩衝區的資訊。
#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/shm.h>
int main(int argc,char* argv[]){
	printf("程序1登入。\n");
	if(argc != 2){
		exit(1);
	}
	sem_t *sem;
	//cmd傳參獲取共享記憶體編號
	int shm_id = atol(argv[1]);
	//獲取共享記憶體地址
	char* shm_buf;
	if((shm_buf = shmat(shm_id,0,0))<(char*)0){
		perror("shm_id");
		exit(1);	
	}
	printf("程序1就緒。\n");
	//開啟訊號量
	sem = sem_open("semname",0);
	while(1){
		//等待訊號量
		sem_wait(sem);
		printf("1. 程序1受到訊息:%s\n",shm_buf);
		//將緩衝區裡面的資訊清空
		strcpy(shm_buf,"");
	}
	return 0;
}
③ 第二個程序,讀取緩衝區的資訊。
#include <semaphore.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/shm.h>
int main(int argc,char* argv[]){
	printf("程序2登入。\n");
	if(argc != 2){
		exit(1);
	}
	sem_t *sem;
	//cmd傳參獲取共享記憶體編號
	int shm_id = atol(argv[1]);
	//獲取共享記憶體地址
	char* shm_buf;
	if((shm_buf = shmat(shm_id,0,0))<(char*)0){
		perror("shm_id");
		exit(1);
	}
	printf("程序2就緒。\n");
	//開啟訊號量
	sem = sem_open("semname",0);
	while(1){
		//等待訊號量
		sem_wait(sem);
		printf("2. 程序2受到訊息:%s\n",shm_buf);
		//將緩衝區裡面的資訊清空
		strcpy(shm_buf,"");
	}
	return 0;
}

一、讀取一行按鍵資訊存放在緩衝區,由兩個程序併發讀取(readStr.out):

先輸出共享記憶體的編號,用於手動傳參給程序一和程序二。

按順序輸入資訊“訊息[1 – 20]”。

二、程序一,讀取緩衝區資料的情況(getStr1.out)

進入程序一的時候輸出“程序1登入”。

準備工作(讀取共享記憶體地址,開啟訊號量)完成以後,輸出“程序1就緒”,表示可以開始接受資訊了。

隨著readStr.out輸入資訊,程序1開始輸出資訊,但是都是奇數號的資訊。

三、程序二,讀取緩衝區資料的情況(getStr2.out)


進入程序二的時候輸出“程序2登入”。

準備工作(讀取共享記憶體地址,開啟訊號量)完成以後,輸出“程序2就緒”,表示可以開始接受資訊了。

隨著readStr.out輸入資訊,程序2開始輸出資訊,但是都是偶數號的資訊。


該程式碼出現的問題:


在這裡,“訊息[1-20]”這20行資訊提前複製好,然後黏貼在輸入中。


“訊息[1-20]”黏貼上去後,所有訊號量都被程序一getStr1.out獲取了,但是輸出的全部資訊只有最後的“訊息20”。


而程序二getStr2.out並沒有獲取到訊號量。