#include <sys/ipc.h>
#include <sys/shm.h>
#if 0
如何防止共享内存访问冲突:
如果是mmap一个文件的方式,就要注意是否原来的数据被sync了再写新的数据。跟读DMA要先回写cache一样。
=====
新建或绑定到一块共享内存。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
key: 用来标识一块共享内存区域。即使用相同的key可访问同一块共享内存。这个key是系统范围的。
size: 共享内存的大小,这个大小是PAGE_SIZE对齐的,即如果你传入3K,该函数也会创建一个4K(页大小)的shm。
shmflg: 可指定一些标志位。
IPC_CREAT 新建一个共享内存块。如果不带这个标记,则shmget()会去寻找key是否已被绑定现有块,以及用户是否有权限访问。
IPC_EXCL 用来与IPC_CREAT配合,来确定一个块是否已经存在。
(mode_flags) shmflg的低9位,表示owner/group/others的的rwx访问权限,目前x(执行)权限没有使用。
SHM_HUGETLB 使用巨页来分配共享内存块的空间(用户需要有CAP_IPC_LOCK权限才能设置该标记)。
SHM_NORESERVE 是否为该共享内存块预留交换分区空间。如果设置的话,系统对内存分配变得严格,可能在物理内存不足时写shm而发生OOM。
如果是新建共享内存,则shmflg需要携带IPC_CREAT标记,如果此时key对应的共享内存块已经存在,则该函数返回失败。
如果shmflg同时设置了IPC_CREAT和IPC_EXCL,并且此时key对应的共享内存块已经存在,则该函数返回错误,errno置为EEXIST。(类似于open()的O_CREAT | O_EXCL标记。)
如果shmflg没有设置IPC_CREAT又想新建,则失败,errno=ENOENT。
如果key的值为IPC_PRIVATE,则shmget不会去理会shmflg除低9bit之外的其他标记,总会新建一个共享内存且创建成功。(如果key已存在呢?应该不会新建吧,不然有毛用啊?)
新建共享内存块时,如果size<SHMMIN或size>SHMMAX,则返回错误,errno=EINVAL。其中SHMMAX可在/proc/sys/kernel/shmmax中查看和修改。
如果key的块已存在,但size大于该内存块的大小,则返回错误,errno=EINVAL。size可以小于key块的大小,说明该进程只想共享前size大小的部分。
新创建的共享内存块的内容会全部初始化为0。
该函数返回一个标记这块共享内存块的shmid,供进程后续操作这块内存用。如果出错则返回-1。
系统中所有共享内存的大小上限为SHMALL,系统中共享内存块的个数上限为SHMMNI。如果此次调用导致超过这两个限制,都会出错,errno=ENOSPC。
其中SHMALL的值可在/proc/sys/kernel/shmall中查看和修改,单位是page。SHMMNI的值可在/proc/sys/kernel/shmmni中查看和修改。每个进程的shmid个数没有特别限制。
----
将进程关联到共享内存。
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
将shmid(shmget()的返回值)对应的共享内存块关联(attach)到本进程的虚拟地址空间,关联的地址由参数shmaddr指定:
1.如果shmaddr为NULL,则由系统来选择一个未使用的地址。
2.如果shmaddr不为NULL,且shmflg参数设置了SHM_RND,则系统会将shmaddr向前取到与SHMLBA(通常是一页)对齐的地址。如果没有SHM_RND标记,则需要用户自己给shmaddr指定一个页对齐的地址。
3.如果shmflg设置了SHM_REMAP,则shmaddr必须不为NULL,SHM_REMAP意思是在关联该共享内存块的同时,将shmaddr开始size大小的区域中已有的mapping都覆盖掉(如果这段区域的某一段已被映射过,通常会返回EINVAL错误)。
建议shmaddr赋值为NULL,让系统自己确定映射的地址,在兼容性上更好些,相应的,在attach之后用于访问共享块的指针需是相对shmaddr(shmat()的返回值)的相对地址,而不要是绝对地址。
如果shmflg带了SHM_RDONLY标记,则表示以只读的方式attach到共享块(当然该进程要有对块的读权限),如果没带SHM_RDONLY标记,则表示以读写的方式attach到共享块(当然该进程要有对块的读写权限)。目前没有只写的关联方式。
该函数返回该共享块实际被关联到的地址空间的起始地址,供进程后续访问该块内存用。如果失败,返回(void *) -1,
另外需要说明的几点:
-关联一个块并不会增加进程堆的大小(共享内存是通过匿名文件来创建的),在进程退出时,会自动解除与该块的关联关系。
-在一个进程中,你可以通过shmat()多次关联同一个共享块,例如一处关联成只读,另一处关联为读写,这样也会相应的映射两块不同的虚拟地址空间。
-在进程调用fork()后,子进程会继承父进程已经attach的共享内存块。在进程调用execve()或_exit()后,该进程所有attach的共享内存块都会被detach掉。
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
解除该进程与shmaddr指定的共享块的关联关系,shmaddr即为shmat()的返回值。
解绑成功返回0,失败返回-1。如果指定的shmaddr并没有关联到一个共享块,或者shmaddr没有页对齐,都会返回错误,errno=EINVAL。
注意,每个共享内存块都会在标准库中维护一个shmid_ds结构,该结构也用来与内核态(shm_ids[]结构)交互,其中的shm_nattch成员是一个引用计数器,被attach就+1,被detach就-1,当shm_nattch被减到0,该共享内存块就被销毁了。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
控制共享内存块shmid的属性。要修改的属性和属性内容分别由cmd和buf指定。
buf是指向struct shmid_ds结构的指针:
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and
SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
cmd可取的值:
IPC_STAT 获取共享内存块的状态,是一个struct shmid_ds结构,保存在参数buf中。
IPC_SET 设置共享内存块的某些属性,包括shm_perm.uid, shm_perm.gid和shm_perm.mode的低9bit权限位,同时会更新shm_ctime。
IPC_RMID 标记该共享内存块将被销毁(实际销毁仍在最后一个进程detach之后)。
IPC_INFO 获取系统中一些共享内存的限制。结果由buf返回(struct shminfo结构),因此这时要将buf强制类型转换一下。
struct shminfo {
unsigned long shmmax; /* Maximum segment size */
unsigned long shmmin; /* Minimum segment size;
always 1 */
unsigned long shmmni; /* Maximum number of segments */
unsigned long shmseg; /* Maximum number of segments
that a process can attach;
unused within kernel */
unsigned long shmall; /* Maximum number of pages of
shared memory, system-wide */
};
SHM_INFO 获取系统中共享内存对物理内存的消耗情况,结果是struct shm_info结构。
struct shm_info {
int used_ids; /* # of currently existing
segments */
unsigned long shm_tot; /* Total number of shared
memory pages */
unsigned long shm_rss; /* # of resident shared
memory pages */
unsigned long shm_swp; /* # of swapped shared
memory pages */
unsigned long swap_attempts;
/* Unused since Linux 2.4 */
unsigned long swap_successes;
/* Unused since Linux 2.4 */
};
SHM_STAT 获取共享内存状态,是一个struct shmid_ds结构,保存在参数buf中。和IPC_STAT类似,但此时传入的shmid并不是共享块的id,而是内核内部管理所有共享块的数组的index。
SHM_LOCK 禁止共享块的内存换出到swap分区。
SHM_UNLOCK 允许共享块的内存换出到swap分区。
使用shmctl()进行上述cmd操作的返回值如下:
1. IPC_INFO和SHM_INFO,如果成功,返回shmid对应共享块在内核数组中目前的最高index。这样,就可以与SHM_STAT配合,在当前进程中获得系统中所有共享块的状态。
2. SHM_STAT,如果成功,返回index对应的共享块的shmid。
3. 其他cmd,成功返回0。所有cmd,如果失败都会返回-1并设置errno。
SYNOPSIS
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
DESCRIPTION
The ftok() function uses the identity of the file named by the given pathname (which must refer
to an existing, accessible file) and the least significant 8 bits of proj_id (which must be
nonzero) to generate a key_t type System V IPC key, suitable for use with msgget(2), semget(2),
or shmget(2).
The resulting value is the same for all pathnames that name the same file, when the same value
of proj_id is used. The value returned should be different when the (simultaneously existing)
files or the project IDs differ.
RETURN VALUE
On success, the generated key_t value is returned. On failure -1 is returned, with errno indi‐
cating the error as for the stat(2) system call.
Of course no guarantee can be given that the resulting key_t is unique. Typically, a best
effort attempt combines the given proj_id byte, the lower 16 bits of the inode number, and the
lower 8 bits of the device number into a 32-bit result. Collisions may easily happen, for
example between files on /dev/hda1 and files on /dev/sda1.
==================
mmap实现共享内存:
mmap() : 将文件或设备映射到用户的虚拟地址空间。
内核中相应的实现了系统调用:
sys_mmap() : 以字节为单位。
sys_mmap2() : 以页为单位。
取消映射:munmap()。
相关的5个系统调用:
#include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags,
int fd, off_t offset); /* arch/mips/kernel/syscall.c: mips_mmap */
int munmap(void *start, size_t length); /* mm/mmap.c */
void * mremap(void *old_address, size_t old_size , size_t new_size, int flags); /* mm/mremap.c */
void * mmap2(void *start, size_t length, int prot,
int flags, int fd, off_t pgoffset); /* arch/mips/kernel/syscall.c: mips_mmap2 */
int remap_file_pages(void *start, size_t size, int prot, ssize_t pgoff, int flags); /* mm/fremap.c */
既然说到脏页,就再多说一句,mmap()映射时有Shared_Dirty和Private_Dirty的概念,大家注意把这里的dirty和脏页区分开,mmap可以映射匿名页和文件,
在映射匿名页时,Shared_Dirty和Private_Dirty是指还没有被msync()或unmap()同步的,这时跟系统的脏页没有任何关系;
而mmap在映射文件时,除了Shared_Dirty和Private_Dirty也会产生脏页,因为产生了写文件。
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
int munmap(void *addr, size_t length);
#include <sys/mman.h>
int msync(void *addr, size_t length, int flags);
将mmap映射的addr到addr+length的内容写回到磁盘。
注意,addr必须是PAGESIZE的整数倍。
flags参数可以是MS_ASYNC, MS_SYNC, MS_INVALIDATE,但MS_ASYNC和MS_SYNC不能同时置位。MS_ASYNC表示异步,函数立即返回;而MS_SYNC表示同步,函数阻塞直到回写完成。MS_INVALIDATE则将这个文件的其他映射都无效,保证这次回写的内容不被其他映射回写覆盖掉。
回写成功返回0,如果指定内存区域并没有被映射过,返回ENOMEM,如果MS_INVALIDATE置位但这段内存已经被锁住(如mlock()等)了,返回EBUSY。
#include <linux/mman.h>
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sys/mman.h>
void *mremap(void *old_address, size_t old_size,
size_t new_size, int flags, ... /* void *new_address */);
将已经mmap的内存(old_address和old_size指定)重新进行映射,大小由new_size指定,实际上就是对原映射内存进行扩大或缩小。如果有第五个参数new_address则也同时指定新映射的起始地址。
这和realloc()的含义基本相同,实际上mremap()的一个用途就是实现一个更为高效的realloc(),因为mremap只是修改虚拟页地址到物理地址的映射关系即更新页表。
注意old_address必须是页对齐的。
flags可以是0,也可以置下面两个bit位;
MREMAP_MAYMOVE: 由于默认情况下如果old_address起始地址后没有足够的new_size空闲虚拟地址空间用来映射,mremap就会失败。如果加上MREMAP_MAYMOVE标记,mremap在这种情况下就会找一个新的起始地址来映射new_size大小的内存。
MREMAP_FIXED: 设置这个标记,mremap就会接受第五个参数new_address,强制指定新映射的(页对齐的)起始地址。如果设置了这个标记,那MREMAP_MAYMOVE也必须同时被设置。同时,new_address和new_size这段空间原来已存在的映射都会被unmap掉。
哎?MREMAP_FIXED的时候指定的地址如果后面空间不够,是不是还是会帮你重新选地址??
如果原来的old_address映射区有内存锁(如mlock()),那这个锁也会被集成到新的size或新的location映射区。
函数成功,返回指向新的虚拟地址空间的指针,错误返回MAP_FAILED,即(void *)-1。
注意,new_size不能是0,另外,old_address,old_size和new_address,new_size不能重叠(为啥??)。
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sys/mman.h>
int remap_file_pages(void *addr, size_t size, int prot,
ssize_t pgoff, int flags);
这个就不看也不翻译了。在csdn里收藏了一篇文章讲的很明白。
===========
我在虚拟机和Hi3520d板子上都没有看到有mmap2(),实际上内核里也没找到(只在powerpc里找到有)。因此不用看了。
#include <sys/mman.h>
void *mmap2(void *addr, size_t length, int prot,
int flags, int fd, off_t pgoffset);
DESCRIPTION
The mmap2() system call operates in exactly the same way as mmap(2),
except that the final argument specifies the offset into the file in
4096-byte units (instead of bytes, as is done by mmap(2)). This enables
applications that use a 32-bit off_t to map large files (up to 2^44
bytes).
RETURN VALUE
On success, mmap2() returns a pointer to the mapped area. On error -1 is
returned and errno is set appropriately.
ERRORS
EFAULT Problem with getting the data from userspace.
EINVAL (Various platforms where the page size is not 4096 bytes.) offset
* 4096 is not a multiple of the system page size.
mmap2() can return any of the same errors as mmap(2).
VERSIONS
mmap2() is available since Linux 2.3.31.
CONFORMING TO
This system call is Linux-specific.
NOTES
Nowadays, the glibc mmap() wrapper function invokes this system call
rather than the mmap(2) system call.
On ia64, the unit for offset is actually the system page size, rather
than 4096 bytes.
--共享内存同步要注意什么--
使用共享内存的时候,除了设计上减少同步,还有别的要注意么?
1,创建任何东西先带着CREAT | EXCL去创建,失败了则直接打开,这是原子性必备的。
2,共享内存初始化之前如何同步?即如何保证创建chm的进程对共享内存初始化好了,其他进程才能够shmget? 设置mode的x位(用shmctl设置IPC_SET,因为x位未使用)后开始初始化共享内存,结束后取消x位,任何进程打开共享内存后stat轮询检查x位(用shmctl获取IPC_STAT)是否复位,复位后才可以开始操作。
3,进程共享的mutex, cond,你应该都会用,不会用看书或者man pthread.h找接口。
4,共享内存可以做成chunk list内存块链表,一般用于在共享内存中建立树型数据结构,或者建死的多级hash表,或者循环队列,都是可以做的。
================
进程间通信的信号量接口: semget, semctl, semop
#endif
共享内存接口记录
最新推荐文章于 2025-05-31 16:02:29 发布