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写