7
18
2014
0

模块化系统盘正在使用的文件系统类型

背景:

在我的博客将文件系统模块化一文中提到了系统盘所在分区的文件系统,不能运用那篇文章介绍的方法。原因很简单:举例说明:

系统盘所在的分区为/dev/sda1。该分区的文件系统内类型为ext4。启动系统,为保证系统能运行,系统就自动将内核中的ext4.ko插入到模块中了。所以已经不给你机会再插入自己编译的该模块了。

那如果我偏偏就像研究ext4文件系统呢,难不成要重装系统,然后把系统盘改成ext2,或者ext3格式~~~~No No!!用不成那么麻烦。其实呢方法很简单,就是把ext4模块重命名,比如说命名成a_ext4,嘿嘿~系统很傻的,这样它就会报重名的错误了。

具体怎么做呢?见下文,还是有一些需要注意的地方。

方法:

这里的具体做法参见文件系统模块化

step1:编译内核

将ext4改成M模式

step2:修改ext4的Makefile文件。

运行重新编译的内核。将内核源码目录下\linux-2.6.34.14\fs\ext4文件拷贝到/home/modulefs文件夹,修改/home/modulefs/ext4中的Makefile文件:

源文件如下:
 
#
# Makefile for the linux ext4-filesystem routines.
#

obj-$(CONFIG_EXT4_FS) += ext4.o

ext4-y	:= balloc.o bitmap.o dir.o file.o fsync.o ialloc.o inode.o \
		ioctl.o namei.o super.o symlink.o hash.o resize.o extents.o \
		ext4_jbd2.o migrate.o mballoc.o block_validity.o move_extent.o

ext4-$(CONFIG_EXT4_FS_XATTR)		+= xattr.o xattr_user.o xattr_trusted.o
ext4-$(CONFIG_EXT4_FS_POSIX_ACL)	+= acl.o
ext4-$(CONFIG_EXT4_FS_SECURITY)		+= xattr_security.o

 

修改成如下

 
#
# Makefile for the linux ext4-filesystem routines.
#

obj-m += a_ext4.o

a_ext4-objs     := balloc.o bitmap.o dir.o file.o fsync.o ialloc.o inode.o \
                ioctl.o namei.o super.o symlink.o hash.o resize.o extents.o \
                ext4_jbd2.o migrate.o mballoc.o block_validity.o move_extent.o
KERNELDIR:=/usr/src/linux-2.6.34.14/
PWD:=$(shell pwd)
a_ext4-$(CONFIG_EXT4_FS_XATTR)          += xattr.o xattr_user.o xattr_trusted.o
a_ext4-$(CONFIG_EXT4_FS_POSIX_ACL)      += acl.o
a_ext4-$(CONFIG_EXT4_FS_SECURITY)               += xattr_security.o
default:
        make -C $(KERNELDIR) M=$(PWD) modules
clean:
        rm -rf *.o *.mod.c *.ko *.symvers

与上篇博客文件系统模块化中修改Makefile相比,可以发现我们将ext4模块重新命名了,命名为a_ext4

 

step3.修改内核代码

既然在我们改变了模块中ext4的名字,改成了a_ext4,则在/home/modulefs/ext4的代码中我们也做出相应的改变。

修改/home/modulefs/ext4/super.c中的如下内容

static struct file_system_type ext4_fs_type 以及

static int __init init_ext4_fs(void)

源文件如下:

static struct file_system_type ext4_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "ext4",
	.get_sb		= ext4_get_sb,
	.kill_sb	= kill_block_super,
	.fs_flags	= FS_REQUIRES_DEV,
};

static int __init init_ext4_fs(void)
{
	int err;

	ext4_check_flag_values();
	err = init_ext4_system_zone();
	if (err)
		return err;
	ext4_kset = kset_create_and_add("ext4", NULL, fs_kobj);
	if (!ext4_kset)
		goto out4;
	ext4_proc_root = proc_mkdir("fs/ext4", NULL);
	err = init_ext4_mballoc();
	if (err)
		goto out3;

	err = init_ext4_xattr();
	if (err)
		goto out2;
	err = init_inodecache();
	if (err)
		goto out1;
	register_as_ext2();
	register_as_ext3();
	err = register_filesystem(&ext4_fs_type);
	if (err)
		goto out;
	return 0;
out:
	unregister_as_ext2();
	unregister_as_ext3();
	destroy_inodecache();
out1:
	exit_ext4_xattr();
out2:
	exit_ext4_mballoc();
out3:
	remove_proc_entry("fs/ext4", NULL);
	kset_unregister(ext4_kset);
out4:
	exit_ext4_system_zone();
	return err;
}

 

把如下三处'ext4' 修改成a_ext4
修改前

.name = "ext4",

ext4_kset = kset_create_and_add("ext4", NULL, fs_kobj);

ext4_proc_root = proc_mkdir("fs/ext4", NULL);

修改后

.name = "a_ext4",

ext4_kset = kset_create_and_add("a_ext4", NULL, fs_kobj);

ext4_proc_root = proc_mkdir("fs/a_ext4", NULL);

 

step4:编译模块,然后插入模块

具体做法,同文件系统模块化

这里注意格式化和挂载磁盘时指令如下:

make clean

make

insmod a_ext4

mkfs -t a_ext4 /dev/ram0

mount /dev/ram0 /mnt/a_ext4

 

 总结:

    其实模块化系统盘所在分区文件系统的具体做法(称为A) 与 文件系统模块化(称为B) 有两个不一样:

1.A的Makefile需要改变模块的名字,B不需要

2.A需要修改内核的代码,B不需要

 

7
17
2014
10

Linux内核 ext2 文件系统 makefile详解


先给出两个makefile相关链接:

http://www.yayu.org/book/gnu_make/make-04.html

http://www.yayu.org/book/gnu_make/make-06.html

例1:linux-2.6.34.14\fs\ext2\makefile详解​​

#
# Makefile for the linux ext2-filesystem routines.
#

obj-$(CONFIG_EXT2_FS) += ext2.o

ext2-y := balloc.o dir.o file.o ialloc.o inode.o \
	  ioctl.o namei.o super.o symlink.o

ext2-$(CONFIG_EXT2_FS_XATTR)	 += xattr.o xattr_user.o xattr_trusted.o
ext2-$(CONFIG_EXT2_FS_POSIX_ACL) += acl.o
ext2-$(CONFIG_EXT2_FS_SECURITY)	 += xattr_security.o
ext2-$(CONFIG_EXT2_FS_XIP)	 += xip.o

1.obj-$(CONFIG_EXT2_FS) += ext2.o  obj- 表示如何编译 y m n分别表示进入内核编译,m 模块编译,n不编译。其中$(CONFIG_EXT2_FS)表示取CONFIG_EXT2_FS值操作,所以ext2的编译方式取决于编译内核是make config时CONFIG_EXT2_FS选定的值。

 

2.ext2-y或者ext2-objs都表示ext2模块(注:-objs用法见例二)

 

3.:= 表示赋值操作

 

4.ext2-y := balloc.o dir.o file.o ialloc.o inode.o \

 ioctl.o namei.o super.o symlink.o 表示ext2.o模块依赖于后面的*.o文件。-y和-objs都表示模块。
 

5.+= 表示追加方式复值:

objects = main.o foo.o bar.o utils.o

objects += another.o

上边的两个操作之后变量“objects”的值成:“main.o foo.o bar.o utils.o another.o”。使用“+=”操作符,就相当于:

objects = main.o foo.o bar.o utils.o

objects := $(objects) another.o

 

6.ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o xattr_user.o xattr_trusted.o  表示如果make config步骤中CONFIG_EXT2_FS_XATTR赋值为y,则ext2模块在前面的编译基础上再加上xattr.o xattr_user.o xattr_trusted.o三个文件。

 

7ext2-$(CONFIG_EXT2_FS_POSIX_ACL) += acl.o

ext2-$(CONFIG_EXT2_FS_SECURITY) += xattr_security.o
ext2-$(CONFIG_EXT2_FS_XIP) += xip.o
这三行解释同6
 
 
例2.pcmfs的makefile详解:
#
# Makefile for the Linux pcmfs filesystem routines.
#

obj-m += simplefs.o
simplefs-objs := bitmap.o itree_common.o namei.o inode.o file.o dir.o
KERNELDIR:=/usr/src/linux-2.6.34.14/
PWD:=$(shell pwd)
default:
	make -C $(KERNELDIR) M=$(PWD) modules
clean:
	rm -rf *.o *.mod.c *.ko *.symvers

1.obj-m直接指定编译方式为模块化

2.simplefs-objs := bitmap.o itree_common.o namei.o inode.o file.o dir.o  其中-objs为模块。表示simplefs.o模块依赖后面的文件。

3.KERNELDIR:=/usr/src/linux-2.6.34.14/ 将变量KERNELDIR赋值,为后面提供使用

4.PWD:=$(shell pwd)赋值为当前目录

5.make -C $(KERNELDIR) M=$(PWD) modules

 其中make的 -C表示转换路径,可以再linux下通过man make查看:

        -C dir, --directory=dir

            Change to directory dir before reading the makefiles  or  doing
            anything  else.   If multiple -C options are specified, each is
            interpreted relative to the previous one: -C / -C etc is equiv‐
            alent  to -C /etc.  This is typically used with recursive invo‐
            cations of make.
 
 其中make M=的含义:M不是make中的选项,而是内核根目录下的makefile中使用的变量。M= dir 程序会自动在所指定的dir目录中查找模块代码,将其编译,生成.ko文件
 
 
 
 

 

6
20
2014
0

文件系统写浅析--博客阅读笔记

博客阅读笔记

http://hi.baidu.com/_kouu/item/4e9db87580328244ef1e53d0

文件读写浅析笔记

1.VFS.

how f_op->read/f_op->write被掉用,见http://hi.baidu.com/_kouu/item/6bfca5cc5d9778d4964452d0

 

2.Disk Caches 磁盘高速缓存

磁盘上数据缓存在内存中,加速读写。一般,read/write只与缓存打交道。

how实现:每个文件的inode内嵌了一个address_space结构,通过inode->i_mapping访问。address_space结构维护了一颗radix树,高速缓存的内存页面就挂在该树上。故磁盘高速缓存与inode相关,打开文件的每一个进程共用一份缓存。

inode刚被载入内存时,对应的radix树为空,随着读写进行,radix树逐渐有了内容。

当需要读写的内容未载入radix树,read/write与底层的“通用块层”发起读请求,将数据读入。

若打开文件指定了O_DIRCET,则绕开磁盘高速缓存,直接与“通用块层”打交道

内核线程pdflush定期将每个inode上的脏页更新到磁盘,同时将radix页面回收。

 

3.Generic Block Layer 通用块层

linux内核把块设备抽象成统一的模型,即由扇区组成的数据空间。扇区是磁盘设备读写的最小单位。

上层的读写请求在通用块层被构造成了一个或多个bio结构

上层的一次请求可能跨多个扇区,形成非连续扇区段。每个扇区段,对应一个bio结构。

接下来拓展讨论内存的组织。上层的一次请求肯能跨扇区,故它页可能跨磁盘高速缓存上的多个页。故一个bio可能对应一组内存页。内存页单独分配,故内存地址可能不连续。块设备访问能一组连续扇区,关于是否能访问不连续的内存地址,这与DMA有关。

块设备一般通过DMA,将开设备上的一组连续扇区的数据拷贝到一组连续的内存页面。这里不扩展了。结论是,支持io-mmu体系,则可以在一次DMA中完成。若不支持,则一个bio在设备驱动程序中被拆分为多个设备请求。

每个bio都会提交到IO调度器中

 

4 I/O SchedulerLayer I/O调度器层

IO调度器要做的是,在完成所有任务的前提下,让磁头尽量少移动,从而提高磁盘读写效率。

在IO调度器中,上层提交的bio构造成request,一个request结构中包含了一组顺序的bio,每一个物理设备对应一个request_queue,里面顺序放着request。

新的bio可能被合并request_queue中已有的request结构中(甚至合并到已有的bio中),也可能生成新的request插入到request_queue对应位置。这取决于IO调度算法。

IO调度器除了重排请求顺序,还可能延迟触发队请求的处理。因为在请求队列有一定数目,才用调度算法,否则,先来先服务

延迟请求是对request_queue的plug/unplug实现的。plug停用,unplug恢复。请求少时request_queue停用,请求到达一定数目或者超市,request_queue才恢复,恢复时,驱动程序提供的回调函数被调用,于是程序开始处理request_queue。

一般来说,read/write系统调用到这里就返回了。

 

5.Device Driver 设备驱动程序

设备驱动层从request_queue中取出请求,操作硬件设别,逐个执行这些请求。

设备驱动程序有权选择IO调度算法。甚至可将IO调度层屏蔽,直接对bio处理。即,IO调度器是内核提供给设备驱动程序的一组方法,使用与否,是否使用,选择权在于设备驱动程序。

Category: 文件系统 | Tags: 文件系统;
5
27
2014
0

list_entry函数

在文件系统内核代码中有list_entry函数,详细解说见:

http://blog.csdn.net/chuchuanchuan/article/details/8138009

http://blog.csdn.net/sh_sige/article/details/9814673

list_entry的宏定义如下:

#define list_entry(ptr,type,member) container_of(ptr,type,member),由于containner_of宏中包含offsetof宏,故如下分析这两个宏

1.#define offsetof(TYPE,MEMBER) ((size_t)&((TYPE *)0)->MEMBER)

宏功能:获得一个结构体变量在该结构体中的偏移量。

#include <stdio.h>
#define offsetof(TYPE, MEMBER) ((int)(&((TYPE *)0)->MEMBER))
struct _test_{
    int  x;
    int  y;
    float z;
};

int main(void)
{
 int temp = -1;
 temp = offsetof(struct _test_, z);
 printf("temp = %d\n", temp);
 return 0;
}

 运行结果:temp=8

即求出结构体成员变量z在结构体中的偏移为8

 

2 #define container_of(ptr,type,member)({const typeof(((type *)0)->member) *_mptr=(ptr); (type *)((char *)_mptr-offsetof(type,member));})

宏功能:由结构体(type)某成员变量(member)指针(ptr),求出该结构体(type)的首指针。ps 该首指针是包含该成员变量实例的首指针。

#include <stdio.h>
#define offsetof(TYPE, MEMBER) ((int)(&((TYPE *)0)->MEMBER))

#define container_of(ptr, type, member) ({   \
        const typeof( ((type *)0)->member ) *__mptr = (ptr); \
        (type *)( (char *)__mptr - offsetof(type,member) );})

struct _test_
{
 int  x;
 int  y;
 int  z;
};

void Assignment(struct _test_ *t)
{
 t->x = 1;
 t->y = 2;
 t->z = 3;
}

void GetheadPoint(int *tz)//传入成员变量的指针tz,即可以得到该成员变量首指针temp
{
 struct _test_ *p;
 int temp = -1;
 p = container_of(tz,struct _test_, z);   //根据成员变量的地址获得该结构体的首地址
 temp = p->y;                            //根据首地址获得其中另外一个成员变量的值
 printf("line31 = %d\n", temp);
}

int main(void)
{
 int temp = -1;
 struct _test_ tmp;                       //定义一个结构体变量

 Assignment(&tmp);                        //给这个变量赋值
 GetheadPoint(&tmp.z);                    //只传给这个函数一个结构体成员变量的地址

 printf("line43 tmp - >x = %d\n", tmp.x); 
 return 0;
}

运行结果:由getheadpoint函数得知,由穿入成员变量指针,可以得到该struct实例的首指针

Category: 文件系统 | Tags:
5
26
2014
0

硬链接&软链接

硬链接是一个指针,指向文件的索引结点。不为其分配新的inode。

软连接要分配新的inode,完成dentry->inode->block的分配(dentry中的name就是创建软连接文件的文件名)。block指向另一个dentry文件。

 

先看硬链接、软连接的构造方法。

删除一个文件或者目录。实际上是把inode的链接数减1,链接数是统计的硬链接,不影响其他指向此inode的链接。而会对指向该文件和目录的软链接有影响。

所以说硬链接是直接指向文件的inode,而软连接是指向目录的。

在截图中我们可以看出,ls -F 指令,软连接用@标识

上图中第一列是inode号,第三列是硬链接数,可以看到a.txt和b.txt是共享一个inode号的,且文件属性相同。由于共享了inode号,故不同的文件系统中是不能建立硬链接的。

而软链接c.txt与a.txt,b.txt的inode号以及文件属性都不同。即软连接有自己的inode号。

此时a.txt和b.txt的硬链接数都是2

以下两点软硬链接用法区别

1.硬链接不许对目录创建,而软链接而对目录创建

原因,有两点:

1.防止遍历时出现死循环。软连接之所以不会出现死循环是因为系统可以在遍历目录时,可以判断出这是一个符号链接,若连续出现符号链接超过某个此处,则终端循环。

2.dentry(目录和文件都有自己的dentry)中有一个指向父目录的指针。若父目录中有两个dentry地址,则该目录项中的每个dentry的parent向不知道指向哪个了。

 

2.硬链接不能跨文件系统,软链接可以跨文件系统

 

硬链接实现的是多个文件名指向同一个inode,即多个dentry对应一个inode。也就是每个硬链接对应一个目录项

这部分没有完全描述清楚,之后又了解再及时补充

 

下面插入一幅图:

 

通过上图我们可以上到软连接的数据区实际上指向的是源文件的dentry,所以说:

对于软连接:创建一个软链接,inode的引用计数不会增加,删除被链接的源文件,则相关软连接变成了死链接。

对于硬链接:创建硬链接,inode的引用计数会加1.

这部分有参考:

http://www.cnblogs.com/stli/archive/2010/11/10/1873212.html

http://blog.csdn.net/kension/article/details/3796603

Category: 文件系统 | Tags:
5
26
2014
1

dentry与inode

首先看dentry数据结构。位于include/linux/dcache.h中 struct dentry

ps:dentry虽然是目录的意思,但是在vfs中,目录和文件都有自己的dentry。(dentry中存了文件名,同一文件存在别名就是这个结构实现的)

struct dentry {
atomic_td_count;目录项对象使用计数器
unsignedintd_flags;目录项标志
structinode*d_inode;与文件名关联的索引节点
structdentry*d_parent;父目录的目录项对象
structlist_headd_hash;散列表表项的指针
structlist_headd_lru;未使用链表的指针
structlist_headd_child;父目录中目录项对象的链表的指针
structlist_headd_subdirs;对目录而言,表示子目录目录项对象的链表
structlist_headd_alias;相关索引节点(别名)的链表
intd_mounted;对于安装点而言,表示被安装文件系统根项
structqstrd_name;文件名
unsignedlongd_time;/*usedbyd_revalidate*/
structdentry_operations*d_op;目录项方法
structsuper_block*d_sb;文件的超级块对象
vunsignedlongd_vfs_flags;
void*d_fsdata;与文件系统相关的数据
unsignedchard_iname[DNAME_INLINE_LEN];存放短文件名
};

再看inode数据结构。include/linux/fs.h

struct inode {/*vfs中数据结构 数据结构中各项的含义见 Linux 内核 465页*/
	struct hlist_node	i_hash;
	struct list_head	i_list;		/* backing dev IO list */
	struct list_head	i_sb_list;
	struct list_head	i_dentry; /*引用该索引节点的目录项对象链表表头*/
	unsigned long		i_ino;/*索引节点号*/
	atomic_t		i_count;
	unsigned int		i_nlink;
	uid_t			i_uid;
	gid_t			i_gid;
	dev_t			i_rdev;
	unsigned int		i_blkbits;
	u64			i_version;
	loff_t			i_size;
#ifdef __NEED_I_SIZE_ORDERED
	seqcount_t		i_size_seqcount;
#endif
	struct timespec		i_atime;
	struct timespec		i_mtime;
	struct timespec		i_ctime;
	blkcnt_t		i_blocks;
	unsigned short          i_bytes;
	umode_t			i_mode;
	spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
	struct mutex		i_mutex;
	struct rw_semaphore	i_alloc_sem;
	const struct inode_operations	*i_op;/*与索引结点有关的操作*/
	const struct file_operations	*i_fop;	/* former ->i_op->default_file_ops */
	struct super_block	*i_sb;
	struct file_lock	*i_flock;
	struct address_space	*i_mapping;
	struct address_space	i_data;
#ifdef CONFIG_QUOTA
	struct dquot		*i_dquot[MAXQUOTAS];
#endif
	struct list_head	i_devices;
	union {
		struct pipe_inode_info	*i_pipe;
		struct block_device	*i_bdev;
		struct cdev		*i_cdev;
	};

	__u32			i_generation;

#ifdef CONFIG_FSNOTIFY
	__u32			i_fsnotify_mask; /* all events this inode cares about */
	struct hlist_head	i_fsnotify_mark_entries; /* fsnotify mark entries */
#endif

#ifdef CONFIG_INOTIFY
	struct list_head	inotify_watches; /* watches on this inode */
	struct mutex		inotify_mutex;	/* protects the watches list */
#endif

	unsigned long		i_state;
	unsigned long		dirtied_when;	/* jiffies of first dirtying */

	unsigned int		i_flags;

	atomic_t		i_writecount;
#ifdef CONFIG_SECURITY
	void			*i_security;
#endif
#ifdef CONFIG_FS_POSIX_ACL
	struct posix_acl	*i_acl;
	struct posix_acl	*i_default_acl;
#endif
	void			*i_private; /* fs or device private pointer */
}

"文件"即按一定形式存储在介质上的信息,该信息包含两方面,其一是存储的数据本身,其二是概述的索引。在内存中,每个文件都有一个inode和dentry结构。dentry记录文件名,上级目录,子目录等信息,正是我们看到的树状结构;inode记录着文件在存储介质上的位置和分布,dentry->d_inode指向对应的inode结构。inode代表物理意义上的文件,通过inode可以得到一个数组,这个数组记录文件内容的位置,若数组为(4,5,9),则对应数据位于硬盘的4,5,9块。其索引节点号为inode->ino,根据ino就可以计算出对应硬盘中inode的具体位置。


dentry所描述的是逻辑意义上的文件,所描述的是文件逻辑上的属性,所以dentry在磁盘上没有对应的影像;而inode结构代表的是物理意义上的文件,故磁盘上也有inode结构

inode结构中有一个队列i_dentry,凡是代表同一个文件的所有目录项都通过d_alias域挂入响应的inode结构中的i_dentry队列。


进程打开一个文件,就会有一个file结构与之对应,同一个进程可以多次打开同一个文件而得到多个不同的file结构,file结构描述了被打开文件的属性,读写偏移指针等

两个不同的file可以对应同一个dentry结构,进程多次打开一个文件,对应只有一个dentry结构,dentry存的是目录项和对应文件的inode的信息。

在介质中,每个文件对应一个inode结点,每个文件可有多个文件名,即可以通过不同的文件名访问同一个文件,多个文件名对应一个文件的关系就是数据结构中dentry和inode多对一的关系。inode中不存储文件名字,只有节点号,通过节点号(ino),可以找到数据在介质中的具体位置,即通过内存inode结构中的ino可以定位到磁盘上的inode结构;而dentry则保存文件名和对应的节点号(inode号),这样就可以实现不同文件名访问一个inode。不同的dentry是通过ln指令实现的。


dentry树描绘了文件系统目录结构,但整个目录结构不能长驻内存,因为非常大。内存装不下。

初始状态下,系统只有代表根目录的dentry和所指向的inode(在根文件系统中挂载生成)。此时要打开一个文件,文件路径对应的结点不存在,根目录的dentry无法找到需要的子节点。这时需要通过inode->i_op中lookup方法找到inode的子节点,找到后,再创建一个dentry与之关联。

由这个过程可以看出。现有inode再有dentry。

当生成的dentry无人使用时被释放,d_count字段记录了dentry的引用计数,引用为0时,dentry被释放。这里的释放不是直接销毁,而是将dentry放入一个“最近最少使用”队列。当队列过大,内存紧缺时,dentry才被释放。这个LRU队列就像是一个缓存池, 加速了对重复的路径的访问. 而当dentry被真正释放时, 它所对应的inode将被减引用. 如果引用为0, inode也被释放.

故需找一个文件路径是有三种情况:

1.dentry引用大于0,直接在dentry树种找

2.dentry不在树种,在lru队列中找,LRU队列中的dentry被散列到一个散列表中,便于查找,若找到,则重新添加到dentry树种。

3.若2也未找到,则区找inode,找到后,再创建对应的dentry

这里补充每个目录项对象可以出于以下四种状态之一:

1:空闲状态  即该目录项不包含有效的信息,且没被VFS使用。

2.未使用状态  该对象的d_count计数为0,但d_inode指向关联的索引节点

3.正在使用状态  该对象的d_count计数不为0,但d_inode指向关联的索引节点

4.负状态   与目录项关联的索引结点不存在了


mount过程:

linux首先找到磁盘分区的super block,然后通过解析磁盘的inode table与file data。构建出自己的dentry列表和inode列表。VFS是按照Ext的方法进行构建的,两者非常相似。如inode结点。ext_inode结点中的一些成员变量其实是没有用的,如引用计数等,保留目的是为了和vfs-inode保持一致,这样在用ext_inode构建vfs_inode时,就不需要一个个赋值,只需要一次拷贝。

故非ext格式的文件系统,mount的过程会慢一些


根目录有一个dentry结构,而根目录里的文件盒目录都链接到这个跟dentry;同样的道理,二级目录里的文件和目录链接到二级目录,这样一层层,就形成了一颗dentry树。从树顶可以遍历整个文件系统。

为了加快对dentry的查找,内核使用hash表来缓存dentry,称为dentry cache,dentry一般现在dentry cache中查找。

 

dentry结构里有d_subdirs成员和d_child成员。d_subdirs是子项的链表头,所有的子项都要链接在这个链表上,d_child是自身链表头,需要连接到父dentry的d_subdirs成员。

d_parent是指针,指向父dentry结构。

d_hash是连接到dentry cache的hash链表。

d_name保存目录或文件的名字。打开一个文件的时候,根据这个成员和用户输入的名字来搜寻目标。

d_mounted用来指示dentry是否是一个挂载点。如果是,则成员不为零。


dentry的hash定位,通过d_hash()函数将父目录dentry的地址和所要查找的文件名的hash值结合起来,重新计算一个hash值,并根据其定位到dentry_hashtable哈希表中(即定位某个表头,缩小查找范围),该表是dentry缓存一部分,接下来扫描该链表,从中找到目标dentry,若没有找到,则通过real_lookup()函数从磁盘中查找。


目录项对象存放在dentry_cache的slab高速缓存中。故目录项的创建和删除是通过kmem_cache_alloc()和kmem_cache_free()实现的。目录项高速缓存由两种类型数据结构组成:

1.处于正在使用、未使用或负状态的目录项对象的集合。

2.一个散列表。用于hash快速查找给定文件名和目录名对应的目录项对象。散列表是由dentry_hashtable数组实现。数组中每个元素是一个指向链表的指针。

 

参考:

http://book.2cto.com/201312/38229.html

http://blog.csdn.net/vanbreaker/article/details/8208173
http://blog.chinaunix.net/uid-20184656-id-3151111.html

http://blog.csdn.net/fudan_abc/article/details/1775313

Category: 文件系统 | Tags:
5
13
2014
37

pcmfs的mkfs分析

在文章最前面说一下mkfs的意义:即在dev设备上建立相应的数据布局。也就是你输入mkfs指令后,要跟上一个设备参数,于是乎,mkfs执行完毕后,就有了常见的数据布局。

在我们这个mkfs函数中,最终数据布局如下

 

 

一:函数流程图

下面是mkfs.pcmfs.c函数流程图

          图1 流程图

解释:

1.根据参数获取blocksize

   在输入mkfs指令时,要输入设备参数。如mkfs.pcmfs /dev/sdb1。该指令就是把/dev/sdb1设备格式化为pcmfs文件系统。Main函数会获取参数的设备名,然后通过get_size函数计算出该设备的字节数。在计算出设备的字节数后,在根据blocksize大小计算出该设备的block总数。    get_size函数获取设备大小主要通过count_block函数实现。基本思想是通过read函数返回值得正负来判断是否越界。该函数先利用二乘的思想来确定该dev的粗略的范围,即n未越界,则下次判断2n是否越界。若2n越界则可判断该设备大小在n与2n之间,粗略判断后在通过在n和2n之间的二分查找确定该设备的具体大小。

2.计算block数目

  在上述操作后获取了该设备的字节数,于是可以通过BLOCKS=get_size(device_name)/blocksize,来得到该设备包含的block数目。

3.初始化superblock以及bitmap所需要的数据结构

    Mkfs的目的是格式化非易失介质,即将非意识介质上文件系统的必要的信息准备好,而superblock以及bitmap的结构是文件系统格式化时写入非易失介质的重要信息,将该信息写入到非意识介质中有两步,第一步是在内存中申请一个对应的空间,将该空间的内容赋值成我们所需要写入非易失介质中的数据;第二步就是通过调用write函数将该数据写入到介质当中。

    mkfs.pcmfs.c文件通过setup_tables函数在内存中准备好要写入非易失介质中的superblock信息以及bitmap区域的信息,其中bitmap有inode的bitmap以及block的bitmap。分别对应着IMAPS以及ZMAPS。

    Superblock的该文件系统整体信息的赋值,如第一个data区域所在的block编号,IMAP、ZMAP所占的block数目的赋值等。在该pcmfs文件系统中inode的bitmap占两个block,block的bitmap占8个block。

    同样在内存中申请IMAPS和ZMAPS所占的空间大小,然后通过memset的函数把该区域的数据置0。

4. 初始化根节点inode的数据结构

    在初始化文件系统时,必须初始化根节点的信息,整个系统才有了开始索引、操作的入口。根节点所指向的数据区域是整个data区的第一个数据块。即inode->i_zone[0] =Super.s_firstdatazone;

    由于根节点是一个目录,所以其数据区其实就是一个个的目录项。每个目录项占16个字节。目录项的定义在头文件Pcmfs_fs.h中。目录项如下

struct pcmfs_dir_entry {

       __u16 inode;

       char name[0];

}

   初始化是需要在目录项中写入两个内容。即当前目录的inode号,以及父目录的inode号。由于为根节点,故父目录的inode号与当前目录inode号实则是一样的。写入的两项内容如下

#define ROOT_INO_STRING "\001\000"

static char root_dentry[BLOCK_SIZE] =

ROOT_INO_STRING ".\0\0\0\0\0\0\0\0\0\0\0\0\0"

ROOT_INO_STRING "..\0\0\0\0\0\0\0\0\0\0\0\0";

其中ROOT_INO_STRING是inode号,".\0\0\0\0\0\0\0\0\0\0\0\0\0"为该inode号对应内容。这里实则填充了两项,表示的是当前目录 . 以及父目录 .. 的inode号。为了方便理解,见下图

图2 dentry结构图

如上图,每个目录的数据区都可理解为上述表,每一个表项有16个字节,前两个表项的文件名是固定的,分别是当前目录对应的inode号以及父目录对应的inode号。上述初始化实则初始前两项。在内存中准备好该数据后,在通过write函数写到非易失介质中。

5.将初始化的数据写到介质中

   该功能更通过write_tables函数实现。该函数主要通过调用write函数将准备好在内存中的超级块的内容以及bitmap的内容写入到介质中。这里指的注意的是write函数要按顺序写。由于pcmfs文件系统的整体结构图如下

图3 pcmfs文件系统结构图

    仔细观察上图。inode bitmap 占2个block;data bitmap占8个block。故该文件系统设计中,理想情况,每个inode索引节点只是的文件占4个block(8/2=4),这样就不会出现data bitmap用完,而inode bitmap和inode table未用完 或者 inode bitmap和inode table用完,而data bitmap未用完的情况。

   故在调用write函数,要按顺序写入,即先写superblock,然后写bitmap区域。因为write函数写的区域是设备上当前指针所指向的区域。在写过程中,指针后移,故要按顺序写入。

   另外注意一点的是图3中若block大小为1k,(1024B),则inode bitmap的起始block为第3个block。若为blokc大小大于1k,如为2k,3k等等,则预留字节和super block的字节都可以写在第一个block中,即inode bitmap的起始block为第2个block

   至此,设备的mkfs工作完毕。

 

Category: 文件系统 | Tags:
5
13
2014
8

pcmfs初始化过程

此文是基于基于minxfs修改的文件系统得到的,不过文件系统大体的流程是大同小异的,故初始化过程其实与ext2,ext3是通用的

inode.c中有模块的入口。即module_init(init_pcmfs_fs).初始化这儿主要分析的pcmfs_fill_super函数。该函数即mount时从磁盘读取数据,填充内存超级块字段信息。该函数主要做了如下三件事情

1.sbi = kzalloc(sizeof(struct pcmfs_sb_info), GFP_KERNEL);申请内存超级快的空间,然后把磁盘超级块的内容拷贝到其中。在此处需要把VFS的 s->s_fs_info = sbi;赋值,即全局有很多文件系统数据结构,如ext2,ext3等。此处即指定当前为该sb代表的文件系统

2.申请bitmap表的空间。然后根据bitmap使用情况算出s_used_inode以及s_used_block

i = (sbi->s_imap_blocks + sbi->s_zmap_blocks) * sizeof(bh);

map = kzalloc(i, GFP_KERNEL);//step2:申请空间2 bitmap位
 
3.获取根目录的信息。即根目录所指向的数据块的dentry信息。root结点的数据信息为第一个数据块中的信息
root_inode = pcmfs_iget(s, PCMFS_ROOT_INO);
Category: 文件系统 | Tags:

Host by is-Programmer.com | Power by Chito 1.3.3 beta | Theme: Aeros 2.0 by TheBuckmaker.com