linux内核管理pagecache的一丢丢知识整理
pagecache是linux內(nèi)核為了提高程序運(yùn)行效率開(kāi)辟出來(lái)的內(nèi)存。通俗點(diǎn)理解,程序在硬盤(pán)里是整齊碼放的,但是運(yùn)行的時(shí)候是需要哪一塊就把哪一塊load到內(nèi)存里使用,如果程序運(yùn)行過(guò)程中發(fā)現(xiàn)需要的代碼沒(méi)有l(wèi)oad進(jìn)內(nèi)存,就產(chǎn)生一個(gè)軟終端的系統(tǒng)調(diào)用,然后切換到內(nèi)核態(tài)去硬盤(pán)里load這部分內(nèi)容到內(nèi)存里,繼續(xù)運(yùn)行程序。一個(gè)程序的重復(fù)的進(jìn)程復(fù)用這些內(nèi)存里的pagecache。這么做的好處當(dāng)然是程序運(yùn)行更快了,缺點(diǎn)顯而易見(jiàn)就是容易丟數(shù)據(jù)(程序正在瘋狂IO,突然踢電源了,在在內(nèi)存中的數(shù)據(jù)沒(méi)有寫(xiě)到磁盤(pán)上,就丟了)。
linux緩存的幾個(gè)參數(shù)
命令行 sysctl -a | grep dirty 可以看到
- vm.dirty_background_ratio 是內(nèi)存可以填充臟數(shù)據(jù)的百分比。這些臟數(shù)據(jù)稍后會(huì)寫(xiě)入磁盤(pán),pdflush/flush/kdmflush這些后臺(tái)進(jìn)程會(huì)稍后清理臟數(shù)據(jù)。比如,我有3.2G內(nèi)存,那么有3.2G的臟數(shù)據(jù)可以待著內(nèi)存里,超過(guò)3.2G的話就會(huì)有后臺(tái)進(jìn)程來(lái)清理。
- vm.dirty_ratio是可以用臟數(shù)據(jù)填充的絕對(duì)最大系統(tǒng)內(nèi)存量,當(dāng)系統(tǒng)到達(dá)此點(diǎn)時(shí),必須將所有臟數(shù)據(jù)提交到磁盤(pán),同時(shí)所有新的I/O塊都會(huì)被阻塞,直到臟數(shù)據(jù)被寫(xiě)入磁盤(pán)。這通常是長(zhǎng)I/O卡頓的原因,但這也是保證內(nèi)存中不會(huì)存在過(guò)量臟數(shù)據(jù)的保護(hù)機(jī)制。
- vm.dirty_background_bytes和vm.dirty_bytes是另一種指定這些參數(shù)的方法。如果設(shè)置_bytes版本,則_ratio版本將變?yōu)?,反之亦然。
- vm.dirty_expire_centisecs 指定臟數(shù)據(jù)能存活的時(shí)間。在這里它的值是30秒。當(dāng) pdflush/flush/kdmflush 在運(yùn)行的時(shí)候,他們會(huì)檢查是否有數(shù)據(jù)超過(guò)這個(gè)時(shí)限,如果有則會(huì)把它異步地寫(xiě)到磁盤(pán)中。畢竟數(shù)據(jù)在內(nèi)存里待太久也會(huì)有丟失風(fēng)險(xiǎn)。
- vm.dirty_writeback_centisecs 指定多長(zhǎng)時(shí)間 pdflush/flush/kdmflush 這些進(jìn)程會(huì)喚醒一次,然后檢查是否有緩存需要清理。
丟數(shù)據(jù)
以java程序代碼為例,java在BIO模型下有兩種IO方法,一種是普通的寫(xiě),一種是用buffer寫(xiě),普通寫(xiě)法很慢(為什么慢看這里),在pagecache參數(shù)設(shè)置合適時(shí)丟的就少,buffer寫(xiě)很快,丟的多。代碼:
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(); // 讀寫(xiě)交替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);}/*** 測(cè)試文件的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 堆外和文件關(guān)聯(lián)的映射的byte not objectMappedByteBuffer map = rafChannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096);// 不是系統(tǒng)調(diào)用,但是數(shù)據(jù)會(huì)到達(dá)內(nèi)核的pagecache// 曾經(jīng)我們要out.write()這樣的系統(tǒng)調(diào)用,才能讓程序的data進(jìn)入內(nèi)核的pagecache// 曾經(jīng)必須有用戶態(tài)和內(nèi)核太的切換// mmap的內(nèi)存映射,依然是內(nèi)核的pagecache體系鎖約束的// 換言之,丟數(shù)據(jù)// github上有一些c程序員寫(xiě)的jni的擴(kuò)展,使用linux內(nèi)核的Direct IO,忽略了linux的pagecache// 是吧pagecache 交給了程序自己開(kāi)辟一個(gè)字節(jié)數(shù)組當(dāng)做pagecache,動(dòng)用代碼邏輯來(lái)維護(hù)一致性/dirty等一些列復(fù)雜的問(wèn)題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寫(xiě)的,調(diào)用syscall(8kb 字節(jié)數(shù)組)*/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"分別是用普通寫(xiě)法和buffer寫(xiě)法死循環(huán)往/home/ljf/out.txt里瘋狂寫(xiě) "123456789\n"數(shù)據(jù),看增長(zhǎng)的速度能明顯區(qū)別出來(lái)。下面模擬丟數(shù)據(jù)。
設(shè)置pagecache參數(shù),到90%再寫(xiě)磁盤(pán)。
vi /etc/sysctl.conf,在最后加上
vm.dirty_background_ratio = 90 # 當(dāng)內(nèi)存占用達(dá)到可用內(nèi)存的90%了再用后臺(tái)異步線程寫(xiě)磁盤(pán)
vm.dirty_ratio = 90 # 當(dāng)內(nèi)存占用達(dá)到可用內(nèi)存的90%了阻塞所有IO,寫(xiě)磁盤(pán)
vm.dirty_writeback_centisecs = 5000 #這個(gè)時(shí)間設(shè)置大點(diǎn),臟頁(yè)可以寫(xiě)50s。
vm.dirty_expire_centisecs = 30000 # 這個(gè)時(shí)間設(shè)置大點(diǎn),臟頁(yè)可以過(guò)期50s。
保存退出,然后 sysctl -p 讓配置生效。
然后用buffer的方法寫(xiě)
瞬間就漲到2.5g了
踢電源,再連進(jìn)來(lái)看,只有380m了,丟了好多數(shù)據(jù)。為什么redis,mysql的持久化策略有每秒寫(xiě)磁盤(pán)就是為了防止丟太多數(shù)據(jù)
總結(jié)
以上是生活随笔為你收集整理的linux内核管理pagecache的一丢丢知识整理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Bio->Nio->Selector->
- 下一篇: 在Linux系统下防火墙开放所需要访问的