Linux系統(tǒng)內存管理知識補充
Linux系統(tǒng)是虛擬內存系統(tǒng),虛擬內存并不是真正的物理內存,而是虛擬的連續(xù)內存地址空間。虛擬內存又分為內核空間和用戶空間,內核空間是內核程序運行的地方,用戶空間是用戶進程代碼運行的地方,只有內核才能直接訪問物理內存并為用戶空間映射物理內存(MMU)。內核會為每個進程分配獨立的連續(xù)的虛擬內存空間,并且在需要的時候映射物理內存,為了完成內存映射,內核為每個進程都維護了一張頁表,記錄虛擬地址與物理地址的映射關系,這個頁表就是存在于MMU中;用戶進程訪問內存的時候,通過頁表把虛擬內存地址轉換為物理內存地址進而訪問數據;其實對于用戶進程而言,虛擬內存就是內存一般的存在(當作內存看待就好)。這樣的設計可以把用戶程序和系統(tǒng)程序分開,互不影響;內核可以對所有的用戶程序進行管理,比如限制內存濫用等。
虛擬內存的最小單位是頁,通常是4KB大小,所以虛擬內存會有很多很多的頁組成,當然也有大頁,顧名思義就是大的虛擬內存空間,比如12KB,2MB。虛擬內存和物理內存的映射都是等空間的,映射的物理內存是多大的,那么占用的虛擬內存差不多也是多大,都是4KB的整數倍。比如映射了一個1KB的內存空間,那么也是占用一頁4KB虛擬內存。
用戶進程在處于用戶態(tài)時,只能訪問用戶空間;只有進入內核態(tài)后,才可以訪問內核空間。雖然每個進程的地址空間都包含了內核空間,但這些內核空間映射的物理內存都是相同的,所以當進程切換到內核態(tài)后可以快速的訪問內核空間數據。
內核其實就是一段特殊的代碼程序,運行于內核空間,控制著計算機的CPU、IO、內存等,提供了一系列的系統(tǒng)接口供外部調用,通常叫做系統(tǒng)調用。只有線程或者進程處于內核態(tài)的時候才能進行系統(tǒng)調用,如果處于用戶態(tài)的話,是需要轉換為內核態(tài)才能訪問。其實就是權限不同,內核態(tài)(Ring0)擁有比用戶態(tài)(Ring3)更高的權限,擁有著訪問系統(tǒng)硬件資源的權限。
一般用戶線程或者進程是不需要切換到內核態(tài)運行的,除非:
1. 系統(tǒng)調用,其實系統(tǒng)調用本身就是中斷,但是軟件中斷,跟硬中斷不同。
2. 異常:如果當前進程運行在用戶態(tài),如果這個時候發(fā)生了異常事件,就會觸發(fā)切換。
例如:缺頁異常。
3. 外設中斷:當外設完成用戶的請求時,會向CPU發(fā)送中斷信號。
比如讀取硬盤數據,除了IO屬于系統(tǒng)操作需要切換為內核態(tài)來獲取權限的原因外還要一原因是:
為了減少磁盤的IO操作,為了提高性能而考慮的,因為我們的程序訪問一般都帶有局部性,也就是所謂的局部性原理,即我們訪問了文件的某一段數據,那么接下去很可能還會訪問接下去的一段數據,由于磁盤IO操作的速度比直接訪問內存慢了好幾個數量級,所以OS根據局部性原理會在一次 read()系統(tǒng)調用過程中預讀更多的文件數據緩存在內核IO緩沖區(qū)中,當繼續(xù)訪問的文件數據在緩沖區(qū)中時便直接拷貝數據到進程私有空間,避免了再次的低效率磁盤IO操作。
傳統(tǒng)IO發(fā)送文件
1. 用戶程序調用read,進入內核態(tài),上下文切換由用戶空間切換為內核空間,由DMA(Direct Memory Access)加載文件數據到內核空間。
2. CPU把數據從內核空間復制到用戶空間,轉換為用戶態(tài),上下文由內核空間切換為用戶空間。
3. 用戶程序調用write,再次進入內核態(tài),CPU把數據從用戶空間復制到socket關聯的內核空間。
4. 最后通過DMA 將內核模式下的socket緩沖區(qū)中的數據復制到網卡設備中傳送,進而返回用戶空間進入用戶態(tài)。
sendfile零拷貝(<Linux 2.4)
1. 用戶程序調用read,進入內核態(tài),上下文切換由用戶空間切換為內核空間,由DMA(Direct Memory Access)加載文件數據到內核空間,第一步和傳統(tǒng)IO相同。
2. 在內核態(tài)下,CPU把數據從內核空間復制到socket關聯的內核空間。
3. 最后通過DMA 將內核模式下的socket緩沖區(qū)中的數據復制到網卡設備中傳送,進而返回用戶空間進入用戶態(tài),最后一步也是和傳統(tǒng)IO相同。
與傳統(tǒng)IO相比,缺少了把數據從內核空間復制到用戶空間,再由用戶空間復制到內核空間,比原來缺少了一次CPU復制(復制3次,CPU參與復制一次),少了兩次上下文切換(兩次)。
**從內核空間角度來看,其實已經是“ZERO COPY”了,因為沒有往用戶空間復制的操作。**
sendfile零拷貝(>=Linux 2.4)
1. 用戶程序調用read,進入內核態(tài),上下文切換由用戶空間切換為內核空間,由DMA(Direct Memory Access)加載文件數據到內核空間,第一步和傳統(tǒng)IO相同。
2. 在內核態(tài)下,描述符(包含了數據的位置和長度等信息)追加到socket關聯的緩沖區(qū)中,并沒有進行數據的拷貝。
3. 最后DMA根據提供的位置和偏移量信息直接將內核空間緩沖區(qū)中的數據拷貝到協(xié)議引擎上進而返回用戶空間進入用戶態(tài)。
**這次優(yōu)化點在于沒有CPU參與復制,兩次DMA數據復制,不過還是兩次上下文切換。**
# 通過mmap實現的零拷貝(常用來處理大文件)
當進行mmap系統(tǒng)調用的時候,將文件的內容的全部或一部分直接映射到進程的地址空間(虛擬內存),映射完成后,進程可以像訪問普通內存一樣做其他的操作,mmap并不分配物理地址空間,它只是占有進程的虛擬地址空間。
當進程訪問內核中的緩沖區(qū)時候,并沒有實際拷貝數據,這時MMU在地址映射表中是無法找到與ptr相對應的物理地址的,也就是MMU失敗,就會觸發(fā)缺頁中斷。內核將文件的這一頁數據讀入到內核高速緩沖區(qū)中,并更新用戶進程的頁表,使頁表指向內核緩沖中的這一頁,實現了用戶空間和內核空間數據的直接交換,可以看待為內核空間和用戶空間共享的一段物理內存。
Java調用零拷貝
FileInputStream input = new FileInputStream('1.txt');
FileChannel channel = input.getChannel();
FileOutputStream out = new FileOutputStream('2.txt');
channel.transferTo(0, channel.size(), out.getChannel());
上面這種方式其實調用的是Linux系統(tǒng)的sendfile系統(tǒng)指令,無論什么語言代碼實現的零拷貝其實調用的都是操作系統(tǒng)本身提供的系統(tǒng)指令,只是做了封裝而已。
FileInputStream input = new FileInputStream('1.txt');
FileChannel channel = input.getChannel();
MappedByteBuffer mappedBuffer = channel.map(MapMode.READ_ONLY, 0, channel.size());
System.out.println(Charset.forName('utf-8').decode(mappedBuffer).toString());
上面這種方式其實調用的是Linux系統(tǒng)的mmap系統(tǒng)指令;在讀取大文件的時候用這種方法映射大文件的一部分到內存空間,比較方便快捷。
//mmap寫數據
Instant now = Instant.now();
RandomAccessFile outFile = new RandomAccessFile('1.txt','rw');
FileChannel channel = outFile.getChannel();
long size = 1024*1024*60;
MappedByteBuffer mappedBuffer = channel.map(MapMode.READ_WRITE, 0, size);
for(int i=0;i<1000000;i++) {
mappedBuffer.put('11111111111111111111111111111111111111111111111111111111111\n'.getBytes());
}
System.out.println(ChronoUnit.MILLIS.between(now, Instant.now()));
//fileOutputStream寫數據
Instant nowStream = Instant.now();
FileOutputStream outStream = new FileOutputStream('2.txt');
for(int i=0;i<1000000;i++) {
outStream.write('11111111111111111111111111111111111111111111111111111111111\n'.getBytes());
}
System.out.println(ChronoUnit.MILLIS.between(nowStream, Instant.now()));
118
9130
通過上面的測試可以看出在頻繁的寫入文件操作上mmap占有很多大的優(yōu)勢,數量級的優(yōu)勢。但是把上例的循環(huán)次數改為50的話,mmap就不占優(yōu)勢了,因為在映射的時候需要新開辟內存空間,這個耗時相對于極少量的寫操作而言顯得占比重就大了。
來源:
https://www.toutiao.com/i6810663802636337677/
聯系客服