共享内存应该是进程间通信最有效的方式,同一块物理内存被映射到两个不同进程 A、B 各自的地址空间;进程 A 可以立即看到进程 B 对共享内存中数据的更新,反之亦然。
接下来简单介绍下与共享内存相关的内容。
简介
采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。
注意 共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量、互斥锁。
使用示例
共享内存主要用于进程间通信,Linux 有几种方式的共享内存 (Shared Memory) 机制:
System V shared memory(shmget/shmat/shmdt)
,用于不相关进程的通讯;POSIX shared memory(shm_open/shm_unlink)
,同样不同进程通讯,相比接口更简单;mmap(), shared mappings
包括了 A) 匿名映射 (通过fork关联),B) 文件映射,通常用于不相关的进程通讯;
SYSV 和 POSIX 两者提供功能基本类似,也就是 semaphores
shared memory
message queues
,不过由于 SYSV 的历史原因使用更广泛,不过两者的实现基本相同。
The POSIX shared memory object implementation on Linux 2.4 makes use of
a dedicated filesystem, which is normally mounted under /dev/shm.
也就是说,POSIX 共享内存是基于 tmpfs 来实现的,而 SYSV 共享内存在内核也是基于 tmpfs 实现的;从内核文档 tmpfs.txt 可以看到如下内容:
1) There is always a kernel internal mount which you will not see at
all. This is used for shared anonymous mappings and SYSV shared
memory.
This mount does not depend on CONFIG_TMPFS. If CONFIG_TMPFS is not
set, the user visible part of tmpfs is not build. But the internal
mechanisms are always present.
2) glibc 2.2 and above expects tmpfs to be mounted at /dev/shm for
POSIX shared memory (shm_open, shm_unlink). Adding the following
line to /etc/fstab should take care of this:
tmpfs /dev/shm tmpfs defaults 0 0
Remember to create the directory that you intend to mount tmpfs on
if necessary.
This mount is _not_ needed for SYSV shared memory. The internal
mount is used for that. (In the 2.3 kernel versions it was
necessary to mount the predecessor of tmpfs (shm fs) to use SYSV
shared memory)
3) Some people (including me) find it very convenient to mount it
e.g. on /tmp and /var/tmp and have a big swap partition. And now
loop mounts of tmpfs files do work, so mkinitrd shipped by most
distributions should succeed with a tmpfs /tmp.
在上述的内容中,tmpfs 与共享内存相关的主要有两个作用:
- SYSV 还有匿名内存映射,这部分由内核管理,用户不可见;
- POSIX 由用户负责 mount,而且一般 mount 到
/dev/shm
,依赖CONFIG_TMPFS
。
SYSV
----- 查看以及调整支持的最大内存,shmmax shmall shmmni
$ cat /proc/sys/kernel/shmmax
33554432
----- 修改为32M
# echo 33554432 > /proc/sys/kernel/shmmax
----- 尝试创建65M的system V共享内存时失败
$ ipcmk -M 68157440
ipcmk: create share memory failed: Invalid argument
----- 创建10M共享内存
$ ipcmk -M 10485760
Shared memory id: 19431492
----- 查看刚创建的共享内存
$ ipcs -m -i 19431492
Shared memory Segment shmid=19431492
uid=0 gid=0 cuid=0 cgid=0
mode=0644 access_perms=0644
bytes=10485760 lpid=0 cpid=28064 nattch=0
att_time=Not set
det_time=Not set
change_time=Sun May 28 10:15:50 2015
----- 删除刚创建的资源
# ipcrm -m 19431492
注意,这里看到的 system v 共享内存的大小并不受 /dev/shm
的影响。
配置项
与共享内存相关的内容可以参考 /etc/sysctl.conf
文件中的配置,简介如下:
kernel.shmmax = 4398046511104
kernel.shmall = 1073741824 系统可用共享内存的总页数
kernel.shmmni = 4096
POSIX
大多数的 Linux 发行版本中,内存盘默认使用的是 /dev/shm
路径,文件系统类型为 tmpfs,默认大小是内存实际的大小,其大小可以通过 df -h
查看。
----- 查看当前大小
$ df -h /dev/shm
Filesystem Size Used Avail Use% Mounted on
tmpfs 3.9G 17M 3.9G 1% /dev/shm
----- 修改挂载点的大小,然后重新挂载
$ cat /etc/fstab
tmpfs /dev/shm tmpfs defaults,size=4096M 0 0
# mount -o remount /dev/shm
----- 不用重启重新挂载
# mount -o remount,size=256M /dev/shm
# mount -o size=1500M -o nr_inodes=1000000 -o noatime,nodiratime -o remount /dev/shm
$ df -h /dev/shm
Filesystem Size Used Avail Use% Mounted on
tmpfs 256M 82M 175M 32% /dev/shm
$ ls /dev/shm/shm1 -alh
-rw-r--r-- 1 andy andy 65M 5月 28 10:57 /dev/shm/shm1
$ stat /dev/shm/shm1
注意,如果申请的内存超过了限制,那么会报 Bus error
的错误。
总结
虽然 System V 与 POSIX 共享内存都是通过 tmpfs 实现,但是两个不同实例,对于 /proc/sys/kernel/shmmax
只会影响 SYS V 共享内存,/dev/shm
只会影响 Posix 共享内存。
tmpfs
tmpfs 是基于内存/交换分区的文件系统,ramdisk 是作为块设备,基于 ext 的文件系统,所以不会绕过 page cache 的内存复制;而 tmpfs 直接操作内存做为文件系统的,而不是基于块设备的。
其源码实现在 mm/shmem.c
中,根据 CONFIG_SHMEM
是否配置,略微有所区别。
static struct file_system_type shmem_fs_type = {
.owner = THIS_MODULE,
.name = "tmpfs",
.mount = shmem_mount,
.kill_sb = kill_litter_super,
.fs_flags = FS_USERNS_MOUNT,
};
在函数 init_tmpfs()
里,实际会通过 register_filesystem()
函数将 tmpfs 注册到文件系统中,在 shmem_file_setup()
中,更改了 file->f_op = &shmem_file_operations;
下面来看具体的结构体。
static struct file_operations shmem_file_operations = {
.mmap = shmem_mmap,
#ifdef CONFIG_TMPFS
.llseek = generic_file_llseek,
.read = shmem_file_read,
.write = shmem_file_write,
.fsync = simple_sync_file,
.sendfile = shmem_file_sendfile,
#endif
};
也就是说在操作在 tmpfs 文件时候,并没有使用常用的 ext 文件系统中的函数 do_sync_read()
,而是调用了 tmpfs 自己封装的函数 shmem_file_read()
,当然在 shmem_file_read()
并没有对 page cache 进行操作,虽然里面还是使用了 page cache 中 maping、file、inode 等结构体和算法。