2006-8-3 mm/shmem.c 实际上这是一个文件系统。 Resizable simple fs for linux,heavily base on ramfs。 --- from code comment. 有必要简单介绍一下,tmpfs 就象虚拟磁盘ramdisk,tmpfs 可以使用您的 RAM,但 它也可以使用您的交换分区。而且传统的虚拟磁盘是个块设备,并需要一个 mkfs 之类 的命令才能真正地使用它,tmpfs 则是一个文件系统,您只是安装它,它就可以使用了。 并且tempfs不建立于任何设备之上,空间也是动态分配的。 什么是tempfs?
执行一下df命令可以看到如下信息: ======================================================
文件系统 1k-块 已用 可用 % 挂接点 tmpfs 388116 0 388116 0% /dev/shm
=======================================================
这里的文件系统tmpfs就是shemem.c所实现的.但是在我们分析的这个版本中fs 的名字叫做"shm"(见717行 static DECLARE_FSTYPE(shmem_fs_type, "shm", shmem_read_super, FS_LITTER); )而不是"tmpfs",多少令人有些疑惑.其实上,2.4.20已经不是这样声明了,而是如下:
#ifdef CONFIG_TMPFS /* type "shm" will be tagged obsolete in 2.5 */ static DECLARE_FSTYPE(shmem_fs_type, "shm", shmem_read_super, FS_LITTER); static DECLARE_FSTYPE(tmpfs_fs_type, "tmpfs", shmem_read_super, FS_LITTER); #else static DECLARE_FSTYPE(tmpfs_fs_type, "tmpfs", shmem_read_super, FS_LITTER|FS_NOMOUNT); #endif
第一部分: 外部接口
已经明确说明shm这个名字就要过时了. 文件系统有一个定义的很好的接口,所 以这里大部分函数和变量(函数表)都是静态的,并且是一个module.先来看看模块提 供的几个接口函数:
1)shmem_nopage :ipc/shm.c 使用了这个接口.和shemem.c一样都是作为标准接口 vm_operations_struct::nopage使用.在讨论filemap.c的时候已经讨论过这个接口了. filemap.c, shemem.c, shm.c都提供了vm_operations_struct,作为页面调入的标准 接口,这个接口在内核的位置是memory.c ->handle_mm_fault->handle_pte_fault-> do_no_page.同时可以看到swap不是以vm_operations_struct的接口出现的,而是 handle_pte_fault->do_swap_page.
2)module_init(init_shmem_fs) & module_exit(exit_shmem_fs):模块接口函数. /* *注册文件系统 *进行虚拟安装,以便内核自己使用此文件系统 */ static int __init init_shmem_fs(void) { int error; struct vfsmount * res;
if ((error = register_filesystem(&shmem_fs_type))) {/*挂入list: file_systems*/ printk (KERN_ERR "Could not register shmem fs\n"); return error; }
/*将此文件系统安装到虚拟的根:shmem_fs_type->kern_mnt*/ /*内核就可以通过此根访问这个文件系统*/ res = kern_mount(&shmem_fs_type); if (IS_ERR (res)) { printk (KERN_ERR "could not kern_mount shmem fs\n"); unregister_filesystem(&shmem_fs_type); return PTR_ERR(res); }
devfs_mk_dir (NULL, "shm", NULL); /*devfs文件系统相关*/ return 0; } 这里也看看kern_mount的作用: /* * 安装到自己,给内核自己使用的文件系统 * 记录在fs type->kern_mnt */ struct vfsmount *kern_mount(struct file_system_type *type) { kdev_t dev = get_unnamed_dev();/*获得一个unnamed dev,专门为 虚拟文件系统准备*/ struct super_block *sb; struct vfsmount *mnt; if (!dev) return ERR_PTR(-EMFILE);
/*malloc sb, and type->read_super,like shmem_read_super*/ sb = read_super(dev, NULL, type, 0, NULL, 0); if (!sb) { put_unnamed_dev(dev); return ERR_PTR(-EINVAL); }
/*仅仅获得一个mnt 结构, 安装点是自己(自环), mnt_parent也是自己*/ mnt = add_vfsmnt(NULL, sb->s_root, NULL); if (!mnt) { kill_super(sb, 0); return ERR_PTR(-ENOMEM); }
type->kern_mnt = mnt; /*内核可以使用此文件系统,对kernel 的user不可见*/ return mnt; } 这里read_super->type->readsuper->shmem_read_super
/* 解析安装参数 * 分配一个dir inode, 分配根节点的inode,并建立root和root indoe的关系 * sb->root= new root, sb->s_op= &shmem_ops */ super_block *shmem_read_super(struct super_block * sb, void * data, int silent) { struct inode * inode; struct dentry * root; unsigned long blocks = ULONG_MAX; /* unlimited */ unsigned long inodes = ULONG_MAX; /* unlimited */ int mode = S_IRWXUGO | S_ISVTX;
if (shmem_parse_options (data, &mode, &blocks, &inodes)) { printk(KERN_ERR "shmem fs invalid option\n"); return NULL; } spin_lock_init (&sb->u.shmem_sb.stat_lock); .... //设置解析的参数,略 sb->s_op = &shmem_ops;
/*为根节点分配inode*/ inode = shmem_get_inode(sb, S_IFDIR | mode, 0); if (!inode) return NULL;
/*分配根'/' 的dentry,其parent 是null,并建立和inode的关系*/ root = d_alloc_root(inode); if (!root) { iput(inode); return NULL; } sb->s_root = root; return sb; }
最后是shmem_get_inode: /* * malloc things for shmem inode * * 根据类型设置inode->i_op,inode->i_fop,或者是设备文件 * inode->i_mapping->a_ops = &shmem_aops; list_add (&inode->u.shmem_i.list, &shmem_inodes); */ struct inode *shmem_get_inode(struct super_block *sb, int mode, int dev) { struct inode * inode;
.... //santi check, 是否还有inode,略
inode = new_inode(sb); if (inode) { ....//忽略简单变量设置 inode->i_mapping->a_ops = &shmem_aops; .... switch (mode & S_IFMT) { default: /*也支持设备文件*/ init_special_inode(inode, mode, dev); break; case S_IFREG: /*普通文件*/ inode->i_op = &shmem_inode_operations; inode->i_fop = &shmem_file_operations; break; case S_IFDIR: /*目录*/ inode->i_op = &shmem_dir_inode_operations; inode->i_fop = &shmem_dir_operations; break; case S_IFLNK: /*符号连接*/ inode->i_op = &page_symlink_inode_operations; break; } spin_lock (&shmem_ilock); //加入shmem的inode 列表 list_add (&inode->u.shmem_i.list, &shmem_inodes); spin_unlock (&shmem_ilock); } return inode; } 总值模块初始化时候建立了kern_mnt,sb,sb root, sb inode直接的相互关系,图 暂缺.以后补上.最好自己画一个.
3) shmem_file_setup,shmem_zero_setup: shm.c, 和mmap共同使用的一个接口. mmap.c已经讨论过了,这里要补充一个mmap的 一种用法:MAP_SHARED|MAP_ANONYMOUS,意思是采用共享匿名的映射.其含义逐步分析如下: 先是sys_i386.c do_mmap2--> .... if (!(flags & MAP_ANONYMOUS)) { //如果不是匿名映射,必须指定文件 file = fget(fd); if (!file) goto out; }
然后看do_mmap_pgoff(mm/mmap.c): .......... if (file) { if (vma->vm_flags & VM_DENYWRITE) { error = deny_write_access(file);/*禁止作为普通文件的写操作*/ if (error) goto free_vma; correct_wcount = 1; } vma->vm_file = file; get_file(file); error = file->f_op->mmap(file, vma);/*ext2就是generic_file_mmap *就是设置vma->vm_ops从而使 *vma->vm_ops->readpage就是 *filemap_nopage */ if (error) goto unmap_and_free_vma; } else if (flags & MAP_SHARED) {//匿名共享映射支持 error = shmem_zero_setup(vma); //为vma指定一个文件,dev/zero if (error) goto free_vma; } ..........
7.20开始写此文件的分析,直到8.1号才到这里,因为有个问题困惑了近两周:既 然为vma指定了文件 dev/zero,ls /dev/shm为何不能看到这个文件?借此机会了解到 自己的无知--大部分文件系统重复安装会使用同一个mnt->mnt_root。而这里安装于 /dev/shm 的系统使用了不同的mnt root 见kernel_mount,do_mount->get_sb_nodev.
kernel_mount和/dev/shm下的tmpfs不是同一个根目录,不过即便是同一个,dcache _readdir也会不显示此文件,因为没有hash的dentry不能显示出来,详见dcache _readdir和系列文章--019_using uml.txt。
闲话少叙,看: int shmem_zero_setup(struct vm_area_struct *vma) { struct file *file; loff_t size = vma->vm_end - vma->vm_start;
/*这里的文件"dev/zero" 仅仅存在于函数init_tmpfs 所安装的fs中*/ /*安装于/dev/shm 的系统使用了不同的mnt root 见 shmem_fill_super*/ file = shmem_file_setup("dev/zero", size); if (IS_ERR(file)) return PTR_ERR(file);
if (vma->vm_file) fput (vma->vm_file); vma->vm_file = file; vma->vm_ops = &shmem_shared_vm_ops; return 0; }
/* * shmem_file_setup - get an unlinked file living in shmem fs * @name: name for dentry (to be seen in /proc/<pid>/maps * @size: size to be set for the file * (是一个被删除了的文件) * /dev/shm中不能被ls 看到dev/zero 不是因为它 * 已经被删除了。 实际上原因如下: * 1.kern_mount 的tmpfs 和/dev/shm的tmpfs根本不 * 是同一个根 * 2.shmem 的readdir操作是dcache_readdir,需要将 * 条件 if (!list_empty(&de->d_hash) && de->d_inode) * 删除,这两点线索可以让dev/zero 可见 * 3. /proc/<pid>/maps 文件中可以看到相关信 * 息 */ struct file *shmem_file_setup(char * name, loff_t size) { int error; struct file *file; struct inode * inode; struct dentry *dentry, *root; struct qstr this; int vm_enough_memory(long pages);
error = -ENOMEM; if (!vm_enough_memory((size) >> PAGE_SHIFT)) goto out;
this.name = name; this.len = strlen(name); this.hash = 0; /* will go */ root = shmem_fs_type.kern_mnt->mnt_root; dentry = d_alloc(root, &this); /*分配dentry,建立subdir和child的关系*/ if (!dentry) goto out;
error = -ENFILE; file = get_empty_filp(); /*分配file 结构,just malloc things*/ if (!file) goto put_dentry;
error = -ENOSPC; inode = shmem_get_inode(root->d_sb, S_IFREG | S_IRWXUGO, 0);/*分配inode*/ if (!inode) goto close_file;
d_instantiate(dentry, inode);/*建立inode 和dentry 的关系*/ dentry->d_inode->i_size = size;
/*建立file 的关键关系*/ file->f_vfsmnt = mntget(shmem_fs_type.kern_mnt); file->f_dentry = dentry; file->f_op = &shmem_file_operations; file->f_mode = FMODE_WRITE | FMODE_READ; inode->i_nlink = 0; /* It is unlinked */ return(file);
close_file: put_filp(file); put_dentry: dput (dentry); out: return ERR_PTR(error); }
这两个函数建立如下的结构: file_system_type vfsmount +------------+ +----------------------+ | name | |mnt_root |----+root dentry | read_super |-->shmem_read_super |mnt_mountpoint | +--------+ | kern_mnt |--------------------->|mnt_parent | | | +------------+ |list_ent mnt_instances| | | |list mnt_clash | | | |mnt_sb | | | |list_head mnt_mounts | |d_subdir| |list_head mnt_child | | / | +---------/---------.--+ +-.----|-+ 这里 | | | | +-------------+ file | | | | | vma->file |--------->+-----------------+ | | | | | vma->vm_ops-|------+ | file->f_vfsmnt >------\ \--------<| | +-------------+ | | file->f_op |-->&shmem_file_operations|| | | | file->f_mode=rw | || | | | file->f_dentry |------->+dentry+ || | | +-----------------+ +-------------+ || | | | | || | | |list d_vfsmnt>>-\| | | |d_parent----->>--\ | + |list d_hash | | shmem_shared_vm_ops |list d_child<---------\ +---------------------+ |list d_subdir| |nopage: shmem_nopage | |list d_alias----/ +---------------------+ /----<d_inode | | | |qstr d_name | | | |d_op | | | |d_sb | | | |d_iname | | | +-------------+ | | | inode \ | +--------+ | |i_sb | | |i_dentry>>-------------------\ | | | | +--------+
4) shmem_unuse:swapfile.c使用这个接口. 看代码的注释即可明白了: /* * unuse_shmem() search for an eventually swapped out shmem page. * 当系统swapoff的时候要释放swap设备 * swap entry 对应的数据已经加载到page * 此函数要释放对应的swap entry,以便可以swapoff */ void shmem_unuse(swp_entry_t entry, struct page *page) { struct list_head *p; struct inode * inode;
spin_lock (&shmem_ilock); list_for_each(p, &shmem_inodes) {//遍历shmme 的inode inode = list_entry(p, struct inode, u.shmem_i.list);
//找到使用此swap entry的inode,并释放swap 空间 if (shmem_unuse_inode(inode, entry, page)) break;/*找到了就不用再遍历了*/ } spin_unlock (&shmem_ilock); }
/* * 在inode中寻找引使用了entry的部分,释放swap 空间(如果有) */ static int shmem_unuse_inode (struct inode *inode, swp_entry_t entry, struct page *page) { swp_entry_t **base, **ptr; unsigned long idx; int offset; struct shmem_inode_info *info = &inode->u.shmem_i;
idx = 0; spin_lock (&info->lock); /*在直接索引块搜索*/ if ((offset = shmem_clear_swp (entry,info->i_direct, SHMEM_NR_DIRECT)) >= 0) goto found;
idx = SHMEM_NR_DIRECT; if (!(base = info->i_indirect)) goto out;
/*搜索二级引用块*/ for (ptr = base; ptr < base + ENTRIES_PER_PAGE; ptr++) { if (*ptr && (offset = shmem_clear_swp (entry, *ptr, ENTRIES_PER_PAGE)) >= 0) goto found; idx += ENTRIES_PER_PAGE; } out: spin_unlock (&info->lock); return 0; found: /*找到了,将对应page重新加入page cache ,不再需要swap 空间了*/ add_to_page_cache(page, inode->i_mapping, offset + idx); set_page_dirty(page); SetPageUptodate(page); UnlockPage(page); info->swapped--; spin_unlock(&info->lock); return 1; }
第二部分 文件系统特性 在第一部分中我们已经讨论了tmpfs作为文件系统的一些特性的实现,包括kernel mount 相关的super block,inode ,dentry的一些函数 。 以及和mmap密切相关联系 的shmem_zero_setup,shmem_file_setup等。
接下来讨论此文件系统的其他特性之实现。注意2.4.0的内核没有实现read,write 操作. 2.6.14已经实现.(不知道啥时候搞的)
I) vm_operations 和 address_space operations (shmem_aops) 这两个个接口和mmap/swap/filemap紧密相关,分别对应缺页中断和swap out/fsyn. 对应的说明应在函数中注明,包括shmem_writepage,shmem_nopage. mmap的匿名共享影射通过如下路径使用shmem_nopage memory.c ->handle_mm_fault->handle_pte_fault->do_no_page:vm_ops->nopage
filemap/swap的情形如下: page_launder:page->mapping->a_ops->writepage filemap_fdatasync-> page->mapping->a_ops->writepage
/* * shmem_nopage - either get the page from swap or allocate a new one * * If we allocate a new one we do not mark it dirty. That's up to the * vm. If we swap it in we mark it dirty since we also free the swap * entry since a page cannot live in both the swap and page cache */ /* mmap的匿名共享影射通过如下路径使用此函数 * memory.c ->handle_mm_fault->handle_pte_fault->do_no_page:vm_ops->nopage */ struct page * shmem_nopage(struct vm_area_struct * vma, unsigned long address, int no_share) { unsigned long size; /*以page size 为单位的文件大小*/ struct page * page; unsigned int idx; swp_entry_t *entry; struct inode * inode = vma->vm_file->f_dentry->d_inode; struct address_space * mapping = inode->i_mapping; struct shmem_inode_info *info;
idx = (address - vma->vm_start) >> PAGE_SHIFT;/*vma 内的page 偏移*/ idx += vma->vm_pgoff; /*vma 地址在对应的文件内的偏移*/
down (&inode->i_sem); size = (inode->i_size + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;/*文件大小*/ page = NOPAGE_SIGBUS; if ((idx >= size) && (vma->vm_mm == current->mm)) goto out;
/* retry, we may have slept *//*睡眠过程中可能已经调入pagecache*/ page = __find_lock_page(mapping, idx, page_hash (mapping, idx)); if (page) goto cached_page;
info = &inode->u.shmem_i; entry = shmem_swp_entry (info, idx);/*找到tmpfs 文件对应的swap entry*/ if (!entry) goto oom; if (entry->val) {/*非0时代表一个swap entry*/ unsigned long flags;
/* Look it up and read it in.. */ page = lookup_swap_cache(*entry);/*在swap cache 查找*/ if (!page) {/*未找到*/ lock_kernel(); swapin_readahead(*entry);/*swap in 异步预读*/ page = read_swap_cache(*entry); /*等待读入指定页面完成*/ unlock_kernel(); if (!page) goto oom; }
/* We have to this with page locked to prevent races */ spin_lock (&info->lock); swap_free(*entry);/*不需要swap entry了*/ lock_page(page); delete_from_swap_cache_nolock(page);/*从swap cache删除*/ *entry = (swp_entry_t) {0}; flags = page->flags & ~((1 << PG_uptodate) ...//line too long,cut page->flags = flags | (1 << PG_dirty); add_to_page_cache_locked(page, mapping, idx);/*加入page cache*/ info->swapped--; spin_unlock (&info->lock); } else {//为0代表根本没有交换到swap 设备,分配新页面即可 spin_lock (&inode->i_sb->u.shmem_sb.stat_lock); if (inode->i_sb->u.shmem_sb.free_blocks == 0) goto no_space; inode->i_sb->u.shmem_sb.free_blocks--; spin_unlock (&inode->i_sb->u.shmem_sb.stat_lock); /* Ok, get a new page */ page = page_cache_alloc();/*分配新页面*/ if (!page) goto oom; clear_user_highpage(page, address); inode->i_blocks++; add_to_page_cache (page, mapping, idx); /*加入page cahche*/ } /* We have the page */ SetPageUptodate (page);
cached_page: UnlockPage (page); up(&inode->i_sem);
if (no_share) {/*copy on write *//*见do_no_page,read 情形下no_share为0*/ struct page *new_page = page_cache_alloc();
if (new_page) { copy_user_highpage(new_page, page, address); flush_page_to_ram(new_page); } else new_page = NOPAGE_OOM; page_cache_release(page); return new_page; }
flush_page_to_ram (page); return(page); no_space: spin_unlock (&inode->i_sb->u.shmem_sb.stat_lock); oom: page = NOPAGE_OOM; out: up(&inode->i_sem); return page; }
/* * Move the page from the page cache to the swap cache * (未做真正写入,留给swap cache 写入) */ /* page_launder:page->mapping->a_ops->writepage * filemap_fdatasync-> page->mapping->a_ops->writepage */ static int shmem_writepage(struct page * page) { int error; struct shmem_inode_info *info; swp_entry_t *entry, swap;
/* * */ info = &page->mapping->host->u.shmem_i; if (info->locked) return 1; swap = __get_swap_page(2); /* 分配swap page*/ if (!swap.val) return 1;
spin_lock(&info->lock); /*寻找tmpfs内记录swap entry 的散列表*/ entry = shmem_swp_entry (info, page->index); if (!entry) /* this had been allocted on page allocation */ BUG(); error = -EAGAIN; if (entry->val) { /*已经有了swap entry与之对应*/ __swap_free(swap, 2); goto out; }
*entry = swap; error = 0; /* Remove the from the page cache */ lru_cache_del(page); remove_inode_page(page);
/* Add it to the swap cache */ add_to_swap_cache(page, swap); page_cache_release(page); set_page_dirty(page); info->swapped++; out: spin_unlock(&info->lock); UnlockPage(page); return error; }
II)普通文件操作函数 因为没有读写,简单些。只支持fop mmap: shmem_mmap和inode ops truncate: shmem_truncate。
shmem_mmap:将tmpfs 的文件进行非匿名mmap。 代码省略,就是设置vma->vm_ops.
shmem_truncate:删除或者截断文件的时候,遍历inode的直接索引和间接索引,释放 swap entry 以及可能存在的swap page.代码略。
III)目录文件操作 fops:read: generic_read_dir(空函数) readdir: dcache_readdir (遍历目录,见文章using_uml,搞定uml调试)
inode ops: create: shmem_create, /*创建普通文件->shmem_mknod*/ mkdir: shmem_mkdir, /*创建目录文件->shmem_mknod*/ mknod: shmem_mknod, /*提供各种创建功能*/ lookup: shmem_lookup, /*fs/namei.c real_lookup:dir->i_op->lookup*/ link: shmem_link, /* fs/namei.c vfs_link*/ unlink: shmem_unlink, /* 取消link */ symlink: shmem_symlink,/*符号连接*/ rmdir: shmem_rmdir, /*同shmme_unlink*/ rename: shmem_rename, /*重名功能*/
shmem_create, shmem_mkdir 都使用shmme_mknod,一个万能create 函数(^_^): /* * File creation. Allocate an inode, and we're done.. */ static int shmem_mknod(struct inode *dir, struct dentry *dentry, int mode, int dev) { struct inode * inode = shmem_get_inode(dir->i_sb, mode, dev); int error = -ENOSPC;
if (inode) { d_instantiate(dentry, inode); //将dentry 挂入inode dget(dentry); /* Extra count - pin the dentry in core */ error = 0; } return error; }
shmem_link: Link a file.. inode->i_nlink++, 将dentry->d_alias 挂入inode->i_dentry
shmem_symlink:(其他函数不再罗列)
sys_symlink->vfs_symlink:dir->i_op->symlink->shmem_symlink /* * 在目录dir下创建detnry,并符号连接到symname */ static int shmem_symlink(struct inode * dir, struct dentry *dentry, const char * symname) { int error;
error = shmem_mknod(dir, dentry, S_IFLNK | S_IRWXUGO, 0); if (!error) { int l = strlen(symname)+1; struct inode *inode = dentry->d_inode; error = block_symlink(inode, symname, l);/*此inode 记录下symlink路径(memcp)*/ } return error; }
不再分析其他函数了。理解之后,代码并不难。
|