为什么/dev/shm中看不到shmget创建的内存文件及其它(上)

一、共享内存及传统sys v IPC机制
这个机制在SYS V系统中最早引入,是为了提高进程间通讯效率的一种新的机制,不同的进程可以通过不同的逻辑地址来操作相同的物理地址,这样相当于在地址空间隔离的基础上大家建立DMZ(非军事化区),不同的进程在这里毫不戒备,大家坦诚交流,直接操作内存,这样一个进程的修改对另一个进程马上可见,效率很高。根据毛德操《Linux内核情景分析》的说明,该机制以及消息队列、信号量等机制最早由sys V引入,也称为传统的Sys V IPC,它们的实现位于内核根目录下的ipc文件夹。
经典的共享内存接口通过shmget、shmat、shmdt、shmctl之类的接口来实现,它们的特点就是整个看不到字符串的形式,而这也正是软件的一个大忌。我们想一下,字符串是对人类最为友好的语言,而数值是对机器最为友好的。访问网站的时候大家都是通过记忆网站名称而不是记录网站的IP,虽然这样计算机还要经过DNS处理,但是大家一定觉得这点效率对于自己对网站的记忆是最为方便的。同样的,电话本中的通讯录也是以人名为索引,虽然其中的手机只识别数值电话号码。
在新的rt库中,对于这些传统的通讯方式又都添加了对应的“类文件”接口,文件的特点就是它背后有一套成熟而系统的操作接口和规范,另一方面就是它的操作接口都是通过字符串形式来完成的,而文件和文件夹的名称往往可以给出很多信息,这样不同的功能创建的文件名称一般不同,所以这也可以避免命名的冲突,并且字符串的命名空间也比数值大。
所以shmget、semget、msgget这些接口在rt库中又有了各自的shm_open、sem_open、mq_open之类的接口(当然也有各自的XXX_unlink接口),它们可以返回自己使用的数据结构。它们底层的实现可能各有各的不同,例如sem的实现就借助了内存文件、而消息队列的创建则直接使用了新的系统API,这些具体的细节这里先放在一边,只是大致说了一下自己了解的一些背景,对于一个coder来说,这当然不是我的强项,只是把自己想到的一些东西做个简单总结。
二、shmget实现
glibc的实现代码为
glibc-2.7\sysdeps\unix\sysv\linux\shmget.c
int  shmget (key, size, shmflg)
     key_t key;
     size_t size;
     int shmflg;
{
  return INLINE_SYSCALL (ipc, 5, IPCOP_shmget, key, size, shmflg, NULL);
}
也就是通过sys_ipc进入内核,这个sys_ipc是一个大杂烩接口,这一点内核的注释中非常直接的表达了这个意思,它的猥琐程度应该和sys_socketcall有一比。前者是Unix的变种sysv的主要进程间通讯机制,而后者则是另一变种BSD的通讯机制。所以这个具体的转发过程我们就不详细分析了,因为繁琐而无趣,这里就直奔主题了。
sys_ipc--->>>sys_shmget--->>newseg
其中的实现可以看到,每次调用shmget的时候,内核都会代劳创建一个“内存文件”(这种文件的名称和shm非常像,叫做shmem,可能好ID都被抢先用完了吧),并且这个文件的命名规则在newseg中的构造方法为
        sprintf (name, "SYSV%08x", key);
        file = shmem_file_setup(name, size, acctflag);
也就是创建的共享文件的名字是SYSV加上8个16进制数(4字节int的范围),然后使用相对通用的内存文件系统tmpfs来创建内存文件。看一下我的系统的tmpfs文件系统的挂载情况
[tsecer@Harry gotoOnly]$ mount
/dev/mapper/vg_harry-lv_root on / type ext4 (rw)
proc on /proc type proc (rw)
sysfs on /sys type sysfs (rw)
devpts on /dev/pts type devpts (rw,gid=5,mode=620)
tmpfs on /dev/shm type tmpfs (rw,rootcontext="system_u:object_r:tmpfs_t:s0")
这个内容在
[tsecer@Harry gotoOnly]$ cat /etc/fstab 
……
/dev/mapper/vg_harry-lv_root /                       ext4    defaults        1 1
UUID=c2c3fd6d-82a7-4e6c-936a-f7e3e2f4d918 /boot                   ext4    defaults        1 2
/dev/mapper/vg_harry-lv_swap swap                    swap    defaults        0 0
tmpfs                   /dev/shm                tmpfs   defaults        0 0
中由系统启动时自动挂载。
三、验证一下内存文件创建
因为内核中给shm文件的命名是比较言简意赅的,所以如果我创建多个的话,那么它们工工整整排列在一起,看起来应该会蔚为壮观。写个最为原始的程序测试一下:
[tsecer@Harry shmget]$ cat shmget.c 
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main()
{
int shmid = shmget(IPC_PRIVATE,0x1000,IPC_CREAT);
printf("shmid is %d errno is %d \n",shmid ,errno);
}
[tsecer@Harry shmget]$ gcc shmget.c 
[tsecer@Harry shmget]$ ./a.out 
shmid is 1900568 errno is 0 
[tsecer@Harry shmget]$ ./a.out 
shmid is 1933337 errno is 0 
[tsecer@Harry shmget]$ ls /dev/shm/ -l
total 432
-r--------. 1 tsecer tsecer 67108904 2012-02-17 21:44 pulse-shm-1152960118
-r--------. 1 tsecer tsecer 67108904 2012-02-17 21:29 pulse-shm-2614187708
-r--------. 1 tsecer tsecer 67108904 2012-02-17 21:29 pulse-shm-2706987868
-r--------. 1 tsecer tsecer 67108904 2012-02-17 21:29 pulse-shm-2937262336
-r--------. 1 gdm    gdm    67108904 2012-02-17 21:29 pulse-shm-460714389
-r--------. 1 tsecer tsecer 67108904 2012-02-17 21:45 pulse-shm-566993672
比较遗憾的是里面并没有SYSV之类的文件,虽然我连续执行了好几次,这终究还是让人有些困惑,那么我创建的文件到底哪里去了呢?会不会是创建之后删除了?
[tsecer@Harry shmget]$ cat /proc/sysvipc/shm | wc -l
27
[tsecer@Harry shmget]$ ./a.out 
shmid is 1966106 errno is 0 
[tsecer@Harry shmget]$ cat /proc/sysvipc/shm | wc -l
28
可以看到文件的数量的确是在增加的,但就是看不到文件。
四、tmpfs文件系统与proc文件系统有何不同
linux-2.6.21\mm\shmem.c
static int shmem_get_sb(struct file_system_type *fs_type,
    int flags, const char *dev_name, void *data, struct vfsmount *mnt)
{
    return get_sb_nodev(fs_type, flags, data, shmem_fill_super, mnt);
}

static struct file_system_type tmpfs_fs_type = {
    .owner        = THIS_MODULE,
    .name        = "tmpfs",
    .get_sb        = shmem_get_sb,
    .kill_sb    = kill_litter_super,
};
int get_sb_nodev(struct file_system_type *fs_type,
    int flags, void *data,
    int (*fill_super)(struct super_block *, void *, int),
    struct vfsmount *mnt)
{
    int error;
    struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);
这里体现了tmpfs文件系统和proc文件系统的一个重要差别,对于tmpfs文件系统,在每次执行一次mount的时候,内核都会为这次mount分配一个专用的struct super_bloc结构,然后mount框架再分配vfsmount结构,这样虽然是同一个没有对应任何设备的文件系统,每次挂载生成的super都不相同,而对于文件系统挂载来说,例如,我们执行
ls -l  /dev/shm
它事实上显示的就是挂载在这个/dev/shm文件夹下的文件根文件系统的内容,所以不同的挂接点之间互不影响,这一点和proc文件系统的实现不同,我们看一下proc文件系统的实现
linux-2.6.21\fs\proc\root.c

static int proc_get_sb(struct file_system_type *fs_type,
    int flags, const char *dev_name, void *data, struct vfsmount *mnt)
{
……
    return get_sb_single(fs_type, flags, data, proc_fill_super, mnt);
}
这也就是说,对于proc文件系统,它的vfsmount在整个系统中只有一个,不论在哪个地方挂载,看到的内容都是相同的,而tmpfs则刚好相反,每个地方都是不同的。
对于内核中通过shmem_file_setup创建的文件系统,它的创建文件使用的位置都是在内核单独使用,没有挂载到用户态地址的一个vfsmount结构中
    root = shm_mnt->mnt_root;
    dentry = d_alloc(root, &this);
由于这个内核全局变量shm_mnt结构对用户态空间不可见,所以shmget在该文件夹下创建的文件同样在用户态没有办法看到他们的名称。但是可以在shmat之后从 /proc/pid/maps中看到一个文件的名称,而对于系统中所有的已经创建的共享内存,没有找到查看方法。
五、todo
这个shmget+shmat的实现和shm_open+mmap的实现套路比较详细,shmget的操作更为复杂一些,中间设计到了比较特殊的逻辑地址操作结构
static struct vm_operations_struct shm_vm_ops = {
    .open    = shm_open,    /* callback for a new vm-area open */
    .close    = shm_close,    /* callback for when the vm-area is released */
    .nopage    = shm_nopage,
#if defined(CONFIG_NUMA)
    .set_policy = shm_set_policy,
    .get_policy = shm_get_policy,
#endif
};
而对于大部分文件mmap是不会定义vm_operations_struct的open和close实现的,而且每次shmat的时候都会通过get_empty_filp分配一个struct file结构,但是这个结构由于没有安装到任务的文件句柄表中,所以通过/proc/pid/fd中也无法看到该文件。这个中间文件有何作用,对于一个shm文件被多少个线程共享为什么不使用shmem_file中的f_counts而要使用struct shmid_kernel 结构中的unsigned long        shm_nattch;?
 
 
 
 
 

posted on 2019-03-06 21:33  tsecer  阅读(1069)  评论(0编辑  收藏  举报

导航