模型 1. 創建/獲取共用記憶體fd :shm_open() 2. 創建者調整文件大小 :ftruncate() 3. 映射fd到記憶體 :mmap() 4. 去映射fd :munmap() 5. 刪除共用記憶體 :shm_unlink() 頭文件 shm_open oflag Access Mode: ...
模型
- 創建/獲取共用記憶體fd :shm_open()
- 創建者調整文件大小 :ftruncate()
- 映射fd到記憶體 :mmap()
- 去映射fd :munmap()
- 刪除共用記憶體 :shm_unlink()
頭文件
#include <unistd.h> //for fstat()
#include <sys/types.h> //for fstat()
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
shm_open
//創建/獲取共用記憶體的文件描述符,成功返迴文件描述符,失敗返回-1
//Link with -lrt.
int shm_open(const char *name, int oflag, mode_t mode);
oflag
- Access Mode:
- O_RDONLY以只讀的方式打開共用記憶體對象
- O_RDWR以讀寫的方式打開共用記憶體對象
- Opening-time flags(Bitwise Or):
- O_CREAT 表示創建共用記憶體對象,剛被創建的對象會被初始化為0byte可以使用ftuncate()調整大小
- O_EXCL用來確保共用記憶體對象被成功創建,如果對象已經存在,那麼返回錯誤
- O_TRUNC表示如果共用記憶體對象已經存在那麼把它清空
mode: eg,0664 etc
ftruncate()
//調整fd指向文件的大小,成功返回0,失敗返回-1設errno
//VS truncate()
int ftruncate(int fd, off_t length);
如果原文件大小>指定大小,原文件中多餘的部分會被截除
int res=ftruncate(fd,3*sizeof(Emp));//要用sizeof,且是Emp(類型)不是emp(對象)
if(-1==res)
perror("ftruncate"),exit(-1);
fstat()
//獲取文件狀態,成功返回0,失敗返回-1設errno
//VS stat()
int lstat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);
buf:stat類型的指針
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */ 八進位 usigned int o%
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */ ld%
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
struct timespec st_atim; /* time of last access */
struct timespec st_mtim; /* time of last modification */ ld%,秒
struct timespec st_ctim; /* time of last status change */
};
//eg:
st_mode=100664 //100是文件類型
//664是許可權, 通過100664和0777BitwiseAND得到
st_mtime=1462787968 //秒
mmap()
//映射文件或設備到進程的虛擬記憶體空間,映射成功後對相應的文件或設備操作就相當於對記憶體的操作
//映射以頁為基本單位,文件大小, mmap的參數 len 都不能決定進程能訪問的大小, 而是容納文件被映射部分的最小頁面數決定傳統文件訪問
//要求對文件進行可讀可寫的的打開!!!
//成功返回映射區的指針,失敗返回-1設errno
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); //prot:protection, 許可權
addr:映射的起始地址, 如果為NULL則由kernel自行選擇->最合適的方法
length:映射的區域長度
prot:映射記憶體的保護許可權
- PROT_EXEC表示映射的記憶體頁可執行
- PROT_READ表示映射的記憶體可被讀
- PROT_WRITE表示映射的記憶體可被寫
- PROT_NONE表示映射的記憶體不可訪問
flags
must include one of :
- MAP_SHARED表示共用這塊映射的記憶體,讀寫這塊記憶體相當於直接讀寫文件,這些操作對其他進程可見,由於OS對文件的讀寫都有緩存機制,所以實際上不會立即將更改寫入文件,除非帶哦用msync()或mumap()
- MAP_PRIVATE表示創建一個私有的copy-on-write的映射, 更新映射區對其他映射到這個文件的進程是不可見的
can be Bitwise ORed:
- MAP_32BIT把映射區的頭2GB個位元組映射到進程的地址空間,僅限域x86-64平臺的64位程式,在早期64位處理器平臺上,可以用來提高上下文切換的性能。當設置了MAP_FIXED時此選項自動被忽略
- MAP_ANONYMOUS映射不會備份到任何文件,fd和offset參數都被忽略,通常和MAP_SHARED連用
- MAP_DENYWRITEignored.
- MAP_EXECUTABLEignored
- MAP_FILE用來保持相容性,ignored
- MAP_FIXED不要對addr參數進行處理確確實實的放在addr指向的地址,此時addr一定時頁大小的整數倍,
- MAP_GROWSDOWN用在棧中,告訴VMM映射區應該向低地址擴展
- MAP_HUGETLB (since Linux 2.6.32)用於分配"大頁"
fd: file decriptor
offset: 文件中的偏移量
void* pv=mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,0,0);
if(MAP_FAILED==pv)
perror("mmap"),exit(-1);
映射機制小解
- mmap()就是建立一個指針,這個指針指向頁高速緩存的一頁,並假設這個頁有我們想要訪問的文件內容(此時都在虛擬地址空間),當然,這個頁描述符會自動的加入的調用進程的頁表中。當我們第一次使用這個指針,去訪問這個虛擬地址的頁時,發現這個頁還沒有分配物理頁框,沒有想要的文件,引起缺頁中斷,系統會把相應的(fd)文件內容放到高速緩存的物理頁框(此時才會有對物理地址空間的讀寫)
- 映射過程中使用的文件相當於藥引子,因為所有進程都是可以通過VFS訪問磁碟文件的,所以這個文件相當於對映射記憶體的一個標識,有了這個位於磁碟的藥引子,很多進程都可以根據它找到同一個物理頁框,進而實現記憶體的共用,並不是說就在磁碟上讀寫
- 頁高速緩存的內容不會立即寫到磁碟中,會等幾秒,這種機制可以提高效率並保護磁碟
- 只要記憶體夠大,頁高速緩存的內容就會一直存在記憶體中,以後再有進程對該緩存頁的內容訪問的需求,就不需要從磁碟中搜索,直接訪問緩存頁(把這個頁加入到進程的頁表)就行
- mmap()是系統調用,使用一次的開銷比較大,但比文件讀寫的read()/write()進行內核空間到用戶空間的數據複製要快,通常只有需要分配的記憶體>128KB(malloc一次分配33page就是128KB)的時候才會使用mmap()
- /shm是一個特殊的文件系統,它不對應磁碟中的區域,而是記憶體中,所以使用mmap()在這個文件系統中創
- /proc 不占用任何磁碟空間
- linux採用的是頁式管理機制。對於用mmap()映射普通文件來說,進程會在自己的地址空間新增一塊空間,空間大小由mmap()的len參數指定,註意,進程並不一定能夠對全部新增空間都能進行有效訪問。進程能夠訪問的有效地址大小取決於文件被映射部分的大小。
- 簡單的說,能夠容納文件被映射部分大小的最少頁面個數決定了進程從mmap()返回的地址開始,能夠有效訪問的地址空間大小。超過這個空間大小,內核會根據超過的嚴重程度返回發送不同的信號給進程。
- 經過內核!=在內核空間和用戶空間來回切換!=在內核空間和用戶空間傳遞複製的數據
- 頁是記憶體映射的基本單位, 可以理解為實際分配給物理記憶體的基本單位, 但不是數據操作的基本單位;
- 頁機制是操作系統和CPU約定好的一種方式,OS按照頁給CPU按頁發虛擬地址,CPU按頁解析並處理
- 操作系統(包括Linux)大量使用的緩存的兩個原理:
- CPU訪問記憶體的速度遠遠大於訪問磁碟的速度(訪問速度差距不是一般的大,差好幾個數量級)
- 數據一旦被訪問,就有可能在短期內再次被訪問(臨時局部原理)
- 頁高速緩存(page cache)是個記憶體區域,是Linux 內核使用的主要磁碟高速緩存,在絕大多數情況下,內核在讀寫磁碟時都引用頁高速緩存,新頁被追加到頁高速緩存以滿足用戶態進程的讀請求,如果頁不在高速緩存中,新頁就被加到高速緩存中,然後就從磁碟讀出的數據填充它,如果記憶體有足夠的空閑空間,就讓該頁在高速緩存中長期保留,使其他進程再使用該頁時不再訪問磁碟, 即磁碟上的文件緩存到記憶體後,它的虛擬記憶體地址可以有多個,但是物理記憶體地址卻只能有一個
- 我們要讀寫磁碟文件時,實質是對頁高速緩存進行讀寫,所以無論讀寫,都會首先檢查頁高速緩存有沒有這個文件對應的頁,如果有,就直接訪問,如果沒有,就引起缺頁中斷,給OS發信號,讓它把文件放到高速緩存再進行讀寫,這個過程不經過內核空間到用戶空間複製數據
- OS中的頁機制,對應到硬體中可不一定在主存中,也可以是高速緩存etc,但不會是磁碟,因為磁碟文件的地址和記憶體不一樣,不是按照32位編址的,而是按照ext2 etc方式編址的,需要使用文件管理系統,在Linux中使用VFS和實際文件管理系統來管理文件,所以對於Linux,有兩個方式使用系統資源:VMM,VFS,前者用來管理絕大部分的記憶體,後者用來管理所有的文件和部分特殊文件系統(eg:/shm是記憶體的一塊區域)
- page cache可以看作二者的橋梁,把磁碟文件放到高速緩存,就可以按照記憶體的使用方式使用磁碟的文件,使用完再釋放或寫回磁碟page cache中的頁可能是下麵的類型:
- 含有普通文件數據的頁
- 含有目錄的頁
- 含有直接從塊設備(跳過文件系統層)讀出的數據的頁
- 含有用戶態進程數據的頁,但頁中的數據已經被交換到硬碟
- 屬於他書文件系統文件的頁
- 映射:一個線性區可以和磁碟文件系統的普通文件的某一部分或者塊設備文件相關聯,這就意味著內核把對區線性中頁內某個位元組的訪問轉換成對文件中相應位元組的操作
- TLB(Translation Lookaside Buffer)高速緩存用於加快線性地址的轉換,當一個線性地址第一次被使用時,通過慢速訪問RAM中的頁表計算出相應的物理地址,同時,物理地址被存放在TLB表項(TLB entry),以便以後對同一個線性地址的引用可以快速得到轉換
- 在初始化階段,內核必須建立一個物理地址映射來指定哪些物理地址範圍對內核可用,哪些不可用
- swap(記憶體交換空間)的功能是應付物理記憶體不足的情況下所造成的記憶體擴展記錄的功能,CPU所讀取的數據都來自於記憶體,那當記憶體不足的時候,為了讓後續的程式可以順序運行,因此在記憶體中暫不使用的程式和數據就會被挪到swap中,此時記憶體就會空出來給需要執行的程式載入,由於swap是使用硬碟來暫時放置記憶體中的信息,所以用到swap時,主機硬碟燈就會開始閃個不同
- Q:CPU只能對記憶體進行讀寫,但又是怎麼讀寫硬碟的呢???A:把數據寫入page cache,再經由。。。寫入磁碟(包括swap) --《鳥哥》P10
- 記憶體本身沒有計算能力,定址之類的都是CPU的事,只是為了簡便起見,我們通常畫成從記憶體地址A跳到記憶體地址B
- OS是軟體的核心,CPU是執行的核心
- 前者給後者髮指令我要乾什麼,CPU把他的指令變成現實
- 二者必須很好的匹配電腦才能很好的工作
- Linux內核中與文件Cache操作相關的API有很多,按其使用方式可以分成兩類:一類是以拷貝方式操作的相關介面, 如read/write/sendfile等,其中sendfile在2.6系列的內核中已經不再支持;另一類是以地址映射方式操作的相關介面,如mmap等。
- 第一種類型的API在不同文件的Cache之間或者Cache與應用程式所提供的用戶空間buffer之間拷貝數據,其實現原理如圖7所示。
- 第二種類型的API將Cache項映射到用戶空間,使得應用程式可以像使用記憶體指針一樣訪問文件,Memory map訪問Cache的方式在內核中是採用請求頁面機制實現的,首先,應用程式調用mmap(),陷入到內核中後調用do_mmap_pgoff。該函數從應用程式的地址空間中分配一段區域作為映射的記憶體地址,並使用一個VMA(vm_area_struct)結構代表該區域,之後就返回到應用程。當應用程式訪問mmap所返回的地址指針時(圖中4),由於虛實映射尚未建立,會觸發缺頁中斷。之後系統會調用缺頁中斷處理函數,在缺頁中斷處理函數中,內核通過相應區域的VMA結構判斷出該區域屬於文件映射,於是調用具體文件系統的介面讀入相應的Page Cache項,並填寫相應的虛實映射表。經過這些步驟之後,應用程式就可以正常訪問相應的記憶體區域了。
mumap()
//接觸文件或設備對記憶體的映射,成功返回0,失敗返回-1設errno
int munmap(void *addr, size_t length);
shm_unlink()
//關閉進程打開的共用記憶體對象,成功返回0,失敗返回-1
//Link with -lrt.
int shm_unlink(const char *name);