02-你真的了解Linux中的pagecache吗?

394 阅读7分钟

为了从更深层次了解计算机的IO ,需要对计算的文件系统进行深入学习,下边是本人的学习笔记,请大家一起参考,要是有说的不对的地方,请大家指正!

1、Linux 中的PageCache

我们通过VMvare虚拟机,来做个实验来证明linux 中的pagecache
编写java 代码

import org.junit.Test;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class OSFileIO {

    static byte[] data = "123456789\n".getBytes();
    static String path =  "/root/testfileio/out.txt";
    public static void main(String[] args) throws Exception {
        switch ( args[0]) {
            case "0" :
                testBasicFileIO();
                break;
            case "1":
                testBufferedFileIO();
                break;
            case "2" :
                testRandomAccessFileWrite();
            default:

        }
    }

    //最基本的file写

    public static  void testBasicFileIO() throws Exception {
        File file = new File(path);
        FileOutputStream out = new FileOutputStream(file);
        while(true){
            Thread.sleep(10);
            out.write(data);
        }
    }
    //测试buffer文件IO
    //  jvm  8kB   syscall  write(8KBbyte[])

    public static void testBufferedFileIO() throws Exception {
        File file = new File(path);
        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
        while(true){
            Thread.sleep(10);
            out.write(data);
        }
    }
    //测试文件NIO
        public static void testRandomAccessFileWrite() throws  Exception {
        RandomAccessFile raf = new RandomAccessFile(path, "rw");
        raf.write("hello niotest\n".getBytes());
        raf.write("hello seanzhou\n".getBytes());
        System.out.println("write------------");
        System.in.read();
        raf.seek(4);
        raf.write("ooxx".getBytes());

        System.out.println("seek---------");
        System.in.read();

        FileChannel rafchannel = raf.getChannel();
        //mmap  堆外  和文件映射的   byte  not  objtect
        MappedByteBuffer map = rafchannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096);


        map.put("@@@".getBytes());  //不是系统调用  但是数据会到达 内核的pagecache
            //曾经我们是需要out.write()  这样的系统调用,才能让程序的data 进入内核的pagecache
            //曾经必须有用户态内核态切换
            //mmap的内存映射,依然是内核的pagecache体系所约束的!!!
            //换言之,丢数据
            //你可以去github上找一些 其他C程序员写的jni扩展库,使用linux内核的Direct IO
            //直接IO是忽略linux的pagecache
            //是把pagecache  交给了程序自己开辟一个字节数组当作pagecache,动用代码逻辑来维护一致性/dirty。。。一系列复杂问题

        System.out.println("map--put--------");
        System.in.read();

//        map.force(); //  flush



        raf.seek(0);

        ByteBuffer buffer = ByteBuffer.allocate(8192);
//        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

        int read = rafchannel.read(buffer);   //buffer.put()
        System.out.println(buffer);
        buffer.flip();
        System.out.println(buffer);

        for (int i = 0; i < buffer.limit(); i++) {
            Thread.sleep(200);
            System.out.print(((char)buffer.get(i)));
        }
    }


 

准备shell 脚本

[root@node20 testfileio]# cat /opt/file/myshell.sh 
#!/bin/bash
rm -rf *out*
/usr/local/soft/jdk1.8.0_191/bin/javac OSFileIO.java
strace -ff -o out /usr/local/soft/jdk1.8.0_191/bin/java OSFileIO $1

前提是安装好java 环境变量,运行shell 脚本

[root@node20 file]# sh myshell.sh 0

然后打开一个新的窗口看目录/root/testfileio/out.txt 中out.txt 文件大小的变化

[root@node20 testfileio]# ll
total 448
-rw-r--r-- 1 root root 339390 Sep 24 03:41 out.txt
[root@node20 testfileio]# ll
total 960
-rw-r--r-- 1 root root 671910 Sep 24 03:41 out.txt
[root@node20 testfileio]# ll
total 960
-rw-r--r-- 1 root root 876440 Sep 24 03:41 out.txt
[root@node20 testfileio]# ll
total 1984
-rw-r--r-- 1 root root 1051820 Sep 24 03:41 out.txt
[root@node20 testfileio]# ll
total 1984
-rw-r--r-- 1 root root 1237410 Sep 24 03:41 out.txt
[root@node20 testfileio]# ll
total 1984
-rw-r--r-- 1 root root 1480060 Sep 24 03:41 out.txt
[root@node20 testfileio]# ll
total 1984
-rw-r--r-- 1 root root 1734110 Sep 24 03:41 out.txt
[root@node20 testfileio]# ll
total 4032
-rw-r--r-- 1 root root 2169910 Sep 24 03:41 out.txt

可以看到文件一直在不停的增长,这个时候,我们在VMvare 上操作关机,

image.png ps:红色框框框起来的地方说明是强制拔电源关机,没给电脑准备的时间,如果点上面的'关闭客户机'属于在本机计算机按了关机按钮,如果是“关闭客户机”,会经历刷将内存中的字节数组刷写到磁盘的过程,而“关机”不会经历将内存中的自己数组刷写到磁盘的过程
我们再次开机,查看/root/testfileio/out.txt 该文件的大小

[root@node20 /]# cd /root/testfileio/
[root@node20 testfileio]# ll
total 0
[root@node20 testfileio]# 

发现该文件已经没有了,说明我们生产的文件,都是到了内核中,虚拟机关机,内核中 的数据没有刷写到硬盘就已经消失了。

2、调整Linux的pageCache

在Linux系统中 通过如下命令可以查看操作系统对pagecache的设置

[root@node20 ~]# sysctl -a |grep dirty
sysctl: reading key "net.ipv6.conf.all.stable_secret"
sysctl: reading key "net.ipv6.conf.default.stable_secret"
sysctl: reading key "net.ipv6.conf.ens33.stable_secret"
sysctl: reading key "net.ipv6.conf.lo.stable_secret"
vm.dirty_background_bytes = 0
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_ratio = 30
vm.dirty_writeback_centisecs = 500
[root@node20 ~]#

参数说明:
vm.dirty_background_ratio当pagecahe中的数据占了内存可用空间的百分比,就会将脏页中的数据写入到磁盘;举个例子,假设我们的内存可用容量是1个G,也就是说pagecacha中的数据到达1024MB*10%=102.4MB的时候,才会将pagecache中的数据刷到硬盘
vm.dirty_background_bytes这个参数与上边的类似,只不过单位不在是百分比,而是byte,这两个只能有一个生效,不生效的那一项需要设置为0
vm.dirty_ratio这个值说的是当pagecache中的数据到达系统可用内存空间的百分比后,就无法在pagecache中写入新的数据了
vm.dirty_bytes = 0这个也和上边的类似,单位为byte
vm.dirty_expire_centisecs表示数据在cache中允许被缓存多长时间。以百分之一秒为单位
vm.dirty_writeback_centisecs表示间隔多长时间,系统去检查一次cache中的数据量是否超过门限值,以百分之一秒为单位

2.1、调整相关参数,使用插件来查看pagecache的变化

我们先将vm.dirty_background_ratiovm.dirty_ratio故意调整的大一点,这里我们都设置成90,设置这两个参数需要修改/etc/sysctl.conf这个文件,修改内容为

[root@node20 /]# vim /etc/sysctl.conf 
[root@node20 /]# sysctl -p
vm.dirty_ratio = 90
vm.dirty_background_ratio = 90
vm.dirty_writeback_centisecs = 5000
vm.dirty_expire_centisecs = 30000

sysctl -p这个命令是修改生效,然后我们继续上边的代码,不过传参改成1,使用 BufferedOutputStream来包装一下,使写文件的速度快一点。
pcstat 可以查看某一个进程或者某个文件的pagecache 的变化,我打开两个标签页,一个标签页用来执行我们的脚本,一个标签页用来实时查看我们的pagecache的使用情况
第一个标签页

[root@node20 file]# sh myshell.sh 1

第二个标签页

[root@node20 testfileio]# ll -h && pcstat out.txt
total 249M
-rw-r--r-- 1 root root 214M Sep 25 02:17 out.txt
+---------+----------------+------------+-----------+---------+
| Name    | Size (bytes)   | Pages      | Cached    | Percent |
|---------+----------------+------------+-----------+---------|
| out.txt | 228550140      | 55799      | 55799     | 100.000 |
+---------+----------------+------------+-----------+---------+
[root@node20 testfileio]# 
[root@node20 testfileio]# ll -h && pcstat out.txt
total 505M
-rw-r--r-- 1 root root 369M Sep 25 02:17 out.txt
+---------+----------------+------------+-----------+---------+
| Name    | Size (bytes)   | Pages      | Cached    | Percent |
|---------+----------------+------------+-----------+---------|
| out.txt | 391842360      | 95665      | 95665     | 100.000 |
+---------+----------------+------------+-----------+---------+
[root@node20 testfileio]# ll -h && pcstat out.txt
total 505M
-rw-r--r-- 1 root root 427M Sep 25 02:17 out.txt
+---------+----------------+------------+-----------+---------+
| Name    | Size (bytes)   | Pages      | Cached    | Percent |
|---------+----------------+------------+-----------+---------|
| out.txt | 450400860      | 109962     | 109962    | 100.000 |
+---------+----------------+------------+-----------+---------+
[root@node20 testfileio]# ll -h && pcstat out.txt
total 505M
-rw-r--r-- 1 root root 472M Sep 25 02:17 out.txt
+---------+----------------+------------+-----------+---------+
| Name    | Size (bytes)   | Pages      | Cached    | Percent |
|---------+----------------+------------+-----------+---------|
| out.txt | 498467970      | 121697     | 121697    | 100.000 |
+---------+----------------+------------+-----------+---------+
[root@node20 testfileio]# ll -h && pcstat out.txt
total 1017M
-rw-r--r-- 1 root root 513M Sep 25 02:17 out.txt
+---------+----------------+------------+-----------+---------+
| Name    | Size (bytes)   | Pages      | Cached    | Percent |
|---------+----------------+------------+-----------+---------|
| out.txt | 541621080      | 132232     | 132232    | 100.000 |
+---------+----------------+------------+-----------+---------+
[root@node20 testfileio]# ll -h && pcstat out.txt
total 1017M
-rw-r--r-- 1 root root 555M Sep 25 02:17 out.txt
+---------+----------------+------------+-----------+---------+
| Name    | Size (bytes)   | Pages      | Cached    | Percent |
|---------+----------------+------------+-----------+---------|
| out.txt | 585069030      | 142840     | 142840    | 100.000 |
+---------+----------------+------------+-----------+---------+
[root@node20 testfileio]# ll -h && pcstat out.txt
total 1017M
-rw-r--r-- 1 root root 596M Sep 25 02:17 out.txt
+---------+----------------+------------+-----------+---------+
| Name    | Size (bytes)   | Pages      | Cached    | Percent |
|---------+----------------+------------+-----------+---------|
| out.txt | 628901910      | 153541     | 153541    | 100.000 |
+---------+----------------+------------+-----------+---------+
[root@node20 testfileio]# ll -h && pcstat out.txt
total 1017M
-rw-r--r-- 1 root root 638M Sep 25 02:17 out.txt
+---------+----------------+------------+-----------+---------+
| Name    | Size (bytes)   | Pages      | Cached    | Percent |
|---------+----------------+------------+-----------+---------|
| out.txt | 674159850      | 164590     | 164590    | 100.000 |
+---------+----------------+------------+-----------+---------+

我们可以看到文件大小在快速的增长,pcstat 文件也写入了cache中,我本机的虚拟机为2G内存,根据上边的参数设置,也就是说当我们的文件大小在2G*90%=1.8G大小的时候,才会刷写进磁盘,我们在文件大小还没有到达1.8G 的时候,执行刚才的关机操作,再次打开虚拟机发现out.txt文件已经不存在了。

3、总结

linux系统将文件写入磁盘的时候,先将文件写入pagecache,当写的文件触发pagecache的极限的时候,操作系统在将pagecache中的文件写入磁盘。