Linux核心筆記005 - 越界訪問記憶體,Linux核心處理過程
jmpcall發表於2020-06-06
1. 幾個重要的資料結構和函式
- 記憶體管理本質
在Linux核心筆記004中,已經引出了"分配"的概念,它本質上就是在保護模式下,做兩件事:
① 隔離同一程式已使用和未使用虛擬記憶體空間,以及整個系統的已使用和未使用實體記憶體空間
具體實現:記錄已每個程式已使用的虛擬地址,和整個系統已使用的實體地址,分配時使用未使用的。
②
隔離不同程式的使用者空間
具體實現:為每個程式0-3G範圍的虛擬地址,建立獨立的對映,對映不同的實體地址(記憶體共享除外)。
現實生活中,所有的管理都依賴一道"關卡",同樣的,軟體層"記憶體管理"的實現,依賴CPU硬體層的"地址對映"特性。比如,在真實模式狀態下,每個程式,都可以用指令中的地址,直接訪問到實體地址(比如Linux核心筆記004中記錄的"ljmp 0x100000"跳轉指令),就沒有這道"關卡"。
- 實體地址管理
之前的筆記,已經詳細描述了對映過程,包括CPU內部執行"段式/頁式地址對映"的過程,以及Linux核心為"0xC0000000 - 0xC0000000+8MB"這塊虛擬空間建立對映的過程,緊接著的學習,就是關於核心對"已使用/未使用"地址的管理,以下為實體地址管理相關的結構:
① struct page
typedef struct page { struct list_head list; struct address_space *mapping; unsigned long index; struct page *next_hash; atomic_t count; unsigned long flags; /* atomic flags, some possibly updated asynchronously */ struct list_head lru; unsigned long age; wait_queue_head_t wait; struct page **pprev_hash; struct buffer_head * buffers; void *virtual; /* non-NULL if kmapped */ struct zone_struct *zone; } mem_map_t;
這個結構,跟記錄"已使用/未使用"有什麼關係呢?
Linux核心,利用的是80386的頁式管理,所以分配釋放的最小單位必須是"頁",每個4K倍數的地址,都是一頁記憶體的開始,如果只是記錄"已使用/未使用",使用一個點陣圖即可,每個位的0/1值代表相應頁的"已使用/未使用"狀態,然而,記憶體管理還需要記錄其它很多資訊,從而定義了以上結構,裡面的成員,隨著後續的學習,都會接觸到,暫時不用關心。
此外,核心在啟動時,根據實際記憶體的大小,建立了一個mem_map[]陣列,陣列的每個成員都是一個struct page"物件",0下標對應"0地址頁"資訊,1下標對應"4K地址頁"資訊..,所以struct page中沒有表示頁地址的成員,另外,"已使用/未使用"狀態,是透過list成員掛在"已使用區/未使用區"區分。
start_kernel() |- setup_arch() |- paging_init() |- free_area_init() |- free_area_init_core() | // 根據實際的實體記憶體大小,建立mem_map[]陣列(*gmap == mem_map) |- *gmap = alloc_bootmem_node()
一定要注意,struct page"物件",跟頁面本身不是同一個東西,它只是用於記錄一塊4K大小的實體記憶體的使用情況而已,它們在位置上也沒有任何聯絡。
② struct zone_struct
Linux核心將所有物理頁面,劃分成三個管理區:ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM。
為什麼要劃分一個ZONE_DMA管理區?
DMA設計的用意是:磁碟資料往記憶體的讀寫,不用CPU參與。那麼,DMA本身就有訪問記憶體的能力,同真實模式/保護模式類似,DMA訪問的地址,可以直接是實體地址,也可以是需要經過對映的虛擬地址,取決於記憶體管理單元(MMU)是單獨實現,還是整合在CPU內部實現。80386就沒有設計單獨的MMU,所在DMA直接訪問實體記憶體。
首先,由於有些外設,不能訪問過高的地址,所以要在低地址區,劃分一塊ZONE_DMA管理區,專門用於DMA,避免被其它程式佔用。
其次,如果某個外設希望透過DMA訪問連續8K的記憶體,那就需要兩個連續的物理頁面,而不能像經過MMU那樣,只要保證虛擬地址連續即可,前4K對映到一個物理頁面,後4K對映到另一個相隔很遠的物理頁面都沒關係,所以也要單獨劃分一塊ZONE_DMA管理區,方便保證這一點。
最後,整個記憶體也有被用完的時候,單獨劃分一塊ZONE_DMA管理區,也能保證DMA始終有記憶體可用。
除了ZONE_DMA管理區,在實際記憶體大於1G時,還會劃分一個ZONE_HIGHMEM管理區(暫不關心,後期學習),其餘部分則為ZONE_NORMAL管理區。
每個管理區資訊對應的結構如下:
typedef struct zone_struct { /* * Commonly accessed fields: */ spinlock_t lock; unsigned long offset; unsigned long free_pages; unsigned long inactive_clean_pages; unsigned long inactive_dirty_pages; unsigned long pages_min, pages_low, pages_high; /* * free areas of different sizes */ struct list_head inactive_clean_list; free_area_t free_area[MAX_ORDER]; /* * rarely used fields: */ char *name; unsigned long size; /* * Discontig memory support fields. */ struct pglist_data *zone_pgdat; unsigned long zone_start_paddr; unsigned long zone_start_mapnr; struct page *zone_mem_map; } zone_t;
③ struct pglist_data
如下圖所示,CPU訪問不同記憶體條,代價是不一樣的,有的需要跨匯流排,有的不需要,這種情況叫做"非均勻儲存結構(NUMA)"。
比如下面是一臺機器的CPU資訊,"Socket(s)"表示實際插在主機板上的CPU個數為2,"Core(s) per socket"表示每個CPU上的物理核數為8,"Thread(s) per core"表示每個物理核開了2個超執行緒,所以總共有32個核,這些核就分別靠近兩個不同的NUMA節點。
Linux核心,會對不同NUMA節點中的記憶體,進行獨立管理。假設有2塊屬於不同NUMA節點的記憶體條,大小都是2G,將整個4G記憶體看作一個整體也是沒問題的,但是Linux核心是將這2個2G記憶體,分別劃分成3個管理區,從而在程式碼中定義了struct
pglist_data結構,用於記錄一個NUMA節點的資訊。
typedef struct pglist_data { zone_t node_zones[MAX_NR_ZONES]; zonelist_t node_zonelists[NR_GFPINDEX]; struct page *node_mem_map; unsigned long *valid_addr_bitmap; struct bootmem_data *bdata; unsigned long node_start_paddr; unsigned long node_start_mapnr; unsigned long node_size; int node_id; struct pglist_data *node_next; } pg_data_t;
到此為止,可以得出一個結論:利用struct pglist_data結構,可以表達系統中有多少個NUMA節點,利用struct zone_struct結構,可以表達每個NUMA節點中有哪些管理區,利用struct page結構,可以表達每個管理區包含的物理頁面,最終相當於描述了一個物理頁面"倉庫",物理頁面的管理,也正是對這個"倉庫"的管理。
④ 夥伴演算法
struct zone_struct有一個陣列成員:
free_area_t free_area[MAX_ORDER]; // MAX_ORDER為10
陣列的每個成員,又是一個struct free_area_struct"物件":
typedef struct free_area_struct { struct list_head free_list; unsigned int *map; } free_area_t;
每個struct free_area_struct"物件"包含一個連結串列頭,用於掛接連續的"空閒頁面塊"(透過頁面塊中首個頁面對應的struct page"物件"的list成員),並且free_area[0]、free_area[1]..、free_area[9]的free_list,分別用於掛接大小為2、4..、1024(2^10)的連續頁面塊(所以可以分配的最大連續頁面塊為1024*4K=4M)。
雖然保護模式下,程式使用的是虛擬地址,即使分配超大塊的記憶體時,只要保證虛擬地址是連續的即可,對映到的物理頁面是不是連續,對於程式是"透明"的,但是"分配"發生頻率超高,並且經常需要對映到多個物理頁面,那麼付出少量的代價,維持儘量多的連續物理頁面,能在"分配"這一面獲取非常大的效率收益。
夥伴演算法就是用於快速將小頁面塊,合併為大頁面塊:
上圖是我絞盡腦汁想到一種理解的方法:假設實際記憶體最開始16個頁面的狀態如上圖,藍色表示已分配、白色表示未分配,然後先別管抽象,無腦的跟著以下步驟去做:
將第一行(2n, 2n+1)下標處的內容(即(0,1)、(2,3)..夥伴演算法不會將(1,2)、(3,4)..當成夥伴),兩兩圈在一起,它們各自屬於一對夥伴。夥伴都忙(藍色數字),表示其中一個釋放,不可以合併成塊,在下一行中用藍色的0表示;夥伴都閒(黑色數字),表示已經成塊,不需要再次合併,在下一行中用黑色的0表示;否則等正在忙的那個夥伴被釋放時,就可以合併成塊,在下一行中用藍色的1表示(總之下一行的值,是由上一行中兩個夥伴的顏色決定,而不是值)。
然後依次對第二行、第三行..做同樣的操作,直到只剩一個數字,無法組成夥伴的那一行。此時再去驗證每一個0、1,比如:
order(0)中的第3個1:page4已分配,page5空閒,當page5釋放後就可以和page4合併;
order(1)中的第2個0:page4-7有3個已分配頁面,只釋放其中一個,不能進行合併;
order(2)中的第2個1:page8-15,只有page11已分配,當它釋放後,就可以合併成一個8頁面的空閒塊;
...
然後,是不是就理解夥伴演算法了?
夥伴演算法用於釋放時,將小塊合併成大塊,並將其移動到更高下標的free_area[]中,直到最大支援的1024個。而分配時,可能會將大塊切分成小塊:比如分配連續2個物理頁面,程式會優先到free_area[0]中找,如果為空,就到free_area[1]中找,假設還為空,就到free_area[2]中找,假設不再為空了,此時就找到一個8頁面空閒塊,程式會將它切分成兩個4頁面塊,其中一個掛接到free_area[1],另一個繼續切分成2個2頁面塊,其中一個掛接到free_area[0],剩餘一個就可以作為分配到的2個連續物理頁面了。
⑤ 頁表項(PTE)低12位
頁表項用於指向最終對映到的物理頁面,而物理頁面的地址,都是按4K對齊(因為第一個頁面的地址為0,每個頁面的大小為4K),所以CPU只把PTE的高20位當作地址(左移12位即可),低12位用於PTE所指頁面的屬性,在後續的換入換出管理中,就可以看到對這些屬性的利用。
另外,每個目錄項指向的也是頁面,並且,它指向的頁面都被核心當作頁表使用。
- 虛擬地址管理
虛擬地址也是資源,每個程式可以獨立使用的有隻有3G個地址,即使在64位系統中,數量就目前來講已經不成問題了,那也得避免邏輯上的歧義,不能用同一個邏輯地址指向兩個不同"物件",所以至少要對正在使用的虛擬地址,做記錄管理,以下為虛擬地址管理相關的結構和函式:
① struct vm_area_struct
虛擬地址的分配,依賴程式本身,每個程式編譯後,就有一部分要使用的虛擬地址是確定的了,比如程式碼段、資料段佔用的虛擬地址,對於動態分配,要分配的連續大小也是由程式指定的,根據這樣理解,虛擬地址管理的邏輯,本身就已經很固定了,所以整個邏輯,比對物理頁面的管理要簡單的多,struct vm_area_struct結構,就是程式指定要分配多大的記憶體時,核心分配這樣一個"物件",將該虛擬地址區域記錄下來,各個成員的含義在書中有詳細的說明,筆記中不再重複。
struct vm_area_struct { struct mm_struct * vm_mm; /* VM area parameters */ unsigned long vm_start; unsigned long vm_end; /* linked list of VM areas per task, sorted by address */ struct vm_area_struct *vm_next; pgprot_t vm_page_prot; unsigned long vm_flags; /* AVL tree of VM areas per task, sorted by address */ short vm_avl_height; struct vm_area_struct * vm_avl_left; struct vm_area_struct * vm_avl_right; /* For areas with an address space and backing store, * one of the address_space->i_mmap{,shared} lists, * for shm areas, the list of attaches, otherwise unused. */ struct vm_area_struct *vm_next_share; struct vm_area_struct **vm_pprev_share; struct vm_operations_struct * vm_ops; unsigned long vm_pgoff; /* offset in PAGE_SIZE units, *not* PAGE_CACHE_SIZE */ struct file * vm_file; unsigned long vm_raend; void * vm_private_data; /* was vm_pte (shared mem) */ };
② find_vma()函式
查詢某個虛擬地址所在的虛擬區間。
2. 越界訪問堆區
接下來,書中設定了一個情景:某個應用程式存在bug,它執行時,先透過mmap()函式獲取一塊記憶體,但會在munmap()之後,繼續訪問這塊記憶體中的地址,從而發生"段錯誤"。
剛開始學習核心的時候,我始終想不明白核心為什麼比應用程式"有權"(Linus寫的程式碼可以執行CPU特權指令,我寫的卻不可以),以及它是如何將"權力"控制在自己手裡。其實之前的筆記已經舉了一些例子,用於感性的體會,這裡再相對完整的理一遍:
核心對"權力"的把握,其實就是對CPU狀態和核心空間控制權的把握:
① 開機時,CPU執行的入口是核心程式碼,所以說"權力"一開始就給了核心;
② 核心在為應用程式的執行,創造好準備條件之後,是先將CPU切換到低許可權狀態,才跳轉,所以使用者態的程式碼就不能執行特權指令;
③ 應用程式的程式碼在低許可權狀態執行,如果希望CPU回到高許可權狀態,必須穿過一道核心設定的"門"(詳細內容在書中第三章——中斷、異常和系統呼叫),許可權提高的同時,指令也必須回到核心程式碼執行;
④ 應用程式的程式碼在低許可權狀態執行,就無法利用特權指令,建立/修改虛擬地址到實體地址的對映狀態,再加上虛擬地址到實體地址對映的過程,CPU會進行許可權檢查,從而核心又掌握了對核心空間的"控制權",雖然每個程式都可以訪問核心空間,但同樣必須是穿過一道"門"進入核心態,呼叫核心的介面訪問;
⑤ 核心除了將核心空間設定為"高許可權才能訪問"外,同時也保證將每一個核心空間中的虛擬地址,對映到的相同的實體地址,讓核心空間成為所有程式的"公共空間"。
綜上五點,最終的效果就是,任何程式都必須穿過"門",回到核心態,呼叫核心介面,進入到公共的核心空間,才能修改/獲取整個系統中某個全域性的管理資訊,應用程式如果有bug,也只能影響到自己使用者空間對映的那塊獨立的實體記憶體。
關於"門",仍然可以先感性的體會一下:程式如何才可以進入"門"?
- 主動進入
#include <stdio.h> int main() { printf("hello\n"); return 0; }
這個程式中呼叫的printf()函式,叫libc函式,libc庫封裝了很多"系統呼叫"函式(核心提供給應用程式的介面),比如執行"gcc test.c -g -Wall"編譯上面的程式,然後執行"strace ./a.out",就可以列出來printf()內部呼叫了哪些"系統呼叫"函式,其中就有一個是write()函式,它在執行的過程中,就會切換到核心態,write()函式也可以不經過libc的封裝直接呼叫,不管哪種方式,都屬於應用程式主動進入核心態。
- 被動進入
如果只依賴應用程式主動進入核心態,那麼,當應用程式執行到while(1){}迴圈裡面的時候,CPU就只能永遠為這個程式執行指令了,因為其它程式的資訊都在核心空間,無法回到核心態,當然也就沒有機會切換到其它程式。
所以硬體設計了一個"時鐘中斷"的功能,每過一段時間,不管CPU目前在幹什麼,都把當前的狀態儲存下來,進入"核心態"執行一個"定時函式",這個函式是由核心設定。
除了"時鐘中斷",如果應用程式訪問一個還未對映到實體地址的虛擬地址,或者執行"除以0"這些異常操作,CPU也會自動切換到核心態,並且執行核心事先設定的"異常/陷阱"函式,這些雖然觸發的源頭是應用程式,但往往是"不經意"的,比如"/0"一切是程式有bug,然後由於"段錯誤"而被迫停止執行。
到這裡,就可以回到書中設定的情景了,由於地址的對映關係,已經被munmap()函式撤消了,再次訪問時,CPU在執行對映過程中,必定會遇到值為0的目錄項或頁表項,這種情況下,CPU就會按照上面描述的,儲存當前狀態,切換到核心態,並且跳轉到核心的do_page_fault()函式執行。
跳轉到do_page_fault(),完整和正規的描述,書中第三章有詳細的介紹,目前直接分析do_page_fault()的執行過程,在分析之前,可以先看一下Linux核心對程式虛擬空間的佈局(按照書中對程式碼邏輯的解釋,在Linux-2.4.0版本的時候,應該是如下左圖):
do_page_fault()主要流程:
// 出現上述情景,CPU隱式跳轉到do_page_fault()函式執行,並且自動把當時處於使用者態/核心態、讀/寫訪問資訊,壓棧作為error_code引數 do_page_fault() | // CPU也會自動把訪問出錯的虛擬地址,存入CR2暫存器,這裡取出,方便在C函式中使用 |- __asm__("movl %%cr2,%0":"=r" (address)) |- 獲取核心為當前程式設定的程式管理結構,然後從該結構中獲取虛擬地址管理結構 | // 暫時將上述隱式跳轉到的函式,理解為中斷函式,那麼do_page_fault()本身就是中斷函式,in_interrupt()表示跳轉過來前,也是在中斷函式 | // 所有應用程式,都有自己獨立的虛擬空間,所以mm一定不為空,上述情景不滿足以下判斷條件 |- if (in_interrupt() || !mm) | // 當前程式一定能在已使用的使用者空間中,找出結束地址大於出錯地址的第一個區間,因為至少也有個棧區在最上方 |- find_vma() | // 當前情景找到的區間,會是從堆中動態分配的,並且起始地址也大於出錯地址,因為出錯地址所在的區間已經被munmap()函式撤消了 | // 從堆中分配的區間,增長方向向上,所以符合以下判斷條件,goto到bad_area處執行 |- if (!(vma->vm_flags & VM_GROWSDOWN)) | // 以下判斷出錯時是否在使用者態,上述情景符合判斷 |- if (error_code & 4) | // 設定軟中斷,讓程式coredump(詳細內容在書中第三章——中斷、異常和系統呼叫) |- info.si_signo = SIGSEGV
根據do_page_fault()的邏輯可以看出,訪問沒有對映的地址,必然會coredump,但按照平時的實際經驗,又會發現,應用程式中越界訪問堆區,有時候並不會coredump。那是因為應用程式使用的是libc中的malloc()/free(),呼叫free(),libc層並不一定立即呼叫核心的brk()函式,而是等到一定程度,才會真正撤消對映。
3. 越界訪問棧區
上述說明了核心對堆區越界訪問(分配範圍以外)的處理過程,書中接著又設定了一個對棧區越界訪問的情景:
Linux核心建立新程式時,會為新程式分配一塊初始大小的棧區,當程式執行到某個狀態,需要向棧中壓入很多區域性變數和函式引數時,初始分配的棧可能就不夠用了,比如如同下圖的狀態,再向棧區存入一個變數,esp暫存器就要指向未分配區 了:
需要注意的是,這種情景跟安全領域中的"棧溢位"不是一個概念。這個情景針對的是分配,影響的是esp的指向,並且程式也不一定存在bug,而"棧溢位"針對的是對變數的寫操作,影響的是已分配棧空間的內容,並且程式一定是存在bug,比如:
#include <stdio.h> #include <string.h> int main() { char buf[10] = { 0 }; // 會在buf[10]處寫一個'\0'字元,超出了陣列的範圍 strcpy(buf, "0123456789"); printf("%s\n", buf); return 0; }
回到書中設定的情景,do_page_fault()主要執行流程如下:
do_page_fault() | // 到達這個判斷條件之前,和munmap()的情景一樣,但這時找到的虛擬區間,就會是棧區了 |- if (!(vma->vm_flags & VM_GROWSDOWN)) | // 這個情景也是在使用者態訪問地址的 |- if (error_code & 4) | | // 壓棧資料最多的是pusha指令,可以一次性壓入32位元組 | | // 核心無法完全判斷應用程式的邏輯錯誤,比如*(&區域性變數-xx),也有可能訪問到這個範圍,那樣程式一定會出現別的異常現象 | |- if (address + 32 < regs->esp) | // 棧是向下增長,正常的壓棧操作,不符合以上判斷,執行到這裡對棧空間的大小進行擴充套件 |- expand_stack() | | // Linux對每個程式的棧空間,限制了大小,執行"ulimit -s"可以檢視,超過就不能繼續擴充套件了 | |- if (RLIMIT_STACK檢查) | |- 擴充套件虛擬區間大小 |- 進入讀寫操作檢查,產生異常時是寫操作,而棧區肯定是可寫的,所以透過檢查 | // 為虛擬區間中剛才擴充套件的部分,分配實體地址並建立對映 |- handle_mm_fault() | // .org 0x1000 | // ENTRY(swapper_pg_dir) | // 整個系統只需要一個目錄項,在系統初始化階段就確定位置了(見"Linux核心筆記004") |- pgd_offset() | // 32位CPU上,不使用中間目錄,所以還是返回目錄頁的地址,不影響對映過程 |- pmd_alloc() | // 一個頁表可以容納1024個頁表項,第一個需要這個頁表的分配操作,分配這個頁表,其它的直接使用 |- pte_alloc() | // 以上獲取的pte,可能是正在使用中的,它們它的低12位可以知道物理頁面是否換出到交換分割槽了 | // 本次設定情景,由於訪問的是之前沒有分配過的記憶體,所以pte也一定是0 |- handle_pte_fault() |- if (!pte_present(entry)) |- if (pte_none(entry)) | | // 本次情景會執行到這裡 | |- do_no_page() | | // mmap()、交換分割槽,都需要虛擬空間和檔案有聯絡,vma->vm_ops就包含一些檔案操作的函式指標 | |- if (!vma->vm_ops || !vma->vm_ops->nopage) | | | // 棧空間跟檔案沒有聯絡,所以本次情景會執行這裡 | | |- do_anonymous_page() | | |- if (write_access) | | | | // 分配物理頁面,如果是讀訪問,先對映到ZERO_PAGE,寫的時候再分配(COW) | | | |- alloc_page() | | | |- pte_mkwrite() | | | // 讓pte指令新分配的物理頁面 | | |- set_pte() | |- vma->vm_ops->nopage() |- do_swap_page()
核心執行完do_page_fault(),會再回到應用程式中壓棧的那條指令執行,這樣,就是壓棧的那條指令,在應用程式"感受"不到的情況下,導致程式進入核心走了一圈,擴充套件了棧的大小後,又回到了這條指令,這個過程叫做"缺頁異常"。
最後順便提一下,在以上的內容中,提到"中斷",異常和中斷,在從核心態返回使用者態時,是有區別的:
相關文章
- Linux核心筆記004 - 從記憶體管理開始,認識Linux核心2020-05-28Linux筆記記憶體
- Linux 核心處理中斷全過程解析2021-01-12Linux
- Linux 核心配置筆記2024-07-05Linux筆記
- 記憶體訪問全過程2020-05-10記憶體
- linux記憶體管理(六)- 核心新struct - folio2024-06-11Linux記憶體Struct
- Linux核心學習筆記(5)– 程式排程概述2018-09-06Linux筆記
- Linux核心筆記002 - i386 的頁式記憶體管理機制2020-05-13Linux筆記記憶體
- Linux核心自旋鎖使用筆記2019-05-14Linux筆記
- Linux記憶體不足的處理方法2021-09-09Linux記憶體
- Linux核心記憶體保護機制:aslr和canary2024-12-10Linux記憶體
- 01-0006 C++記憶體訪問越界 [問題整理]2020-10-19C++記憶體
- Linux核心Kernel啟動過程2024-05-28Linux
- Linux核心筆記003 - Linux核心程式碼裡面的C語言和組合語言2020-05-14Linux筆記C語言組合語言
- 【Linux】核心學習筆記(一)——程序管理2024-03-21Linux筆記
- 記憶體分配問題處理2024-02-05記憶體
- 記一次linux主機中病毒處理過程2019-01-21Linux
- 《Linux核心完全註釋》學習筆記:2.7 Linux核心原始碼的目錄結構2024-06-12Linux筆記原始碼
- 《奔跑吧 Linux核心》之處理器體系結構2019-02-28Linux
- [Linux]共享記憶體2024-12-07Linux記憶體
- CVE-2018-4990 Acrobat Reader堆記憶體越界訪問釋放漏洞分析2018-05-28BAT記憶體
- 自用學習資料,Linux核心之[記憶體管理]的一些分享2021-12-23Linux記憶體
- Linux 虛擬記憶體2019-07-05Linux記憶體
- Linux共享記憶體(二)2018-03-09Linux記憶體
- Linux實體記憶體管理2024-11-28Linux記憶體
- 如何增強 Linux 核心中的訪問控制安全2018-12-13Linux
- Linux:深入淺出 Linux 共享記憶體2021-01-04Linux記憶體
- linux記憶體管理(一)實體記憶體的組織和記憶體分配2024-06-07Linux記憶體
- Linux核心筆記009 - 中斷、異常、陷阱、Bottom half、訊號2020-09-18Linux筆記
- Linux核心筆記001 - Intel X86 CPU 系列的定址方式2020-05-12Linux筆記Intel
- 認識linux核心(linux核心的作用)2024-05-07Linux
- 記一次LNMP環境配置過程,以及Linux命令筆記2020-11-04LNMPLinux筆記
- windows核心程式設計--記憶體堆疊2020-04-04Windows程式設計記憶體
- Linux 記憶體區管理 slab2024-04-26Linux記憶體
- linux記憶體管理(二)- vmalloc2024-06-11Linux記憶體
- Linux記憶體、Swap、Cache、Buffer2021-01-03Linux記憶體
- Linux共享記憶體的管理2018-06-07Linux記憶體
- 不生成core檔案的記憶體越界快速定位方法/記憶體越界定位/地址崩潰定位方法2024-10-16記憶體
- Linux核心之 核心同步2020-12-31Linux