Linux系统的共享内存的使用

在linux系统中,每个进程都有独立的虚拟内存空间,也就是说不同的进程访问同一段虚拟内存地址所得到的数据是不一样的,这是因为不同进程相同的虚拟内存地址会映射到不同的物理内存地址上。
但有时候为了让不同进程之间进行通信,需要让不同进程共享相同的物理内存,linux通过 共享内存 来实现这个功能。下面先来介绍一下linux系统的共享内存的使用。
共享内存使用 1. 获取共享内存 要使用共享内存,首先需要使用 shmget() 函数获取共享内存,shmget() 函数的原型如下:
int shmget(key_t key, size_t size, int shmflg); 参数 key 一般由 ftok() 函数生成,用于标识系统的唯一ipc资源。 参数 size 指定创建的共享内存大小。 参数 shmflg 指定 shmget() 函数的动作,比如传入 ipc_creat 表示要创建新的共享内存。 函数调用成功时返回一个新建或已经存在的的共享内存标识符,取决于shmflg的参数。失败返回-1,并设置错误码。
2. 关联共享内存 shmget() 函数返回的是一个标识符,而不是可用的内存地址,所以还需要调用 shmat() 函数把共享内存关联到某个虚拟内存地址上。shmat() 函数的原型如下:
void *shmat(int shmid, const void *shmaddr, int shmflg); 参数 shmid 是 shmget() 函数返回的标识符。 参数 shmaddr 是要关联的虚拟内存地址,如果传入0,表示由系统自动选择合适的虚拟内存地址。 参数 shmflg 若指定了 shm_rdonly 位,则以只读方式连接此段,否则以读写方式连接此段。 函数调用成功返回一个可用的指针(虚拟内存地址),出错返回-1。
3. 取消关联共享内存 当一个进程不需要共享内存的时候,就需要取消共享内存与虚拟内存地址的关联。取消关联共享内存通过 shmdt() 函数实现,原型如下:
int shmdt(const void *shmaddr); 参数 shmaddr 是要取消关联的虚拟内存地址,也就是 shmat() 函数返回的值。 函数调用成功返回0,出错返回-1。
共享内存使用例子 下面通过一个例子来介绍一下共享内存的使用方法。在这个例子中,有两个进程,分别为 进程a 和 进程b,进程a 创建一块共享内存,然后写入数据,进程b 获取这块共享内存并且读取其内容。
进程a #include #include #include #include #include #define shm_path /tmp/shm#define shm_size 128int main(int argc, char *argv[]){    int shmid;    char *addr;    key_t key = ftok(shm_path, 0x6666);        shmid = shmget(key, shm_size, ipc_creat|ipc_excl|0666);    if (shmid < 0) {        printf(failed to create share memory);        return -1;    }        addr = shmat(shmid, null, 0);    if (addr <= 0) {        printf(failed to map share memory);        return -1;    }        sprintf(addr, %s, hello world);        return 0;} 进程b #include #include #include #include #include #include #define shm_path /tmp/shm#define shm_size 128int main(int argc, char *argv[]){    int shmid;    char *addr;    key_t key = ftok(shm_path, 0x6666);        char buf[128];        shmid = shmget(key, shm_size, ipc_creat);    if (shmid < 0) {        printf(failed to get share memory);        return -1;    }        addr = shmat(shmid, null, 0);    if (addr  shmmax */  struct vm_area_struct *attaches; /* descriptors for attaches */};static struct shmid_kernel *shm_segs[shmmni]; // shmmni等于128 从注释可以知道 struct shmid_kernel 结构体各个字段的作用,比如 shm_npages 字段表示共享内存使用了多少个内存页。而 shm_pages 字段指向了共享内存映射的虚拟内存页表项数组等。
另外 struct shmid_ds 结构体用于管理共享内存的信息,而 shm_segs数组 用于管理系统中所有的共享内存。
shmget() 函数实现 通过前面的例子可知,要使用共享内存,首先需要调用 shmget() 函数来创建或者获取一块共享内存。shmget() 函数的实现如下:
asmlinkage long sys_shmget (key_t key, int size, int shmflg){ struct shmid_kernel *shp; int err, id = 0; down(¤t->mm->mmap_sem); spin_lock(&shm_lock); if (size  shmmax) {  err = -einval; } else if (key == ipc_private) {  err = newseg(key, shmflg, size); } else if ((id = findkey (key)) == -1) {  if (!(shmflg & ipc_creat))   err = -enoent;  else   err = newseg(key, shmflg, size); } else if ((shmflg & ipc_creat) && (shmflg & ipc_excl)) {  err = -eexist; } else {  shp = shm_segs[id];  if (shp->u.shm_perm.mode & shm_dest)   err = -eidrm;  else if (size > shp->u.shm_segsz)   err = -einval;  else if (ipcperms (&shp->u.shm_perm, shmflg))   err = -eacces;  else   err = (int) shp->u.shm_perm.seq * shmmni + id; } spin_unlock(&shm_lock); up(¤t->mm->mmap_sem); return err;} shmget() 函数的实现比较简单,首先调用 findkey() 函数查找值为key的共享内存是否已经被创建,findkey() 函数返回共享内存在 shm_segs数组 的索引。如果找到,那么直接返回共享内存的标识符即可。否则就调用 newseg() 函数创建新的共享内存。newseg() 函数的实现也比较简单,就是创建一个新的 struct shmid_kernel 结构体,然后设置其各个字段的值,并且保存到 shm_segs数组 中。
shmat() 函数实现 shmat() 函数用于将共享内存映射到本地虚拟内存地址,由于 shmat() 函数的实现比较复杂,所以我们分段来分析这个函数:
asmlinkage long sys_shmat (int shmid, char *shmaddr, int shmflg, ulong *raddr){ struct shmid_kernel *shp; struct vm_area_struct *shmd; int err = -einval; unsigned int id; unsigned long addr; unsigned long len; down(¤t->mm->mmap_sem); spin_lock(&shm_lock); if (shmid u.shm_segsz))) // 获取一个空闲的虚拟内存空间   goto out;  if(addr & (shmlba - 1)) {   addr = (addr + (shmlba - 1)) & ~(shmlba - 1);   goto again;  } } else if (addr & (shmlba-1)) {  if (shmflg & shm_rnd)   addr &= ~(shmlba-1);       /* round down */  else   goto out; } 上面的代码主要找到一个可用的虚拟内存地址,如果在调用 shmat() 函数时没有指定了虚拟内存地址,那么就通过 get_unmapped_area() 函数来获取一个可用的虚拟内存地址。
 spin_unlock(&shm_lock); err = -enomem; shmd = kmem_cache_alloc(vm_area_cachep, slab_kernel); spin_lock(&shm_lock); if (!shmd)  goto out; if ((shp != shm_segs[id]) || (shp->u.shm_perm.seq != (unsigned int) shmid / shmmni)) {  kmem_cache_free(vm_area_cachep, shmd);  err = -eidrm;  goto out; } 上面的代码主要通过调用 kmem_cache_alloc() 函数创建一个 vm_area_struct 结构,在内存管理一章知道,vm_area_struct 结构用于管理进程的虚拟内存空间。
 shmd->vm_private_data = shm_segs + id; shmd->vm_start = addr; shmd->vm_end = addr + shp->shm_npages * page_size; shmd->vm_mm = current->mm; shmd->vm_page_prot = (shmflg & shm_rdonly) ? page_readonly : page_shared; shmd->vm_flags = vm_shm | vm_mayshare | vm_shared    | vm_mayread | vm_mayexec | vm_read | vm_exec    | ((shmflg & shm_rdonly) ? 0 : vm_maywrite | vm_write); shmd->vm_file = null; shmd->vm_offset = 0; shmd->vm_ops = &shm_vm_ops; shp->u.shm_nattch++;     /* prevent destruction */ spin_unlock(&shm_lock); err = shm_map(shmd); spin_lock(&shm_lock); if (err)  goto failed_shm_map; insert_attach(shp,shmd);  /* insert shmd into shp->attaches */ shp->u.shm_lpid = current->pid; shp->u.shm_atime = current_time; *raddr = addr; err = 0;out: spin_unlock(&shm_lock); up(¤t->mm->mmap_sem); return err; ...} 上面的代码主要是设置刚创建的 vm_area_struct 结构的各个字段,比较重要的是设置其 vm_ops 字段为 shm_vm_ops,shm_vm_ops 定义如下:
static struct vm_operations_struct shm_vm_ops = { shm_open,  /* open - callback for a new vm-area open */ shm_close,  /* close - callback for when the vm-area is released */ null,   /* no need to sync pages at unmap */ null,   /* protect */ null,   /* sync */ null,   /* advise */ shm_nopage,  /* nopage */ null,   /* wppage */ shm_swapout  /* swapout */}; shm_vm_ops 的 nopage 回调为 shm_nopage() 函数,也就是说,当发生页缺失异常时将会调用此函数来恢复内存的映射。
从上面的代码可看出,shmat() 函数只是申请了进程的虚拟内存空间,而共享内存的物理空间并没有申请,那么在什么时候申请物理内存呢?答案就是当进程发生缺页异常的时候会调用 shm_nopage() 函数来恢复进程的虚拟内存地址到物理内存地址的映射。
shm_nopage() 函数实现 shm_nopage() 函数是当发生内存缺页异常时被调用的,代码如下:
static struct page * shm_nopage(struct vm_area_struct * shmd, unsigned long address, int no_share){ pte_t pte; struct shmid_kernel *shp; unsigned int idx; struct page * page; shp = *(struct shmid_kernel **) shmd->vm_private_data; idx = (address - shmd->vm_start + shmd->vm_offset) >> page_shift; spin_lock(&shm_lock);again: pte = shp->shm_pages[idx]; // 共享内存的页表项 if (!pte_present(pte)) {   // 如果内存页不存在  if (pte_none(pte)) {   spin_unlock(&shm_lock);   page = get_free_highpage(gfp_highuser); // 申请一个新的物理内存页   if (!page)    goto oom;   clear_highpage(page);   spin_lock(&shm_lock);   if (pte_val(pte) != pte_val(shp->shm_pages[idx]))    goto changed;  } else {   ...  }  shm_rss++;  pte = pte_mkdirty(mk_pte(page, page_shared));   // 创建页表项  shp->shm_pages[idx] = pte;                      // 保存共享内存的页表项 } else  --current->maj_flt;  /* was incremented in do_no_page */done: get_page(pte_page(pte)); spin_unlock(&shm_lock); current->min_flt++; return pte_page(pte); ...} shm_nopage() 函数的主要功能是当发生内存缺页时,申请新的物理内存页,并映射到共享内存中。由于使用共享内存时会映射到相同的物理内存页上,从而不同进程可以共用此块内存。


Orange Pi 5与前代产品相比有哪些升级?
三星针对TWS设备推出多合一电源管理IC,单片上集成多达10个独立组件
细数电子可靠性常见的十大误区
2021年室内定位技术就是拐点期 Wifi/蓝牙/UWB谁主沉浮
物联网对于企业无人机有什么驱动效果
Linux系统的共享内存的使用
变频调速控制技术应用于福建恒源自来水厂的经济型系统调度方案
iPhone 14 Max概念图曝光
高压差分示波器探头有什么作用呢?
亲爱的开发者,您收到一个启动智能世界的魔方
BAT将要改变汽车行业?
leopold静音红轴键盘体验 静音效果表现真的很出色
什么是皮卡/多点电喷
黎巴嫩银行正在研究推出国家支持的加密货币
胡润:明年首富是谁?马云的概率比王健林大
奥迪Q2的深度学习概念探索了自学智能停车技术的可能性
串行数模转换实验
阿根廷2.95亿美元锂盐扩建项目获批准 Olaroz总产能每年将达到约42500吨
如何去实现柔性电路板自动上料
苹果A4芯片采用Cortex A8核心