Linux 進程通信 -上 (有無名管道,消息隊列,mmap,共享內存)

進程間的通信

在這裏插入圖片描述

進程間的通信方式(重要!!)

在這裏插入圖片描述

1、無名管道(重要)

管道(pipe)又稱無名管道。 無名管道是一種特殊類型的文件,在應用層體現爲兩個打開的文件描述符。
函數說明:

#include <unistd.h>
int pipe(int filedes[2]);
功能:
    經由參數filedes返回兩個文件描述符
參數:
    filedes爲int型數組的首地址,其存放了管道的文件描述符filedes[0]、filedes[1]。
    filedes[0]爲讀而打開,filedes[1]爲寫而打開管道,filedes[0]的輸出是filedes[1]的輸入。
返回值:
成功:返回 0
失敗:返回-1

案例:

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
	//創建一個無名管道
	int fd[2];
	int ret = pipe(fd);
	if(ret == -1)
	{
		perror("pipe");
		exit(0);
	}
	
	printf("管道的讀端fd[0]=%d\n", fd[0]);
	printf("管道的寫端fd[1]=%d\n", fd[1]);
	
	
	//關閉文件描述符
	close(fd[0]);
	close(fd[1]);
	
	return 0;
}

運行結果:
在這裏插入圖片描述

無名管道 用於進程的通信

在這裏插入圖片描述

案例:

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	//父發消息 子接受消息
	//創建管道
	int fd[2];
	pipe(fd);
	
	//創建進程
	pid_t pid = fork();
	if(pid == 0)//子進程
	{
		//fd[0]讀  fd[1]寫
		//由於子進程只用到了fd[0] 可以先關寫端fd[1]
		close(fd[1]);//先關閉無用的
		
		//子進程接受消息 循環接受 while
		char buf[128]="";
		read(fd[0],buf,sizeof(buf));//默認阻塞
		printf("子進程%d接收到的消息爲:%s\n",getpid(),buf);
		
		//子進程對fd[0]使用完畢 應該關閉fd[0]
		close(fd[0]);//最後關閉 使用
		exit(0);//顯示的退出
	}
	else if(pid > 0)//父進程
	{
		//fd[0]讀  fd[1]寫
		//由於父進程只用到了fd[1] 可以先關讀端fd[0]
		close(fd[0]);
		
		//父進程發送消息
		printf("父進程%d:5s後將發送消息\n",getpid());
		sleep(5);
		printf("父進程%d:已發送消息\n",getpid());
		write(fd[1],"hello msg",strlen("hello msg"));
		
		wait(NULL);
		//父進程對fd[1]使用完畢 應該關閉fd[1]
		close(fd[1]);//最後關閉 使用
	}
	
	return 0;
}

運行結果:
在這裏插入圖片描述

無名管道的特點:

1、默認用read函數從管道中讀數據是阻塞的。
2、調用write函數向管道里寫數據,當緩衝區已滿時write也會阻塞。
3、通信過程中,讀端口全部關閉後,寫進程向管道內寫數據時,寫進程會(收到SIGPIPE信號)退出。
4、寫端意外關閉 讀端的read將解阻塞

編程時可通過fcntl函數設置文件的阻塞特性。
設置爲阻塞: fcntl(fd, FSETFL, 0);
設置爲非阻塞: fcntl(fd, FSETFL, O_NONBLOCK);

調用write函數向管道里寫數據,當緩衝區已滿時write也會阻塞

在這裏插入圖片描述
通信過程中,讀端口全部關閉後,寫進程向管道內寫數據時,寫進程會(收到SIGPIPE信號)退出。

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
	//父發消息 子接受消息
	//創建管道
	int fd[2];
	pipe(fd);
	
	//創建進程
	pid_t pid = fork();
	if(pid == 0)//子進程
	{
		close(fd[1]);
		int i=0;
		for(i=0;i<4;i++)
		{
			char buf[128]="";
			read(fd[0], buf,sizeof(buf));
			printf("子進程讀到的消息%s\n", buf);
		}
		printf("子進程將關閉讀端\n");
		close(fd[0]);
	}
	else if(pid > 0)//父進程
	{
		close(fd[0]);
		int i=0;
		for(i=0;i<10;i++)
		{
			write(fd[1],"hehehehe", 4);
			printf("i=%d\n",i);
			sleep(1);
		}
	}
	
	return 0;
}

運行結果:
在這裏插入圖片描述

設置爲非阻塞: fcntl(fd, FSETFL, O_NONBLOCK);

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
int main()
{
	//父發消息 子接受消息
	//創建管道
	int fd[2];
	pipe(fd);
	
	//創建進程
	pid_t pid = fork();
	if(pid == 0)//子進程
	{
		close(fd[1]);
		
		//設置讀端爲非阻塞
		fcntl(fd[0], F_SETFL, O_NONBLOCK);
		
		int i=0;
		for(i=0;i<10;i++)
		{
			char buf[128]="";
			read(fd[0], buf,sizeof(buf));
			printf("%d秒子進程讀到的消息%s\n",i, buf);
			sleep(1);
		}
		
	}
	else if(pid > 0)//父進程
	{
		printf("父進程將在5秒後發送信息\n");
		sleep(5);
		write(fd[1],"hehe", 4);
		printf("父進程已發送信息\n");
	}
	
	return 0;
}

運行結果:
在這裏插入圖片描述

1.1、文件描述符複製 dup dup2

#include <unistd.h>
int dup(int oldfd);複製oldfd文件描述符  複製的結果==是系統最小可用的文件描述符

在這裏插入圖片描述
案例:

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
int main()
{
	int old_fd = open("a.txt",O_WRONLY|O_CREAT,0777);
	printf("old_fd = %d\n", old_fd);
	
	//用new_fd複製old_fd
	int new_fd = dup(old_fd);
	printf("new_fd = %d\n", new_fd);
	
	write(new_fd,"hello dup",strlen("hello dup"));
	
	close(new_fd);
	close(old_fd);
	
	return 0;
}

運行結果:
在這裏插入圖片描述

案例1:我讓標準輸出文件描述符1 指向磁盤文件

用printf 往文件中輸入數據
在這裏插入圖片描述

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
int main()
{
	int old_fd = open("b.txt", O_WRONLY|O_CREAT,0777);
	printf("old_fd = %d\n", old_fd);
	//關閉1 讓1是最小可用的文件描述符
	close(1);
	
	//複製old_fd  讓最小可用的1 複製 old_fd
	int ret = dup(old_fd);
	printf("ret = %d\n",ret);
	printf("hello udp\n");
	
	close(old_fd);
	close(ret);
	
	return 0;
}

運行結果:
在這裏插入圖片描述

案例2:實現 ps -aux | grep bash(理解)

在這裏插入圖片描述

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
int main()
{
	//創建一個管道
	int fd[2];
	pipe(fd);
	
	pid_t pid = fork();
	if(pid == 0)//子進程
	{
		//關閉0 讓0最小可用
		close(0);
		//0重定向fd[0]
		dup(fd[0]);
		//execlp執行grep
		execlp("grep", "grep","bash",NULL);
		exit(0);
	}
	else if(pid >0)//父進程 寫
	{
		//關閉1 讓1最小可用
		close(1);
		//1重定向到fd[1]
		dup(fd[1]);
		
		//execlp執行ps
		execlp("ps","ps","-aux",NULL);
		wait(NULL);
	}
}

運行結果:
在這裏插入圖片描述

1.2、dup2

#include <unistd.h>
 int dup2(int oldfd, int newfd);
 //如果newfd是打開的  dup2會先自動關閉newfd 然後再複製oldfd

dup步驟:close(new_fd) dup(oldfd) 例如:close(1)---->dup(oldfd)
dup2步驟:dup(oldfd,newfd) 例如:dup2(oldfd,1);

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
int main()
{
	int oldfd = open("c.txt", O_WRONLY|O_CREAT,0666);
	
	//讓1 指向 oldfd
	//close(1)---->dup(oldfd)
	dup2(oldfd,1);
	
	printf("hello dup2\n");
	fflush(stdout);
	
	close(oldfd);
	close(1);

	return 0;
}

運行結果:
在這裏插入圖片描述

2、有名管道FIFO(沒有血緣關係的進程間通信)

FIFO在文件系統中作爲一個特殊的文件而存在,但FIFO中的內容卻存放在內存中。
創建命令管道:只是將文件名 和 內存關係起來

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo( const char *pathname, mode_t mode);
參數:
pathname:FIFO的路徑名+文件名。
mode:mode_t類型的權限描述符。
	返回值:
	成功:返回 0
失敗:如果文件已經存在,則會出錯且返回-1

有名管道的特點

命名管道(FIFO)和管道(pipe)基本相同,但也有一些顯著的不同,其特點是:
1、半 雙工,數據在同一時刻只能在一個方向上流動。
2、寫入FIFO中的數據遵循先入 先出的規則。
3、FIFO所傳送的數據是無格式的,這要求FIFO的讀出方與寫入方 必須事先約定好數據的格式,如多少字節算一個消息等。
4、FIFO在文件系統中 作爲一個特殊的文件而存在,但FIFO中的內容卻存放在內存中。
5、管道在內存 中對應一個緩衝區。不同的系統其大小不一定相同。
6、從FIFO讀數據是一次 性操作,數據一旦被讀,它就從FIFO中被拋棄,釋放空間以便寫更多的數據。
7、當使用FIFO的進程退出後,FIFO文件將繼續保存在文件系統中以便以後使 用。
8、FIFO有名字,不相關的進程可以通過打開命名管道進行通信。

案例

寫端:

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
int main()
{
	//創建一個命令管道
	mkfifo("my_fifo", 0666);
	
	//open打開my_fifo 寫
	//(阻塞對方以讀的方式打開)
	int fd = open("my_fifo", O_WRONLY);
	printf("寫端打開了\n");
	
	printf("請輸入堯發送的數據\n");
	char buf[128]="";
	fgets(buf,sizeof(buf),stdin);
	write(fd,buf,strlen(buf));
	printf("寫端已發送數據\n");
	
	close(fd);
	return 0;
}

讀端:

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
int main()
{
	//創建一個命令管道(如果存在)
	mkfifo("my_fifo", 0666);
	
	//open打開my_fifo 讀
	//阻塞到 對方以寫的方式 打開
	int fd = open("my_fifo", O_RDONLY);
	printf("讀端打開了\n");
	
	char buf[128]="";
	printf("讀端準備讀取數據中....\n");
	read(fd, buf,sizeof(buf));
	printf("讀到的數據爲:%s\n",buf);
	
	close(fd);
	return 0;
}

在這裏插入圖片描述
運行結果:
在這裏插入圖片描述

案例:單機聊天

在這裏插入圖片描述
lucy.c

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
int main()
{
    int i = 0;
    for(i = 0; i<2; i++)
    {
        pid_t pid = fork();
        if(pid == 0)
        break;

    }
    if(i == 0)//子進程 1 發
    {
        //lucy_to_peter
        mkfifo("lucy_to_peter",0666);               //創建有名管道
        int fd = open("lucy_to_peter",O_WRONLY);    //打開管道

        while (1)
        {
            printf("LUCY:");
            fflush(stdout);                         //刷新輸出緩衝區
            char msg[128] = "";
            fgets(msg,sizeof(msg),stdin);           //獲取鍵盤輸入
            msg[strlen(msg)-1]=0;
            write(fd,msg,strlen(msg));
            if(strcmp("bye",msg)==0)
				break;
        }
        close(fd);
        
    }
    else if(i==1)//子進程2
	{
		mkfifo("peter_to_lucy",0666);
		int fd = open("peter_to_lucy",O_RDONLY);
		
		while(1)
		{
			char msg[128]="";
			read(fd,msg,sizeof(msg));
			printf("\nPETER發送的消息爲:%s\n",msg);
			if(strcmp("bye",msg)==0)
				break;
		}
	}
    else if(i==2)//父進程
	{
		//等待子進程的退出
		while(1)
		{
			pid_t pid = waitpid(-1,NULL, WNOHANG);
			if(pid > 0)//等到一個退出
			{
				printf("子進程%d退出了\n",pid);
			}
			else if(pid ==0)//還有子進程在運行
			{
				continue;
			}
			else if(pid == -1)//所有子進程都退出了
			{
				break;
			}
		}
	
	}
	
	return;
}

peter.c

#include<stdio.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
int main()
{
	int i=0;
	for(i=0;i<2; i++)
	{
		pid_t pid = fork();
		if(pid == 0)
			break;
	}
	if(i==0)//子進程1 發
	{
		//peter_to_lucy
		mkfifo("peter_to_lucy",0666);
		int fd = open("peter_to_lucy", O_WRONLY);
		
		while(1)
		{
			printf("PETER:");
			fflush(stdout);
			char msg[128]="";
			fgets(msg,sizeof(msg),stdin);
			msg[strlen(msg)-1]=0;
			write(fd,msg,strlen(msg));
			if(strcmp("bye",msg)==0)
				break;
		}
		
		close(fd);
		
	}
	else if(i==1)//子進程2 收
	{
		mkfifo("lucy_to_peter",0666);
		int fd = open("lucy_to_peter",O_RDONLY);
		
		while(1)
		{
			char msg[128]="";
			read(fd,msg,sizeof(msg));
			printf("\nLUCY發送的消息爲:%s\n",msg);
			if(strcmp("bye",msg)==0)
				break;
		}
	}
	else if(i==2)//父進程
	{
		//等待子進程的退出
		while(1)
		{
			pid_t pid = waitpid(-1,NULL, WNOHANG);
			if(pid > 0)//等到一個退出
			{
				printf("子進程%d退出了\n",pid);
			}
			else if(pid ==0)//還有子進程在運行
			{
				continue;
			}
			else if(pid == -1)//所有子進程都退出了
			{
				break;
			}
		}
	
	}
	return;
}

運行結果:
在這裏插入圖片描述

總結:無名、命名管道 都是一對一的通信

3、消息隊列(多對多通信)

消息隊列的特點:

消息隊列是消息的鏈表,存放在內存中,由內核維護 消息隊列的特點
1、消息隊列中的消息是有類型的。
2、消息隊列中的消息是有格式的。
3、消息隊列可以實現消息的隨機查詢。消息不一定要以先進先出的次序讀取,編程時可以按消息的類型讀取。
4、消息隊列允許一個或多個進程向它寫入或者讀取消息。
5、與無名管道、命名管道一樣,從消息隊列中讀出消息,消息隊列中對應的數據都會被刪除。
6、每個消息隊列都有消息隊列標識符,消息隊列的標識符在整個系統中是唯一的。
7、只有內核重啓或人工刪除消息隊列時,該消息隊列纔會被刪除。若不人工刪除消息隊列,消息隊列會一直存在於系統中

在ubuntu 某些版本中消息隊列限制值如下:

每個消息內容最多爲8K字節
每個消息隊列容量最多爲16K字節
系統中消息隊列個數最多爲1609個
系統中消息個數最多爲16384個

消息隊列的創建:

1、獲得唯一的key值

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:
    獲得項目相關的唯一的IPC鍵值。
參數:
    pathname:路徑名
    proj_id:項目ID,非0整數(只有低8位有效)
返回值:
    成功返回key值,失敗返回 -1

2、根據唯一的key創建消息隊列:

#include <sys/msg.h>
int msgget(key_t key, int msgflg);
功能:
    創建一個新的或打開一個已經存在的消息隊列。不同的進程調用此函數,只要用相同的key值就能得到同一個消息隊列的標識符。
參數:
    key:IPC鍵值。
    msgflg:標識函數的行爲及消息隊列的權限。
參數:
    msgflg的取值:
    IPC_CREAT:創建消息隊列。
    IPC_EXCL:檢測消息隊列是否存在。
    位或權限位:消息隊列位或權限位後可以設置消息隊列的訪問權限,格式和open函數的mode_t一樣,但可執行權限未使用。
返回值:
   成功:消息隊列的標識符,失敗:返回-1
//通ftok得到唯一key值
	key_t key = ftok("/", 2020);
	printf("key = %#x\n", key);
	
	//通過key創建消息隊列
	int msg_id = msgget(key, IPC_CREAT|0666);
	printf("msg_id = %d\n", msg_id);

使用shell命令操作消息隊列: (ipcs -q)

在這裏插入圖片描述

刪除消息隊列(ipcrm -q msqid)

在這裏插入圖片描述

消息隊列的消息的格式(重要!!!)

typedef struct _msg
{
    long mtype; /*消息類型 固定的*/ 
    char mtext[100]; /*消息正文*/
    ... /*消息的正文可以有多個成員*/
}MSG;

消息類型必須是長整型的,而且必須是結構體類 型的第一個成員,類型下面是消息正文,正文可以 有多個成員(正文成員可以是任意數據類型的)。

3、發送消息

#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp,size_t msgsz, int msgflg);
功能:
    將新消息添加到消息隊列。
參數:
    msqid:消息隊列的標識符。
    msgp:待發送消息結構體的地址。
    msgsz:消息正文的字節數。
msgflg:函數的控制屬性
0:msgsnd調用阻塞直到條件滿足爲止。
IPC_NOWAIT: 若消息沒有立即發送則調用該函數的進程會立即返回。
返回值:
成功:0;失敗:返回-1。
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
//消息的結構體
typedef struct
{
	//消息的類型
	long mtype;
	
	//消息的正文
	char mtext[128];
}MSG;
int main()
{
	//通ftok得到唯一key值
	key_t key = ftok("/", 2020);
	printf("key = %#x\n", key);
	
	//通過key創建消息隊列
	int msg_id = msgget(key, IPC_CREAT|0666);
	printf("msg_id = %d\n", msg_id);
	
	//發送消息
	MSG msg;
	memset(&msg,0,sizeof(msg));
	msg.mtype = 10;//賦值類型
	strcpy(msg.mtext,"hello msg");//賦值正文內容
	//注意:正文的大小
	msgsnd(msg_id, &msg,sizeof(MSG)-sizeof(long), 0);
	
	return 0;
}

在這裏插入圖片描述

4、消息隊列的接收

#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp,  size_t msgsz, long msgtyp, int msgflg);
功能:
    從標識符爲msqid的消息隊列中接收一個消息。一旦接收消息成功,則消息在消息隊列中被刪除。
參數:
msqid:消息隊列的標識符,代表要從哪個消息列中獲取消息。
msgp: 存放消息結構體的地址。
msgsz:消息正文的字節數。
msgtyp:消息的類型、可以有以下幾種類型
msgtyp = 0:返回隊列中的第一個消息
msgtyp > 0:返回隊列中消息類型爲msgtyp的消息
msgtyp < 0:返回隊列中消息類型值小於或等於msgtyp絕對值的消息,如果這種消息有若干個,則取類型值 最小的消息。
注意:
   若消息隊列中有多種類型的消息,msgrcv獲取消息的時候按消息類型獲取,不是先進先出的。
在獲取某類型消息的時候,若隊列中有多條此類型的消息,則獲取最先添加的消息,即先進先出原
msgflg:函數的控制屬性
0:msgrcv調用阻塞直到接收消息成功爲止。
MSG_NOERROR:若返回的消息字節數比nbytes字節數多,則消息就會截短到nbytes字節,且不通知消息發送進程。
IPC_NOWAIT:調用進程會立即返回。若沒有收到消息則立即返回-1。
返回值:
    成功返回讀取消息的長度,失敗返回-1
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
//消息的結構體
typedef struct
{
	//消息的類型
	long mtype;
	
	//消息的正文
	char mtext[128];
}MSG;
int main()
{
	//通ftok得到唯一key值
	key_t key = ftok("/", 2020);
	printf("key = %#x\n", key);
	
	//通過key創建消息隊列
	int msg_id = msgget(key, IPC_CREAT|0666);
	printf("msg_id = %d\n", msg_id);
	
	//接收消息消息
	MSG msg;
	memset(&msg,0,sizeof(msg));
	
	printf("等待消息隊列的消息\n");
	int len = msgrcv(msg_id, &msg, sizeof(msg)-sizeof(long), 10, 0);
	
	printf("收到類型爲%d的消息爲:%s 長度爲:%d\n", 10,msg.mtext,len );
	return 0;
}

運行結果:
在這裏插入圖片描述
消息一旦從消息隊列中 讀走 消息將從消息隊列中刪除
在這裏插入圖片描述

消息隊列的控制:

#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:
    對消息隊列進行各種控制,如修改消息隊列的屬性,或刪除消息消息隊列。
參數:
    msqid:消息隊列的標識符。
    cmd:函數功能的控制。
    buf:msqid_ds數據類型的地址,用來存放或更改消息隊列的屬性。
	cmd:函數功能的控制
	IPC_RMID:刪除由msqid指示的消息隊列,將它從系統中刪除並破壞相關數據結構。
	IPC_STAT:將msqid相關的數據結構中各個元素的當前值存入到由buf指向的結構中。
	IPC_SET:將msqid相關的數據結構中的元素設置爲由buf指向的結構中的對應值。
返回值:成功:返回 0;失敗:返回 -1
//刪除消息隊列
msgctl(msg_id, IPC_RMID, NULL);

案例:消息隊列實現多人聊天

消息結構體類型
typedef struct msg
{
    long type; //接收者類型
    char text[100]; //發送內容
    char name[20]; //發送者姓名
}MSG;

在這裏插入圖片描述
lucy.c

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
//lucy類型10 bob類型11 tom類型12
typedef struct msg
{
    long type; //接收者類型
    char text[100]; //發送內容
    char name[20]; //發送者姓名
}MSG;
char *p_name[]={"lucy","bob","tom"};
int n = sizeof(p_name)/sizeof(p_name[0]);

int p_type[]={10,11,12};
int main()
{
	//獲得唯一的key
	key_t key = ftok("/", 2020);
	
	//創建一個消息隊列
	int msg_id = msgget(key, IPC_CREAT|0666);
	printf("msg_id = %d\n", msg_id);
	
	int i=0;
	for(i=0;i<2; i++)
	{
		pid_t pid = fork();
		if(pid == 0)
			break;
	}
	if(i==0)//子進程1  發
	{
		while(1)
		{
			char name[32]="";
			printf("請輸入發送的姓名:");
			scanf("%s",name);
			getchar();
			int i=0;
			for(i=0;i<n; i++)
			{
				if(strcmp(name, p_name[i]) == 0 )
					break;
			}
			if(i == n)//名字不匹配
			{
				printf("輸入的名字有誤!!!\n");
				continue;
			}
			else
			{
				printf("請輸入要發送的消息:");
				char buf[128]="";
				fgets(buf,sizeof(buf),stdin);
				buf[strlen(buf)-1] = 0;
				
			
				MSG msg;
				memset(&msg,0,sizeof(msg));
			
				msg.type = p_type[i];//類型
				strcpy(msg.text, buf);//發送的內容
				strcpy(msg.name, "lucy");//發送的內容
				
				//注意:正文的大小
				msgsnd(msg_id, &msg,sizeof(MSG)-sizeof(long), 0);
			}
		
		}
		
		exit(0);
	}
	else if(i==1)//子進程2 收
	{
		
		while(1)
		{
			MSG msg;
			memset(&msg, 0,sizeof(msg));
			msgrcv(msg_id,&msg, sizeof(msg)-sizeof(long), 10, 0);
			printf("\r來自%s的消息:%s\n", msg.name, msg.text);
			printf("請輸入發送的姓名:");
			fflush(stdout);
		}
	}
	else if(i==2)//父進程
	{
		//等待子進程的退出
		while(1)
		{
			pid_t pid = waitpid(-1,NULL, WNOHANG);
			if(pid > 0)//等到一個退出
			{
				printf("子進程%d退出了\n",pid);
			}
			else if(pid ==0)//還有子進程在運行
			{
				continue;
			}
			else if(pid == -1)//所有子進程都退出了
			{
				break;
			}
		}
	
	}
}

bob.c

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
//lucy類型10 bob類型11 tom類型12
typedef struct msg
{
    long type; //接收者類型
    char text[100]; //發送內容
    char name[20]; //發送者姓名
}MSG;
char *p_name[]={"lucy","bob","tom"};
int n = sizeof(p_name)/sizeof(p_name[0]);

int p_type[]={10,11,12};
int main()
{
	//獲得唯一的key
	key_t key = ftok("/", 2020);
	
	//創建一個消息隊列
	int msg_id = msgget(key, IPC_CREAT|0666);
	printf("msg_id = %d\n", msg_id);
	
	int i=0;
	for(i=0;i<2; i++)
	{
		pid_t pid = fork();
		if(pid == 0)
			break;
	}
	if(i==0)//子進程1  發
	{
		while(1)
		{
			char name[32]="";
			printf("請輸入發送的姓名:");
			scanf("%s",name);
			getchar();
			
			int i=0;
			for(i=0;i<n; i++)
			{
				if(strcmp(name, p_name[i]) == 0 )
					break;
			}
			if(i == n)//名字不匹配
			{
				printf("輸入的名字有誤!!!\n");
				continue;
			}
			else
			{
				printf("請輸入要發送的消息:");
				char buf[128]="";
				fgets(buf,sizeof(buf),stdin);
				buf[strlen(buf)-1] = 0;
				
			
				MSG msg;
				memset(&msg,0,sizeof(msg));
			
				msg.type = p_type[i];//類型
				strcpy(msg.text, buf);//發送的內容
				strcpy(msg.name, "bob");//發送的內容
				
				//注意:正文的大小
				msgsnd(msg_id, &msg,sizeof(MSG)-sizeof(long), 0);
			}
		
		}
		
		exit(0);
	}
	else if(i==1)//子進程2 收
	{
		
		while(1)
		{
			MSG msg;
			memset(&msg, 0,sizeof(msg));
			msgrcv(msg_id,&msg, sizeof(msg)-sizeof(long), 11, 0);
			printf("\r來自%s的消息:%s\n", msg.name, msg.text);
			printf("請輸入發送的姓名:");
			fflush(stdout);
		}
	}
	else if(i==2)//父進程
	{
		//等待子進程的退出
		while(1)
		{
			pid_t pid = waitpid(-1,NULL, WNOHANG);
			if(pid > 0)//等到一個退出
			{
				printf("子進程%d退出了\n",pid);
			}
			else if(pid ==0)//還有子進程在運行
			{
				continue;
			}
			else if(pid == -1)//所有子進程都退出了
			{
				break;
			}
		}
	
	}
}

tom.c

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
//lucy類型10 bob類型11 tom類型12
typedef struct msg
{
    long type; //接收者類型
    char text[100]; //發送內容
    char name[20]; //發送者姓名
}MSG;
char *p_name[]={"lucy","bob","tom"};
int n = sizeof(p_name)/sizeof(p_name[0]);

int p_type[]={10,11,12};
int main()
{
	//獲得唯一的key
	key_t key = ftok("/", 2020);
	
	//創建一個消息隊列
	int msg_id = msgget(key, IPC_CREAT|0666);
	printf("msg_id = %d\n", msg_id);
	
	int i=0;
	for(i=0;i<2; i++)
	{
		pid_t pid = fork();
		if(pid == 0)
			break;
	}
	if(i==0)//子進程1  發
	{
		while(1)
		{
			char name[32]="";
			printf("請輸入發送的姓名:");
			scanf("%s",name);
			getchar();
			
			int i=0;
			for(i=0;i<n; i++)
			{
				if(strcmp(name, p_name[i]) == 0 )
					break;
			}
			if(i == n)//名字不匹配
			{
				printf("輸入的名字有誤!!!\n");
				continue;
			}
			else
			{
				printf("請輸入要發送的消息:");
				char buf[128]="";
				fgets(buf,sizeof(buf),stdin);
				buf[strlen(buf)-1] = 0;
				
			
				MSG msg;
				memset(&msg,0,sizeof(msg));
			
				msg.type = p_type[i];//類型
				strcpy(msg.text, buf);//發送的內容
				strcpy(msg.name, "tom");//發送的內容
				
				//注意:正文的大小
				msgsnd(msg_id, &msg,sizeof(MSG)-sizeof(long), 0);
			}
		
		}
		
		exit(0);
	}
	else if(i==1)//子進程2 收
	{
		
		while(1)
		{
			MSG msg;
			memset(&msg, 0,sizeof(msg));
			msgrcv(msg_id,&msg, sizeof(msg)-sizeof(long), 12, 0);
			printf("\r來自%s的消息:%s\n", msg.name, msg.text);
			printf("請輸入發送的姓名:");
			fflush(stdout);
		}
	}
	else if(i==2)//父進程
	{
		//等待子進程的退出
		while(1)
		{
			pid_t pid = waitpid(-1,NULL, WNOHANG);
			if(pid > 0)//等到一個退出
			{
				printf("子進程%d退出了\n",pid);
			}
			else if(pid ==0)//還有子進程在運行
			{
				continue;
			}
			else if(pid == -1)//所有子進程都退出了
			{
				break;
			}
		}
	
	}
}

運行結果:
在這裏插入圖片描述

4、mmap存儲映射

在這裏插入圖片描述
mmap映射:
在這裏插入圖片描述

mmap使用流程:

1、打開一個open磁盤文件 獲得文件描述符

2、truncate拓展文件大小

int truncate(const char *path, off_t length);
path  要拓展的文件
length 要拓展的長度

3、mmap 函數根據 文件描述符 映射內存空間

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
	addr 地址,填NULL
	length 長度 要申請的映射區的長度
	prot 權限
	PROT_READ 可讀
	PROT_WRITE 可寫
	flags 標誌位
	MAP_SHARED 共享的 -- 對映射區的修改會影響源文件
	MAP_PRIVATE 私有的
	fd文件描述符 需要打開一個文件
	offset  指定一個偏移位置 ,從該位置開始映射
返回值
	成功 返回映射區的首地址
	失敗 返回 MAP_FAILED ((void *) -1)

4、操作 內存空間 完成通信

5、munmap 釋放映射空間

釋放映射區:
int munmap(void *addr, size_t length);
	addr  映射區的首地址
	length 映射區的長度
返回值
	成功 返回0
	失敗 返回 -1

案例:寫

#include<stdio.h>
#include<string.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
	//打開open一個文件
	int fd = open("tmp.txt", O_RDWR|O_CREAT, 0666);
	
	//2、拓展文件大小
	truncate("tmp.txt", 16);
	
	//3、映射空間
	char *str = (char *)mmap(NULL, 16, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
	
	//4、使用空間(寫操作)
	strcpy(str,"hello map");
	
	//5、解除空間映射
	munmap(str, 16);
	
	return 0;
}

運行結果:
在這裏插入圖片描述

案例:讀操作

#include<stdio.h>
#include<string.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
	//打開open一個文件
	int fd = open("tmp.txt", O_RDWR|O_CREAT, 0666);
	
	//2、拓展文件大小
	truncate("tmp.txt", 16);
	
	//3、映射空間
	char *str = (char *)mmap(NULL, 16, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
	
	//4、使用空間(讀操作)
	printf("文件的內容爲:%s\n",str);
	
	//5、解除空間映射
	munmap(str, 16);
	
	return 0;
}

運行結果:
在這裏插入圖片描述

5、內存共享

在這裏插入圖片描述

共享內存的特點:

1、共享內存是進程間共享數據的一種最快的方法。 一個進程向共享的內存區域寫入了數據,共享這個內存區域的所有進程就可以立刻看到其中的內容。

2、使用共享內存要注意的是多個進程之間對一個給定存儲區訪問的互斥。 若一個進程正在向共享內存區寫數據,則在它做完這一步操作前,別的進程不應當去讀、寫這些數據。
在ubuntu 部分版本中共享內存限制值如下 共享存儲區的最小字節數:1 共享存儲區的最大字節數:32M 共享存儲區的最大個數:4096 每個進程最多能映射的共享存儲區的個數:4096

共享內存的步驟:

1、獲得唯一的key值

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:
    獲得項目相關的唯一的IPC鍵值。
參數:
    pathname:路徑名
    proj_id:項目ID,非0整數(只有低8位有效)
返回值:
    成功返回key值,失敗返回 -1

2、通過key得到獲得一個共享存儲標識符id

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size,int shmflg);
功能:創建或打開一塊共享內存區
參數:
    key:IPC鍵值
    size:該共享存儲段的長度(字節)
    shmflg:標識函數的行爲及共享內存的權限。
參數:
	shmflg:
	IPC_CREAT:如果不存在就創建
	IPC_EXCL:如果已經存在則返回失敗
	位或權限位:共享內存位或權限位後可以設置共享內存的訪問權限,格式和open函數的mode_t一樣,但可執行權限未使用。
返回值:
	成功:返回共享內存標識符。
	失敗:返回-1。

使用shell命令操作共享內存: 查看共享內存 ipcs -m , 刪除共享內存 ipcrm -m shmid

3、將物理內存 映射到虛擬內存:

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr,
int shmflg);
功能:
    將一個共享內存段映射到調用進程的數據段中。
參數:
    shmid:共享內存標識符。
    shmaddr:共享內存映射地址(若爲NULL則由系      統自動指定),推薦使用NULL。
shmflg:共享內存段的訪問權限和映射條件
0:共享內存具有可讀可寫權限。
SHM_RDONLY:只讀。
SHM_RND:(shmaddr非空時纔有效)
    沒有指定SHM_RND則此段連接到shmaddr所指定的地址上(shmaddr必需頁對齊)。
    指定了SHM_RND則此段連接到shmaddr- shmaddr%SHMLBA 所表示的地址上。
返回值:
成功:返回共享內存段映射地址
失敗:返回 -1

注意:
   shmat函數使用的時候第二個和第三個參數一般設爲NULL和0,
   即系統自動指定共享內存地址,並且共享內存可讀可寫

4、操作空間內容

5、斷開映射

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
功能:
       將共享內存和當前進程分離(僅僅是斷開聯繫並不刪除共享內存)。
參數:
	shmaddr:共享內存映射地址。
返回值:
	成功返回 0,失敗返回 -1。

6、物理內存控制(不必須):

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd,struct shmid_ds *buf);
功能:共享內存空間的控制。
參數:
	shmid:共享內存標識符。
	cmd:函數功能的控制。
	buf:shmid_ds數據類型的地址,用來存放或修改共享內存的屬性。
	cmd:函數功能的控制
	IPC_RMID:刪除。
	IPC_SET:設置shmid_ds參數。
	IPC_STAT:保存shmid_ds參數。
	SHM_LOCK:鎖定共享內存段(超級用戶)。
	SHM_UNLOCK:解鎖共享內存段。
返回值:
    成功返回 0,失敗返回 -1。
注意:  SHM_LOCK用於鎖定內存,禁止內存交換。
並不代表共享內存被鎖定後禁止其它進程訪問。
其真正的意義是:被鎖定的內存不允許被交換到虛擬內存中。  
這樣做的優勢在於讓共享內存一直處於內存中,從而提高程序性能

案例:寫

#include<stdio.h>
#include<string.h>
#include<sys/shm.h>
#include<sys/ipc.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
	//1、ftok獲得key值
	key_t key = ftok("/", 2020);
	
	//2、得到物理內存標識
	int shm_id = shmget(key, 16, IPC_CREAT|0666);
	printf("shm_id=%d\n", shm_id);
	
	//3、將物理內存 映射到虛擬內存:
	char *str = (char *)shmat(shm_id,NULL, 0);
	
	//4、操作空間內容
	strcpy(str,"hello shm");
	
	//5、解除映射
	shmdt(str);
}

案例:讀

#include<stdio.h>
#include<string.h>
#include<sys/shm.h>
#include<sys/ipc.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
	//1、ftok獲得key值
	key_t key = ftok("/", 2020);
	
	//2、得到物理內存標識
	int shm_id = shmget(key, 16, IPC_CREAT|0666);
	printf("shm_id=%d\n", shm_id);
	
	//3、將物理內存 映射到虛擬內存:
	char *str = (char *)shmat(shm_id,NULL, 0);
	
	//4、操作空間內容
	printf("共享內存的內容爲:%s\n",str);
	
	//5、解除映射
	shmdt(str);
	
}

運行結果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章