Linux-进程间通信(mmap,pipe,fifo)-day03

mmap:

mmap也就是共享内存。通过借助共享内存存放到磁盘文件,然后 就可以借助指针来访问共享内存,达到通信的效果。而如何借助mmap来实现呢,也就是一个函数的事—>mmap(由于mmap较为复杂,所以需要先讲一下原函数)
原函数:void *mmap(void *addr,size_t length, int prot, int flags,intn fd, off_t offset);
返回值:成功:映射区首地址,失败:MAP_FAILED 这个宏
参数:
addr: 建立映射区的首地址,由linux内核决定,使用的时候直接传递NULL即可。
length: 想创建的映射区的大小
prot: 映射区权限(不是文件权限) 有三个宏: PROT_READ, PROT_WRITE, PROT_READ|PROT_WRITE(读,写,读和写)
flags: 标志位参数(主要用来设定更新物理内存,设置共享,创建匿名映射区)
主要的两个宏:
MAP_SHARED :将映射区的操作反映到物理设备(磁盘)
MAP_PRIVATE: 映射区所做的修改不会反映到物理设备
(由于我们需要达到进程间通信的目的,所以需要我们一般使用MAP_SHARED)
fd: 用来建设映射区的文件描述符
offset:映射文件的偏移(4K的整数倍,(4k=4096))
使用mmap的注意事项:
1.当你使用mmap的fd文件如果为刚创建的文件则,需要使用ftruncate(fd,size)来拓展一下文件。是因为不能使用mmap来创建一个空大小的文件映射区(第二个参数不能为零)。而且映射区的大小必须小于文件的大小
2.munmap(关闭映射区)指定的地址和mmap返回的地址必须保持一致。二者不能有偏差。
3.mmap的权限(第三个参数)必须
小于等于
打开文件对应的权限。(注意:mmap创建映射区的时候时隐含一次读操作,所以当你开文件的时候必须有O_RDONLY这个权限)
4.由于映射区是由内核中的mmu进行映射,而mmu的单位就是4K
5.注意mmap应检测返回值
6.一旦建立好文件映射区,文件描述符就可以关闭。
简单实例代码:

#include
#include
#include
#include
#include
#include
#include
int main()
{
	pid_t pid;
	int fd=open("temp",O_RDWR|O_CREAT|O_TRUNC,0644);
	if(fd<0)
	{
		perror("open error");
		exit(1);
	}
	unlink("temp");//删除临时文件的目录项,使之具备被释放条件
	//当占用该文件的所有进程结束时,该文件就被释放
	ftruncate(fd,1024);
	char *p;
	p=mmap(NULL,1024,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	if(p==MAP_FAILED)
	{
		perror("mmapfailed");
		exit(1);
	}
	close(fd);

	pid=fork();
	if(pid<0)
	{
	
		perror("mmapfailed");
		exit(1);
	}
	else if(pid==0){
		strcpy(p,"mmap changed");
		printf("son :%s\n",p);

	}
	else{
		sleep(1);
		printf("father: %s\n",p);

	}
	munmap(p,1024);
	return 0;
}

Linux-进程间通信(mmap,pipe,fifo)-day03_第1张图片

(补充一下:既然我们可以利用同一个文件描述符进行通信,那我们也可以在血缘关系中利用主参数传参进行对同一个文件进行通信,原理一模一样)

mmap匿名通信:如果你不想借助文件,只借助mmap就可以使用mmap匿名通信,也就是在第四个参数或上MAP_ANON,并将文件标识符设置为-1。由于区别不大所以直接看代码就行了

#include
#include
#include
#include
#include
#include
int main()
{
pid_t pid;

char *fun=(char *)mmap(NULL,1024,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
if(fun==MAP_FAILED)
{
perror("mmap wrong");
exit(1);
}
pid=fork();
if(pid<0)
{
perror("fork error");
exit(1);
}
else if(pid==0){
strcpy(fun,"mmap shared\n");
printf("son: %s",fun);
}
else{
sleep(1);
printf("father: %s",fun);
}
munmap(fun,1024);


}

Linux-进程间通信(mmap,pipe,fifo)-day03_第2张图片
蓝色标注线的地方就是结果

pipe(无名管道):

管道是一种最基本的ipc机制(IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程,也就是进程间的通信)
作用于有关系(如父子关系)的进程之间。通过调用pipe函数即可创建。并且有以下几个特点
1.为一个伪文件(不占用系统内存)
2.由两个文件描述符来操作,一个表示读取数据,一个表示写入数据
3.数据从管道的写段流入,从读端流出。
原理:
管道为内核使用环形队列机制,借助内核缓冲区(4k,可以用ulimit -a查看(会发现共有八个扇区(512字节)) )实现。
管道的局限性:
1.数据不能自己读同时自己写
2.数据一旦被读走就会丢失,不可以重复读
3.由于管道采取双向半双工的通信方式,因此数据只能在一个方向上流动。
4.只能在有公共祖先的进程间使用
/* 这里说明一下什么是双向半双工,我们先假设有A,B这两个进程需要利用管道进行通信,A可以给B发数据,B也可以给A发数据,这就是双向。但是 当A给B发信息的时候,B不能同时给A发信息,必须得等A发完信息后才可以通过修改一定的配置(就是修改操作管道的那两个文件描述符),让B发给A信息。这种不能同时相互通信就是半双工,如果是起到两个人打电话的功能,就是全双工*/
具体实现代码:

#include
#include
#include
#include
int main()
{
	int fd[2];
	pid_t pid;
	int ret=pipe(fd);
	if(ret==-1)
	{
		perror("pipe error");
		exit(1);
	}
	pid =fork();
	if(pid==-1)
	{
	perror("fork error");
	exit(1);
	}
	else if(pid==0)//子进程
	{
	close(fd[1]);//关闭写端
	sleep(1);//睡一会防止子进程提前读
	char buf[100];
	ret=read(fd[0],buf,sizeof(buf));
	if(ret==0)
	{
	printf("nothing\n");
	}
	write(STDOUT_FILENO,buf,ret);//将buf写到标准输出上(就是打印一下)
	
	}
	else{
		close(fd[0]);//关闭读端;
		write(fd[1],"my pipe\n",strlen("my pipe\n"));
	}

}

fifo(有名管道):

fifo 的原理与pipe类似,这里就不再赘述。我们谈一下其中的差别:
1.fifo有名管道,也就是在你编写程序之前,需要利用
mkfifo myfifo来创造一个fifo文件。以便以后面的使用
2.fifo与pipe不同的是,fifo需要利用open函数进行打开
写端:
int fd=open(argv[1],O_WRONLY);//argv[1]是主函数传的参数
读端:
int fd=open(argv[1],O_RDONLY);//argv[1]是主函数传的参数
然后通过文件描述符进行读写。
3.当你使用fifo打开一端时,比如读端,程序就会在在阻塞等待fifo另一端(写端)打开。反之亦然。

读端代码:


#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
	if(argc!=2)
	{
		perror("main error");
		exit(1);
	}
	int fd=open(argv[1],O_RDONLY);
	char buf[1024];
	int num=0;
	while(1)
	{
		sleep(1);
		memset(buf,0,sizeof(buf));
		read(fd,buf,1024);
printf("%s\n",buf);
		sleep(1);
	}
	close(fd);


}

写端代码:

#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
	if(argc!=2)
	{
		perror("main error");
		exit(1);
	}
	int fd=open(argv[1],O_WRONLY);
	char buf[1024];
	int num=0;
	while(1)
	{
		memset(buf,0,sizeof(buf));
		sprintf(buf,"坤坤爱篮球%d",num++);
		write(fd,buf,sizeof(buf));
		sleep(1);
	}
	close(fd);


}

fw 为写端的程序 myfifo是之前利用mkfifo命令创建的 fifo文件
Linux-进程间通信(mmap,pipe,fifo)-day03_第3张图片
fr为读端的应用程序
注:而且运行时,博主是先打开写端,后打开读端,然而读端仍然是从0开始打印,所以可以体现写端阻塞等待。
Linux-进程间通信(mmap,pipe,fifo)-day03_第4张图片

附录:

追踪段错误的方法:首先gcc -g进行编译 ,然后 gdb直接调试,run之后代码停止的行数就是出现段错误的位置。

目录项:目录项中存储了inode的编号。inode 是一种结构体,存放了该文件的大小,权限,类型,所有者,和一个存储地址的指针(该指针指向文件内容在磁盘上的位置)。而创建一个硬连接实际上就是创建一个目录项,unlink就是删除一个硬链接。而正常情况一个文件对应一个目录项。所以在你删除该目录项之后,系统就会自动将不被占用且没有目录项的inode释放。放置空间占用。而且我们在进行进程间通信的时候,只需要使用该文件达到通信的功能,而通信的内容不用保存,所以这个时候,就可以将临时文件unlink掉,使进程结束的时候空间被自动释放。

strace +可执行文件:追踪可执行文件执行的系统调用。

告诫:

花里胡哨的不要碰~~~

你可能感兴趣的:(linux,系统编程)