存储映射 I/O
1. 存储映射:
将磁盘空间/设备空间(LCD显存)映射到虚拟空间中的一个缓冲区,于是当从缓冲区中取
数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件
这样,就可在不适用 read 和 write 函数的情况下,使用地址(指针)完成 I/O 操作,指针的函数多!!!
2. 存储映射实现---mmap()函数
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 创建共享内存映射
参数:
addr: 指定映射区的首地址。通常传NULL,表示让系统自动分配
length:共享内存映射区的大小。(<= 文件的实际大小)(回想怎么求文件大小?lseek(), truncate())
prot: 共享内存映射区的读写属性。PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
flags: 标注共享内存的共享属性。MAP_SHARED、MAP_PRIVATE
fd: 用于创建共享内存映射区的那个文件的 文件描述符。
offset:默认0,表示映射文件全部。偏移位置。需是 4k 的整数倍。
返回值:
成功:映射区的首地址。
失败:MAP_FAILED (void*(-1)), errno
3. 释放映射空间
int munmap(void *addr, size_t length); 释放映射区。
addr:mmap 的返回值
length:大小
示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
char *p = NULL;
int fd;
fd = open("testmap", O_RDWR|O_CREAT|O_TRUNC, 0644); // 创建文件用于创建映射区
if (fd == -1)
sys_err("open error");
/*
lseek(fd, 10, SEEK_END); // 两个函数等价于 ftruncate()函数
write(fd, "\0", 1);
*/
ftruncate(fd, 20); // 需要借助写权限,才能够对文件进行拓展
int len = lseek(fd, 0, SEEK_END);
p = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED) {
sys_err("mmap error");
}
// 使用 p 对文件进行读写操作.
strcpy(p, "hello mmap"); // 写操作
printf("----%s\n", p); // 读操作
int ret = munmap(p, len); // 释放映射区
if (ret == -1) {
sys_err("munmap error");
}
return 0;
}
思考:
0. 可以open的时候O_CREAT一个新文件来创建映射区吗?
可以。但是必须对文件大小进行扩展。否则出现以下错误:
1. 用于创建映射区的文件大小为 0,实际指定非0大小创建映射区,出 “总线错误”。
2. 用于创建映射区的文件大小为 0,实际制定0大小创建映射区, 出 “无效参数”。
1. 如果mem++,munmap可否成功?
不能,munmap用于释放的 地址,必须是mmap申请返回的地址。
2. 如果open时O_RDONLY, mmap时PROT参数指定PROT_READ|PROT_WRITE会怎样?
用于创建映射区的文件读写属性为,只读。映射区属性为 读、写。 出 “无效参数”。
结论:创建映射区,mmap必须要需要read权限。当访问权限指定为“共享”MAP_SHARED。mmap的读写权限,应该<=文件的open权限。注意: mmap只写不行。
3. 如果文件偏移量为1000会怎样?
offset 必须是 4096的整数倍。(MMU 映射的最小单位 4k )
4. 如果不检测mmap的返回值,会怎样?
出错可能性多,要检测!!!
5. mmap什么情况下会调用失败?
除了第一个参数NULL, 另外5个参数均有可能发生错误
6. 对mem越界操作会怎样?
对申请的映射区内存,不能越界访问。
7. 文件描述符先关闭,对mmap映射有没有影响?
文件描述符fd,在mmap创建映射区完成即可关闭。后续访问文件,用 地址访问。
8. 映射区属性为private会怎么样?
映射区访问权限为 “私有”MAP_PRIVATE, 对内存所做的所有修改,只在内存有效,不会反应到物理磁盘上
映射区访问权限为 “私有”MAP_PRIVATE, 只需要open文件时,有读权限,用于创建映射区即可。
mmap函数的保险调用方式:
1. fd = open("文件名", O_RDWR);
2. mmap(NULL, 有效文件大小, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
mmap父子进程间通信
父子进程使用 mmap 进程间通信:
父进程 先 创建映射区。 open( O_RDWR) mmap( MAP_SHARED );
指定 MAP_SHARED 权限
fork() 创建子进程。
一个进程读, 另外一个进程写。
示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/wait.h>
int var = 100;
int main(void)
{
int *p;
pid_t pid;
int fd = open("/dev/zero", O_RDWR);
//p = (int *)mmap(NULL, 40, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
p = (int *)mmap(NULL, 490, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
//p = (int *)mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
if(p == MAP_FAILED){ //注意:不是p == NULL
perror("mmap error");
exit(1);
}
close(fd);
pid = fork(); //创建子进程
if(pid == 0){
*p = 7000; // 写共享内存
var = 1000;
printf("child, *p = %d, var = %d\n", *p, var);
} else {
sleep(1);
printf("parent, *p = %d, var = %d\n", *p, var); // 读共享内存
wait(NULL);
int ret = munmap(p, 4); //释放映射区
if (ret == -1) {
perror("munmap error");
exit(1);
}
}
return 0;
}
mmap非血缘关系进程间通信
原理:同一个文件的mmap映射区在内核中是同一块缓冲区
无血缘关系进程间 mmap 通信: 【会写】
两个进程 打开同一个文件,创建映射区。
指定flags 为 MAP_SHARED。
一个进程写入,另外一个进程读出。
【注意】:无血缘关系进程间通信。mmap:数据可以重复读取。
fifo:数据只能一次读取。
示例:
mmap_w.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>
struct student{
int id;
char name[256];
int age;
};
int main()
{
struct student stu = {10, "xioayuan", 18};
struct student *p;
int fd = open("testmmap", O_RDWR|O_CREAT|O_TRUNC, 0664);
if (fd == -1){
perror("open testmmap error\n");
exit(1);
}
ftruncate(fd, sizeof stu);
p = (struct student *)mmap(NULL, sizeof(stu),PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED){
perror("mmap error\n");
exit(2);
}
close(fd);
while (1){
memcpy(p, &stu, sizeof stu);
stu.id++;
sleep(1);
}
munmap(p, sizeof stu);
return 0;
}
mmap_r.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>
struct student{
int id;
char name[256];
int age;
};
int main()
{
struct student stu;
struct student *p;
int fd = open("testmmap", O_RDWR);
if (fd == -1){
perror("open testmmap error\n");
exit(1);
}
p = (struct student *)mmap(NULL, sizeof(stu),PROT_READ, MAP_SHARED, fd, 0);
if (p == MAP_FAILED){
perror("mmap error\n");
exit(2);
}
close(fd);
while (1){
printf ("id = %d , name = %s , age = %d\n", p->id, p->name, p->age);
sleep(1);
}
munmap(p, sizeof stu);
return 0;
}
匿名映射
通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创
建映射区一定要依赖一个文件才能实现。通常为了建立映射区要 open 一个 temp 文件,创建好了再 unlink、close
掉,比较麻烦。 可以直接使用匿名映射来代替。其实 Linux 系统给我们提供了创建匿名映射区的方法,无需依赖一
个文件即可创建映射区。同样需要借助标志位参数 flags 来指定。
匿名映射:只能用于 血缘关系进程间通信。
p = (int *)mmap(NULL, 40, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);