linux内核管理pagecache的一丢丢知识整理
pagecache是linux内核为了提高程序运行效率开辟出来的内存。通俗点理解,程序在硬盘里是整齐码放的,但是运行的时候是需要哪一块就把哪一块load到内存里使用,如果程序运行过程中发现需要的代码没有load进内存,就产生一个软终端的系统调用,然后切换到内核态去硬盘里load这部分内容到内存里,继续运行程序。一个程序的重复的进程复用这些内存里的pagecache。这么做的好处当然是程序运行更快了,缺点显而易见就是容易丢数据(程序正在疯狂IO,突然踢电源了,在在内存中的数据没有写到磁盘上,就丢了)。
linux缓存的几个参数
命令行 sysctl -a | grep dirty 可以看到
- vm.dirty_background_ratio 是内存可以填充脏数据的百分比。这些脏数据稍后会写入磁盘,pdflush/flush/kdmflush这些后台进程会稍后清理脏数据。比如,我有3.2G内存,那么有3.2G的脏数据可以待着内存里,超过3.2G的话就会有后台进程来清理。
- vm.dirty_ratio是可以用脏数据填充的绝对最大系统内存量,当系统到达此点时,必须将所有脏数据提交到磁盘,同时所有新的I/O块都会被阻塞,直到脏数据被写入磁盘。这通常是长I/O卡顿的原因,但这也是保证内存中不会存在过量脏数据的保护机制。
- vm.dirty_background_bytes和vm.dirty_bytes是另一种指定这些参数的方法。如果设置_bytes版本,则_ratio版本将变为0,反之亦然。
- vm.dirty_expire_centisecs 指定脏数据能存活的时间。在这里它的值是30秒。当 pdflush/flush/kdmflush 在运行的时候,他们会检查是否有数据超过这个时限,如果有则会把它异步地写到磁盘中。毕竟数据在内存里待太久也会有丢失风险。
- vm.dirty_writeback_centisecs 指定多长时间 pdflush/flush/kdmflush 这些进程会唤醒一次,然后检查是否有缓存需要清理。
丢数据
以java程序代码为例,java在BIO模型下有两种IO方法,一种是普通的写,一种是用buffer写,普通写法很慢(为什么慢看这里),在pagecache参数设置合适时丢的就少,buffer写很快,丢的多。代码:
import java.io.*; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel;/*** Author: ljf* CreatedAt: 2021/4/4 下午8:57*/ public class OSFileIO {static String path = "/home/ljf/out.txt";private static byte[] data = "123456789\n".getBytes();public static void main(String[] args) {switch (args[0]) {case "0":testBasicFileIO();break;case "1":testBufferedFileIO();break;case "2":testRandomAccessFileWrite();break;case "3":whatByteByffer();break;default:break;}}private static void whatByteByffer() {ByteBuffer buffer = ByteBuffer.allocate(1024); // ByteBuffer buffer = ByteBuffer.allocateDirect(1024);System.out.println("position: " + buffer.position());System.out.println("limit: " + buffer.limit());System.out.println("capacity: " + buffer.capacity());System.out.println("mark: " + buffer);buffer.put("123".getBytes());System.out.println("put 123 ------------------");System.out.println("mark: " + buffer);buffer.flip(); // 读写交替System.out.println("----------------- flip ---------------------");System.out.println("mark: " + buffer);buffer.get();System.out.println("--------------- get --------------");System.out.println("mark: " + buffer);buffer.compact();System.out.println("---------------- compact --------------");System.out.println("mark: " + buffer);buffer.clear();System.out.println("-------------------- clear ----------------");System.out.println("mark: " + buffer);}/*** 测试文件的NIO*/private static void testRandomAccessFileWrite() {try {RandomAccessFile raf = new RandomAccessFile(path, "rw");raf.write("hello faithgreen\n".getBytes());raf.write("hello luopeiling\n".getBytes());System.out.println("write ----------------------");System.in.read();raf.seek(4);raf.write("xxoo".getBytes());System.out.println("seek -------------------");System.in.read();FileChannel rafChannel = raf.getChannel();// mmap 堆外和文件关联的映射的byte not objectMappedByteBuffer map = rafChannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096);// 不是系统调用,但是数据会到达内核的pagecache// 曾经我们要out.write()这样的系统调用,才能让程序的data进入内核的pagecache// 曾经必须有用户态和内核太的切换// mmap的内存映射,依然是内核的pagecache体系锁约束的// 换言之,丢数据// github上有一些c程序员写的jni的扩展,使用linux内核的Direct IO,忽略了linux的pagecache// 是吧pagecache 交给了程序自己开辟一个字节数组当做pagecache,动用代码逻辑来维护一致性/dirty等一些列复杂的问题map.put("@@@".getBytes());System.out.println("map put -------------------");System.in.read();// map.force(); flushraf.seek(0);ByteBuffer buffer = ByteBuffer.allocate(8192); // ByteBuffer buffer1 = 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.println(buffer.get(i));}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}}/*** jvm的buffer 8kb 8kb写的,调用syscall(8kb 字节数组)*/private static void testBufferedFileIO() {File file = new File(path);try {BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));while (true) {try {bos.write(data);} catch (IOException e) {e.printStackTrace();}}} catch (FileNotFoundException e) {e.printStackTrace();}}private static void testBasicFileIO() {File file = new File(path);try {FileOutputStream out = new FileOutputStream(file);while (true) {out.write(data);}} catch (FileNotFoundException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}} }case “0” 和 case "1"分别是用普通写法和buffer写法死循环往/home/ljf/out.txt里疯狂写 "123456789\n"数据,看增长的速度能明显区别出来。下面模拟丢数据。
设置pagecache参数,到90%再写磁盘。
vi /etc/sysctl.conf,在最后加上
vm.dirty_background_ratio = 90 # 当内存占用达到可用内存的90%了再用后台异步线程写磁盘
vm.dirty_ratio = 90 # 当内存占用达到可用内存的90%了阻塞所有IO,写磁盘
vm.dirty_writeback_centisecs = 5000 #这个时间设置大点,脏页可以写50s。
vm.dirty_expire_centisecs = 30000 # 这个时间设置大点,脏页可以过期50s。
保存退出,然后 sysctl -p 让配置生效。
然后用buffer的方法写
瞬间就涨到2.5g了
踢电源,再连进来看,只有380m了,丢了好多数据。为什么redis,mysql的持久化策略有每秒写磁盘就是为了防止丢太多数据
总结
以上是生活随笔为你收集整理的linux内核管理pagecache的一丢丢知识整理的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: Bio->Nio->Selector->
- 下一篇: 系统调用回答为什么要用buffer写