Linux的共享内存

47 篇文章 3 订阅
9 篇文章 0 订阅

一、shm方式和mmap方式区分

  1. 二者本质上是类似的,mmap可以看到文件的实体,而 shmget 对应的文件在交换分区上的 shm 文件系统内,无法直接 cat 查看

  2. 安全性:mmap 方式对应的真实文件,如果用户有权限即可查看,甚至删除
                     shmget 方式其实也一样,好了一层皮罢了(ipcrm -m …)

  3. 一致性:mmap 方式下各进程映射文件的相同部分可以共享内存
                     shmget 时各个进程共享同一片内存区
        不建议使用交叠的方式使用 mmap

  4. 持续性:进程挂了重启不丢失内容,二者都可以做到
                     机器挂了重启,mmap 可以不丢失内容(文件内保存了OS同步过的映像),而 shmget 会丢失

  5. 易用性:mmap 的接口会简单一些

  6. 通用性:posix 的 mmap 会相对广泛一些

  7. 其他:mmap在某些内核版本下会频繁读写磁盘,需要注意一下

二、shm使用

与信号量一样,在Linux中也提供了一组函数接口用于使用共享内存,而且使用共享共存的接口还与信号量的非常相似,而且比使用信号量的接口来得简单。它们声明在头文件 sys/shm.h 中。

1、shmget()函数

该函数用来创建共享内存,它的原型为:

int shmget(key_t key, size_t size, int shmflg);

第一个参数,与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget()函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.

不相关的进程可以通过该函数的返回值访问同一共享内存,它代表程序可能要使用的某个资源,程序对所有共享内存的访问都是间接的,程序先通过调用shmget()函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget()函数的返回值),只有shmget()函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。

第二个参数,size以字节为单位指定需要共享的内存容量

第三个参数,shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。

2、shmat()函数    -- at:attach

第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:

void *shmat(int shm_id, const void *shm_addr, int shmflg);

第一个参数,shm_id是由shmget()函数返回的共享内存标识。

第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。

第三个参数,shm_flg是一组标志位,通常为0。

调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.

3、shmdt()函数    -- dt:detach

该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:

int shmdt(const void *shmaddr);

参数shmaddr是shmat()函数返回的地址指针,调用成功时返回0,失败时返回-1.

4、shmctl()函数    -- ctl:control

与信号量的semctl()函数一样,用来控制共享内存,它的原型如下:

int shmctl(int shm_id, int command, struct shmid_ds *buf);

第一个参数,shm_id是shmget()函数返回的共享内存标识符。

第二个参数,command是要采取的操作,它可以取下面的三个值 :

  • IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
  • IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
  • IPC_RMID:删除共享内存段

第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。

shmid_ds结构 至少包括以下成员:

1

2

3

4

5

6

struct shmid_ds

{

    uid_t shm_perm.uid;

    uid_t shm_perm.gid;

    mode_t shm_perm.mode;

};

三、mmap使用

1、shm_open(path, crw_mode, pri_mode)注意打开方式和权限控制到共享内存文件块,返回shmid为文件fd

2、ftruncate(shmid, size)

3、mmap(0, size, prot_rw_mode, flag, fd, offset)注意映射虚拟地址空间大小申请需大于最大预分配文件内容利于后续扩容

4、msync(ptr, size, mode)内存同步到共享内存(磁盘)

5、mumap(ptr, size)取消内存映射虚拟地址,虚拟地址不可访问

6、shm_unlink(path)删除共享文件,映射虚拟地址仍可读写,linux磁盘取消该文件节点但文件存储块仍与内存关联内容未格式化,复用冲突会出错

mmap-将文件或设备映射或取消映射到内存中
头文件
   #include <sys / mman.h>
函数
   void * mmap(void * addr ,size_t length ,int prot ,int 标志,  int fd ,off_t offset ); 
   int munmap(void * addr ,size_t length );
说明
         mmap()在虚拟地址空间中创建一个新的映射调用过程。新映射的起始地址是 在addr中指定。length参数指定映射的长度
(必须大于0)。
如果addr为NULL,则内核选择创建映射的(页面对齐)地址;这是创建新映射的最便捷的方法。如果addr不为NULL,则内核将 其用作放置映射的提示;在Linux上,内核将选择附近的页面边界(但始终大于或等于/ proc / sys / vm / mmap_min_addr 指定的值),并尝试在此处创建映射。如果那里已经存在另一个映射,则内核会选择一个可能取决于或可能不取决于提示的新地址。调用的结果将返回新映射的地址。
munmap(解除内存映射):函数说明 munmap()用来取消参数start所指的映射内存起始地址,参数length则是欲取消的内存大小。当进程结束或利用exec相关函数来执行其他程序时,映射内存会自动解除,但关闭对应的文件描述词时不会解除映射。

参数
addr:映射区的开始地址. 如果addr为NULL,则内核选择
创建映射的(页面对齐)地址;这是
创建新映射的最便捷的方法。如果addr不为NULL,则内核将
其用作放置映射的提示

length:映射区的长度

prot: 期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起

    PROT_EXEC:页内容可以被执行

    PROT_READ:页内容可以被读取

    PROT_WRITE:页可以被写入

    PROT_NONE:页不可访问



flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体:

    **MAP_FIXED** : 使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。
    **MAP_SHARED** :与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
    **MAP_PRIVATE** : 建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
    **MAP_DENYWRITE** :这个标志被忽略。
    **MAP_EXECUTABLE** 同上
    **MAP_NORESERVE** :不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。
    **MAP_LOCKED **: 锁定映射区的页面,从而防止页面被交换出内存。
    **MAP_GROWSDOWN** : 用于堆栈,告诉内核VM系统,映射区可以向下扩展。
    **MAP_ANONYMOUS** : 匿名映射,映射区不与任何文件关联。
    **MAP_ANON** : MAP_ANONYMOUS的别称,不再被使用。
    **MAP_FILE** : 兼容标志,被忽略。
    **MAP_32BIT** : 将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。
    **MAP_POPULATE** :为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。
    **MAP_NONBLOCK** :仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。


fd :有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1

offset :被映射对象内容的起点, offset必须是 通过sysconf(_SC_PAGE_SIZE)得到的页面的整数倍.

返回值
    **成功时**,mmap()返回一个指向映射区域的指针。**错误时**,该值MAP_FAILED(即,(void*)-1)被返回,error被
设置为显示错误的原因。
​ 如果成功,则munmap()返回0。如果失败,则返回-1,并且将errno 设置为指示错误的原因(可能为EINVAL)。

例子

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

#define handle_error(msg) {\
    do {\
        perror(msg); \
        exit(EXIT_FAILURE);\
    } while (0);\
}
//mmap parh offset length
int main(int argc, char *argv[]) {
    char *addr;
    int fd;
    struct stat sb;
    off_t offset, pa_offset;
    size_t length;
    ssize_t s;

    if (argc < 3 || argc > 4) {
        fprintf(stderr, "%s file offset [length]\n", argv[0]);
        exit(EXIT_FAILURE);
        //exit(1);
    }

    fd = open(argv[1], O_RDONLY);//int open(const char *pathname, int flags);打开文件argv[1]并只读;
    if (fd == -1)
        handle_error("open()");//成功返回文件描述符, 失败返回-1;

    if (fstat(fd, &sb) == -1)   //int fstat(int fd, struct stat a*stabuf)获取文件状态,成功返回0,失败返回-1,设置errno        /* To obtain file size */获取文件大小
        handle_error("fstat()");

    offset = atoi(argv[2]);//字符串转整数 
    pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);//sysconf在运行时获取配置信息,获取页面大小,pa_offset是获取需要映射到虚拟空间的大小, 页对齐.
    /* offset for mmap() must be page aligned */

    if (offset >= sb.st_size) {//当截取的字节的起始位置大于读取文件时报错
        fprintf(stderr, "offset is past end of file\n");
        exit(EXIT_FAILURE);
    }

    if (argc == 4) {
        length = atoi(argv[3]);//打印的字节长度
        if (offset + length > sb.st_size) {//打印范围超过文件的大小时
            length = sb.st_size - offset;
        }
        /* Can't display bytes past end of file */
    } else {/* No length arg ==> display to end of file */
        length = sb.st_size - offset;
    }

    //起始地址为空,映射空间的长度为length + offset - pa_offset,内存保护标志是页面可读取,映射对象类型flags
    //是创建一个私有的写时复制映射
    addr = mmap(NULL, length + offset - pa_offset, PROT_READ, MAP_PRIVATE, fd, pa_offset);
    if (addr == MAP_FAILED)//成功时,mmap()返回一个指向映射区域的指针。错误时,返回MAP_FAILED
        handle_error("mmap");

    s = write(STDOUT_FILENO, addr + offset - pa_offset, length);
    if (s != length) {
        if (s == -1)
            handle_error("write");

        fprintf(stderr, "partial write");
        exit(EXIT_FAILURE);
    }

    munmap(addr, length + offset - pa_offset);
    close(fd);

    exit(EXIT_SUCCESS);
}

四、多进程共享内存读写冲突问题

方法一:共享内存块映射进程锁实现

#include <pthread.h>

pthread_mutex_t *mutex;
pthread_mutexattr_t mutexattr;

//创建、打开共享内存1
int shm_id1 = shm_open(shm_path,O_RDWR|O_CREAT,0744);
//调整内存空间大小
ftruncate(shm_id1,1024);
//映射共享内存到互斥量
void *shmaddr = mmap(NULL,1024,PROT_READ|PROT_WRITE,MAP_SHARED,shm_id1,0);
mutex = (pthread_mutex_t *)shmaddr;

//互斥量属性初始化
pthread_mutexattr_init(&mutexattr);
pthread_mutexattr_setpshared(&mutexattr, PTHREAD_PROCESS_SHARED);
pthread_mutexattr_setrobust(&mutexattr, PTHREAD_MUTEX_ROBUST);
//互斥量初始化
pthread_mutex_init(mutex,&mutexattr)

while(1){
    pthread_mutex_lock(mutex);
    //偏移锁内存到数据块位置
	memcpy(((pthread_mutex_t *)shmaddr)+1,"wtf", 3);	
	pthread_mutex_unlock(mutex);
}

//互斥量属性销毁
pthread_mutexattr_destroy(&mutexattr);
//互斥量销毁
pthread_mutex_destroy(mutex);
//解除虚拟内存映射
munmap(shmaddr,1024);
//释放共享内存
shm_unlink(shm_path);



共享内存块的互斥锁,进程间和进程内使用锁的线程互斥,不影响进程间其他未使用锁判断的线程

共享内存的pthread_mutexattr_t和pthread_mutex_t结构可在单独进程创建,另外的进程销毁释放

方法二:实现共享内存原子操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值