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);
	
}

运行结果:
在这里插入图片描述

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