Open arthur-zhang opened 3 years ago
read/write 读写有一个buffer;
内核为了加速I/O提供的机制
pageCache 在内核空间
向硬盘写文件时,默认异步情况下,并不是直接把文件内容写入到硬盘中才返回的,而是成功拷贝到内核的page cache后就直接返回,所以大多数情况下,硬盘写操作不会是性能瓶颈。写入到内核page cache的pages成为dirty pages,稍后会由内核线程pdflush真正写入到硬盘上。
从硬盘读取文件时,同样不是直接把硬盘上文件内容读取到用户态内存,而是先拷贝到内核的page cache,然后再“拷贝”到用户态内存,这样用户就可以访问该文件。因为涉及到硬盘操作,所以第一次读取一个文件时,不会有性能提升;不过,如果一个文件已经存在page cache中,再次读取该文件时就可以直接从page cache中命中读取不涉及硬盘操作,这时性能就会有很大提高。
查看pageCache的工具:vmtouch。https://github.com/hoytech/vmtouch
- vmtouch
居然看到有人提到了 vmtouch,很不错啊,忘记讲了
- 进程发起读文件请求。
- 内核通过查找进程文件符表,定位到内核已打开文件集上的文件信息,从而找到此文件的 inode。
- inode 在 address_space 上查找要请求的文件页是否已经缓存在页缓存 (内核)中。如果存在,则直接返回这片文件页的内容。
- 如果不存在,则通过 inode 定位到文件磁盘地址,将数据从磁盘复制到页缓存(第一次copy)。之后再次发起读页面过程,进而将页缓存中的数据发给用户进程(第二次copy)。
常规的文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于 >>页缓存处在内核空间<<,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的 Buffer 在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝。
#define size 1024*1024*500
#define LEN 1024
#include <stdio.h>
int main()
{
FILE *fp1,*fp2;
char *buf = (char *) malloc(size);
int i,j;
fp1=fopen("/home/parallels/tmp/data","rb");
fp2=fopen("/home/parallels/tmp/data.out","wb");
for(j=0;j<1024*500;j++)
{
fread(buf,1024,1,fp1);
for(i=0;i<LEN;i++)
buf[i]++;
fwrite(buf,LEN,1,fp2);
}
printf("ok!\n");
fclose(fp1);
fclose(fp2);
}
初始化数据:dd if=/dev/zero of=/home/parallels/tmp/data bs=1024k count=500
执行多次的数据: 时间的花费主要是在用户态的运行时间
mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间 (虚拟空间),实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。
在内存映射的过程中,并没有实际的数据copy, 文件没有被载入内存,只是逻辑上被放入了内存,当真正的读写数据的时候才会将数据写入到内存中,但是写数据的过程已经和内存映射没有关系了
如下图的过程
过程1: 通过mmap函数完成内存映射,使得磁盘文件的地址和虚拟空间的地址完成一一对应的关系,并获取到一个指针ptr,指向虚拟空间的一个地址。
过程2: 当通过指针ptr访问数据的时候,会通过MMU将虚拟地址转换成物理地址,
过程3: 因为建立内存映射并没有实际拷贝数据,这时,MMU在地址映射表中是无法找到与ptr相对应的物理地址的,也就是MMU失败,将产生一个缺页 中断,缺页中断的中断响应函数会在swap中寻找相对应的页面,如果找不到(也就是该文件从来没有被读入内存的情况),则会通过mmap()建立 的映射关系,从硬盘上将文件读取到物理内存中
过程4: 如果在拷贝数据时,发现物理内存不够用,则会通过虚拟内存机制(swap)将暂时不用的物理页面交换到硬盘上
mmap 进行的文件操作中,首先会创建新的虚拟内存区域与文件磁盘地址之间的映射关系,在之后数据访问中,如果发现内存中并无相应的数据,则发起缺页异常,通过已经建立好的映射关系,只使用一次数据拷贝就将数据从磁盘中拷贝到用户空间中,供用户态进程使用
优点:
1. 减少了数据的拷贝次数,用内存读写取代 I/O 读写,提高了文件读取效率。
2. 实现了用户空间和内核空间的高效交互(映射)方式。各自的空间修改操作都会直接反映在共享(Shared)区域内,从而被对方空间及时捕捉到。
3. 提供不同进程间共享内存及相互通信的方式。
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#define size 1024*1024*500
#define LEN 1024
int main()
{
int fdin,fdout;
struct stat statbuf;
void *src,*dst;
char *p;
int i,j;
fdin=open("/home/parallels/tmp/data",O_RDONLY);
fdout=open("/home/parallels/tmp/data2.out",O_RDWR|O_CREAT|O_TRUNC);
if((src=mmap(0,size,PROT_READ,MAP_SHARED,fdin,0))==MAP_FAILED)
{
printf("src map error\n");
return -1;
}
lseek(fdout,size-1,SEEK_SET);
write(fdout,"\0",1);
if((dst=mmap(0,size,PROT_READ|PROT_WRITE,MAP_SHARED,fdout,0))==MAP_FAILED)
{
printf("dest map error\n");
return -1;
}
memcpy(dst,src,size);
p=(char*)dst;
// 通过指针直接操作文件数据
//for(i=0;i<size/LEN;i++)
//{
// for(j=0;j<LEN;j++)
// p[j]++;
// p+=LEN;
//}
printf("ok\n");
close(fdout);
return 0;
}
执行多次的结果:时间的花费主要是在内核态的时间,用户态并没有耗费时长
文件读写:dd 命令
dd if=/dev/zero of=/home/ljt/dd.out bs=4096 count=$((1024*512))
free -w | cat /proc/meminfo | grep -iE "buffers|cached"
time 查看命令执行时长命令
(1)了解常用的IO函数 ssize_t read(int fd,const void buffer,size_t count); ssize_t read(int fd,const void buffer,size_t count); (2)page cahce page cahce是由物理page组成的,对应磁盘上的block,大小是可以动态变化的,一个page对应多个block
当内核发起一个读请求(例如read())时,会先检查page cache是否有数据,有的话直接从内存读取,没有的话,再从磁盘中读取数据,并缓存到cache中。page 可以缓存文件的部分内容,也可以缓存整个文件。 当内核发起一个写请求(例如write())时,也是先往cache中写入,写入后,page页会被标记为dirty,内核会周期性地将dirty list中的page写回到磁盘上。
(3)page cahce和buffer的区别 page cache是对文件系统上的文件数据进行读写的缓存 buffer cache是对块设备进行读写时的缓存
(4)实验 free -w -m 可以查看buffer和cache的大小 echo 3 > /proc/sys/vm/drop_caches可以清空cache大小 命令 dd,拷贝文件 dd if=/dev/zero of=dd.out bs=4096 count=$((1024*128)) free -m 查看cache和buffer的大小 time 查看命令的时间
主要内容: