一、如何看到sysV IPC shm文件名
1、file_system_type.get_sb修改
正如上篇所说,在用户态无法看到可shm文件的名称,不同的挂载点使用不同的dentry,而对于tmpfs文件,它的readdir的系统实现就是通过dcache_readdir函数来实现的,但是这个dentry的不同本质上是由于
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);
}
函数实现的,它会为每次挂载分配一个新的super_block结构,这个实现在get_sb_nodev中。作为对比我们,proc文件无论挂接在哪里,它看到的内容都是一致的,所以可以以proc文件系统为例进行修改,将这里的shmem_get_sub修改为如下形式
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_single(fs_type, flags, data, shmem_fill_super, mnt);
}
这里注意到一个细节,那就是sys_mount—>>>do_new_mount—>>do_kern_mount—->>>vfs_kern_mount—>>alloc_vfsmnt这个调用连中,vfsmount结构是始终分配的,每次挂载都将会分配一个独立的vfsmount结构,而所谓的single_sb,它只是说它的super_block是唯一的,进一步说,这些vfsmount结构中的mnt_root指针指向的位置是相同的。然后在path_lookup—>>>do_path_lookup—>>>link_path_walk—->>__link_path_walk—>>>do_lookup—>>>__follow_mount—>>lookup_mnt
这条路径中将会返回这个vfsmount结构的mnt_root作为新的文件夹(这一点在__follow_mount函数中的path->dentry = dget(mounted->mnt_root)完成)。
2、修改内核挂接属性
进行上面修改之后,在用户态通过
mount -t tmpfs none myshm
挂载,会提示参数错误,这个地方时内核在挂载内核使用的shm_mnt的时候加了选项限制不能从用户态挂载,所以此处也要做相应修改。
linux-2.6.21mmshmem.c
static int __init init_tmpfs(void)
    shm_mnt = vfs_kern_mount(&tmpfs_fs_type, MS_NOUSER,删除该选项,从而可以在用户态挂载文件
                tmpfs_fs_type.name, NULL);
3、修改dentry DCACHE_UNHASHED 属性
做上面修改之后,用户态可以挂载文件,在不同挂接点中看到的内容一致,也就是说不同挂接点均可以看到,但是遗憾的时候通过shmget隐形创建的文件依然不可见,此时再次跟踪,发现问题所在。在
linux-2.6.21mmshmem.c
struct file *shmem_file_setup(char *name, loff_t size, unsigned long flags)
{
……
    dentry = d_alloc(root, &this);
……
}
在d_alloc中创建的dentry项刚开始是出于删除状态(也就是DCACHE_UNHASED状态),这个状态也就是内存文件的特征,它可以在内存中的dentry结构引用计数降低为零的时候从内存中直接释放,这也正是tmpfs中”tmp”属性的体现。偏偏在执行文件夹内容显示的dcache_readdir中:
for (p=q->next; p != &dentry->d_subdirs; p=p->next) {
                struct dentry *next;
                next = list_entry(p, struct dentry, d_u.d_child);
                if (d_unhashed(next) || !next->d_inode)这里对于出于DCACHE_UNHASHED状态的目录项不予显示,所以内核中挂接点依然无法看到
                    continue;
对于通常的实体文件系统(例如ext2,),它们在调用d_alloc之后一般要通过d_splice_alias—>>>d_rehash—>>_d_rehash
static void __d_rehash(struct dentry * entry, struct hlist_head *list)
{

     entry->d_flags &= ~DCACHE_UNHASHED
;
     hlist_add_head_rcu(&entry->d_hash, list);
}
在这里通过该操作来清除对于文件的删除状态,从而让它在内存中生效。由于shmem_file_setup并没有代劳执行这个操作,所以我们就不按套路出牌,强制在shmem_setup_file函数中,新分配的dentry结构中清除这个标志。
经过这么一番折腾,用户态终于可以看到内核创建的文件了,由于我是通过IPC_PRIVATE创建了多个共享内存,所以在该文件夹下可以看到很多个同名的文件,它们都是“SYSV00000000”,这也说明一个问题,那就是并非所有的文件系统都不能在同一个文件夹下有重名文件,文件系统的限制只是为了避免二义性。
二、为什么do_shmat中每次会分配一个struct file结构
可以看到,在newseg函数中,已经通过shmem_setup_file分配了一个struct file结构,但是在每次执行shmat的时候还会再次分配一个结构,这个结构是不是冗余的呢?后来我看了一下2.4.37的内核版本,在sys_shmat调用中的确是没有分配这个结构的,使用的就是newseg中创建的那个file结构。所以为什么在新的版本中必须要再次分配还真是不太容易理解。但是看一下两个文件的差别,可以看新的版本的shm_file_operations定义为
static const struct file_operations shm_file_operations = {
    .mmap        = shm_mmap,
    .fsync        = shm_fsync,
    .release    = shm_release,
    .get_unmapped_area    = shm_get_unmapped_area,
};
而2.4版本中对应内容为
static struct file_operations shm_file_operations = {
    mmap:    shm_mmap
};
可见新的版本中定义了一些新的file_operations接口。并且其中还有一个判断使用到了这个结构
int is_file_shm_hugepages(struct file *file)
{
    int ret = 0;

    if (file->f_op == &shm_file_operations) {
        struct shm_file_data *sfd;
        sfd = shm_file_data(file);
        ret = is_file_hugepages(sfd->file);
    }
    return ret;
}
所以猜测这里是为了支持hugepages功能而添加的一个功能?不管如何,这个都是一个猜测了,没有实作,也算是一个疑案吧,知道的同学请指教。
三、为什么要定义shm_vm_ops结构中的open/close接口
这个是一个比较特殊的例子,因为它在mmap的时候定义了vm_operations_struct结构中的open和close接口,这两个接口的调用位置分别为
copy_process–>>copy_mm—>>>dup_mm—>>>dup_mmap
        if (tmp->vm_ops && tmp->vm_ops->open)
            tmp->vm_ops->open(tmp);

do_exit—->>>exit_mm—->>>mmput—>>exit_mmap—>>remove_vma
    if (vma->vm_ops && vma->vm_ops->close)
        vma->vm_ops->close(vma);
在通过shmget创建一个tmpfs文件之后,这个文件不论是否执行shmat,这个文件是始终存在的,删除这个文件的方法就是通过shmctl RMID的方法来删除这个文件。但是这里有一个比较奇特但是又比较典型的策略,就是当一个资源被多个实例共享时,如果该资源被删除,那么删除并不马上执行,而只是减少引用计数,只有当计数值降低为零的时候才真正执行删除操作。这一点在文件的删除上比较常见和典型。
现在假设说一个shm被通过shmctl删除,内核只是对这个文件做了一个标记SHM_DEST属性,而真正的触发则只有框架来支持,也就是说这里只能以“回调”的形式注册给系统,从而在引用附加和移除的时候得到通知并进行计算,进而根据计算结果做出可能的删除操作
四、shm/shmem文件何时释放
1、shm文件
这里所说的shm文件是在shmat中通过file = get_empty_filp();分配的struct file结构。它的示范同样是由mmap框架来完成。在前一节说vm_operations_struct的open/close函数的附近,各自都有对vma区间是否有文件映射的判断,如果是文件映射,会分别递增和递减这个文件的引用次数。
dup_mmap函数中为
        if (file) {
            struct inode *inode = file->f_path.dentry->d_inode;
            get_file(file);
remove_vma函数中
    if (vma->vm_file)
        fput(vma->vm_file);
然后在
fput—>>__fput
    if (file->f_op && file->f_op->release)
        file->f_op->release(inode, file);
在其中执行自己私有数据的释放。
2、shmem文件的释放
这里所说内容为newseg中创建的文件,它的释放是通过
shm_destroy—>>>fput—>>>__fput—>>>dput—>>>dentry_iput—>>>iput—>>>generic_delete_inode—>>>shmem_delete_inode
释放,由于该文件是内存文件,所以inode dentry这些内存结构被删除之后就无法恢复,也意味着它们从系统中消失了。