进程间通信之共享内存

1.共享内存

1.1 概念和原理

        共享内存是进程间通信的方式之一。共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容,其他进程都会察觉到这个更改。

        基本原理:共享内存允许多个进程将同一块物理内存映射到各自的虚拟地址空间中,从而实现数据的直接读写和共享。各进程可以直接访问这块内存区域,无需内核反复拷贝数据,通信速度快。

        举个例子:就像多个人(进程)在同一间会议室(物理内存),每个人都能看到和改动会议室中的白板(共享内存)。

                      

1.2 共享内存的特点

  1. 共享内存是进程间共享数据的一种最快方式。一个进程向共享的内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
  2. 使用共享内存要注意的是多个进程之间对一个给定存储区访问的互斥。若一个进程正在向共享内存区数据,则在它做完这一步操作之前,别的进程不应当去读、写这些数据。

1.3 核心机制

共享内存的使用包括 :

  • 调用 shmget()创建共享内存
  • 调用 shmat() 映射共享内存至进程虚拟空间
  • 调用 shmdt()接触映射关系
  1. 操作系统内核分配内存区
    • 进程通过系统调用(如 shmget, mmap等)请求一块共享内存区域
    • 内核分配一块实际的物理内存,并为其分配一个唯一的标识符(如 shmid)
  2. 进程映射到自己的地址空间
    • 进程通过 shmat、mmap等系统调用将这块物理内存映射到自己的虚拟地址空间
    • 同一块物理内存可以被多个进程映射,各自获得一个指针,指向"同样的数据"
  3. 读写共享
    • 进程直接读取/写入这块共享内存,所有映射到此内存的进程都能“看到“数据的变化
    • 无需内核缓冲区的拷贝,效率高

1.4 创建共享内存

共享内存的创建通过shmget()实现。

#include <sys/ipc.h> 
#include <sys/shm.h> 
int shmget(key_t key, size_t size, int shmflg);

参数:

  • key: 共享内存的键值,用于标识共享内存段。通常使用 ftok 函数生成键值。
  • size: 共享内存段的大小,以字节为单位
  • shmflg: 共享内存的标志位,用于指定创建共享内存的权限和行为

返回值:

  • 成功时,返回共享内存的标识符(即共享内内存的ID)
  • 失败时,返回-1,并设置相应的错误码
int
shmget (key_t key, size_t size, int shmflg)
{
#ifdef __ASSUME_DIRECT_SYSVIPC_SYSCALLS
  return INLINE_SYSCALL_CALL (shmget, key, size, shmflg, NULL);
#else
  return INLINE_SYSCALL_CALL (ipc, IPCOP_shmget, key, size, shmflg, NULL);
#endif
}

        其中,INLINE_SYSCALL_CALL是封装系统调用的宏。展开看,其实就是最终会调用Linux系统调用接口(实际会转为 syscall : 是对底层内核系统调用的直接封装,会触发软中断进入内核)。

       在Linux内核 ipc/shm.c 中给出了syscall函数实现:

SYSCALL_DEFINE3(shmget, key_t, key, size_t, size, int, shmflg)
{
	return ksys_shmget(key, size, shmflg);
}
long ksys_shmget(key_t key, size_t size, int shmflg)
{
	struct ipc_namespace *ns;
	static const struct ipc_ops shm_ops = {
		.getnew = newseg,
		.associate = security_shm_associate,
		.more_checks = shm_more_checks,
	};
	struct ipc_params shm_params;

	ns = current->nsproxy->ipc_ns;

	shm_params.key = key;
	shm_params.flg = shmflg;
	shm_params.u.size = size;

	return ipcget(ns, &shm_ids(ns), &shm_ops, &shm_params);
}

         该函数创建对应的 ipc_namespace 指针并指向对应的 ipc_ns, 初始化共享内存对应的操作shm_ops,并将传参key, size, shmflg封装为传参shm_params,最终调用 ipcget()。

/**
 * ipcget - Common sys_*get() code
 * @ns: namespace
 * @ids: ipc identifier set
 * @ops: operations to be called on ipc object creation, permission checks
 *       and further checks
 * @params: the parameters needed by the previous operations.
 *
 * Common routine called by sys_msgget(), sys_semget() and sys_shmget().
 */
int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids,
			const struct ipc_ops *ops, struct ipc_params *params)
{
	if (params->key == IPC_PRIVATE)
		return ipcget_new(ns, ids, ops, params);
	else
		return ipcget_public(ns, ids, ops, params);
}

参数:

  • ns: 命名空间指针,支持容器/多实例的IPC资源隔离
  • ids: 当前IPC 对象(如共享内存/信号量消息队列)集合的数据结构
  • ops: 用于对象创建、权限检查等操作函数集合
  • params: 本次操作的参数 (如:key、flags、size等)

ipc_get根据传入参数params->key进行不同的操作

/**
 * ipcget_new -	create a new ipc object
 * @ns: ipc namespace
 * @ids: ipc identifier set
 * @ops: the actual creation routine to call
 * @params: its parameters
 *
 * This routine is called by sys_msgget, sys_semget() and sys_shmget()
 * when the key is IPC_PRIVATE.
 */
static int ipcget_new(struct ipc_namespace *ns, struct ipc_ids *ids,
		const struct ipc_ops *ops, struct ipc_params *params)
{
	int err;

	down_write(&ids->rwsem);
	err = ops->getnew(ns, params);
	up_write(&ids->rwsem);
	return err;
}


/**
 * ipcget_public - get an ipc object or create a new one
 * @ns: ipc namespace
 * @ids: ipc identifier set
 * @ops: the actual creation routine to call
 * @params: its parameters
 *
 * This routine is called by sys_msgget, sys_semget() and sys_shmget()
 * when the key is not IPC_PRIVATE.
 * It adds a new entry if the key is not found and does some permission
 * / security checkings if the key is found.
 *
 * On success, the ipc id is returned.
 */
static int ipcget_public(struct ipc_namespace *ns, struct ipc_ids *ids,
		const struct ipc_ops *ops, struct ipc_params *params)
{
	struct kern_ipc_perm *ipcp;
	int flg = params->flg;
	int err;

	/*
	 * Take the lock as a writer since we are potentially going to add
	 * a new entry + read locks are not "upgradable"
	 */
	down_write(&ids->rwsem);
	ipcp = ipc_findkey(ids, params->key);
	if (ipcp == NULL) {
		/* key not used */
		if (!(flg & IPC_CREAT))
			err = -ENOENT;
		else
			err = ops->getnew(ns, params);
	} else {
		/* ipc object has been locked by ipc_findkey() */

		if (flg & IPC_CREAT && flg & IPC_EXCL)
			err = -EEXIST;
		else {
			err = 0;
			if (ops->more_checks)
				err = ops->more_checks(ipcp, params);
			if (!err)
				/*
				 * ipc_check_perms returns the IPC id on
				 * success
				 */
				err = ipc_check_perms(ns, ipcp, ops, params);
		}
		ipc_unlock(ipcp);
	}
	up_write(&ids->rwsem);

	return err;
}

       当key为 IPC_PRIVATE时调用 ipcget_new() 函数,首先通过 down_write(&ids->rwsem)为写加锁,保证IPC对象集合的并发安全(防止并发创建/修改),再 ops->getnew(ns, params)根据具体IPC对象类型创建新的对象,最后 up_write()释放写锁,返回新对象的IPC id或错误码。

        而不为 IPC_PRIVATE时调用的 ipcget_public: 同样先对写加锁,然后通过 ipc_findkey()函数按照key查找 struct kern_ipc_perm:

        (1)如果没有找到ipcp == NULL此时应检查是否存在 IPC_CREATE标志

                1)如果没有,说明用户只想查找不想新建,则 err = -ENOENT(不存在)

                2)如果存在,则通过 ops->getnew 新建对象(和 ipcget_new逻辑一致)

        (2)如果对象存在

                1)如果同时存在 IPC_CREATE和IPC_EXCL,说明”只要存在会报错“(独占创建),返回 err = -EEXIST

                2)否则:执行 ops->more_checks(进一步检查)

                                再通过 ipc_check_perms检查当前进程对该对象是否具有足够权限

所以新的创建最后都会走到注册的newseg()函数。该函数主要逻辑为

  • 通过 kvmalloc() 在直接映射区分配一个 struct shmid_kernel 结构体,该结构体用于描述共享内存。
  • 调用hugetlb_file_setup()或shmem_kernel_file_setup()关联文件。虚拟地址空间可以和物理内存关联,但是页表的申请条件中会避开已分配的映射,即物理内存是某个进程独享的。所以如何实现物理内存向多个进程的虚拟内存映射呢?这里就要靠文件来实现了:虚拟地址空间也可以映射到一个文件,文件是可以跨进程共享的。这里我们并不是映射到硬盘上存储的文件,而是映射到内存文件系统上的文件。这里定要注意区分 shmem 和 shm ,前者是一个文件系统,后者是进程通信机制。
  • 通过 ipc_addid() 将新创建的 struct shmid_kernel 结构挂到 shm_ids 里面的基数树上,返回相应的 id,并且将 struct shmid_kernel 挂到当前进程的 sysvshm 队列中。
/**
 * newseg - Create a new shared memory segment
 * @ns: namespace
 * @params: ptr to the structure that contains key, size and shmflg
 *
 * Called with shm_ids.rwsem held as a writer.
 */
static int newseg(struct ipc_namespace *ns, struct ipc_params *params)
{
    key_t key = params->key;
    int shmflg = params->flg;
    size_t size = params->u.size;
    int error;
    struct shmid_kernel *shp;
    size_t numpages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
    struct file *file;
    char name[13];
......
    shp = kvmalloc(sizeof(*shp), GFP_KERNEL);
......
    shp->shm_perm.key = key;
    shp->shm_perm.mode = (shmflg & S_IRWXUGO);
    shp->mlock_user = NULL;
    shp->shm_perm.security = NULL;
......
    if (shmflg & SHM_HUGETLB) {
......
        file = hugetlb_file_setup(name, hugesize, acctflag,
                  &shp->mlock_user, HUGETLB_SHMFS_INODE,
                (shmflg >> SHM_HUGE_SHIFT) & SHM_HUGE_MASK);
    } else {
......
        file = shmem_kernel_file_setup(name, size, acctflag);
    }
......
    shp->shm_cprid = get_pid(task_tgid(current));
    shp->shm_lprid = NULL;
    shp->shm_atim = shp->shm_dtim = 0;
    shp->shm_ctim = ktime_get_real_seconds();
    shp->shm_segsz = size;
    shp->shm_nattch = 0;
    shp->shm_file = file;
    shp->shm_creator = current;
    /* ipc_addid() locks shp upon success. */
    error = ipc_addid(&shm_ids(ns), &shp->shm_perm, ns->shm_ctlmni);
......
    list_add(&shp->shm_clist, &current->sysvshm.shm_clist);
    /*
     * shmid gets reported as "inode#" in /proc/pid/maps.
     * proc-ps tools use this. Changing this will break them.
     */
    file_inode(file)->i_ino = shp->shm_perm.id;
    ns->shm_tot += numpages;
    error = shp->shm_perm.id;
......
}

        实际上shmem_kernel_file_setup()会在shmem文件系统里面创建一个文件:__shmem_file_setup() 会创建新的 shmem 文件对应的 dentry 和 inode,并将它们两个关联起来,然后分配一个 struct file 结构来表示新的 shmem 文件,并且指向独特的 shmem_file_operations。

/**
 * shmem_kernel_file_setup - get an unlinked file living in tmpfs which must be kernel internal.  
 * @name: name for dentry (to be seen in /proc/<pid>/maps
 * @size: size to be set for the file
 * @flags: VM_NORESERVE suppresses pre-accounting of the entire object size */
struct file *shmem_kernel_file_setup(const char *name, loff_t size, unsigned long flags)
{
    return __shmem_file_setup(name, size, flags, S_PRIVATE);
}

static struct file *__shmem_file_setup(const char *name, loff_t size,
               unsigned long flags, unsigned int i_flags)
{
    struct file *res;
    struct inode *inode;
    struct path path;
    struct super_block *sb;
    struct qstr this;
......
    this.name = name;
    this.len = strlen(name);
    this.hash = 0; /* will go */
    sb = shm_mnt->mnt_sb;
    path.mnt = mntget(shm_mnt);
    path.dentry = d_alloc_pseudo(sb, &this);
    d_set_d_op(path.dentry, &anon_ops);
......
    inode = shmem_get_inode(sb, NULL, S_IFREG | S_IRWXUGO, 0, flags);
    inode->i_flags |= i_flags;
    d_instantiate(path.dentry, inode);
    inode->i_size = size;
......
    res = alloc_file(&path, FMODE_WRITE | FMODE_READ,
        &shmem_file_operations);
    return res;
}

1.5 共享内存的映射

        从上述代码详解中可知,共享内存的数据结构 struct shmid_kernel通过成员 struct file* shm_file来管理内存文件系统 shmem上的内存文件。无论这个共享内存是否被映射,shm_file都是存在的。

        对于用户来说,共享内存的映射是通过调用 shmat()完成的。

void *
shmat (int shmid, const void *shmaddr, int shmflg)
{
#ifdef __ASSUME_DIRECT_SYSVIPC_SYSCALLS
  return (void*) INLINE_SYSCALL_CALL (shmat, shmid, shmaddr, shmflg);
#else
  unsigned long resultvar;
  void *raddr;

  resultvar = INTERNAL_SYSCALL_CALL (ipc, IPCOP_shmat, shmid, shmflg,
				     &raddr, shmaddr);
  if (INTERNAL_SYSCALL_ERROR_P (resultvar))
    return (void *) INLINE_SYSCALL_ERROR_RETURN_VALUE (INTERNAL_SYSCALL_ERRNO (resultvar));

  return raddr;
#endif
}

参数:

  • shmid:共享内存段ID(由shmget()返回)
  • shmaddr:建议映射到的地址(通常为NULL,让内核自动分配)
  • shmflg:标志,如:SHM_RDONLY(只读映射)、SHM_RND(地址对齐)

—— 直接系统调用

#ifdef __ASSUME_DIRECT_SYSVIPC_SYSCALLS
  return (void*) INLINE_SYSCALL_CALL (shmat, shmid, shmaddr, shmflg);
  • 直接通过 glibc 的 INLINE_SYSCALL_CALL 宏,发起 shmat 系统调用。
  • 该宏最终转为一个 syscall 指令,进入内核态执行真正的映射(相当于 syscall(SYS_shmat, shmid, shmaddr, shmflg))。

——ipc多路系统调用

#else
  unsigned long resultvar;
  void *raddr;

  resultvar = INTERNAL_SYSCALL_CALL (ipc, IPCOP_shmat, shmid, shmflg,
				     &raddr, shmaddr);
  if (INTERNAL_SYSCALL_ERROR_P (resultvar))
    return (void *) INLINE_SYSCALL_ERROR_RETURN_VALUE (INTERNAL_SYSCALL_ERRNO (resultvar));

  return raddr;
#endif
  • 兼容老内核或特殊平台:通过 ipc 这个多路系统调用(用第一个参数区分功能)。
  • INTERNAL_SYSCALL_CALL 实际会做:
    • syscall(SYS_ipc, IPCOP_shmat, shmid, shmflg, &raddr, shmaddr)
    • 其中 IPCOP_shmat 表示这是 shmat 子功能。
  • 返回值处理:
    • 如果调用失败,返回一个错误指针((void*) -1),并设置 errno。
    • 成功则返回映射到进程空间的实际地址 raddr

        在 Linux中 /ipc/shm.c 中系统调用的实现为:

SYSCALL_DEFINE3(shmat, int, shmid, char __user *, shmaddr, int, shmflg)
{
	unsigned long ret;
	long err;

	err = do_shmat(shmid, shmaddr, shmflg, &ret, SHMLBA);
	if (err)
		return err;
	force_successful_syscall_return();
	return (long)ret;
}

        该syscall调用 do_shmat() 函数

/*
 * Fix shmaddr, allocate descriptor, map shm, add attach descriptor to lists.
 *
 * NOTE! Despite the name, this is NOT a direct system call entrypoint. The
 * "raddr" thing points to kernel space, and there has to be a wrapper around
 * this.
 */
long do_shmat(int shmid, char __user *shmaddr, int shmflg,
	      ulong *raddr, unsigned long shmlba)
{
	struct shmid_kernel *shp;
	unsigned long addr = (unsigned long)shmaddr;
	unsigned long size;
	struct file *file, *base;
	int    err;
	unsigned long flags = MAP_SHARED;
	unsigned long prot;
	int acc_mode;
	struct ipc_namespace *ns;
	struct shm_file_data *sfd;
	int f_flags;
	unsigned long populate = 0;

	err = -EINVAL;
	if (shmid < 0)
		goto out;

	if (addr) {
		if (addr & (shmlba - 1)) {
			if (shmflg & SHM_RND) {
				addr &= ~(shmlba - 1);  /* round down */

				/*
				 * Ensure that the round-down is non-nil
				 * when remapping. This can happen for
				 * cases when addr < shmlba.
				 */
				if (!addr && (shmflg & SHM_REMAP))
					goto out;
			} else
#ifndef __ARCH_FORCE_SHMLBA
				if (addr & ~PAGE_MASK)
#endif
					goto out;
		}

		flags |= MAP_FIXED;
	} else if ((shmflg & SHM_REMAP))
		goto out;

	if (shmflg & SHM_RDONLY) {
		prot = PROT_READ;
		acc_mode = S_IRUGO;
		f_flags = O_RDONLY;
	} else {
		prot = PROT_READ | PROT_WRITE;
		acc_mode = S_IRUGO | S_IWUGO;
		f_flags = O_RDWR;
	}
	if (shmflg & SHM_EXEC) {
		prot |= PROT_EXEC;
		acc_mode |= S_IXUGO;
	}

	/*
	 * We cannot rely on the fs check since SYSV IPC does have an
	 * additional creator id...
	 */
	ns = current->nsproxy->ipc_ns;
	rcu_read_lock();
	shp = shm_obtain_object_check(ns, shmid);
	if (IS_ERR(shp)) {
		err = PTR_ERR(shp);
		goto out_unlock;
	}

	err = -EACCES;
	if (ipcperms(ns, &shp->shm_perm, acc_mode))
		goto out_unlock;

	err = security_shm_shmat(&shp->shm_perm, shmaddr, shmflg);
	if (err)
		goto out_unlock;

	ipc_lock_object(&shp->shm_perm);

	/* check if shm_destroy() is tearing down shp */
	if (!ipc_valid_object(&shp->shm_perm)) {
		ipc_unlock_object(&shp->shm_perm);
		err = -EIDRM;
		goto out_unlock;
	}

	/*
	 * We need to take a reference to the real shm file to prevent the
	 * pointer from becoming stale in cases where the lifetime of the outer
	 * file extends beyond that of the shm segment.  It's not usually
	 * possible, but it can happen during remap_file_pages() emulation as
	 * that unmaps the memory, then does ->mmap() via file reference only.
	 * We'll deny the ->mmap() if the shm segment was since removed, but to
	 * detect shm ID reuse we need to compare the file pointers.
	 */
	base = get_file(shp->shm_file);
	shp->shm_nattch++;
	size = i_size_read(file_inode(base));
	ipc_unlock_object(&shp->shm_perm);
	rcu_read_unlock();

	err = -ENOMEM;
	sfd = kzalloc(sizeof(*sfd), GFP_KERNEL);
	if (!sfd) {
		fput(base);
		goto out_nattch;
	}

	file = alloc_file_clone(base, f_flags,
			  is_file_hugepages(base) ?
				&shm_file_operations_huge :
				&shm_file_operations);
	err = PTR_ERR(file);
	if (IS_ERR(file)) {
		kfree(sfd);
		fput(base);
		goto out_nattch;
	}

	sfd->id = shp->shm_perm.id;
	sfd->ns = get_ipc_ns(ns);
	sfd->file = base;
	sfd->vm_ops = NULL;
	file->private_data = sfd;

	err = security_mmap_file(file, prot, flags);
	if (err)
		goto out_fput;

	if (mmap_write_lock_killable(current->mm)) {
		err = -EINTR;
		goto out_fput;
	}

	if (addr && !(shmflg & SHM_REMAP)) {
		err = -EINVAL;
		if (addr + size < addr)
			goto invalid;

		if (find_vma_intersection(current->mm, addr, addr + size))
			goto invalid;
	}

	addr = do_mmap(file, addr, size, prot, flags, 0, 0, &populate, NULL);
	*raddr = addr;
	err = 0;
	if (IS_ERR_VALUE(addr))
		err = (long)addr;
invalid:
	mmap_write_unlock(current->mm);
	if (populate)
		mm_populate(addr, populate);

out_fput:
	fput(file);

out_nattch:
	down_write(&shm_ids(ns).rwsem);
	shp = shm_lock(ns, shmid);
	shp->shm_nattch--;

	if (shm_may_destroy(shp))
		shm_destroy(ns, shp);
	else
		shm_unlock(shp);
	up_write(&shm_ids(ns).rwsem);
	return err;

out_unlock:
	rcu_read_unlock();
out:
	return err;
}

        该函数负责将已存在的共享内存段(由 shmid 标识)映射到当前进程的虚拟地址空间,并处理各种权限、参数、内存分配和管理细节。主要逻辑为:

(1)参数与地址合法性检查

  • 检查 shmid 是否为负,若为负则非法。
  • 如果用户指定 shmaddr(希望映射到某处),需检查地址是否满足对齐要求(shmlba,系统页面/大页对齐)。
  • SHM_RND 标志:自动向下对齐到 shmlba 的整数倍。
  • MAP_FIXED 标志:用户要求必须映射到指定地址。

(2)访问权限与 flag 处理

  • SHM_RDONLY → 映射为只读,权限检查 S_IRUGO,文件 O_RDONLY。
  • 否则读写,权限 S_IRUGO | S_IWUGO,O_RDWR。
  • SHM_EXEC → 增加可执行权限。
  • 设定 mmap 的 prot、flags、acc_mode、f_flags。

(3)查找并验证共享内存对象

  • 获取当前命名空间指针。
  • shm_obtain_object_check() 获取 shmid 对应的内核对象 shmid_kernel *shp,并做有效性检查。
  • ipcperms() 检查权限。
  • security_shm_shmat() 进一步 LSM 安全检查。

(4)对象锁定和有效性/销毁检查

  • 锁定 shm_perm,防止并发操作。
  • 检查对象是否正在销毁(已被删除但还有引用)。
  • 增加引用计数(shm_nattch++)。

(5)文件结构准备

  • 为内存文件分配一个 file 结构,分配跟踪数据(struct shm_file_data)。
  • alloc_file_clone() 创建 file 结构体,用于 do_mmap。
  • 设置 file->private_data、权限等。

(6)安全检查&加锁虚拟内存空间

  • security_mmap_file() LSM 检查。
  • 对当前进程 mm 加写锁,防止并发更改。

(7)虚拟内存区域检查与 do_mmap

  • 若指定了地址且不重映射,需确保虚拟区间未被占用。
  • do_mmap() 真正执行mmap,将共享内存文件映射到进程虚拟空间(核心操作)。
  • 返回实际映射地址给用户层。

(8)后处理与资源回收

  • 解锁、减少引用计数。
  • 如果引用计数为0且已被删除,则销毁(shm_destroy)。
  • 释放所有临时结构和文件。
  • 错误处理分支,保证所有引用和锁都能正确释放。

共享内存的映射完整流程:

  1. 用户进程调用 shmat(),通过系统调用进入内核。
  2. 内核层 do_shmat() 完成参数校验、权限检查、内核对象查找与锁定。
  3. 增加引用计数,准备文件结构,安全校验。
  4. 通过 do_mmap() 将共享内存的物理页映射到进程虚拟空间。
  5. 返回虚拟地址给用户进程,进程即可直接访问共享数据。
  6. 进程用完后 shmdt() 解除映射,引用计数递减,最终安全释放物理资源。
  7. 整个流程确保资源安全、权限合规、并发可控,是高效进程间通信的基础。

                ​​​​​​​        ​​​​​​​        ​​​​​​​        

Linux下,进程间通信的一种方式是通过共享内存来实现的。共享内存允许两个或多个进程共享一定的存储区,这样它们就可以直接访问同一块内存区域,而不需要进行数据的复制。共享内存是一种高效的进程间通信方式,因为数据直接写入内存,不需要多次数据拷贝,所以传输速度很快\[2\]。 在使用共享内存进行进程间通信时,需要给共享内存创建一个唯一的身份ID,以便区分不同的共享内存。当进程需要访问共享内存时,需要在映射时带上这个ID,这样就可以确定访问的是哪一个共享内存\[3\]。 需要注意的是,共享内存并没有提供同步机制,也就是说,在一个进程结束对共享内存的写操作之前,并没有自动机制可以阻止另一个进程开始对它进行读取。为了实现多个进程对共享内存的同步访问,通常会使用信号量来实现对共享内存的同步访问控制\[2\]。 总结起来,Linux下的共享内存是一种高效的进程间通信方式,允许多个进程共享一块存储区。通过给共享内存创建唯一的身份ID,可以区分不同的共享内存。然而,共享内存并没有提供同步机制,需要使用信号量来实现对共享内存的同步访问控制\[2\]\[3\]。 #### 引用[.reference_title] - *1* *3* [Linux进程间通信——共享内存实现](https://blog.csdn.net/zhm1949/article/details/124909541)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Linux进程间通信方式——共享内存](https://blog.csdn.net/xujianjun229/article/details/118584955)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值