自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一文吃透Linux虛擬內(nèi)存:原理、機制與優(yōu)化

系統(tǒng) Linux
你是否好奇,為何計算機能同時運行多個看似內(nèi)存需求巨大的程序,卻不會因內(nèi)存不足而頻繁卡頓?Linux 虛擬內(nèi)存究竟是如何在幕后默默施展它的 “魔法”,實現(xiàn)內(nèi)存的高效管理和利用的?

在當(dāng)今的數(shù)字化時代,計算機早已成為我們生活和工作中不可或缺的伙伴。無論是日常辦公處理文檔,還是運行大型游戲享受沉浸式娛樂體驗,亦或是企業(yè)服務(wù)器高效處理海量數(shù)據(jù),計算機都需要迅速且穩(wěn)定地對數(shù)據(jù)進行存取。而在這背后,Linux 虛擬內(nèi)存技術(shù)扮演著至關(guān)重要的角色。它就像一位神奇的管家,巧妙地協(xié)調(diào)著物理內(nèi)存與硬盤空間,為計算機系統(tǒng)的高效運轉(zhuǎn)提供堅實保障。

你是否好奇,為何計算機能同時運行多個看似內(nèi)存需求巨大的程序,卻不會因內(nèi)存不足而頻繁卡頓?Linux 虛擬內(nèi)存究竟是如何在幕后默默施展它的 “魔法”,實現(xiàn)內(nèi)存的高效管理和利用的?接下來,就讓我們一同深入 Linux 虛擬內(nèi)存的奇妙世界,從原理到機制,再到優(yōu)化策略,全方位吃透這一關(guān)鍵技術(shù),探尋它為計算機性能提升帶來的無限可能 。

一、虛擬內(nèi)存技術(shù)

1.1為什么需要使用虛擬內(nèi)存

進程需要使用的代碼和數(shù)據(jù)都放在內(nèi)存中,比放在外存中要快很多。問題是內(nèi)存空間太小了,不能滿足進程的需求,而且現(xiàn)在都是多進程,情況更加糟糕。所以提出了虛擬內(nèi)存,使得每個進程用于3G的獨立用戶內(nèi)存空間和共享的1G內(nèi)核內(nèi)存空間。(每個進程都有自己的頁表,才使得3G用戶空間的獨立)這樣進程運行的速度必然很快了。而且虛擬內(nèi)存機制還解決了內(nèi)存碎片和內(nèi)存不連續(xù)的問題。為什么可以在有限的物理內(nèi)存上達到這樣的效果呢?

1.1虛擬內(nèi)存概述

在深入探討 Linux 虛擬內(nèi)存之前,先來明晰虛擬內(nèi)存的基本概念。虛擬內(nèi)存,簡單來說,是一種內(nèi)存管理技術(shù),它為每個進程提供了一個獨立的、連續(xù)的地址空間,讓進程誤以為自己擁有一塊完整且足夠大的內(nèi)存空間 ,而無需關(guān)心實際物理內(nèi)存的具體布局和大小限制。這就好比你擁有一個超大的虛擬倉庫,你可以隨意規(guī)劃貨物的擺放位置,而不用擔(dān)心倉庫空間不夠。

虛擬內(nèi)存的主要作用之一是實現(xiàn)內(nèi)存地址轉(zhuǎn)換。在 Linux 系統(tǒng)中,每個進程都有自己的虛擬地址空間,這個空間通過頁表(Page Table)與物理內(nèi)存進行映射。頁表就像是一本地址翻譯字典,負(fù)責(zé)將進程使用的虛擬地址翻譯成實際的物理地址。

當(dāng)程序運行時,它所訪問的內(nèi)存地址都是虛擬地址。例如,當(dāng)程序需要讀取某個變量的值時,它會給出一個虛擬地址。CPU 首先會根據(jù)這個虛擬地址中的頁號(Page Number)在頁表中查找對應(yīng)的物理頁框號(Page Frame Number)。如果頁表中存在這個映射關(guān)系(即頁表項有效),CPU 就可以通過物理頁框號和虛擬地址中的頁內(nèi)偏移(Offset)計算出實際的物理地址,從而訪問到物理內(nèi)存中的數(shù)據(jù)。

但如果頁表中沒有找到對應(yīng)的映射關(guān)系(即發(fā)生缺頁異常,Page Fault),系統(tǒng)會認(rèn)為這個虛擬頁還沒有被加載到物理內(nèi)存中。此時,操作系統(tǒng)會介入,從磁盤的交換區(qū)(Swap Area)或者文件系統(tǒng)中找到對應(yīng)的物理頁,并將其加載到物理內(nèi)存中,同時更新頁表,建立虛擬地址與物理地址的映射關(guān)系。之后,程序就可以通過新建立的映射關(guān)系訪問到數(shù)據(jù)了。

為了更直觀地理解,我們可以把虛擬內(nèi)存想象成一個圖書館的目錄系統(tǒng)。每個進程就像是一個讀者,擁有自己的目錄(虛擬地址空間)。當(dāng)讀者想要查找某本書(訪問數(shù)據(jù))時,會先在自己的目錄中找到對應(yīng)的條目(虛擬地址),然后通過這個條目去書架(物理內(nèi)存)上找到實際的書。如果書架上沒有這本書(缺頁異常),圖書館管理員(操作系統(tǒng))就會從倉庫(磁盤)中把書取出來放到書架上,并更新目錄(頁表),以便下次讀者能更快地找到這本書。

例如:對于程序計數(shù)器位數(shù)為32位的處理器來說,他的地址發(fā)生器所能發(fā)出的地址數(shù)目為2^32=4G個,于是這個處理器所能訪問的最大內(nèi)存空間就是4G。在計算機技術(shù)中,這個值就叫做處理器的尋址空間或?qū)ぶ纺芰Α?/span>

照理說,為了充分利用處理器的尋址空間,就應(yīng)按照處理器的最大尋址來為其分配系統(tǒng)的內(nèi)存。如果處理器具有32位程序計數(shù)器,那么就應(yīng)該按照下圖的方式,為其配備4G的內(nèi)存:

圖片圖片

這樣,處理器所發(fā)出的每一個地址都會有一個真實的物理存儲單元與之對應(yīng);同時,每一個物理存儲單元都有唯一的地址與之對應(yīng)。這顯然是一種最理想的情況。

但遺憾的是,實際上計算機所配置內(nèi)存的實際空間常常小于處理器的尋址范圍,這是就會因處理器的一部分尋址空間沒有對應(yīng)的物理存儲單元,從而導(dǎo)致處理器尋址能力的浪費。例如:如下圖的系統(tǒng)中,具有32位尋址能力的處理器只配置了256M的內(nèi)存儲器,這就會造成大量的浪費:

圖片圖片

另外,還有一些處理器因外部地址線的根數(shù)小于處理器程序計數(shù)器的位數(shù),而使地址總線的根數(shù)不滿足處理器的尋址范圍,從而處理器的其余尋址能力也就被浪費了。例如:Intel8086處理器的程序計數(shù)器位32位,而處理器芯片的外部地址總線只有20根,所以它所能配置的最大內(nèi)存為1MB:

圖片圖片

在實際的應(yīng)用中,如果需要運行的應(yīng)用程序比較小,所需內(nèi)存容量小于計算機實際所配置的內(nèi)存空間,自然不會出什么問題。但是,目前很多的應(yīng)用程序都比較大,計算機實際所配置的內(nèi)存空間無法滿足。

實踐和研究都證明:一個應(yīng)用程序總是逐段被運行的,而且在一段時間內(nèi)會穩(wěn)定運行在某一段程序里。

這也就出現(xiàn)了一個方法:如下圖所示,把要運行的那一段程序自輔存復(fù)制到內(nèi)存中來運行,而其他暫時不運行的程序段就讓它仍然留在輔存。

圖片圖片

當(dāng)需要執(zhí)行另一端尚未在內(nèi)存的程序段(如程序段2),如下圖所示,就可以把內(nèi)存中程序段1的副本復(fù)制回輔存,在內(nèi)存騰出必要的空間后,再把輔存中的程序段2復(fù)制到內(nèi)存空間來執(zhí)行即可:

圖片圖片

在計算機技術(shù)中,把內(nèi)存中的程序段復(fù)制回輔存的做法叫做“換出”,而把輔存中程序段映射到內(nèi)存的做法叫做“換入”。經(jīng)過不斷有目的的換入和換出,處理器就可以運行一個大于實際物理內(nèi)存的應(yīng)用程序了?;蛘哒f,處理器似乎是擁有了一個大于實際物理內(nèi)存的內(nèi)存空間。于是,這個存儲空間叫做虛擬內(nèi)存空間,而把真正的內(nèi)存叫做實際物理內(nèi)存,或簡稱為物理內(nèi)存。

那么對于一臺真實的計算機來說,它的虛擬內(nèi)存空間又有多大呢?計算機虛擬內(nèi)存空間的大小是由程序計數(shù)器的尋址能力來決定的。例如:在程序計數(shù)器的位數(shù)為32的處理器中,它的虛擬內(nèi)存空間就為4GB。

可見,如果一個系統(tǒng)采用了虛擬內(nèi)存技術(shù),那么它就存在著兩個內(nèi)存空間:虛擬內(nèi)存空間和物理內(nèi)存空間。虛擬內(nèi)存空間中的地址叫做“虛擬地址”;而實際物理內(nèi)存空間中的地址叫做“實際物理地址”或“物理地址”。處理器運算器和應(yīng)用程序設(shè)計人員看到的只是虛擬內(nèi)存空間和虛擬地址,而處理器片外的地址總線看到的只是物理地址空間和物理地址。

由于存在兩個內(nèi)存地址,因此一個應(yīng)用程序從編寫到被執(zhí)行,需要進行兩次映射。第一次是映射到虛擬內(nèi)存空間,第二次時映射到物理內(nèi)存空間。在計算機系統(tǒng)中,第兩次映射的工作是由硬件和軟件共同來完成的。承擔(dān)這個任務(wù)的硬件部分叫做存儲管理單元MMU,軟件部分就是操作系統(tǒng)的內(nèi)存管理模塊了。

在映射工作中,為了記錄程序段占用物理內(nèi)存的情況,操作系統(tǒng)的內(nèi)存管理模塊需要建立一個表格,該表格以虛擬地址為索引,記錄了程序段所占用的物理內(nèi)存的物理地址。這個虛擬地址/物理地址記錄表便是存儲管理單元MMU把虛擬地址轉(zhuǎn)化為實際物理地址的依據(jù),記錄表與存儲管理單元MMU的作用如下圖所示:

圖片圖片

綜上所述,虛擬內(nèi)存技術(shù)的實現(xiàn),是建立在應(yīng)用程序可以分成段,并且具有“在任何時候正在使用的信息總是所有存儲信息的一小部分”的局部特性基礎(chǔ)上的。它是通過用輔存空間模擬RAM來實現(xiàn)的一種使機器的作業(yè)地址空間大于實際內(nèi)存的技術(shù)。

從處理器運算裝置和程序設(shè)計人員的角度來看,它面對的是一個用MMU、映射記錄表和物理內(nèi)存封裝起來的一個虛擬內(nèi)存空間,這個存儲空間的大小取決于處理器程序計數(shù)器的尋址空間。

可見,程序映射表是實現(xiàn)虛擬內(nèi)存的技術(shù)關(guān)鍵,它可給系統(tǒng)帶來如下特點:

  • 系統(tǒng)中每一個程序各自都有一個大小與處理器尋址空間相等的虛擬內(nèi)存空間;
  • 在一個具體時刻,處理器只能使用其中一個程序的映射記錄表,因此它只看到多個程序虛存空間中的一個,這樣就保證了各個程序的虛存空間時互不相擾、各自獨立的;
  • 使用程序映射表可方便地實現(xiàn)物理內(nèi)存的共享。

二、Linux 虛擬內(nèi)存工作機制

2.1內(nèi)存映射機制

以存儲單元為單位來管理顯然不現(xiàn)實,因此Linux把虛存空間分成若干個大小相等的存儲分區(qū),Linux把這樣的分區(qū)叫做頁。為了換入、換出的方便,物理內(nèi)存也就按也得大小分成若干個塊。由于物理內(nèi)存中的塊空間是用來容納虛存頁的容器,所以物理內(nèi)存中的塊叫做頁框。頁與頁框是Linux實現(xiàn)虛擬內(nèi)存技術(shù)的基礎(chǔ)。

虛擬內(nèi)存的頁、物理內(nèi)存的頁框及頁表

在Linux中,頁與頁框的大小一般為4KB。當(dāng)然,根據(jù)系統(tǒng)和應(yīng)用的不同,頁與頁框的大小也可有所變化。

物理內(nèi)存和虛擬內(nèi)存被分成了頁框與頁之后,其存儲單元原來的地址都被自然地分成了兩段,并且這兩段各自代表著不同的意義:高位段分別叫做頁框碼和頁碼,它們是識別頁框和頁的編碼;低位段分別叫做頁框偏移量和頁內(nèi)偏移量,它們是存儲單元在頁框和頁內(nèi)的地址編碼。下圖就是兩段虛擬內(nèi)存和物理內(nèi)存分頁之后的情況:

圖片圖片

為了使系統(tǒng)可以正確的訪問虛存頁在對應(yīng)頁框中的映像,在把一個頁映射到某個頁框上的同時,就必須把頁碼和存放該頁映像的頁框碼填入一個叫做頁表的表項中。這個頁表就是之前提到的映射記錄表。一個頁表的示意圖如下所示:

圖片圖片

頁模式下,虛擬地址、物理地址轉(zhuǎn)換關(guān)系的示意圖如下所示:

圖片圖片

也就是說:處理器遇到的地址都是虛擬地址。虛擬地址和物理地址都分成頁碼(頁框碼)和偏移值兩部分。在由虛擬地址轉(zhuǎn)化成物理地址的過程中,偏移值不變。而頁碼和頁框碼之間的映射就在一個映射記錄表——頁表中。

話說回來,內(nèi)存映射是 Linux 中一種重要的內(nèi)存管理技術(shù),它允許將一個文件或者其他對象映射到進程的虛擬地址空間中,使得進程可以像訪問內(nèi)存一樣直接訪問文件 。這種技術(shù)的核心優(yōu)勢在于提高了文件訪問的效率,減少了內(nèi)核和用戶空間之間的數(shù)據(jù)拷貝。在 Linux 中,內(nèi)存映射主要通過mmap()系統(tǒng)調(diào)用實現(xiàn)。

mmap()函數(shù)將文件或其他對象映射到虛擬地址空間的一個連續(xù)區(qū)域,返回一個指向映射區(qū)域開始地址的指針 。對該指針進行讀寫操作,實際上就是在訪問文件內(nèi)容。使用munmap()函數(shù)可以解除內(nèi)存映射。例如,在 C 語言中,可以這樣使用mmap()函數(shù):

#include <sys/mman.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    int fd;
    char *map_start;
    off_t file_size;

    // 打開文件
    fd = open("test.txt", O_RDWR);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 獲取文件大小
    file_size = lseek(fd, 0, SEEK_END);
    lseek(fd, 0, SEEK_SET);

    // 創(chuàng)建內(nèi)存映射
    map_start = (char *)mmap(0, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (map_start == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return 1;
    }

    // 關(guān)閉文件描述符,映射依然有效
    close(fd);

    // 訪問映射內(nèi)存,就像訪問文件一樣
    printf("Content of file: %s\n", map_start);

    // 修改映射內(nèi)存中的內(nèi)容
    sprintf(map_start, "This is a new content");

    // 解除內(nèi)存映射
    if (munmap(map_start, file_size) == -1) {
        perror("munmap");
        return 1;
    }

    return 0;
}

在這個例子中,首先打開一個文件,然后使用mmap()函數(shù)將文件映射到進程的虛擬地址空間。通過返回的指針map_start,可以像訪問普通內(nèi)存一樣訪問文件內(nèi)容。修改map_start指向的內(nèi)存區(qū)域,實際上就是在修改文件內(nèi)容。最后,使用munmap()函數(shù)解除內(nèi)存映射。

內(nèi)存映射在實際應(yīng)用中有很多場景。比如在共享庫的加載中,多個進程可以映射同一個共享庫文件,實現(xiàn)代碼和數(shù)據(jù)的共享,減少內(nèi)存占用 。在文件 I/O 操作中,對于大文件的讀寫,內(nèi)存映射可以避免頻繁的系統(tǒng)調(diào)用和數(shù)據(jù)拷貝,提高讀寫效率。例如,數(shù)據(jù)庫系統(tǒng)通常會使用內(nèi)存映射來處理數(shù)據(jù)文件,加快數(shù)據(jù)的讀取和寫入速度。

2.2請頁與交換

虛存頁面到物理頁框的映射叫做頁面的加載

當(dāng)處理器試圖訪問一個虛存頁面時,首先到頁表中去查詢該頁是否已映射到物理頁框中,并記錄在頁表中。如果在,則MMU會把頁碼轉(zhuǎn)換成頁框碼,并加上虛擬地址提供的頁內(nèi)偏移量形成物理地址后去訪問物理內(nèi)存;如果不在,則意味著該虛存頁面還沒有被載入內(nèi)存,這時MMU就會通知操作系統(tǒng):發(fā)生了一個頁面訪問錯誤(頁面錯誤),接下來系統(tǒng)會啟動所謂的“請頁”機制,即調(diào)用相應(yīng)的系統(tǒng)操作函數(shù),判斷該虛擬地址是否為有效地址。

如果是有效的地址,就從虛擬內(nèi)存中將該地址指向的頁面讀入到內(nèi)存中的一個空閑頁框中,并在頁表中添加上相對應(yīng)的表項,最后處理器將從發(fā)生頁面錯誤的地方重新開始運行;如果是無效的地址,則表明進程在試圖訪問一個不存在的虛擬地址,此時操作系統(tǒng)將終止此次訪問。

當(dāng)然,也存在這樣的情況:在請頁成功之后,內(nèi)存中已沒有空閑物理頁框了。這是,系統(tǒng)必須啟動所謂地“交換”機制,即調(diào)用相應(yīng)的內(nèi)核操作函數(shù),在物理頁框中尋找一個當(dāng)前不再使用或者近期可能不會用到的頁面所占據(jù)的頁框。找到后,就把其中的頁移出,以裝載新的頁面。對移出頁面根據(jù)兩種情況來處理:如果該頁未被修改過,則刪除它;如果該頁曾經(jīng)被修改過,則系統(tǒng)必須將該頁寫回輔存。

系統(tǒng)請頁的處理過程如下所示:

為了公平地選擇將要從系統(tǒng)中拋棄的頁面,Linux系統(tǒng)使用最近最少使用(LRU)頁面的衰老算法。這種策略根據(jù)系統(tǒng)中每個頁面被訪問的頻率,為物理頁框中的頁面設(shè)置了一個叫做年齡的屬性。頁面被訪問的次數(shù)越多,則頁面的年齡最小;相反,則越大。而年齡較大的頁面就是待換出頁面的最佳候選者。

圖片圖片

2.3快表

在系統(tǒng)每次訪問虛存頁時,都要在內(nèi)存的所有頁表中尋找該頁的頁框,這是一個很費時間的工作。但是,人們發(fā)現(xiàn),系統(tǒng)一旦訪問了某一個頁,那么系統(tǒng)就會在一段時間內(nèi)穩(wěn)定地工作在這個頁上。所以,為了提高訪問頁表的速度,系統(tǒng)還配備了一組正好能容納一個頁表的硬件寄存器,這樣當(dāng)系統(tǒng)再訪問虛存時,就首先到這組硬件寄存器中去訪問,系統(tǒng)速度就快多了。這組存放當(dāng)前頁表的寄存器叫做快表。

總之,使用虛擬存儲技術(shù)時,處理器必須配備一些硬件來承擔(dān)內(nèi)存管理的一部分任務(wù)。承擔(dān)內(nèi)存管理任務(wù)的硬件部分叫做存儲管理單元MMU。存儲管理單元MMU的工作過程如下圖所示:

圖片圖片

(1)頁的共享

在多程序系統(tǒng)中,常常有多個程序需要共享同一段代碼或數(shù)據(jù)的情況。在分頁管理的存儲器中,這個事情很好辦:讓多個程序共享同一個頁面即可。

具體的方法是:使這些相關(guān)程序的虛擬空間的頁面在頁表中指向內(nèi)存中的同一個頁框。這樣,當(dāng)程序運行并訪問這些相關(guān)頁面時,就都是對同一個頁框中的頁面進行訪問,而該頁框中的頁就被這些程序所共享。下圖是3個程序共享一個頁面的例子:

圖片圖片

(2)頁的保護

由上可知,頁表實際上是由虛擬空間轉(zhuǎn)到物理空間的入口。因此,為了保護頁面內(nèi)容不被沒有該頁面訪問權(quán)限的程序所破壞,就應(yīng)在頁表的表項中設(shè)置一些訪問控制字段,用于指明對應(yīng)頁面中的內(nèi)容允許何種操作,從而禁止非法訪問。下圖是頁表項中存放控制信息的一種可能的形式:

圖片圖片

注意:其中的PCD位表示著是否允許高速緩存(cache)。

如果程序?qū)σ粋€頁試圖進行一個該頁控制字段所不允許的操作,則會引起操作系統(tǒng)的一次中斷——非法訪問中斷,并拒絕這種操作,從而保護該頁的內(nèi)容不被破壞。

(3)多級頁表

需要注意的是,頁表是操作系統(tǒng)創(chuàng)建的用于內(nèi)存管理的表格。因此,一個程序在運行時,其頁表也要存放到內(nèi)存空間。如果一個程序只需要一個頁表,則不會有什么問題。但如果,程序的虛擬空間很大的話,就會出現(xiàn)一個比較大的問題。

比如:一個程序的虛擬空間為4GB,頁表以4KB為一頁,那么這個程序空間就是1M頁。為了存儲這1M頁的頁指針,那么這個頁表的長度就相當(dāng)大了,對內(nèi)存的負(fù)擔(dān)也很大了。所以,最好對頁表也進行分頁存儲,在程序運行時只把需要的頁復(fù)制到內(nèi)存,而暫時不需要的頁就讓它留在輔存中。為了管理這些頁表頁,還要建立一個記錄頁表頁首地址的頁目錄表,于是單級頁表就變成了二級頁表。二級頁表的地址轉(zhuǎn)換如下圖所示:

圖片圖片

當(dāng)然,如果程序的虛擬空間更大,那么也可以用三級頁表來管理。為了具有通用性,Linux系統(tǒng)使用了三級頁表結(jié)構(gòu):頁目錄(Page Directory,PGD)、中間頁目錄(Page Middle Directory,PMD)、頁表(Page Table,PTE)。

2.4Linux的頁表結(jié)構(gòu)

為了通用,Linux系統(tǒng)使用了三級頁表結(jié)構(gòu):頁目錄、中間頁目錄和頁表。PGD為頂級頁表,是一個pgd_t數(shù)據(jù)類型(定義在文件linux/include/page.h中)的數(shù)組,每個數(shù)組元素指向一個中間頁目錄;PMD為二級頁表,是一個pmd_t數(shù)據(jù)結(jié)構(gòu)的數(shù)組,每個數(shù)組元素指向一個頁表;PTE則是頁表,是一個pte_t數(shù)據(jù)類型的數(shù)組,每個元素中含有物理地址。

圖片圖片

為了應(yīng)用上的靈活,Linux使用一系列的宏來掩蓋各種平臺的細(xì)節(jié)。用戶可以在配置文件config中根據(jù)自己的需要對頁表進行配置,以決定是使用三級頁表還是使用二級頁表。

在系統(tǒng)編譯時,會根據(jù)配置文件config中的配置,把目錄include/asm符號連接到具體CPU專用的文件目錄中。例如,對于i386CPU,該目錄符號會連接到include/asm-i386,并在文件pgable-2level-defs.h中定義了二級頁表的基本結(jié)構(gòu),如下圖:

圖片圖片

其中還定義了:

#define PGDIR_SHIFT 22                        //PGD在線性地址中的起始地址為bit22
#define PTRS_PER_PGD 1024                     //PGD共有1024個表項
#define PTRS_PER_PTE 1024                     //PTE共有1024個表項
#endif

在文件include/asm-i386/pgtable.h中定義了頁目錄和頁表項的數(shù)據(jù)結(jié)構(gòu),如下:

typedof struct { unsigned long pte_low; } pte_t;                    //頁表中的物理地址,頁框碼
typedof struct { unsigned long pgd; } pgd_t;                        //指向一個頁表
typedof struct { unsigned long pgprot; } pgprot_t;                  //頁表中的各個狀態(tài)信息和訪問權(quán)限

從定義可知,它們都是只有一個長整型類型(32位)的結(jié)構(gòu)體。

注意:如上文的“頁的保護”部分,頁框碼代表物理地址,只需要高20位就夠了(因為頁框的長度為4KB,因此頁內(nèi)偏移12位)。而后12位可以存放各個狀態(tài)信息和訪問權(quán)限。但是Linux并沒有這樣做,反而重新定義了一個結(jié)構(gòu)體來存放,通過“或”運算來將兩者結(jié)合。

2.5Swap 交換機制

首先呢,提一個概念,交換空間(swap space),這個大家應(yīng)該不陌生,在重裝系統(tǒng)的時候,會讓你選擇磁盤分區(qū),就比如說一個硬盤分幾個部分去管理。其中就會分一部分磁盤空間用作交換,叫做swap space。其實就是一段臨時存儲空間,內(nèi)存不夠用的時候就用它了,雖然它也在磁盤中,但省去了很多的查找時間啊。當(dāng)發(fā)生進程切換的時候,內(nèi)存與交換空間就要發(fā)生數(shù)據(jù)交換一滿足需求。所以啊,進程的切換消耗是很大的,這也說明了為什么自旋鎖比信號量效率高的原因。

那么我們的程序里申請的內(nèi)存的時候,linux內(nèi)核其實只分配一個虛擬內(nèi)存( 線性地址),并沒有分配實際的物理內(nèi)存。只有當(dāng)程序真正使用這塊內(nèi)存時,才會分配物理內(nèi)存。這就叫做延遲分配和請頁機制。釋放內(nèi)存時,先釋放線性區(qū)對應(yīng)的物理內(nèi)存,然后釋放線性區(qū);"請頁機制"將物理內(nèi)存的分配延后了,這樣是充分利用了程序的局部性原來,節(jié)約內(nèi)存空間,提高系統(tǒng)吞吐;就是說一個函數(shù)可能只在物理內(nèi)存中呆了一會,用完了就被清除出去了,雖然在虛擬地址空間還在。(不過虛擬地址空間不是事實上的存儲,所以只能說這個函數(shù)占據(jù)了一段虛擬地址空間,當(dāng)你訪問這段地址時,就會產(chǎn)生缺頁處理,從交換區(qū)把對應(yīng)的代碼搬到物理內(nèi)存上來)

Swap 交換機制是 Linux 虛擬內(nèi)存管理的另一個重要組成部分。簡單來說,Swap 是磁盤上的一塊區(qū)域,當(dāng)物理內(nèi)存不足時,系統(tǒng)會將一部分暫時不用的內(nèi)存頁面(Page)交換到 Swap 空間中,騰出物理內(nèi)存給更需要的進程使用 。當(dāng)被交換出去的頁面再次被訪問時,系統(tǒng)會將其從 Swap 空間換回到物理內(nèi)存中。

Swap 交換機制的工作原理涉及到內(nèi)存回收和頁面置換算法。當(dāng)系統(tǒng)內(nèi)存緊張時,內(nèi)核會啟動內(nèi)存回收機制,掃描內(nèi)存中的頁面,選擇一些不常用或最近最少使用的頁面進行回收。如果這些頁面是匿名頁面(沒有關(guān)聯(lián)到文件的內(nèi)存頁面,如進程的堆和??臻g),就會被交換到 Swap 空間中;如果是文件映射頁面(關(guān)聯(lián)到文件的內(nèi)存頁面,如共享庫、文件緩存等),則會根據(jù)情況進行處理,臟頁面(被修改過的頁面)會被寫回文件,干凈頁面(未被修改過的頁面)可以直接釋放。

Swap 交換機制對系統(tǒng)性能有著重要的影響。當(dāng) Swap 使用頻繁時,說明物理內(nèi)存不足,系統(tǒng)需要頻繁地在物理內(nèi)存和 Swap 空間之間交換頁面,這會導(dǎo)致磁盤 I/O 增加,系統(tǒng)性能下降 。因為磁盤的讀寫速度遠遠低于內(nèi)存,過多的 Swap 操作會使系統(tǒng)變得遲緩。因此,在實際應(yīng)用中,需要合理配置 Swap 空間的大小,并密切關(guān)注系統(tǒng)的內(nèi)存使用情況,避免 Swap 過度使用。

例如,可以通過調(diào)整/proc/sys/vm/swappiness參數(shù)來控制系統(tǒng)對 Swap 的使用傾向,swappiness的值范圍是 0 - 100,表示系統(tǒng)將內(nèi)存頁面交換到 Swap 空間的傾向程度,值越大表示越傾向于使用 Swap 。一般來說,對于內(nèi)存充足的系統(tǒng),可以將swappiness設(shè)置為較低的值,如 10 或 20,以減少不必要的 Swap 操作;對于內(nèi)存緊張的系統(tǒng),可以適當(dāng)提高swappiness的值,但也要注意不要過高,以免嚴(yán)重影響性能。

三、Linux 虛擬內(nèi)存管理工具

在 Linux 系統(tǒng)中,有多個實用工具可以幫助我們查看和管理虛擬內(nèi)存,了解系統(tǒng)的內(nèi)存使用狀態(tài),下面介紹幾個常用的命令。

3.1top 命令

top命令是一個功能強大的系統(tǒng)監(jiān)控工具,它能夠?qū)崟r顯示系統(tǒng)中各個進程的資源使用情況,包括CPU、內(nèi)存等 。通過 top 命令,我們可以直觀地了解到系統(tǒng)中哪些進程占用了較多的虛擬內(nèi)存。在終端中輸入 “top”,即可啟動該命令,其輸出結(jié)果大致如下:

top - 14:20:12 up 2 days,  1:23,  2 users,  load average: 0.00, 0.01, 0.05
Tasks: 152 total,   1 running, 151 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.3 us,  0.3 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  8161844 total,  7433924 free,   149744 used,   578176 buff/cache
KiB Swap:  2097148 total,  2097148 free,        0 used.  7663360 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
    1 root      20   0   12844   7448   4564 S   0.0  0.1   0:02.33 systemd
    2 root      20   0       0      0      0 S   0.0  0.0   0:00.00 kthreadd

在 top 命令的輸出中,與虛擬內(nèi)存相關(guān)的主要是 “VIRT” 列,它表示進程使用的虛擬內(nèi)存總量,單位為 KB 。例如,上述輸出中 PID 為 1 的 systemd 進程,其 VIRT 值為 12844KB,表示該進程使用了 12844KB 的虛擬內(nèi)存。此外,“RES” 列表示進程使用的物理內(nèi)存大小,“SHR” 列表示共享內(nèi)存大小 。通過觀察這些指標(biāo),我們可以了解每個進程對內(nèi)存資源的占用情況,判斷是否存在內(nèi)存使用異常的進程。在實際應(yīng)用中,如果發(fā)現(xiàn)某個進程的 VIRT 值持續(xù)增長且占用大量虛擬內(nèi)存,可能需要進一步分析該進程的行為,看是否存在內(nèi)存泄漏等問題。比如,在一個長時間運行的服務(wù)器程序中,如果其 VIRT 值不斷上升,而業(yè)務(wù)量并沒有明顯增加,就需要檢查程序代碼,查看是否有未釋放的內(nèi)存資源。

3.2free 命令

free 命令用于顯示系統(tǒng)內(nèi)存的使用情況,包括物理內(nèi)存和虛擬內(nèi)存(交換空間) 。它可以幫助我們快速了解系統(tǒng)內(nèi)存的整體使用狀態(tài),判斷是否存在內(nèi)存不足的情況。在終端中輸入 “free”,輸出結(jié)果如下:

total        used        free      shared  buff/cache   available
Mem:        8161844      149744     7433924       34624     578176     7663360
Swap:       2097148           0     2097148

在 free 命令的輸出中,“total” 表示系統(tǒng)內(nèi)存的總量,“used” 表示已使用的內(nèi)存量,“free” 表示空閑內(nèi)存量,“shared” 表示共享內(nèi)存量,“buff/cache” 表示緩沖區(qū)和緩存使用的內(nèi)存量,“available” 表示應(yīng)用程序還可以申請到的內(nèi)存 。其中,與虛擬內(nèi)存相關(guān)的是 “Swap” 部分,“Swap total” 表示交換空間的總量,“Swap used” 表示已使用的交換空間量,“Swap free” 表示空閑的交換空間量 。例如,上述輸出中 Swap total 為 2097148KB,Swap used 為 0KB,Swap free 為 2097148KB,說明當(dāng)前系統(tǒng)的交換空間未被使用,這通常是一個比較理想的狀態(tài),意味著系統(tǒng)的物理內(nèi)存充足,不需要頻繁地進行內(nèi)存交換操作。如果 Swap used 的值較大,說明系統(tǒng)的物理內(nèi)存可能不足,需要將一部分內(nèi)存數(shù)據(jù)交換到磁盤的交換空間中,這可能會導(dǎo)致系統(tǒng)性能下降,因為磁盤的讀寫速度遠遠低于內(nèi)存。在這種情況下,我們可以考慮增加物理內(nèi)存,或者優(yōu)化系統(tǒng)的內(nèi)存使用,減少不必要的內(nèi)存占用。

3.3vmstat 命令

vmstat(Virtual Memory Statistics)命令用于顯示虛擬內(nèi)存統(tǒng)計信息,同時也可以展示進程、CPU、I/O 等系統(tǒng)整體運行狀態(tài) 。它提供了更詳細(xì)的內(nèi)存使用信息,對于深入分析系統(tǒng)性能非常有幫助。在終端中輸入 “vmstat”,輸出結(jié)果如下:

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 7433924 149744 578176    0    0     1     1    1    1  0  0 100  0  0

在 vmstat 命令的輸出中,與虛擬內(nèi)存相關(guān)的字段有 “swpd”“si” 和 “so” ?!皊wpd” 表示使用的虛擬內(nèi)存量(單位為 KB),“si” 表示從磁盤交換到內(nèi)存的內(nèi)存量(單位為 KB/s),“so” 表示從內(nèi)存交換到磁盤的內(nèi)存量(單位為 KB/s) 。例如,上述輸出中 swpd 為 0,表示當(dāng)前系統(tǒng)沒有使用虛擬內(nèi)存;si 和 so 都為 0,表示當(dāng)前沒有發(fā)生內(nèi)存交換操作。

當(dāng) si 和 so 的值不為 0 時,說明系統(tǒng)正在進行內(nèi)存交換,數(shù)值越大,說明內(nèi)存交換越頻繁,這可能會對系統(tǒng)性能產(chǎn)生較大影響,需要進一步分析原因,采取相應(yīng)的優(yōu)化措施,如增加物理內(nèi)存、調(diào)整應(yīng)用程序的內(nèi)存使用策略等。此外,vmstat 命令還可以通過指定刷新時間間隔和刷新次數(shù)來持續(xù)監(jiān)控系統(tǒng)狀態(tài),例如 “vmstat 2 10” 表示每 2 秒刷新一次,共刷新 10 次,這樣可以更直觀地觀察系統(tǒng)內(nèi)存使用情況的變化趨勢。

四、Linux 虛擬內(nèi)存優(yōu)化策略

4.1調(diào)整內(nèi)核參數(shù)

在 Linux 系統(tǒng)中,通過調(diào)整內(nèi)核參數(shù)可以對虛擬內(nèi)存的性能進行優(yōu)化 。其中,vm.swappiness是一個非常重要的內(nèi)核參數(shù),它的值表示系統(tǒng)將內(nèi)存頁面交換到 Swap 空間的傾向程度,取值范圍是 0 - 100 。當(dāng)vm.swappiness的值為 0 時,系統(tǒng)盡量不使用 Swap 空間,只有在物理內(nèi)存完全耗盡時才會考慮交換;當(dāng)值為 100 時,系統(tǒng)會非常積極地將內(nèi)存頁面交換到 Swap 空間 。

可以使用sysctl命令來查看和臨時修改vm.swappiness的值。例如,要查看當(dāng)前vm.swappiness的值,可以在終端中輸入:

sysctl vm.swappiness

如果要將vm.swappiness的值臨時修改為 10,可以使用以下命令:

sysctl vm.swappiness=10

這種修改在系統(tǒng)重啟后會失效。如果想要永久修改vm.swappiness的值,可以編輯/etc/sysctl.conf文件,添加或修改vm.swappiness = 10這一行,然后執(zhí)行sysctl -p使修改生效 。

vm.vfs_cache_pressure也是一個重要的內(nèi)核參數(shù),它控制著文件系統(tǒng)緩存(VFS Cache)被回收的傾向 。該參數(shù)的值越大,文件系統(tǒng)緩存就越容易被回收;值越小,文件系統(tǒng)緩存就越不容易被回收 。默認(rèn)值通常為 100。如果系統(tǒng)中文件 I/O 操作頻繁,可以適當(dāng)降低vm.vfs_cache_pressure的值,以減少文件系統(tǒng)緩存的回收,提高文件訪問性能 。同樣,可以使用sysctl命令來查看和修改這個參數(shù),例如:

sysctl vm.vfs_cache_pressure
sysctl vm.vfs_cache_pressure=50

修改內(nèi)核參數(shù)對虛擬內(nèi)存性能有著顯著的影響。合理調(diào)整vm.swappiness可以避免系統(tǒng)過度依賴 Swap 空間,減少磁盤 I/O 操作,提高系統(tǒng)整體性能 。如果vm.swappiness設(shè)置過高,系統(tǒng)頻繁進行內(nèi)存交換,會導(dǎo)致磁盤 I/O 負(fù)載增加,系統(tǒng)響應(yīng)變慢;如果設(shè)置過低,當(dāng)物理內(nèi)存不足時,可能會導(dǎo)致進程因無法獲取足夠內(nèi)存而被終止 。而調(diào)整vm.vfs_cache_pressure則可以優(yōu)化文件系統(tǒng)緩存的使用,提高文件讀寫效率,對于那些依賴文件系統(tǒng)的應(yīng)用程序(如數(shù)據(jù)庫、文件服務(wù)器等)來說,合理設(shè)置該參數(shù)能有效提升其性能 。

4.2優(yōu)化應(yīng)用程序

應(yīng)用程序的內(nèi)存使用方式對虛擬內(nèi)存性能有著直接的影響 。在實際開發(fā)中,常常會出現(xiàn)應(yīng)用程序內(nèi)存使用不當(dāng)?shù)那闆r,從而導(dǎo)致虛擬內(nèi)存的浪費和系統(tǒng)性能的下降 。比如,在一些 Web 應(yīng)用程序中,可能會存在內(nèi)存泄漏的問題。以使用 Python 的 Flask 框架開發(fā)的 Web 應(yīng)用為例,如果在視圖函數(shù)中創(chuàng)建了大量的對象,卻沒有及時釋放,隨著時間的推移,這些未釋放的對象會占用越來越多的內(nèi)存,導(dǎo)致虛擬內(nèi)存不斷增加 。假設(shè)我們有如下代碼:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    data = []
    for i in range(10000):
        # 創(chuàng)建大量對象但未及時釋放
        obj = {'key': i}
        data.append(obj)
    return 'Hello, World!'


if __name__ == '__main__':
    app.run()

在這個簡單的示例中,每次訪問/路由時,都會創(chuàng)建 10000 個字典對象并添加到data列表中,但在函數(shù)結(jié)束后,這些對象并沒有被及時釋放,會造成內(nèi)存浪費。為了優(yōu)化應(yīng)用程序的內(nèi)存使用,可以采用以下方法:

首先,使用內(nèi)存分析工具(如 Python 中的memory_profiler、C++ 中的Valgrind等)來檢測應(yīng)用程序中的內(nèi)存泄漏和不合理的內(nèi)存使用 。通過這些工具,可以定位到具體的代碼行,找出內(nèi)存問題的根源 。其次,合理設(shè)計數(shù)據(jù)結(jié)構(gòu)和算法,減少不必要的內(nèi)存占用 。比如,在存儲大量整數(shù)數(shù)據(jù)時,如果數(shù)據(jù)范圍較小,可以使用uint8_t(無符號 8 位整數(shù))類型代替int(通常為 32 位或 64 位整數(shù))類型,這樣可以節(jié)省內(nèi)存空間 。

另外,對于一些頻繁創(chuàng)建和銷毀的對象,可以考慮使用對象池技術(shù) 。以數(shù)據(jù)庫連接池為例,在一個 Web 應(yīng)用中,如果每次處理請求都創(chuàng)建新的數(shù)據(jù)庫連接,會消耗大量資源。使用數(shù)據(jù)庫連接池后,可以預(yù)先創(chuàng)建一定數(shù)量的數(shù)據(jù)庫連接對象,當(dāng)有請求時,直接從連接池中獲取連接,使用完畢后再放回連接池,避免了頻繁創(chuàng)建和銷毀連接對象帶來的開銷 。

優(yōu)化應(yīng)用程序的內(nèi)存使用對虛擬內(nèi)存性能有著重要的作用。通過減少內(nèi)存泄漏和優(yōu)化內(nèi)存使用,可以降低應(yīng)用程序?qū)μ摂M內(nèi)存的需求,減少系統(tǒng)內(nèi)存管理的負(fù)擔(dān),提高系統(tǒng)的整體性能和穩(wěn)定性 。當(dāng)應(yīng)用程序能夠高效地使用內(nèi)存時系統(tǒng)可以將更多的內(nèi)存資源分配給其他需要的進程,避免因內(nèi)存不足而導(dǎo)致的頻繁內(nèi)存交換和系統(tǒng)性能下降 。

4.3合理配置 Swap 空間

Swap空間的大小和配置對系統(tǒng)性能有著顯著的影響 。如果Swap空間設(shè)置得太小,當(dāng)物理內(nèi)存不足時,系統(tǒng)可能無法及時將內(nèi)存頁面交換到 Swap 空間中,導(dǎo)致進程因無法獲取足夠內(nèi)存而出現(xiàn)異常,甚至系統(tǒng)崩潰 。相反,如果Swap空間設(shè)置得過大,會占用過多的磁盤空間,而且在系統(tǒng)不需要使用Swap空間時,這些空間就被浪費了 。

在 Linux 系統(tǒng)中,可以通過創(chuàng)建交換分區(qū)或交換文件的方式來設(shè)置 Swap 空間 。以創(chuàng)建交換文件為例,首先需要使用dd命令創(chuàng)建一個指定大小的文件,例如創(chuàng)建一個大小為 2GB 的交換文件:

sudo dd if=/dev/zero of=/swapfile bs=1M count=2048

上述命令中,if=/dev/zero表示從/dev/zero設(shè)備讀取數(shù)據(jù)(/dev/zero是一個特殊的設(shè)備文件,它會不斷返回 0 值字節(jié)流),of=/swapfile表示將數(shù)據(jù)寫入到/swapfile文件中,bs=1M表示每次讀寫的數(shù)據(jù)塊大小為 1MB,count=2048表示總共讀寫 2048 次,即創(chuàng)建一個 2GB 大小的文件 。

創(chuàng)建好文件后,需要使用mkswap命令將其格式化為交換文件:

sudo mkswap /swapfile

最后,使用swapon命令啟用這個交換文件:

sudo swapon /swapfile

如果希望系統(tǒng)在每次啟動時自動啟用這個交換文件,可以將其添加到/etc/fstab文件中,在文件末尾添加一行:

/swapfile none swap sw 0 0

除了設(shè)置 Swap 空間的大小,還可以設(shè)置 Swap 空間的優(yōu)先級 。在 Linux 系統(tǒng)中,每個 Swap 設(shè)備都有一個優(yōu)先級,取值范圍是 - 2^31 到 2^31 - 1 。優(yōu)先級較高的 Swap 設(shè)備會優(yōu)先被使用 ??梢允褂胹wapon命令的-p選項來設(shè)置 Swap 設(shè)備的優(yōu)先級,例如:

sudo swapon -p 10 /swapfile

上述命令將/swapfile這個交換文件的優(yōu)先級設(shè)置為 10 。如果系統(tǒng)中有多個 Swap 設(shè)備,可以根據(jù)實際需求為它們設(shè)置不同的優(yōu)先級,以優(yōu)化內(nèi)存交換的性能 。比如,對于讀寫速度較快的 SSD 磁盤上的 Swap 分區(qū),可以設(shè)置較高的優(yōu)先級,使其在內(nèi)存交換時優(yōu)先被使用,減少因磁盤 I/O 速度慢而導(dǎo)致的性能下降 。

責(zé)任編輯:武曉燕 來源: 深度Linux
相關(guān)推薦

2021-10-06 20:23:08

Linux共享內(nèi)存

2021-04-27 11:28:21

React.t事件元素

2023-08-27 21:29:43

JVMFullGC調(diào)優(yōu)

2010-06-02 11:33:26

Linux 內(nèi)存監(jiān)控

2019-03-20 14:29:46

Linux虛擬內(nèi)存

2010-06-10 17:12:23

Linux 內(nèi)存監(jiān)控

2021-05-12 18:22:36

Linux 內(nèi)存管理

2021-08-30 11:13:28

內(nèi)存交換機制

2021-08-01 08:05:39

Linux信號原理

2023-03-31 08:16:53

Flutter優(yōu)化內(nèi)存管理

2020-04-14 16:03:31

Linux虛擬內(nèi)存操作系統(tǒng)

2024-09-18 13:57:15

2021-02-26 05:24:35

Java垃圾回收

2023-12-15 15:55:24

Linux線程同步

2019-12-26 08:45:46

Linux虛擬內(nèi)存

2021-04-30 19:53:53

HugePages大內(nèi)存頁物理

2021-06-01 08:20:55

Linux虛擬內(nèi)存命令

2021-06-06 13:06:34

JVM內(nèi)存分布

2025-02-03 07:00:00

Java接口工具

2024-08-09 08:41:14

點贊
收藏

51CTO技術(shù)棧公眾號