本文转载自:https://blog.csdn.net/eleven_xiy/article/details/71249365

[摘要]
[背景]
[正文]
[总结]
注意:请使用谷歌浏览器阅读(IE浏览器排版混乱)
【摘要】

本文将以jffs2文件系统的访问过程为例,从全局视角,介绍一下linux文件系统的实现机理。本文不追求细节实现,旨在通过访问过程,把文件系统的基本原理尽量全面地展现在大家面前。

【背景】

1 为日后回顾方便。

2 很多朋友想了解文件系统的实现原理,但不知道如何入门,希望本文能有一点帮助。

【正文】

用户要访问一个文件系统,通俗说即是读写一个文件。最先要做的是挂载相应的文件系统,无论是根文件系统还是普通文件系统都需要挂载。所以如果从应用角度出发,应该从mount开始介绍。不过,文件系统的一些基本信息是在内核启动过程就初始完成的,所以首先简单介绍一下文件系统的初始化过程。

本文虽然已jffs2文件系统为例进行介绍,但是很多内容在其他类型文件系统中也是大同小异,很多原理可以类推过去。

文件系统的初始化过程。

以jffs2为例介绍:

关键函数:init_jffs2_fs->register_filesystem(&jffs2_fs_type);

ps:如果是其他文件系统:register_filesystem(&ubifs_fs_type);

[cpp] view plaincopy
  1. static struct file_system_type jffs2_fs_type = {
  2. .owner=       THIS_MODULE,
  3. .name=                  "jffs2",
  4. .mount=      jffs2_mount,
  5. .kill_sb=       jffs2_kill_sb,
  6. };

linux系统中mount的实现过程:

通过kernel源码的分析,可以知道mount过程中初始了哪些日后访问文件系统所必须的信息,如文件结点inode,超级块super block等。学习文件系统的实现原理,也可以从mount的实现作为切入点。

1 VFS中mount的实现。

我们知道访问一个真实的文件系统必须经过VFS,所以在此简单介绍下。

VFS中mount过程:namespace.c

1.1 关键函数:sys_mount->do_mount->do_new_mount->vfs_kern_mount->mount_fs

[cpp] view plaincopy
  1. long do_mount(const char *dev_name, const char __user *dir_name,
  2. const char *type_page, unsigned long flags, void *data_page)
  3. {
  4. /*此处可以参考文章: linux内核文件权限管理 中对path_openat的介绍->path_init/link_path_walk;
  5. ubifs中使用的user_path接口,和kern_path具有相同功能;
  6. dirname是挂载目录,比如挂载到/mnt/test目录,则此处dirname="/mnt/test";
  7. 通过如下调用kern_path->filename_loopup->path_loopupat找到struct path,
  8. kern_path()找到挂载点的目录项,即此时path.dentry->d_iname=test;
  9. 此时path_init():nd->path.mnt->mnt_root->d_iname="/",根目录超级块是SQUASHFS_MAGIC类型,nd->path.mnt->mnt_sb->s_magic是根目录对应的超级块;
  10. ps:current->fs->root.dentry->d_iname='/';  user_path->path_lookupat->path_init/link_path_walk;
  11. */
  12. retval=user_path(dir_name, &path);
  13. /*
  14. 根据不同文件系统实现挂载,如:jffs2_mount,ubifs_mount等
  15. 主要功能是实现super_block申请及初始化。
  16. 注意此时path.dentry是要挂载到目录 的目录项
  17. */
  18. retval = do_new_mount(&path, type_page, flags, mnt_flags,
  19. dev_name, data_page);
  20. }

1.2 sys_mount->do_mount->do_new_mount->vfs_kern_mount->mount_fs

[cpp] view plaincopy
  1. static int do_new_mount(struct path *path, const char *fstype, int flags,
  2. int mnt_flags, const char *name, void *data)
  3. {
  4. struct file_system_type *type;
  5. struct user_namespace *user_ns = current->nsproxy->mnt_ns->user_ns;
  6. struct vfsmount *mnt;
  7. int err;
  8. if (!fstype)
  9. return -EINVAL;
  10. /*根据"mount -t jffs2 "指定的文件系统类型jffs2找到jffs2_fs_type */
  11. type = get_fs_type(fstype);
  12. if (!type)
  13. return -ENODEV;
  14. if (user_ns != &init_user_ns) {
  15. if (!(type->fs_flags & FS_USERNS_MOUNT)) {
  16. put_filesystem(type);
  17. return -EPERM;
  18. }
  19. if (!(type->fs_flags & FS_USERNS_DEV_MOUNT)) {
  20. flags |= MS_NODEV;
  21. mnt_flags |= MNT_NODEV | MNT_LOCK_NODEV;
  22. }
  23. if (type->fs_flags & FS_USERNS_VISIBLE) {
  24. if (!fs_fully_visible(type, &mnt_flags))
  25. return -EPERM;
  26. }
  27. }
  28. /*
  29. 由此调用下去,会调用到真正的文件系统mount函数,如:jffs2_mount()/ubifs_mount()等
  30. jffs2_mount/ubifs_mount中申请到的超级块,该超级块对应的根目录项、根inode都赋值给了mnt,详见vfs_kern_mount中实现。
  31. 此时:mnt->mnt_root->d_iname="/",这个目录项是此次mount过程创建的超级块对应的根目录项,和根文件系统的"/"对应根目录项不是同一个,只是名字相同。
  32. 即:此处的mnt->mnt_root与do_mount->user_path()中的path.mnt->mnt_root,不是同一个根目录项(user_path得到的是要挂载的目录所在超级块的根目录,如挂载到/mnt/test,
  33. 则超级块对应根文件系统,是squashfs类型的.而此处是挂载时创建的超级块的根目录项,文件系统类型是挂载时mount -t 参数指定的).但根目录项的名字相同(都是‘/’).
  34. */
  35. mnt = vfs_kern_mount(type, flags, name, data);
  36. if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) &&
  37. !mnt->mnt_sb->s_subtype)
  38. mnt = fs_set_subtype(mnt, fstype);
  39. put_filesystem(type);
  40. if (IS_ERR(mnt))
  41. return PTR_ERR(mnt);
  42. err = do_add_mount(real_mount(mnt), path, mnt_flags);
  43. if (err)
  44. mntput(mnt);
  45. return err;
  46. }

1.3 sys_mount->do_mount->do_new_mount->vfs_kern_mount->mount_fs

[cpp] view plaincopy
  1. struct vfsmount *vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
  2. {
  3. struct mount *mnt;
  4. struct dentry *root;
  5. if (!type)
  6. return ERR_PTR(-ENODEV);
  7. mnt = alloc_vfsmnt(name);
  8. if (!mnt)
  9. return ERR_PTR(-ENOMEM);
  10. if (flags & MS_KERNMOUNT)
  11. mnt->mnt.mnt_flags = MNT_INTERNAL;
  12. /*
  13. 该函数创建超级块及该超级块的根目录项和根inode.
  14. 注意此处mount_fs中返回的是目录项sb->s_root,可以在后文看到sb->s_root目录项是指向 '/'
  15. */
  16. root = mount_fs(type, flags, name, data);
  17. if (IS_ERR(root)) {
  18. mnt_free_id(mnt);
  19. free_vfsmnt(mnt);
  20. return ERR_CAST(root);
  21. }
  22. /*
  23. 把此次mount->squashfs_fill_super过程创建的超级块对应的根目录项dentry,根inode和超级块,都赋值给vfsmount ,vfsmount返回到上级调用函数do_new_mount
  24. 此时:mnt->mnt_root->d_iname="/",这个目录项是此次mount过程创建的超级块对应的根目录项,和根文件系统的"/"对应根目录项不是同一个,只是名字相同。
  25. 即:此处的mnt->mnt_root与do_mount->user_path()中的path.mnt->mnt_root,不是同一个根目录项(user_path是要挂载的目录所在超级块的根目录,如挂载到/mnt/test,
  26. 则超级块对应根文件系统,是squashfs类型的,而此处是挂载时创建的超级块的根目录项,文件系统类型是挂载时mount -t 参数指定的).但根目录项的名字相同(都是‘/’)
  27. */
  28. mnt->mnt.mnt_root = root;
  29. mnt->mnt.mnt_sb = root->d_sb;
  30. mnt->mnt_mountpoint = mnt->mnt.mnt_root;
  31. mnt->mnt_parent = mnt;
  32. lock_mount_hash();
  33. list_add_tail(&mnt->mnt_instance, &root->d_sb->s_mounts);
  34. unlock_mount_hash();
  35. return &mnt->mnt;
  36. }

1.4 sys_mount->do_mount->do_new_mount->vfs_kern_mount->mount_fs  :

创建超级块及超级块对应的根目录项. 每个超级块都对应根目录项(目录项名即dentry->d_iname="/")和根节点(inode->i_ino=1);

[cpp] view plaincopy
  1. struct dentry *mount_fs(struct file_system_type *type, int flags, const char *name, void *data)
  2. {
  3. struct dentry *root;
  4. struct super_block *sb;
  5. /*
  6. 从VFS中的mount调用真实文件系统的jffs2_fs_type->mount=jffs2_mount
  7. 创建super_block: sget(fs_type, get_sb_mtd_compare,get_sb_mtd_set, flags, mtd);
  8. 此时name为mount命令中诸如/dev/mtdblock7的设备名;
  9. 注意:
  10. 1)此时type->mount的返回值root是目录项dentry:sb->s_root,而超级块sb是在jffs2_mount->mount_mtd_aux->sget中创建的,sb->s_root目录项在
  11. jffs2_mount->jffs2_fill_super中初始化
  12. 2)而对于ubifs文件系统:sb是在ubifs_mount->sget中创建,sb->s_root是在ubifs_mount->ubifs_fill_super中初始化。
  13. */
  14. root= type->mount(type, flags, name, data);
  15. return root;
  16. }

2 文件系统中的mount

关键函数:mount_fs->jffs2_mount –>mount_mtd_aux

mount_mtd_aux函数主要完成超级块的申请及初始化

ps: ubifs文件系统中直接在ubifs_mount中完成mount_mtd_aux的功能

[cpp] view plaincopy
  1. static dentry *mount_mtd_aux()
  2. {
  3. /* 申请super_block */
  4. sb=sget();
  5. /*jffs2_fill_super中初始化super_block;同理:ubifs_fill_super中初始化super_block;*/
  6. fii_super();
  7. /*
  8. 返回值root是目录项dentry:sb->s_root,而超级块sb是在jffs2_mount->mount_mtd_aux->sget中创建的,
  9. sb->s_root目录项在jffs2_mount->jffs2_fill_super中初始化
  10. */
  11. return dget(sb->s_root);
  12. }

关键函数:jffs2_mount –>mount_mtd_aux–>jffs2_fill_super

ffs2_fill_super系列调用中初始化超级块的基本信息,如:mtd_info(与flash驱动建立关系), s_magic(区分不同文件系统)、struct jffs2_sb_info s_fs_info(该成员记录文件系统超级快的基本信息,文件系统操作过程会经常用到struct super_block->s_fs_info)等。下面开始介绍。

s_magic举例:UBIFS_NODE_MAGIC<0X06101831>/SQUASHFS_MAGIC/JFFS2_SUPER_MAGIC等

2.1 jffs2_fill_super中初始化struct super_block超级块的基本信息

[cpp] view plaincopy
  1. static int jffs2_fill_super(struct super_block *sb, void *data, int silent)
  2. {
  3. struct jffs2_sb_info *c;
  4. int ret;
  5. c = kzalloc(sizeof(*c), GFP_KERNEL);
  6. if (!c)
  7. return -ENOMEM;
  8. c->mtd = sb->s_mtd;
  9. c->os_priv = sb;
  10. /*
  11. s_fs_info与文件系统类型相关,不同文件系统该信息不同,但都保存到超级块的sb->s_fs_info中.
  12. 这部分信息很重要,文件系统中经常用到。
  13. */
  14. sb->s_fs_info = c;
  15. ret = jffs2_parse_options(c, data);
  16. if (ret) {
  17. kfree(c);
  18. return -EINVAL;
  19. }
  20. /* Initialize JFFS2 superblock locks, the further initialization will
  21. * be done later */
  22. mutex_init(&c->alloc_sem);
  23. mutex_init(&c->erase_free_sem);
  24. init_waitqueue_head(&c->erase_wait);
  25. init_waitqueue_head(&c->inocache_wq);
  26. spin_lock_init(&c->erase_completion_lock);
  27. spin_lock_init(&c->inocache_lock);
  28. /*初始化super_block的s_op项,sb->s_op= &jffs2_super_operations;*/
  29. sb->s_op = &jffs2_super_operations;
  30. sb->s_export_op = &jffs2_export_ops;
  31. sb->s_flags = sb->s_flags | MS_NOATIME;
  32. sb->s_xattr = jffs2_xattr_handlers;
  33. /*
  34. 关键函数:jffs2_mount–>jffs2_fill_super->jffs2_do_fill_super();
  35. 初始化struct jffs2_sb_info s_fs_info(该成员记录文件系统超级快的基本信息,
  36. 文件系统操作过程会经常用到struct super_block->s_fs_info)等
  37. */
  38. ret = jffs2_do_fill_super(sb, data, silent);
  39. return ret;
  40. }

初始化super_block的s_op项,sb->s_op= &jffs2_super_operations:

[cpp] view plaincopy
  1. static const struct super_operationsjffs2_super_operations =
  2. {
  3. ……
  4. .alloc_inode= jffs2_alloc_inode,
  5. )

2.2 初始化挂载的超级块的根inode及目录项。

jffs2_mount –>jffs2_fill_super->jffs2_do_fill_super

初始化 sb->s_root目录项和对应的inode,ubifs中在ubifs_fill_super函数中完成该功能。

[cpp] view plaincopy
  1. int jffs2_do_fill_super(struct super_block*sb, void *data, int silent)
  2. {
  3. /*
  4. 该函数能够查询特定超级块下指定inode number的inode。如果inode number不存在,则创建新的inode,并将inode number赋值给新的inode.
  5. 一般来说系统都用类似的jffs2_iget->iget_locked/ubifs_iget->iget_locked来创建新inode,或查询已存在的inode。
  6. 每个超级块都存在一个root inode,其中结点号是inode->i_ino=1,返回值是超级块的根节点inode。
  7. */
  8. root_i= jffs2_iget(sb, 1);
  9. /*每个超级块都存在一个root inode对应的目录项dentry,其中结点号是dentry->d_iname="/" */
  10. sb->s_root= d_make_root(root_i);
  11. /*超级块的魔数等信息*/
  12. sb->s_magic=JFFS2_SUPER_MAGIC;
  13. /*此过程开启了关键线程如下,完成真实的写flash操作*/
  14. if (!(sb->s_flags & MS_RDONLY))
  15. jffs2_start_garbage_collect_thread(c);
  16. return0;
  17. }

文件系统创建inode

jffs2_mount –>jffs2_fill_super->jffs2_do_fill_super->jffs2_iget()

[cpp] view plaincopy
  1. struct inode *jffs2_iget(struct super_block *sb, unsigned long ino)
  2. {
  3. inode->i_fop = jffs2_file_operations;
  4. }

ps:系统中jffs2_file_operations的定义如下:

[html] view plaincopy
  1. const struct file_operations jffs2_file_operations=
  2. {
  3. .llseek=  generic_file_llseek,
  4. .open=             generic_file_open,
  5. .read =              do_sync_read,
  6. .aio_read =      generic_file_aio_read,
  7. .write =   do_sync_write,
  8. .aio_write =    generic_file_aio_write,
  9. .unlocked_ioctl=jffs2_ioctl,
  10. .mmap=          generic_file_readonly_mmap,
  11. .fsync=   jffs2_fsync,
  12. .splice_read= generic_file_splice_read,
  13. };

至此,完成了mount过程的介绍,简要回顾一下,mount过程系统都做了什么?

1> 创建了超级块super_bock,这个超级块类型为mount -t指定的类型。在诸如:jffs2_fill_super->jffs2_do_fill_super中完成.

2> 为新创建的超级块创建根目录项(dentry->d_iname=“/”)和相应的inode(i_ino=1),即:每次mount对应创建一个超级块,每个超级块都有一个名为"/"的根目录项,

和一个inode->i_ino=1的inode,注意如果是通过工具制作好的文件系统,烧录到指定flash分区上,则inode->i_ino要从flash上读取,不一定是1,

在诸如:mount->jffs2_fill_super->jffs2_do_fill_super函数中完成.

3> 将2中新创建的根目录项和根inode赋值给vfsmount变量(vfs_kern_mount函数中完成),并在do_new_mount->do_add_mount系列函数中对vfsmount进行管理。

4> 以后如果在mount挂载到的目录下创建文件或目录,都是基于mount挂载过程中创建的超级块进行操作。

3 文件的访问。

访问文件时,文件结构struct file,超级块结构super_block,inode结构,目录项dentry和address_space结构是重要的。

3.1 struct file文件的初始化。

文件初始化过程是在文件的打开过程中完成的,可以参考另一篇博文:linux文件系统中文件权限管理

1)读写一个文件时都是通过文件句柄fd找到 struct file,然后在通过file操作方法进行操作,那么file是何时创建的呢?

一般来说是open过程创建的struct file并绑定一个fd,如此后续读写操作可根据fd找file,而file的操作方法在finish_open->do_dentry_open中实现

file.f_op=inode.i_fop;如jffs2文件系统中的inode.i_fop=jffs2_file_operations ;

2) 其实,文件的访问过程最重要的是文件的打开,即open过程,open时把大多数资源都初始化好,read、write等过程直接使用open是初始的一些信息即可,这些信息都是通过struct file结构绑定到fd上,从open传递到read,write等文件操作函数中的。

3)如何通过struct file结构体,找到文件的super_block,inode和address_space

struct address_space *mapping = file->f_mapping;--打开文件时do_dentry_open初始化

structinode *inode  = mapping->host;

struct super_block *sb= mapping->host->i_sb

4)通过struct file结构找到inode和super_block

对于同一个文件,如果打开两次,系统中对应的file地址是不同的,但inode和super_block是相同的.

对于同一mount目录,不同文件对应的inode是不同的但super_block是相同的.

[cpp] view plaincopy
  1. 举例:do_sys_open()中加入如下打印:
  2. printk("filename=%s;file=%x;inode=0x%x;super_block=0x%x\n",filename,f,f->f_mapping->host,f->f_mapping->host->i_sb);
  3. ->f_mapping是address_space,它的初始化过程,见下文。->host是对应文件inode,i_sb是super_block.
  4. filename=/mnt/mtd/Config/network;file=85fe1b00;inode=0x86233350;super_block=0x868c9400
  5. filename=/mnt/mtd/Config/network;file=85fe1c80;inode=0x86233350;super_block=0x868c9400
  6. filename=/mnt/mtd/Config/networkip6;file=868ca6c0;inode=0x86233200;super_block=0x868c9400

5)通过struct file获取文件名的方法:

file->f_path.dentry->d_name.name,

file->f_path.dentry->d_iname

6) 通过inode找文件名:

sturct path *path ;

struct dentry *dentry = path->dentry;

struct inode *inode = dentry->d_inode;

struct dentry *pdentry = container_of(inode,structdentry,d_inode);

根据inode得到dentry再根据dentry找到文件名

7) 文件名初始化:

打开文件时初始化(创建文件):

sys_open->do_filp_open-> path_openat->do_last-> lookup_open-> lookup_dcache-> d_alloc-> __d_alloc

mknod时:

kern_path_create->lookup_hash->lookup_dcache->d_alloc-> __d_alloc

8) 特殊文件socket文件和设备文件操作方法初始化:init_special_inode;

3.2 address_space结构,struct address_space *mapping = file->f_mapping.

该结构体与页高速缓存密切相关。下面观察下它的初始化过程:

do_filp_open-> path_openat->do_last(Handle the last step of open)->finish_open()->do_dentry_open:

1) structfile中address_space的初始化,即file->f_mapping 初始化.

Mtd字符设备/dev/mtd0(/dev/mtd/0) , mtdchar.c:

[cpp] view plaincopy
  1. static int mtdchar_open(struct inode*inode, struct file *file)
  2. {
  3. file->f_mapping= mtd_info->i_mapping;
  4. }

Mtd块设备/dev/mtdblock0(/dev/mtdblock/0), mtdblock.c:

块设备:

[cpp] view plaincopy
  1. static int blkdev_open(struct inode *inode, struct file * filp)
  2. {
  3. filp->f_mapping= bdev->bd_inode->i_mapping;
  4. }

普通文件:

[cpp] view plaincopy
  1. static int do_dentry_open(struct file *f,
  2. int (*open)(struct inode *, struct file *),
  3. const struct cred *cred)
  4. {
  5. inode= f->f_inode = f->f_path.dentry->d_inode;
  6. f->f_mapping= inode->i_mapping;
  7. }

2) 以普通文件为例,file中的address_space与inode中的是同一个.

Inode中的address_space是在inode_init_always初始化的,见下.

3.3 文件对应的inode,file->f_mapping->host.

如何找到文件的i节点、根据inode号判断是哪个文件,因为inode->i_ino是不变的:

对于已有文件和新建文件系统用不同方法申请inode.

1)已创建文件:jffs2_iget()中添加如下打印,系统的inode结构体地址在系统重启后会改变,但是inode->i_ino是不变的,可据此判断访问的文件是否为同一文件.

[cpp] view plaincopy
  1. printk("%s(): ino ==%lu,inode=0x%x\n", __func__, ino,inode);
  2. #ls/mnt/mtd/Config –访问已创建文件
  3. [<8009f6a4>] (alloc_inode+0xc/0x9c)from [<800a05fc>] (iget_locked+0x78/0x194)
  4. [<800a05fc>] (iget_locked+0x78/0x194)from [<801165c8>] (jffs2_iget+0x10/0x32c)
  5. [<801165c8>] (jffs2_iget+0x10/0x32c)from [<8010c8e0>] (jffs2_lookup+0xc4/0xf8)
  6. [<8010c8e0>] (jffs2_lookup+0xc4/0xf8)from [<800921f0>] (lookup_real+0x30/0x4c)
  7. [<800921f0>] (lookup_real+0x30/0x4c)from [<80092e70>] (__lookup_hash+0x30/0x38)
  8. [<80092e70>](__lookup_hash+0x30/0x38) from [<8009367c>] (lookup_slow+0x3c/0xa0)
  9. [<8009367c>] (lookup_slow+0x3c/0xa0)from [<80094c84>] (path_lookupat+0x10c/0x7a8)
  10. [<80094c84>](path_lookupat+0x10c/0x7a8) from [<80095340>](filename_lookup.isra.48+0x20/0x5c)
  11. [<80095340>](filename_lookup.isra.48+0x20/0x5c) from [<800972b4>](user_path_at_empty+0x54/0x78)
  12. [<800972b4>](user_path_at_empty+0x54/0x78) from [<800972e8>] (user_path_at+0x10/0x18)
  13. [<800972e8>] (user_path_at+0x10/0x18)from [<8008e200>] (vfs_fstatat+0x44/0x84)
  14. [<8008e200>] (vfs_fstatat+0x44/0x84)from [<8008e75c>] (SyS_lstat64+0x14/0x30)
  15. [<8008e75c>] (SyS_lstat64+0x14/0x30)from [<8000da40>] (ret_fast_syscall+0x0/0x30)

squashfs文件系统:

[cpp] view plaincopy
  1. dCPU: 0 PID: 213 Comm: sh Tainted: G          O 3.10.50 #52
  2. [<80012664>](unwind_backtrace+0x0/0xdc) from [<80010588>] (show_stack+0x10/0x14)
  3. [<80010588>] (show_stack+0x10/0x14)from [<8009f6a4>] (alloc_inode+0xc/0x9c)
  4. [<8009f6a4>] (alloc_inode+0xc/0x9c)from [<800a05fc>] (iget_locked+0x78/0x194)
  5. [<800a05fc>] (iget_locked+0x78/0x194)from [<800e2df4>] (squashfs_iget+0x14/0x64)
  6. [<800e2df4>](squashfs_iget+0x14/0x64) from [<800e3198>] (squashfs_lookup+0x354/0x3d4)
  7. [<800e3198>](squashfs_lookup+0x354/0x3d4) from [<800921f0>] (lookup_real+0x30/0x4c)
  8. [<800921f0>] (lookup_real+0x30/0x4c)from [<80092e70>] (__lookup_hash+0x30/0x38)
  9. [<80092e70>](__lookup_hash+0x30/0x38) from [<8009367c>] (lookup_slow+0x3c/0xa0)
  10. [<8009367c>] (lookup_slow+0x3c/0xa0)from [<80094c84>] (path_lookupat+0x10c/0x7a8)
  11. [<80094c84>](path_lookupat+0x10c/0x7a8) from [<80095340>](filename_lookup.isra.48+0x20/0x5c)
  12. [<80095340>](filename_lookup.isra.48+0x20/0x5c) from [<800972b4>](user_path_at_empty+0x54/0x78)
  13. [<800972b4>](user_path_at_empty+0x54/0x78) from [<800972e8>] (user_path_at+0x10/0x18)
  14. [<800972e8>] (user_path_at+0x10/0x18)from [<8008e200>] (vfs_fstatat+0x44/0x84)
  15. [<8008e200>] (vfs_fstatat+0x44/0x84)from [<8008e72c>] (SyS_stat64+0x14/0x30)
  16. [<8008e72c>] (SyS_stat64+0x14/0x30)from [<8000da40>] (ret_fast_syscall+0x0/0x30)

2)# touch /mnt/mtd/test --新建文件.

jffs2_new_inode()中添加打印如下:创建时才调用打开时不调用.

printk("%s(): ino ==%lu,inode=0x%x\n", __func__, inode->i_ino,inode);

例子:对于/mnt/mtd/Config/network文件 ino保持不变,但每次重启inode内存地址都可能变化.

[cpp] view plaincopy
  1. jffs2_iget(): ino == 42,inode=0x862330b0
  2. filename=/mnt/mtd/Config/network;file=868bfb00;inode=0x862330b0;super_block=0x868d0400
  3. dCPU: 0 PID: 224 Comm: touch Tainted:G          O 3.10.50 #52
  4. [<80012664>](unwind_backtrace+0x0/0xdc) from [<80010588>] (show_stack+0x10/0x14)
  5. [<80010588>] (show_stack+0x10/0x14)from [<8009f6a4>] (alloc_inode+0xc/0x9c)
  6. [<8009f6a4>] (alloc_inode+0xc/0x9c)from [<800a13ac>] (new_inode_pseudo+0x8/0x60)
  7. [<800a13ac>](new_inode_pseudo+0x8/0x60) from [<800a140c>] (new_inode+0x8/0x1c)
  8. [<800a140c>] (new_inode+0x8/0x1c)from [<80116a00>] (jffs2_new_inode+0x1c/0x218)
  9. [<80116a00>](jffs2_new_inode+0x1c/0x218) from [<8010c72c>] (jffs2_create+0x38/0x128)
  10. [<8010c72c>](jffs2_create+0x38/0x128) from [<80093b00>] (vfs_create+0x70/0xa8)
  11. [<80093b00>] (vfs_create+0x70/0xa8)from [<80095da0>] (do_last.isra.52+0x574/0xad8)
  12. [<80095da0>](do_last.isra.52+0x574/0xad8) from [<800963b8>](path_openat.isra.53+0xb4/0x41c)
  13. [<800963b8>](path_openat.isra.53+0xb4/0x41c) from [<8009731c>](do_filp_open+0x2c/0x78)
  14. [<8009731c>] (do_filp_open+0x2c/0x78)from [<80089940>] (do_sys_open+0xe0/0x248)
  15. [<80089940>] (do_sys_open+0xe0/0x248)from [<8000da40>] (ret_fast_syscall+0x0/0x30)
  16. jffs2_new_inode(): ino ==43,inode=0x8627f130
  17. filename=/mnt/mtd/test;file=850d0b40;inode=0x8627f130;super_block=0x866ca400

3)页目录项dentry->d_inode初始化jffs2_create->:

[cpp] view plaincopy
  1. static void __d_instantiate(struct dentry*dentry, struct inode *inode)
  2. {
  3. dentry->d_inode= inode;
  4. }

4)inode中address_space结构的初始化.

static int ubifs_mknod(struct inode *dir,struct dentry *dentry,umode_t mode, dev_t rdev)->ubifs_new_inode->alloc_inode

static int ubifs_mkdir(struct inode *dir,struct dentry *dentry, umode_t mode)

jffs2_new_inode->new_inode()->alloc_inode

[cpp] view plaincopy
  1. static struct inode *alloc_inode(structsuper_block *sb)
  2. {
  3. structinode *inode;
  4. //ubifs_alloc_inode or jffs2_alloc_inode
  5. if(sb->s_op->alloc_inode)
  6. inode= sb->s_op->alloc_inode(sb);
  7. else
  8. inode= kmem_cache_alloc(inode_cachep, GFP_KERNEL);
  9. if(!inode)
  10. returnNULL;
  11. //初始化 inode->i_mapping;
  12. if(unlikely(inode_init_always(sb, inode))) {
  13. if(inode->i_sb->s_op->destroy_inode)
  14. inode->i_sb->s_op->destroy_inode(inode);
  15. else
  16. kmem_cache_free(inode_cachep,inode);
  17. returnNULL;
  18. }
  19. returninode;
  20. }

3.4文件读操作

vfs_read->generic_file_aio_read->do_generic_file_read-> jffs2_readpage-> jffs2_flash_read

1) jffs2文件系统死锁问题时添加的代码.注意以前读取过程并未对inode或者super_block进行加锁.

2)do_generic_file_read:

[cpp] view plaincopy
  1. static void do_generic_file_read(structfile *filp, loff_t *ppos,
  2. read_descriptor_t*desc, read_actor_t actor)
  3. {
  4. structaddress_space *mapping = filp->f_mapping;
  5. structinode *inode = mapping->host;
  6. index= *ppos >> PAGE_CACHE_SHIFT;
  7. for(;;) {
  8. find_page:
  9. /*
  10. 读文件时,系统会计算读取的数据保存到哪里index = *ppos >> PAGE_CACHE_SHIFT。数据保存到页高速缓存中。
  11. 那么对于一个文件,如何知道哪个页是它的高速缓存呢?
  12. 需要明确以下几点 :
  13. 1)一个文件,操作系统在VFS中把他看作struct file;在文件系统中可以被看作一个inode
  14. 2)file和inode中都有一个struct address_space项即此处的mapping;
  15. 用户每新建一个文件,系统都对应创建一个inode;inode在创建过程中初始了address_space。
  16. 而我们打开一个文件时,file->f_mapping= inode->i_mapping;所以此处的mapping就是inode的mapping。
  17. 3) 而mapping->page_tree中页就是这个文件在内核态下要读取到的内存地址,根据读取位置的不同来区分到底
  18. 要读取到mapping->page_tree的哪个页上,即*ppos >>PAGE_CACHE_SHIFT.
  19. 4)第一次读文件时,该文件对应的inode上的f_mapping->page_tree里没有page,所以会跳转到no_cached_page.
  20. */
  21. page= find_get_page(mapping, index);
  22. gotopage_not_up_to_date_locked;
  23. page_ok:
  24. /*
  25. 读取数据后,把该页加到页活动链表上
  26. 这是为了页高速缓存的释放管理。
  27. */
  28. if(prev_index != index || offset != prev_offset)
  29. mark_page_accessed(page);
  30. readpage:
  31. /*从flash上读取一个page的数据jffs2_readpage
  32. 此时页是lock_page的
  33. */
  34. error= mapping->a_ops->readpage(filp, page);
  35. /*成功读取后unlock_page且SetPageUptodate(page)*/
  36. no_cached_page:
  37. /*
  38. 第一次读文件inode->f_mapping->page_tree上没有页,需要向系统申请,然后通过add_to_page_cache_lru挂到page_tree上.
  39. 1) flash上读出的数据,在内核态下保存到该页,用户读文件时,直接从该页上取数据,不用访问flash,大大节省了时间。
  40. 这就是通常说的页高速缓存。
  41. 2)当系统内存不足时,可以考虑释放这种页,不过如果此时需要频繁访问文件,会大大增加系统开销,io可能会飙升。
  42. 3)注意这种页即使用户访问完了文件也不会马上被释放,可以参考页高速缓存的释放机制。
  43. */
  44. page= page_cache_alloc_cold(mapping);
  45. if(!page) {
  46. desc->error= -ENOMEM;
  47. gotoout;
  48. }
  49. /*
  50. 注意此时会锁住该页:__set_page_locked(page);
  51. 完成一次读过程,会解锁jffs2_do_readpage_unlock-> unlock_page。
  52. */
  53. error= add_to_page_cache_lru(page, mapping,
  54. index,GFP_KERNEL);
  55. }

3.5文件写操作

1)inode加锁

[cpp] view plaincopy
  1. ssize_t generic_file_aio_write(struct kiocb*iocb, const struct iovec *iov,
  2. unsignedlong nr_segs, loff_t pos)
  3. {
  4. /*此时inode被锁住*/
  5. mutex_lock(&inode->i_mutex);
  6. ret= __generic_file_aio_write(iocb, iov, nr_segs, &iocb->ki_pos);
  7. mutex_unlock(&inode->i_mutex);
  8. }

2)软件调用过程:

__generic_file_aio_write ->generic_file_buffered_write->generic_perform_write->

jffs2_write_begin:jffs2_write_end

3)jffs2垃圾回收线程jffs2_garbage_collect_thread,完成flash的实际读写操作(jffs2_flash_read/jffs2_flash_write )

写操作时jffs2_write_end->jffs2_complete_reservation->jffs2_garbage_collect_trigger()

触发垃圾回收,完成真实写flash操作。

4)页高速缓存中的页:真正操作flash前锁定该页,防止换出页高速缓存,代码实现

jffs2_write_begin ->grab_cache_page_write_begin->find_lock_page->lock_page->

__lock_page()->__wait_on_bit_lock(page_waitqueue(page),,sleep_on_page,);

操作完成后解除锁定unlock_page(),真正实现是在

_wake_up_bit(page_waitqueue(page),&page->flags, bit)中唤醒page_waitqueue(page)

/*

如果lock_page一直不成功则会一直io_schedule,此时top命令会看到io很高.

io_schedule过程系统的时间记作io时间.

1)如果同一文件的同一偏移地址被频繁读写时,可能出现该问题.

2)Lock_page时如果page已经是locked的,则需要等待unlock后,才能lock成功,等待过程中执行sleep_on_page.

*/

[cpp] view plaincopy
  1. static int sleep_on_page(void *word)
  2. {
  3. io_schedule();
  4. return0;
  5. }

4 字符设备与块设备

对于外设来说,硬件驱动本身其实并不关注字符设备还是块设备.

字符设备和块设备是实现不同用户需求的两种软件实现方式.

如flash驱动,它即是一个字符驱动如mtdchar.c中创建/dev/mtd;

又是一个块设备驱动如:mtdblock.c中创建/dev/mtdblock0;

当用户擦除flash时(如flash_eraseall)使用/dev/mtd, /dev/mtdblock中不提供ioctl接口.

当挂载文件系统时,使用/dev/mtdblock;

4.1 字符设备操作

文件系统中字符设备和块设备操作方法初始化:init_special_inode

[cpp] view plaincopy
  1. void init_special_inode(struct inode*inode, umode_t mode, dev_t rdev)
  2. {
  3. inode->i_mode= mode;
  4. if(S_ISCHR(mode)) {
  5. inode->i_fop= &def_chr_fops;
  6. inode->i_rdev= rdev;
  7. }else if (S_ISBLK(mode)) {
  8. inode->i_fop= &def_blk_fops;
  9. inode->i_rdev= rdev;
  10. }else if (S_ISFIFO(mode))
  11. inode->i_fop= &pipefifo_fops;
  12. elseif (S_ISSOCK(mode))
  13. inode->i_fop= &bad_sock_fops;
  14. }
  15. const struct file_operations def_chr_fops = {
  16. .open= chrdev_open,
  17. .llseek= noop_llseek,
  18. };

注意chrdev_open

[cpp] view plaincopy
  1. static int chrdev_open(struct inode *inode,struct file *filp)
  2. {
  3. struct cdev *p;
  4. struct cdev *new=NULL;
  5. p=inode->i_cdev;
  6. /*打开字符设备时,此处p=NULL*/
  7. if(!p)
  8. {
  9. /*此次找到kobj,再根据kobj找到cdev,注意cdev_add中添加*/
  10. kobj = kobj_lookup(cdev_map,inode->i_rdev,&idx);
  11. }
  12. /*
  13. 直接获取了字符设备驱动中注册的文件操作方法:mtd_fops
  14. 后续对字符设备的读写操作,直接用mtd_fops中的操作
  15. __register_chrdev(MTD_CHAR_MAJOR,0, 1 << MINORBITS,"mtd", &mtd_fops);
  16. */
  17. filp->f_op= fops_get(p->ops);
  18. /* 此处将fops改为cdev_init时注册的fops */
  19. replace_ops();
  20. }

创建设备结点时:register_chrdev()->_register_chrdev()

[cpp] view plaincopy
  1. test_init()
  2. {
  3. register_chrdev_region()
  4. cdev_init();
  5. /*cdev_add->kobj_map中关联设备号和cdev_map*/
  6. cdev_add();
  7. class_create();
  8. device_create()
  9. }
  10. cdev_init()
  11. {
  12. /*  注册操作函数*/
  13. cdev->ops=fops;
  14. }
  15. cdev_add()
  16. {
  17. /* 为了打开字符设备时,chrdev_open能够找到该cdev */
  18. kobj_map
  19. }

4.2 块设备操作.

1 文件系统中字符设备和块设备操作方法初始化:init_special_inode.

[cpp] view plaincopy
  1. void init_special_inode(struct inode*inode, umode_t mode, dev_t rdev)
  2. {
  3. inode->i_mode= mode;
  4. if(S_ISCHR(mode)) {
  5. inode->i_fop= &def_chr_fops;
  6. inode->i_rdev= rdev;
  7. }else if (S_ISBLK(mode)) {
  8. inode->i_fop= &def_blk_fops;
  9. inode->i_rdev= rdev;
  10. }else if (S_ISFIFO(mode))
  11. inode->i_fop= &pipefifo_fops;
  12. elseif (S_ISSOCK(mode))
  13. inode->i_fop= &bad_sock_fops;
  14. }

注意块设备中def_blk_fops是在文件系统层注册的操作方法fs/block_dev.c

此处与字符设备有区别,字符设备def_chr_fops是在驱动中注册的.

/drivers/mtd/mtdchar.c:

注意块设备没有提供ioctl操作:

[cpp] view plaincopy
  1. const struct file_operations def_blk_fops = {
  2. .open                = blkdev_open,
  3. .release  = blkdev_close,
  4. .llseek               = block_llseek,
  5. .read                 = do_sync_read,
  6. .write                = do_sync_write,
  7. .aio_read= blkdev_aio_read,
  8. .aio_write        =blkdev_aio_write,
  9. .mmap              = generic_file_mmap,
  10. .fsync                = blkdev_fsync,
  11. .unlocked_ioctl        = block_ioctl,
  12. .splice_read    = generic_file_splice_read,
  13. .splice_write   = generic_file_splice_write,
  14. };

块设备的挂载:

Mtd设备:jffs2_mount->mount_mtd().

MMC设备:vfat_mount->mount_bdev().

mount_bdev()

->找到block_device:  bdev = blkdev_get_by_path(dev_name, mode,fs_type);

->找到block_device:  bdev = lookup_bdev(path);

2 块设备创建:

如nand/nor flash驱动创建的/dev/mtdblock*:从0开始计算第1个分区如uboot分区对应:/dev/mtdblock0;

flash驱动(**_nand.c)通过mtd_device_register或mtd_device_parse_register接口根据分区表创建;

如emmc/sd驱动创建:/dev/mmcblk0p*;从1开始计算第1个分区如uboot分区对应:/dev/mmcblk0p1;通过check_partition创建;

4.3普通文件与块设备,字符设备操作差异:

[cpp] view plaincopy
  1. const struct file_operations jffs2_file_operations =
  2. {
  3. .llseek=  generic_file_llseek,
  4. .open=             generic_file_open,
  5. .read =              do_sync_read,
  6. .aio_read =      generic_file_aio_read,
  7. .write =   do_sync_write,
  8. .aio_write =    generic_file_aio_write,
  9. .unlocked_ioctl=jffs2_ioctl,
  10. .mmap=          generic_file_readonly_mmap,
  11. .fsync=   jffs2_fsync,
  12. .splice_read= generic_file_splice_read,
  13. };

以写文件为例:

1)普通文件generic_file_aio_write->__generic_file_aio_write->(jffs2_file_address_operations->write_begin)

2)块设备文件:blkdev_aio_write->__generic_file_aio_write->(def_blk_aops->write_begin = blkdev_write_begin)

3)字符设备:驱动层注册的字符设备文件操作方法(如mtd设备mtdchar_write);

4.4 def_blk_fops与mtd或mmc驱动操作方法:

def_blk_fops->(unlocked_ioctl=block_ioctl)->mtd_block_ops/ mmc_bdops ;

1)Mtd设备:

register_mtd_blktrans ->add_mtd_blktrans_dev->(gendisk ->fops = &mtd_block_ops;)

2)MMC设备:

mmc_blk_probe->mmc_blk_alloc_req->(gendisk->fops= &mmc_bdops;)

4.5 块设备写操作和vfat普通文件写操作到flash驱动的过程:

[cpp] view plaincopy
  1. staticconst struct address_space_operations def_blk_aops = {
  2. .readpage        =blkdev_readpage,
  3. .writepage      =blkdev_writepage,
  4. .write_begin   = blkdev_write_begin,
  5. .write_end      = blkdev_write_end,
  6. .writepages    = generic_writepages,
  7. .releasepage  = blkdev_releasepage,
  8. .direct_IO        = blkdev_direct_IO,
  9. };

blkdev_aio_read->do_generic_file_read->(mapping->a_ops->readpage=blkdev_readpage)

[cpp] view plaincopy
  1. static int blkdev_writepage(struct page*page, struct writeback_control *wbc)
  2. {
  3. return block_write_full_page(page, blkdev_get_block, wbc);
  4. }
  5. static int fat_writepage(struct page *page,struct writeback_control *wbc)
  6. {
  7. return block_write_full_page(page, fat_get_block, wbc);
  8. }

1)用户写操作将数据页入队列:

block_write_full_page->block_write_full_page_endio->__block_write_full_page->

submit_bh-> _submit_bh-> submit_bio->generic_make_request->

(request_queue ->make_request_fn(q,bio);)

2)将数据同步到flash或sd卡中:

以sd卡为例:

mmc_blk_probe-> mmc_blk_alloc/mmc_blk_alloc_parts-> mmc_blk_alloc_req->

mmc_init_queue-> kthread_run(mmc_queue_thread)

mmc_queue_thread->(md->queue.issue_fn= mmc_blk_issue_rq)-> mmc_blk_issue_rw_rq

mmc_blk_issue_rw_rq(mq,req)中完成真正的sd卡读写操作,其中req是mmc_queue_thread从队列struct request_queue*q = mq->queue;上获取的req = blk_fetch_request(q);。

request_queue是在第1步完成的。

Mtd设备是在         INIT_WORK(&new->work, mtd_blktrans_work);完成的。

5 页高速缓存的释放.

系统在读写文件时,会将flash中的数据保存到页高速缓存中。

用户读写时,会根据文件偏移地址,直接找到对应页进行操作,而不用从flash上读写,大大减少了文件访问的时间。当页高速缓存中的页也不能无限多,

1)一般来说,首次访问文件时,是没有这样的页的,需要我们向系统申请内存,并加入页高速缓存中。

2)在访问过程中,如果没有找到偏移地址对应页,也需要申请内存。加入页高速缓存。

那么,页高速缓存中的内存是如何释放的呢?

1 可以通过proc文件系统进行释放:

echo 3 > /proc/sys/vm/drop_caches

对应执行过程如下:

[cpp] view plaincopy
  1. int drop_caches_sysctl_handler()
  2. {
  3. if(write) {
  4. if(sysctl_drop_caches & 1)
  5. iterate_supers(drop_pagecache_sb, NULL);
  6. if(sysctl_drop_caches & 2)
  7. drop_slab();
  8. }
  9. }

2 通过kswapd释放周期性回收.

1)系统在读写文件的过程,会把页从inactive链表迁移到active链表,通过如下函数实现page迁移。值得注意的是迁移不是一次完成的。mark_page_accessed完成page从inactive链表向active链表切换,其中采用了二次机会。

[cpp] view plaincopy
  1. /*
  2. *Mark a page as having seen activity.
  3. *inactive,unreferenced        ->     inactive,referenced
  4. *inactive,referenced             ->     active,unreferenced
  5. *active,unreferenced           ->     active,referenced
  6. */
  7. void mark_page_accessed(struct page *page)
  8. {
  9. /* page-flag.h中定义 */
  10. if(!PageActive(page) && !PageUnevictable(page) &&
  11. PageReferenced(page)&& PageLRU(page)) {
  12. activate_page(page);
  13. ClearPageReferenced(page);
  14. }else if (!PageReferenced(page)) {
  15. SetPageReferenced(page);
  16. }
  17. }

举例:当我们对一个文件进行读操作do_generic_file_read,中会将文件页:

mark_page_accessed(struct page *page)

执行前:inactive,unpreference;执行后:inactive,preference;

如果再次操作同一文件同一偏移地址,则该页被标记为active,preference

3) 与mark_page_accessed功能相反的是page_referenced:page从active向inactive表切换.每当置换算法扫描一次页面,就老化一次。

真正释放可以通过如下方式:

守护进程释放kswapd_init->kswapd->balance_pgdat->shrink_zone->shrink_lruvec->shrink_list

在此,需要注意的是只有在内存少于特定阈值(__zone_watermark_ok)时kswapd才会回收。

3 对于映射页(或文件页)来说,系统在申请page后会执行如下操作:

do_generic_file_read ->add_to_page_cache_lru–>add_to_page_cache_locked-> add_to_page_cache

如此会:

1>锁住该页:

[cpp] view plaincopy
  1. static inline void __set_page_locked(structpage *page)
  2. {
  3. __set_bit(PG_locked,&page->flags);
  4. }

2> 初始化page->mapping为file->f_mapping。

这一点与匿名页的明显不同:

匿名页:handle_pte_fault->do_wp_page->page_move_anon_rmap

[cpp] view plaincopy
  1. void page_move_anon_rmap(struct page *page,
  2. structvm_area_struct *vma, unsigned long address)
  3. {
  4. structanon_vma *anon_vma = vma->anon_vma;
  5. anon_vma= (void *) anon_vma + PAGE_MAPPING_ANON;
  6. page->mapping = (structaddress_space *) anon_vma;
  7. }

系统也可以根据page的mapping变量,来判断该页是匿名页还是文件页:PageAnon()。

3>page_cache_get-> atomic_inc(&page->_count);

[总结]

1 访问文件系统,首先要进行mount,mount过程中系统创建超级块(super_block)结构。该结构初始化了访问文件系统的必要信息,比如文件系统类型.
2 创建超级块的过程会同时创建超级块的根inode和相应的根目录项(“/”为目录项名)。可参看上文。
3 一个普通文件或目录文件在内核中都同时对应一个目录项dentry和inode结构。一般来说,根目录项和根inode是在mount过程创建,其他的是open,mkdir等过程创建。
4 vfs层文件访问是通过struct file结构进行的,open->do_dentry_open过程会对file进行初始化.

linux文件系统实现原理简述【转】相关推荐

  1. 红叶李之Linux文件系统

    Linux文件系统 inode block superblock inode table : inode 元数据 block的存放地址data area : data元数据:文件的属性信息 大小 创建 ...

  2. 图解 Linux 文件系统

    之前我写过有关 Linux 文件系统源码分析的文章,但从源码角度分析文件系统略显枯燥(对新手不友好),所以这次主要通过图文的方式来讲解 Linux 文件系统的原理,而不用陷入源代码的深渊之中. 一.硬 ...

  3. 简述Linux文件系统通过i节点把文件的逻辑结构和物理结构转换的工作过程。

    简述Linux文件系统通过i节点把文件的逻辑结构和物理结构转换的工作过程. 参考答案: Linux通过i节点表将文件的逻辑结构和物理结构进行转换. i 节点是一个64字节长的表,表中包含了文件的相关信 ...

  4. Linux文件系统IO:直接IO原理与实现:缓存I/O、直接I/O

    目录 缓存I/O 缓存I/O的优缺点 直接I/O 直接I/O实现 - direct_IO(), brw_kiovec() 推荐阅读 缓存I/O 一般来说,当调用 open() 系统调用打开文件时,如果 ...

  5. 简述 Linux 文件系统的目录结构

    Linux文件系统结的结构是树形结构,其入口从/开始,了解Linux文件系统的结构,对于我们需要掌握的基础知识点之一. 1.什么是文件系统: 请参见:<Linux 文件系统概述> 2.文件 ...

  6. Linux 文件系统原理 / 虚拟文件系统VFS

    Linux 文件系统原理 / 虚拟文件系统VFS 虚拟文件系统 VFS VFS 定义 VFS 的对象演绎 超级块 super_block 索引节点 inode 目录项 dentry 文件 file 文 ...

  7. Linux 文件系统的工作原理深度透析

    磁盘为系统提供了最基本的持久化存储. 文件系统则在磁盘的基础上,提供了一个用来管理文件的树状结构. 那么,磁盘和文件系统是怎么工作的呢?又有哪些指标可以衡量它们的性能呢? 索引节点和目录项 文件系统, ...

  8. 简述Linux 文件系统的目录结构

    转自:http://www.linuxsir.org/main/node/189 作者:北南南北 来自:LinuxSir.Org 摘要: Linux文件系统是呈树形结构,了解Linux文件系统的目录结 ...

  9. Linux的vx开头的文件,linux文件系统简述

    一.前言 文件系统的其实相当于网络中传输层的概念,负责组织数据,而硬盘驱动则相当链路层,只负责读写二进制数据,不会关心内容,文件系统需要组织数据存储,却也不关心有效载荷的内容. 一个linux文件通常 ...

最新文章

  1. linux挂载卸载不掉 umount target is busy
  2. php复制整个文件夹,PHP实现递归复制整个文件夹的类实例
  3. 计算机c语言二级题型,计算机二级C语言题型和评分标准
  4. 产品经理的肾,是怎么坏的?
  5. 它是那么的渺小freeeim
  6. win10应用安装位置修改方法
  7. 【游戏】基于matlab绘制滚动点阵字幕(跑马灯)【含Matlab源码 911期】
  8. RxBus的使用及解析
  9. java里如何继承一个类_java如何继承类
  10. 云解析 dns 服务器,你知道为什么云解析DNS又快又安全吗?
  11. Radon变换及其Matlab代码实现
  12. RestTemplate 下载文件
  13. 图的遍历 —— 广度优先遍历
  14. 小O地图EXE版V0.9.5.5 - 功能总览
  15. QIIME 2基因云,登10分JHM
  16. sudoku me_Sudoku,一个完整的MFC应用程序。 第7部分
  17. IDEA 解决import类飘红
  18. touchdesigner音频可视化
  19. AWS学习日志之SAA
  20. 2022年全国职业技能大赛网络安全竞赛试题B模块自己解析思路(3)

热门文章

  1. 中层管理者应该做什么?
  2. 01【刘立刚图形学笔记】_图形学整体概述
  3. 学java被“劝退”的第九天
  4. 干货:一文读懂数据仓库设计方案
  5. win 运行scrapy warring UserWarning: You do not have a working installation of the service_identity mo
  6. 《keep studying》————《持续学习》英译汉【istrangeboy精品英文励志短文系列】
  7. 占位符语法-Scala
  8. 2020-09-02
  9. 深度学习(李沐)—————Softmax回归
  10. 快速上手爬虫,有哪些方便实用的工具和服务?