Linux編程之共享內存

Linux 支持兩種方式的共享內存:System V 和 POSIX 共享內存。函數

1. POSIX 共享內存

1.1 POSIX 共享內存的由來

System V 共享內存和共享文件映射的不足:ui

  • System V 共享內存模型使用的是鍵和標識符,這與標準的 UNIX I/O 模型使用文件名和描述符的作法是不一致的。這種差別意味着使用 System V 共享內存須要一整套全新的系統調用和命令。
  • 使用一個共享文件映射來進行 IPC 要求建立一個磁盤文件,即便無需對共享區域進行持久存儲也須要這樣作。除了因須要建立文件所帶來的不便之處,這種技術還會帶來一些文件 I/O 開銷。

基於以上不足,POSIX.1b 定義了一組新的共享內存 API: POSIX 共享內存。code

1.2 POSIX 共享內存概述

POSIX 共享內存可以讓無關進程共享一個映射區域而無需建立一個相應的映射文件。Linux 從內核 2.4 起開始支持 POSIX 共享內存。對象

不少類 UNIX 實現採用了文件系統來標識共享內存對象。一些 UNIX 實現將共享內存對象名建立爲標準文件系統上一個特殊位置處的文件。Linux 使用掛載於 /dev/shm 目錄下的專用 tmpfs 文件系統. 這個文件系統具備內核持久性--它所包含的共享內存對象會一直持久,即便當前不存在任何進程打開它,但這些對象會在系統關閉以後丟失.進程

系統上 POSIX 共享內存區域佔據的內存總量受限於底層的 tmpfs 文件系統的大小。這個文件系統一般會在啓動時使用默認大小(如 256 MB)進行掛載。若是有必要的話,超級用戶可以經過使用命令 mount -o remount,size= 從新掛載這個文件系統來修改它的大小。 ip

使用 POSIX 共享內存對象的流程:內存

  • 使用 shm_open() 函數打開一個與指定的名字對應的對象。shm_open() 函數與 open() 系統調用相似,它會建立一個新共享對象或打開一個既有對象。做爲函數結果,shm_open() 會返回一個引用該對象的文件描述符。
  • 將上一步中得到的文件描述符傳入 mmap() 調用並在其 flags 參數中指定 MAP_SHARED。這會將共享內存對象映射進進程的虛擬地址空間。與 mmap() 的其餘用法同樣,一旦映射了對象以後就可以關閉該文件描述符而不會影響到這個映射。而後,有可能須要將這個文件描述符保持在打開狀態以便後續的 fstat() 和 ftruncate() 調用使用這個文件描述符。

1.3 建立共享內存對象

shm_open() 函數建立和打開一個新的共享內存對象或打開一個既有對象。傳入 shm_open() 的參數與傳入 open()的參數相似。rem

#include <sys/mman.h>
#include <sys/stat.h>        /* For mode constants */
#include <fcntl.h>           /* For O_* constants */

int shm_open(const chart *name, int oflag, mode_t mode);
        Returns file descriptor on success, or -1 on error.
        
Link with -lrt.

name 參數標識出了待建立或待打開的共享內存對象。oflag 參數是一個改變調用行爲的位掩碼。字符串

oflag 參數的位值:string

  • O_CREAT: 對象不存在時建立對象,一個新的共享內存對象最初長度爲 0,對象的大小可使用 ftruncate 來設置。
  • O_EXCL: 與 O_CREAT 互斥地建立對象,即若是 O_CREAT 已經指定了,而且指定名字的共享內存對象已經存在,則會返回錯誤。
  • O_RDONLY: 打開只讀訪問。
  • O_RDWR: 打開讀寫訪問。
  • O_TRUNC: 將對象長度截斷爲零。

在一個新共享內存對象被建立時,其全部權和組全部權將根據調用 shm_open() 的進程的有效用戶和組 ID 來設定,對象權限將會根據 mode 參數中設置的掩碼值來設定。mode 參數能取的位值與文件上的權限位值是同樣的。與 open() 系統調用同樣,mode 中的權限掩碼將會根據進程的 umask 來取值。與 open() 不一樣的是,在調用 shm_open() 時老是須要 mode 參數,在不建立新對象時須要將這個參數值指定爲 0.

shm_open() 返回的文件描述符會設置 close-on-exec 標記,所以當程序執行了一個 exec() 時文件描述符會被自動關閉。

一個新共享內存對象被建立時其初始長度被會設置爲 0。這意味着在建立完一個新共享內存對象以後一般在調用 mmap() 以前須要調用 ftruncate() 來設置對象的大小。在調用完 mmap() 以後可能還須要使用 ftuncate() 來根據需求擴大或收縮共享內存對象。

在擴展一個共享內存對象時,新增長的字節會自動被初始化爲 0。

在任什麼時候候均可以在 shm_open() 返回的文件描述符上使用 fstat() 以獲取一個 stat 結構,該結構的字段會包含與這個共享內存對象相關的信息,包括其大小(st_size)、權限(st_mode)、全部者(st_uid)以及組(st_gid)。

使用 fchmod() 和 fchown() 可以分別修改共享內存對象的權限和全部權。

示例程序: pshm_create.c

該程序建立了一個大小經過命令參數指定的共享內存對象並將該對象映射進進程的虛擬地址空間。

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

int main(int argc, char *argv[])
{
    int flags, opt, fd;
    mode_t perms;
    size_t size;
    void *addr;
    
    /* Create shared memory object and set its size */
    fd = shm_open(argv[1], O_RDWR|O_CREAT, 0777);
    if (fd == -1) 
    {
        printf("shm_open failed");
        exit(-1);
    }
    
    if (ftruncate(fd, 10000) == -1)
    {
        printf("ftruncate failed");
        exit(-1);
    }
    
    /* Map shared memory object */
    addr = mmap(NULL, 10000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) 
    {
        printf("mmap failed");
        exit(-1);
    }
    
    exit(EXIT_SUCCESS);
}

如上程序建立一各 10000 字節的共享內存對象:

# gcc pshm_create.c -o pshm_create -lrt
# ./pshm_create demo_shm
# ls -l /dev/shm/demo_shm 
-rwxr-xr-x 1 root root 10000 Jun 16 03:34 /dev/shm/demo_shm

1.4 使用共享內存對象

pshm_write.c

將數據複製進一個 POSIX 共享內存對象.

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

int main(int argc, char *argv[])
{
    int fd;
    size_t len;
    char *addr;
    
    /* Open existing object */
    fd = shm_open(argv[1], O_RDWR, 0); 
    if (fd == -1) 
    {
        printf("shm_open failed");
        exit(-1);
    }
    
    len = strlen(argv[2]);
    /* Resize object to hold string */
    if (ftruncate(fd, len) == -1) 
    {
        printf("ftruncate failed");
        exit(-1);
    }
    
    addr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) 
    {
        printf("mmap failed");
        exit(-1);
    }
    
    if (close(fd) == -1)
    {
        printf("close failed");
        exit(-1);
    }
    
    printf("copying %ld bytes\n", (long)len);
    /* Copy string to shared memory */
    memcpy(addr, argv[2], len);
    exit(EXIT_SUCCESS);
}

向 1.3 建立的共享內存對象 demo_shm 寫入數據:

# gcc pshm_write.c -o pshm_write -lrt
# ls -l /dev/shm/demo_shm 
-rwxr-xr-x 1 root root 10000 Jun 16 03:34 /dev/shm/demo_shm
# ./pshm_write demo_shm 'hello'
copying 5 bytes
# ls -l /dev/shm/demo_shm 
-rwxr-xr-x 1 root root 5 Jun 16 03:46 /dev/shm/demo_shm

pshm_read.c

從一個 POSIX 共享內存對象中複製數據。

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

int main(int argc, char *argv[])
{
    int fd;
    char *addr;
    struct stat sb;
    
    /* Open existing object */
    fd = shm_open(argv[1], O_RDONLY, 0); 
    if (fd == -1) 
    {
        printf("shm_open failed");
        exit(-1);
    }
    
    /* Use shared memory object size as length argument for mmap()
     * and as number of bytes to write() */
    
    if (fstat(fd, &sb) == -1)
    {
        printf("fstat failed");
        exit(-1);
    }
    
    addr = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
    if (addr == MAP_FAILED) 
    {
        printf("mmap failed");
        exit(-1);
    }
    
    if (close(fd) == -1)
    {
        printf("close failed");
        exit(-1);
    }
    
    write(STDOUT_FILENO, addr, sb.st_size);
    printf("\n");
    exit(EXIT_SUCCESS);
}

顯示剛纔寫到共享內存對象 demo_shm 中的字符串:

# gcc pshm_read.c -o pshm_read -lrt
# ./pshm_read demo_shm
hello

1.5 刪除共享內存對象

POSIX 共享內存對象具備內核持久性,即它們會持續存在直到被顯示刪除或系統重啓。當再也不須要一個共享內存對象時就應該使用 shm_unlink() 刪除它。

#include <sys/mman.h>

int shm_unlink(const char *name);
        Returns 0 on success, or -1 on error

Link with -lrt.

shm_unlink() 函數會刪除經過 name 指定的共享內存對象。刪除一個共享內存對象不會影響對象的既有映射(它會保持有效直到相應的進程調用 munmap() 或終止),但會阻止後續的 shm_open() 調用打開這個對象。一旦全部進程都接觸映射這個對象,對象就會刪除,其中的內容會丟失。

pshm_unlink.c

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

int main(int argc, char *argv[])
{
    if (shm_unlink(argv[1]) == -1)
    {
        printf("shm_unlink failed");
        exit(-1);
    }
    
    exit(EXIT_SUCCESS);
}

刪除上面建立的共享內存對象 demo_shm:

# gcc pshm_unlink.c -o pshm_unlink -lrt
# ./pshm_unlink demo_shm
相關文章
相關標籤/搜索