跳转至

Unix memory

  • 参考 About This Guide 在内存管理上,linux 提供了:
  • 共享内存接口(shm_open、shm_unlink、mmap、munmap)
  • 内存锁接口(mlock、mlockall、munlock、munlockall)
**linux 共享内存是通过 tmpfs 这个文件系统来实现的**,tmpfs 文件系的目录为/dev/shm,/dev/shm 是驻留在内存 RAM 当中的,因此读写速度与读写内存速度一样,/dev/shm 的容量默认尺寸为系统内存大小的一半大小,使用 df -h 命令可以看到。但实际上它并不会真正的占用这块内存,如果/dev/shm/下没有任何文件,它占用的内存实际上就是 0 字节,仅在使用 shm_open 文件时,/dev/shm 才会真正占用内存。
1. `shm_open`相当于`open`,在/dev/shm下创建一个文件;
2. `shm_unlink`相当于unlink,在/dev/shm下删除一个文件;
3. `shm_open`和`shm_unlink`中指定的文件名不需要包括/dev/shm 前缀。
4. 创建了共享内存,我们还需要设置内存大小,而`ftruncate`就扮演这样的角色。
5. 创建了共享内存,其实可以不用mmap去映射,这些访问内存就像操作文件一样,不方便,因此才用mmap去映射;但通常我们使用mmap映射。
  • 功能:
  • shm_open :打开一个共享内存对象,返回一个文件描述符。
  • shm_unlink :删除一个共享内存对象。
  • 接口:
/* Open shared memory segment.  */
extern int shm_open (const char *__name, int __oflag, mode_t __mode);

/* Remove shared memory segment.  */
extern int shm_unlink (const char *__name);

/* Truncate the file FD is open on to LENGTH bytes.  */
extern int ftruncate (int __fd, __off_t __length);
  • 示例
/*文件名:test_memory.c
 *编译:gcc -lrt  test_memory.c */
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>

main()
{
    int md;
    int status;
    long pg_size;
    caddr_t virt_addr;

    /* Create shared memory object */

    md = shm_open("my_memory", O_CREAT | O_RDWR, 0);
    pg_size = sysconf(_SC_PAGE_SIZE);

    if ((ftruncate(md, pg_size)) == -1)
    { /* Set the size */
        perror("ftruncate failure");
        _exit(1);
    }
    /* Map one page */

    virt_addr = mmap(0, pg_size, PROT_WRITE, MAP_SHARED, md, 0);

    status = munmap(virt_addr, pg_size); /* Unmap the page */
    status = close(md);                  /*   Close file   */
    status = shm_unlink("my_memory");    /* Unlink shared-memory object */
}

2 mmap、munmap、mprotect、msync

2.1 mmap

  • 功能:将文件描述符指定的数据映射到内存中
  • 接口:
/*The return value is the actual mapping address chosen or MAP_FAILED
   for errors (in which case `errno' is set).*/
extern void *mmap (void *addr, size_t len, int prot,
           int flags, int fd, __off_t offset);

其中参数解释: - addr :要将文件映射到的内存地址,一般应该传递 NULL 来由 Linux 内核指定 - len:要映射的文件数据长度。 - port:映射的内存区域的操作权限(保护属性),包括 PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE - flags:标志位参数,包括:MAP_SHARED、MAP_PRIVATE与MAP_ANONYMOUS。 - fd:用来建立映射区的文件描述符,用 shm_open 打开或者 open 打开的文件。 - offset:映射文件相对于文件头的偏移位置,应该按 4096 字节对齐。

port 权限有如下几种:

Flag Description
PROT_READ Data can be read
PROT_WRITE Data can be written
PROT_EXEC Data can be executed
PROT_NONE Data cannot be accessed

flag 如下:

Flag Description
MAP_SHARED Share changes
MAP_PRIVATE Changes are private
MAP_FIXED Interpret the addr argument exactly

其中 MAP_SHARED 和 MAP_PRIVATE 标志控制对映射文件或共享内存区域的修改的可见性。MAP_SHARED 标志指定对映射文件区域所做的修改对映射到同一区域并使用 MAP_SHARED 标志的其他进程立即可见。对区域所做的更改将写入文件。

2.2 munmap

  • 功能:取消内存映射
  • 接口:
/*Returns 0 if successful, -1 for errors (and sets errno).  */
extern int munmap (void *addr, size_t len);

其中参数解释如下: 1. addr: 2. len

2.3 mprotect

  • 功能:修改内存映射区的权限
  • 接口:
/*Returns 0 if successful, -1 for errors(and sets errno).  */
extern int mprotect (void *addr, size_t len, int prot);

其中参数解释如下: 1. addr :mmap 映射区域的某一个有效地址 2. len :从 addr 开始的长度 3. port :参考 mmap

2.4 msync

  • 功能:刷新共享内存到映射的文件磁盘上
  • 接口:
extern int msync (void *addr, size_t len, int flags);

其中参数解释如下: 1. addr :mmap 映射区域的某一个有效地址 2. len :从 addr 开始的长度 3. flags :刷新共享内存方式,有 S_ASYNC、MS_SYNC和MS_INVALIDATE

2.5 示例

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>

main()
{
    int fd;
    caddr_t pg_addr;

    int size = 5000;
    int mode = S_IRWXO | S_IRWXG | S_IRWXU;

    /* Create a file */

    fd = shm_open("example", O_RDWR | O_CREAT, mode);
    if (fd < 0)
    {
        perror("open error ");
        _exit(1);
    }

    /* Set the size */

    if ((ftruncate(fd, size)) == -1)
    {
        perror("ftruncate failure");
        _exit(0);
    }

    /* Map the file into the address space of the process */
    pg_addr = (caddr_t)mmap(0, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED,
                            fd, 0);

    if (pg_addr == (caddr_t)-1)
    {
        perror("mmap failure");
        _exit(1);
    }

    /* Lock the mapped region into memory */

    if (mlock(pg_addr, size) != 0)
    {
        perror("mlock failure");
        _exit(1);
    }

    /* Unmap of the address region removes the memory lock */
    /* established on the address region by this process   */

    if (munmap(pg_addr, size) < 0)
        perror("unmap error");
    close(fd);
    shm_unlink("example");
    _exit(1);
}

3 shm_open 和 mmap 使用场景

mmapshm_open 都是用于在进程间共享内存的系统调用,但它们在工作方式和用途上有一些不同。

3.1 mmap

  • 作用: mmap 用于在进程的地址空间中映射文件或其他对象,包括匿名内存映射。
  • 文件映射: 通过 mmap,可以将一个文件映射到进程的地址空间,使得对这块内存的访问可以映射到文件的读写操作。
  • 匿名映射: 除了文件映射,mmap 还可以创建匿名映射,即映射的区域不与任何文件关联,通常用于进程间通信。
  • 典型用途: 读取/写入文件,进程间通信(通过共享匿名映射区域)。

3.2 shm_open

  • 作用: shm_open 用于创建或打开具有名字的 POSIX 共享内存对象。
  • 具名对象:mmap 不同,shm_open 创建的共享内存区域是具有名字的,即通过名称来标识。这使得不同进程可以通过相同的名称来访问同一个共享内存区域。
  • 典型用途: 进程间通信,使得无关的进程可以通过共享的名字来访问同一块共享内存。

3.3 总结

  • mmap 主要用于文件映射和匿名映射,可以将文件映射到进程地址空间或创建无关的匿名映射。
  • shm_open 主要用于创建或打开具有名字的共享内存对象,使得不同进程可以通过相同的名称来访问共享内存。
  • 如果需要创建具有名字的共享内存区域,使得多个进程可以通过名称来访问同一块共享内存,通常会使用 shm_open。如果只是在进程间共享一块匿名内存区域,可能更倾向于使用 mmap

4 mlock、mlockall、munlock、munlockall

内存锁定是确保进程保持在主内存中并免于分页的一种方法。在实时环境中,系统必须能够保证将进程锁定在内存中,以减少数据访问、指令获取、进程之间的缓冲区传递等延迟。将进程的地址空间锁定在内存中有助于确保应用程序的响应时间满足实时要求。一般来说,time-critical 的进程应该锁定在内存中

  • 功能:
    • mlock :锁一段内存区域
    • munlock :解锁一段内存区域
    • mlockall :锁所有内存
    • munlockall :解锁所有内存
  • 接口:
extern int mlock (const void *addr, size_t len);

extern int munlock (const void *addr, size_t len);

/* Flags for `mlockall'.  */
#define MCL_CURRENT 1       /* Lock all currently mapped pages.  */
#define MCL_FUTURE  2       /* Lock all additions to address space.*/
extern int mlockall (int flags);

extern int munlockall (void);

4.1 mlock 示例

# 3.1 #include <unistd.h>   /* Support all standards    */
#include <sys/mman.h> /* Memory locking functions */

#define DATA_SIZE 2048

lock_memory(char *addr,
            size_t size)
{
    unsigned long page_offset, page_size;

    page_size = sysconf(_SC_PAGE_SIZE);
    page_offset = (unsigned long)addr % page_size;

    addr -= page_offset; /* Adjust addr to page boundary */
    size += page_offset; /* Adjust size with page_offset */

    return (mlock(addr, size)); /* Lock the memory */
}

unlock_memory(char *addr,
              size_t size)
{
    unsigned long page_offset, page_size;

    page_size = sysconf(_SC_PAGE_SIZE);
    page_offset = (unsigned long)addr % page_size;

    addr -= page_offset; /* Adjust addr to page boundary */
    size += page_offset; /* Adjust size with page_offset */

    return (munlock(addr, size)); /* Unlock the memory */
}

main()
{
    char data[DATA_SIZE];

    if (lock_memory(data, DATA_SIZE) == -1)
        perror("lock_memory");

    /* Do work here */

    if (unlock_memory(data, DATA_SIZE) == -1)
        perror("unlock_memory");
}

4.2 mlockall 示例

#include <unistd.h>   /* Support all standards    */
#include <stdlib.h>   /* malloc support           */
#include <sys/mman.h> /* Memory locking functions */

#define BUFFER 2048

main()
{
    void *p[3]; /* Array of 3 pointers to void */

    p[0] = malloc(BUFFER);

    /* Currently no memory is locked */

    if (mlockall(MCL_CURRENT) == -1)
        perror("mlockall:1");

    /* All currently allocated memory is locked */

    p[1] = malloc(BUFFER);

    /* All memory but data pointed to by p[1] is locked */

    if (munlockall() == -1)
        perror("munlockall:1");

    /* No memory is now locked */

    if (mlockall(MCL_FUTURE) == -1)
        perror("mlockall:2");

    /* Only memory allocated in the future */
    /*   will be locked */

    p[2] = malloc(BUFFER);

    /* Only data pointed to by data[2] is locked */

    if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1)
        perror("mlockall:3");

    /* All memory currently allocated and all memory that  */
    /* gets allocated in the future will be locked         */
}

5 Semaphores

/*
** These examples use semaphores to ensure that writer and reader
** processes have exclusive, alternating access to the shared-memory region.
*/

/**********  writer.c  ***********/

#include <unistd.h>
#include <semaphore.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/fcntl.h>

char shm_fn[] = "my_shm";
char sem_fn[] = "my_sem";

/**** WRITER ****/

main(){
  caddr_t shmptr;
  unsigned int mode;
  int shmdes, index;
  sem_t *semdes;
  int SHM_SIZE;

  mode = S_IRWXU|S_IRWXG;

  /* Open the shared memory object */

  if ( (shmdes = shm_open(shm_fn,O_CREAT|O_RDWR|O_TRUNC, mode)) == -1 ) {
     perror("shm_open failure");
     exit();
   }

  /* Preallocate a shared memory area */

  SHM_SIZE = sysconf(_SC_PAGE_SIZE);

  if(ftruncate(shmdes, SHM_SIZE) == -1){
    perror("ftruncate failure");
    exit();
  }

  if((shmptr = mmap(0, SHM_SIZE, PROT_WRITE|PROT_READ, MAP_SHARED,
                shmdes,0)) == (caddr_t) -1){
    perror("mmap failure");
    exit();
  }

  /* Create a semaphore in locked state */

 sem_des = sem_open(sem_fn, O_CREAT, 0644, 0);

 if(sem_des == (void*)-1){
   perror("sem_open failure");
   exit();
 }

    /* Access to the shared memory area */

    for(index = 0; index < 100; index++){
       printf("write %d into the shared memory shmptr[%d]\n", index*2, index);
       shmptr[index]=index*2;
       }

  /* Release the semaphore lock */

  sem_post(semdes);
  munmap(shmptr, SHM_SIZE);

  /* Close the shared memory object */

  close(shmdes);

  /* Close the Semaphore */

  sem_close(semdes);

  /* Delete the shared memory object */

  shm_unlink(shm_fn);
}

/*******************************************************************
*******************************************************************/

/**********  reader.c  ***********/

#include <sys/types.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/fcntl.h>

char shm_fn[] = "my_shm";
char sem_fn[] = "my_sem";

/**** READER ****/

main(){
  caddr_t shmptr;
  int shmdes, index;
  sem_t *semdes;
  int SHM_SIZE;

  /* Open the shared memory object */

  SHM_SIZE = sysconf(_SC_PAGE_SIZE);

  if ( (shmdes = shm_open(shm_fn, O_RDWR, 0)) == -1 ) {
     perror("shm_open failure");
     exit();
   }

  if((shmptr = mmap(0, SHM_SIZE, PROT_WRITE|PROT_READ, MAP_SHARED,
               shmdes,0)) == (caddr_t) -1){
     perror("mmap failure");
    exit();
  }

 /* Open the Semaphore */

 semdes = sem_open(sem_fn, 0, 0644, 0);

 if(semdes == (void*) -1){
   perror("sem_open failure");
   exit();
 }

 /* Lock the semaphore */

 if(!sem_wait(semdes)){

  /* Access to the shared memory area */

   for(index = 0; index < 100; index++)
        printf("The shared memory shmptr[%d] = %d\n", index,shmptr[index]);

  /* Release the semaphore lock */

   sem_post(semdes);
  }

  munmap(shmptr, SHM_SIZE);

  /* Close the shared memory object */

  close(shmdes);

  /* Close the Semaphore */

  sem_close(semdes);
  sem_unlink(sem_fn);
}