? ? ? ? 對于每一個進程都會對應一個虛擬地址空間,對于32位的操作系統(其指令的位數最大為32位,因此地址碼最多32位),虛擬地址空間的大小為B即0~4GB的虛擬地址空間,其中內核空間為1GB,如下所示:
? ? ? ? ?每一個進程的進程控制塊PCB都位于內核區,在每一個進程的PCB中有一個文件描述符表(是一個數組),用于標記該進程所打開的所有文件。從文件描述符表可以看出每一個進程最多能打開1024個文件,其中有三個文件默認是一直處于打開狀態的(即進程創建完成時就處于打開狀態),分別是:標準輸入 STDIN_FILENO,其文件描述符為0;標準輸出?STDOUT_FILENO,其文件描述符為1;錯誤輸出?STDERR_FILENO,其文件描述符為2,其中文件描述符0和1可以省略不寫。供我們用戶打開的文件,只能夠占據從3開始的位置(即其文件描述符為3以后的數字,3~1023)。每打開一個文件就會占用一個文件描述符,且使用的是空閑的最小的一個文件描述符。
? ? ? ? Linux下可執行文件的格式為ELF:[root@localhost Calc]# file zsx
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?zsx: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=0x14ef2d34126e7c54141b73c31968bd825ca522ba, not stripped? ? ? ? ? ?//可以看出zsx為64位(即機器指令位數為64位,OS位數)的可執行文件,其格式為ELF。
? ? ? ? 對于每一個程序在執行時(如上圖中的a.out),此時會產生一個相應的進程,系統都會自動為其分配一個0~4G的虛擬地址空間,其中1G的內核空間用于:進程管理、內存管理、設備管理和虛擬文件系統等。下面詳細介紹0~3G的用戶空間。
? ? ? ? ?強調一點:以下說明的各段都是與編程相關的,不包括虛擬地址空間的全部。
? ? ? ? 0~3G的用戶空間。從小到大(從下往上)依次為:保留區(受保護的地址)、代碼段、數據段(.data段)、.bss段、堆空間、內存映射段、棧空間、命令行參數和環境變量。下面依次對每一個段做簡單的介紹:
1.保留區(受保護的地址)
? ? ? ? 保留區即為受保護的地址,大小為0~4K,位于虛擬地址空間的最低部分,未賦予物理地址(不會與內存地址相對應,因此其不會放任何內容)。任何對它的引用都是非法的,用于捕捉使用空指針和小整型值指針引用內存的異常情況。大多數操作系統中,極小的地址通常都是不允許訪問的,如NULL。C語言將無效指針賦值為0也是出于這種考慮,因為0地址上正常情況下不會存放有效的可訪問數據。將指針賦值為0,意味著該指針將永遠不會被使用,從而不會出現野指針情況。#define NULL 0 與 #define NULL (void*)0? ?在C語言中是等效的,而在C++中,只能用#define NULL 0,后面 #define NULL (void*)0的使用會出錯。
2.代碼段
? ? ? ??代碼段也稱正文段或文本段,通常用于存放程序執行代碼(即CPU執行的機器指令)。一般C語言執行語句都編譯成機器代碼保存在代碼段。通常代碼段是可共享的,因此頻繁執行的程序只需要在內存中擁有一份拷貝即可。代碼段通常屬于只讀,以防止其他程序意外地修改其指令(對該段的寫操作將導致段錯誤)。某些架構也允許代碼段為可寫,即允許修改程序。??
3.數據段(.data段)
? ? ? ??數據段通常用于存放程序中已初始化的全局變量和靜態局部變量。數據段屬于靜態內存分配(靜態存儲區),可讀可寫。由于全局變量未初始化時,其默認值為0,因此值為0的全局變量位于.bbs段(不位于數據段)。對于未初始化的局部變量,其值是不可預測的。注意:在代碼段和數據段之間還包括其它段:只讀數據段和符號段等。
4..bbs段
? ? ? ? 該段用于存放未初始化的全局變量和靜態局部變量,包括值為0的全局變量。 數據段和.bbs段又稱為全局數據區,前者初始化,后者未初始化。
? ? ? ? ELF段包括:代碼段、其它段(只讀數據段和符號段等)、.data段(數據段)和.bbs段,都屬于可執行程序部分。
5.堆空間
? ? ? ? new( )和malloc( )函數分配的空間就屬于對空間,用于內存空間的分配,其從下往上。??堆用于存放進程運行時動態分配的內存段,可動態擴張或縮減。堆中內容是匿名的,不能按名字直接訪問,只能通過指針間接訪問。當進程調用malloc(C) 和new (C++)等函數分配內存時,新分配的內存動態添加到堆上(擴張);當調用free(C)/delete(C++)等函數釋放內存時,被釋放的內存從堆中剔除(縮減)?。
6.內存映射段(共享庫)
? ? ? ? 此處,內核將硬盤文件的內容直接映射到內存, 任何應用程序都可通過Linux的mmap()系統調用請求這種映射。內存映射是一種方便高效的文件I/O方式, 因而被用于裝載動態共享庫。如C標準庫函數(fread、fwrite、fopen等)和Linux系統I/O函數,它們都是動態庫函數,其中C標準庫函數都被封裝在了/lib/libc.so庫文件中,都是二進制文件。這些動態庫函數都是與位置無關的代碼,即每次被加載進入內存映射區時的位置都是不一樣的,因此使用的是其本身的邏輯地址,經過變換成線性地址(虛擬地址),然后再映射到內存。而靜態庫不一樣,由于靜態庫被鏈接到可執行文件中,因此其位于代碼段,每次在地址空間中的位置都是固定的。
7.棧空間
? ? ? ? 用于存放局部變量(非靜態局部變量,C語言稱為自動變量),分配存儲空間時從上往下。棧和堆都是后進先出的數據結構。
8.命令行參數
? ? ? ? 該段用于存放命令行參數的內容:argc和argv。
9.環境變量
? ? ? ? 用于存放當前的環境變量,在Linux中用env命令可以查看其值。
10.虛擬地址空間的作用(好處)
? ? ? ? 1.方面編譯器和操作系統安排程序的地址;2.方便實現各個進程空間之間的隔離,互不干擾,因為每個進程都對應自己的虛擬地址空間;3.實現虛擬存儲,從邏輯上擴大了內存。
補充內容:
代碼段(.text段)與只讀數據段和符號段(.rodata段),都屬于只能讀的部分,在鏈接的時候這兩部分會鏈接成為一個整體;而.data段和.bbs段屬于可讀可寫RW的部分。這四個部分都是以頁(每頁4KB)的形式存放在內存中。進程控制塊PCB(又叫進程描述符)放于內核空間。
多個進程在并發執行時,這些進程的用戶空間都是彼此獨立的,因此各個進程的用戶空間在映射為內存空間使都是獨立的,互不干擾,這是MMU地址變換必須要能夠保證的。例如,各個進程的.text段、只讀數據段和符號段、.data段和.bbs段等在用戶空間中使用到的其它數據信息,都會與頁為基本單位放在內存中,各個進程的映射是獨立的。而對于內核空間,由于只有一個操作系統,內核空間主要是 機器指令、操作系統內核的各個模塊等,它們是公用的,因此每個進程的映射方式一樣。強調一點:每個進程用到或即將用到的數據才會調入內存,其余都在磁盤上。但是各個進程內核空間的進程控制塊(進程描述符)映射的地點是不一樣的,也是相互獨立的。共用的模塊才是一樣的。 這些都是MMU的實現機制所決定的。如果感興趣,可以看看MMU的實現機制。