歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Linux POSIX IPC 共享內存

Linux POSIX IPC 共享內存

日期:2017/3/1 9:11:26   编辑:Linux編程

Linux POSIX IPC 共享內存

模型

  1. 創建/獲取共享內存fd :shm_open()
  2. 創建者調整文件大小 :ftruncate()
  3. 映射fd到內存 :mmap()
  4. 去映射fd :munmap()
  5. 刪除共享內存 :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);
//關閉進程打開的共享內存對象,成功返回0,失敗返回-1
//Link with -lrt.
int shm_unlink(const char *name);

Copyright © Linux教程網 All Rights Reserved