文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>018 mm/shmem.c

018 mm/shmem.c

时间:2009-03-27  来源:hylpro

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;
}

不再分析其他函数了。理解之后,代码并不难。
相关阅读 更多 +
排行榜 更多 +
房间毁灭模拟器最新版

房间毁灭模拟器最新版

休闲益智 下载
街头追逐者最新版

街头追逐者最新版

休闲益智 下载
弓箭手2内置作弊菜单

弓箭手2内置作弊菜单

休闲益智 下载