rtems 架构支持:
• Mountable file systems 可挂载
• Hierarchical file system directory structure 分级
• POSIX compliant set of routines for the manipulation of files and directories posix兼容的文件目录操作
独立的文件和目录支持:
- Permissions for read, write and execute
- User ID
- Group ID
- Access time
- Modification time
- Creation time
• Hard links to files and directories
• Symbolic links to files and directories
它提供了类似unix的文件系统。posix的一些对文件和目录的操作都已经实现。rtems文件系统的概念有助于对未来设备的扩展和适应。也正是因为扩展原因,所以rtems文件系统具有可挂载性。
定义在rtems configuration中的设备都被注册为可挂载文件系统中的文件,访问设备驱动和设备可以通过传统的文件操作方式 ,除了IO manager中接口外,还有open(), read(), write(), lseek(), fstat() and ioctl() functions
rtems提供基于ram的文件系统In-Memory File System (IMFS),提供完全的posix文件系统能力
IMFS在其文件系统的每个已安装实例中为每个文件,设备和目录维护节点结构。 节点结构用于管理所有权,访问权限,访问时间,修改时间和创建时间。
术语简介:
磁盘块:块设备对磁盘的一个抽象,对于文件系统而言,磁盘就是一个一个连续的块,每个块通常大小为4KB,并且磁盘块有序编码,每个块对应有一个磁盘块号
文件逻辑块号:从逻辑上讲,一个文件可以看做一系列连续的数据块,每个数据块的大小和磁盘块大小相同;
从物理上讲,一个文件可以对应磁盘上若干个磁盘块,这些磁盘块可以在物理上不连续。
逻辑快号和磁盘块号的对应关系,很像虚拟地址和物理地址的关系:文件给你的感觉就是连续的数据,也就是连续的逻辑块,但实际上文件对应的磁盘块是可以不连续的,也就是不连续的物理块
inode:保存文件的元数据,元数据可以描述一个文件
很可惜inode里没有一下子就能想到的filename。关于filename下一节会讲到
一个inode最基本也最重要的信息就两个
用来标识inode的inode号,每个inode都不一样
用来指明这个文件对应着哪些磁盘块的信息——即逻辑块号和磁盘块号的对应关系
VFS的运行方式:
深入理解linux内核的第12章部分详细描述了VFS的执行方式
简单来说:
文件打开之后有一个file结构体,里面有个file_op,这个结构体每个文件系统都不一样,里面有read指令
用户使用read指令,然后内核开始调用sys_read,sys_read通过f_op中的数据结构,调用文件系统特定的read指令。
VFS要处理很多系统调用
这上面只是一部分,
从文件名到磁盘块:
extent:(inode的一部分?)
而ext4采用的办法是使用extent来保存<文件逻辑块号, 磁盘块号>的映射关系:一个extent对应一系列连续的块号,故可以想到,一个extent最基本的几个域有——文件逻辑块号,起始磁盘块号,块数量 ext4中一个inode可以直接存放4个extent 对于很大的文件,ext4采用extent_tree的方式,其本质同样是一种间接寻址的关系
inode存放文件元数据,但是并没有存放filename——那么ext4是如何把一个filename和一个inode绑定在一起呢?
也就是说存在一个filename和inode号之间的对应关系,而这个关系也是存放在一个文件里——目录文件。如根目录/就是一个文件,这个文件也对应一个inode,文件的数据就是根目录下的文件名和对应的inode号(dentry?不是dentry,dentry存在于内存中,是目录文件的缓存,这个数据结构是内存数据结构,用来存放一个文件路径(如/home)和其对应的数据信息(文件名和inode号的表))
VFS的四种数据结构,超级块,索引节点对象,目录项对象,文件对象FILE,都是内存数据结构,对于具体的文件系统比如Ext4,在挂载的时候根据其磁盘布局来构造出这四种数据结构(如何构造?这个应该是移植的重点),应用程序(如cat命令)均基于VFS提供的文件操作接口进行编程,每种具体的文件系统的实现,如ext4、fat32、ntfs等,需要对其特有的磁盘数据结构进行封装来实现VFS的这四种数据结构(**移植的时候,文件系统需要完成VFS的四种数据结构的封装,这个封装过程是不是使用VFS提供的API进行的?**)
ext2中的inode结构体:ext2_inode
- 最重要的字段:
i_block[EXT2_N_BLOCKS]
,存放磁盘块号 ext2_inode的大小为128字节,一个4KB的块可以存放32个inode
struct ext2_inode{
__le16 i_mode; //文件类型和访问权限
__le16 i_uid; //拥有者id
__le32 i_size; //文件大小,单位byte
__le32 i_atime,i_ctime,i_mtime,i_dtime; //时间相关信息
__le16 i_gid; //用户组id
__le16 i_links_count; //硬链接计数器
__le32 i_blocks; //文件数据块数
__le32 i_flags; //标志
union osd1; //操作系统相关信息
__le32 i_block[EXT2_N_BLOCKS]; //指向数据块的指针,即磁盘块号
__le32 i_generation; //文件版本,用于网络文件系统
__le32 i_file_acl; //文件访问控制列表
__le32 i_dir_acl; //目录访问控制列表
__le32 i_faddr; //片地址(不懂)
union osd2; //操作系统相关信息
}
i_block字段是一个有EXT2_N_BLOCKS个元素的数组,若EXT2_N_BLOCKS的默认值为15
[0,11]为直接寻址,即i_block[0,11]直接存放磁盘块号
[12,13,14]均为间接寻址,[12]指向一个存放磁盘块号的块,[13]为二级指针,[14]为三级指针
若一个block的大小为4KB,直接寻址的范围为48KB,即只使用[0,11];若加上一次间接的[12],则为4.04MB;再加上二次间接的[13]则为4GB;再加上三次间接的[13]则约为4TB
(和多级页表一模一样)、
Ext4中的inode:
ext4中就出现了extent,ext4_inode
定义于/fs/ext4/ext4.h
,ext4_inode
的大小为256字节,一个4KB的块可以保存16个inode,i_block字段和ext2中的一样
字段i_block的大小为60个字节,即__le32 i_block[EXT4_N_BLOCKS]且EXT4_N_BLOCKS=15
前12个字节为extent头,为extent的基本信息
后48个字节可以保存4个extent节点,每个extent节点为12字节大小
extent以树的形式组织,叶节点和非页节点的大小均12字节
叶节点即直接保存了文件逻辑块号、起始磁盘块号、块数
非叶节点同样具有文件逻辑块号,后面内容指向了一个磁盘块号,有两个字节未使用
extent相关结构体定义于/fs/ext4/ext4_extents.h
磁盘布局:
引导块:磁盘的第一个块,ext2对其不管理,其余的块以块组的形式管理
每个块组有以下内容:
- 超级块:1个块
- 组描述符:n个块
- 数据块bitmap:1个块
- 索引节点bitmap:1个块
- 索引节点表:n个块
- 数据块:n个块
其中超级块和组描述符,对于所有块组均相同,且总是缓存在内存中
其余则用于描述本块组管理的inode块和数据块
Ext4中的磁盘数据结构:
超级块的源码:ext4_sb.h
组描述符ext4_group_desc
/*
* Structure of a blocks group descriptor
*/
struct ext4_group_desc
{
__le32 bg_block_bitmap_lo; /* Blocks bitmap block */
__le32 bg_inode_bitmap_lo; /* Inodes bitmap block */
__le32 bg_inode_table_lo; /* Inodes table block */
__le16 bg_free_blocks_count; /* Free blocks count */
__le16 bg_free_inodes_count; /* Free inodes count */
__le16 bg_used_dirs_count; /* Directories count */
__le16 bg_flags; /* EXT4_BG_flags (INODE_UNINIT, etc) */
__u32 bg_reserved[2]; /* Likely block/inode bitmap checksum */
__le16 bg_itable_unused; /* Unused inodes count */
__le16 bg_checksum; /* crc16(sb_uuid+group+desc) */
__le32 bg_block_bitmap_hi; /* Blocks bitmap block MSB */
__le32 bg_inode_bitmap_hi; /* Inodes bitmap block MSB */
__le32 bg_inode_table_hi; /* Inodes table block MSB */
__le16 bg_free_blocks_count_hi;/* Free blocks count MSB */
__le16 bg_free_inodes_count_hi;/* Free inodes count MSB */
__le16 bg_used_dirs_count_hi; /* Directories count MSB */
__le16 bg_itable_unused_hi; /* Unused inodes count MSB */
__u32 bg_reserved2[3];
};
直接通过inode号找文件
找文件的inode号:
使用
ls -i
可以打印出相应的inode号
根据inode号打印出inode内容:
使用df命令找出文件所在的设备,找出当前路经所在的设备(eg:/dev/sda3),然后再使用ls -li xxxx命令
istat命令可以打印出某个设备上的某个inode信息
Group:320表示这个inode存在于块组320上 size:38表示文件的大小为38个字节Direct Blocks:10622354则是这个命令根据i_block解析出来的磁盘快号> 那么我们不直接使用这个块号,我们自己来算一算它,这就需要直接查看inode所在的磁盘数据块才能看到完整的信息 已知a.txt的文件inode为2629310,使用以上命令,得到下面的信息 ```bash $ sudo istat /dev/sda3 2629310 inode: 2629310 Allocated Group: 320//知道块组320 Generation Id: 2014416585 uid / gid: 1000 / 1000 mode: rrw-rw-r— Flags: Extents, size: 38//38个字节 num of links: 1
Inode Times: Accessed: 2017-06-26 21:13:12.184134514 (CST) File Modified: 2017-06-26 21:13:10.628122013 (CST) Inode Modified: 2017-06-26 21:13:10.628122013 (CST) File Created: 2017-06-26 10:18:07.690160310 (CST)
Direct Blocks://这个命令根据i_block解析出来的磁盘块号 //(如果不直接使用这个块号,自己来算一算它,这就需要直接查看inode所在的磁盘数据块才能看到完整的信息) 10622354
接下来fsstat列出文件系统的信息,找出320块组的信息
```bash
$ sudo fsstat /dev/sda3 > dev.sda3.fsstat
$ cat dev.sda3.fsstat | grep "Group: 320" -n
4560:Group: 320:
$ cat dev.sda3.fsstat | head -n 4572 | tail -n 13
Group: 320:
Block Group Flags: [INODE_ZEROED]
Inode Range: 2621441 - 2629632
Block Range: 10485760 - 10518527
Layout:
Data bitmap: 10485760 - 10485760
Inode bitmap: 10485776 - 10485776
Inode Table: 10485792 - 10486303
Data Blocks: 10493984 - 10518527
Free Inodes: 8 (0%)
Free Blocks: 23488 (71%)
Total Directories: 534
Stored Checksum: 0x03D2
磁盘块里面存储的内容:
blkcat命令可以读取一个磁盘块的内容
$ sudo blkcat /dev/sda3 10486283 > dev.sda3.blk.10486283
使用十六进制编辑器hexedit打开,看到二进制的文件,然后根据block的布局,找出里面的信息
ext4 layout网站:
https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout
自己实现一个操作系统的步骤是什么:
https://www.cnblogs.com/wangzahngjun/p/5365310.html(基于VFS实现自己的文件系统)
这部分的步骤,主要是和内核交互,这部分应该是移植的重点,找出安装文件系统中需要和vfs打交道的部分,然后实现(深入理解linux内核里面第12章描述了文件系统如何安装)
首先要做的就是生产自己的super_block,即要重载内核的get_sb()函数,这个函数是在mount的时候调用的
一个具体的文件系统必须先向vfs注册,才能被使用。通过register_filesystem() ,可以将一个“文件系统类型”结构 file_system_type注册到内核中一个全局的链表file_systems 上。
文件系统注册的主要目的,就是让 VFS 创建该文件系统的“超级块”结构。
一个文件系统在内核中用struct file_system_type来表示:
struct file_system_type {
const char *name;
int fs_flags;
int (*get_sb) (struct file_system_type *, int,
const char *, void *, struct vfsmount *);
void (*kill_sb) (struct super_block *);
struct module *owner;
struct file_system_type * next;
struct list_head fs_supers; /*超级块对象链表*/
struct lock_class_key s_lock_key;
struct lock_class_key s_umount_key;
struct lock_class_key i_lock_key;
struct lock_class_key i_mutex_key;
struct lock_class_key i_mutex_dir_key;
struct lock_class_key i_alloc_sem_key;
};
这个结构中最关键的就是 get_sb() 这个函数指针,它就是用于创建并设置 super_block 的目的的。
因为安装一个文件系统的关键一步就是要为“被安装设备”创建和设置一个 super_block,而不同的具体的文件系统的 super_block 有自己特定的信息,因此要求具体的文件系统首先向内核注册,并提供 read_super() 的实现。
安装文件系统:
一个注册了的文件系统必须经过安装才能被VFS所接受。安装一个文件系统,必须指定一个目录作为安装点。一个设备可以同时被安装到多个目录上。 一个目录节点下可以同时安装多个设备。
“根安装点”、“根设备”和“根文件系统”
安装一个文件系统,除了需要“被安装设备”外,还要指定一个“安装点”。“安装点”是已经存在的一个目录节点。例如把 /dev/sda1 安装到 /mnt/win 下,那么 /mnt/win 就是“安装点”。 可是文件系统要先安装后使用。因此,要使用 /mnt/win 这个“安装点”,必然要求它所在文件系统已也经被安装。 也就是说,安装一个文件系统,需要另外一个文件系统已经被安装。
“安装”一个文件系统涉及“被安装设备”和“安装点”两个部分,安装的过程就是把“安装点”和“被安装设备”关联起来,这是通过一个“安装连接件”结构 vfsmount 来完成的。
vfsmount 将“安装点”dentry 和“被安装设备”的根目录节点 dentry 关联起来。
在安装文件系统时,内核的主要工作就是:(在移植工作中,由rtems完成)
1、 创建一个 vfsmount
2、 为“被安装设备”创建一个 super_block,并由具体的文件系统来设置这个 super_block。
3、 为被安装设备的根目录节点创建 dentry
4、 为被安装设备的根目录节点创建 inode, 并由 super_operations->read_inode() 来设置此 inode
5、 将 super_block 与“被安装设备“根目录节点 dentry 关联起来
6、 将 vfsmount 与“被安装设备”的根目录节点 dentry 关联起来
这个步骤由内核中的代码get_sb_single 完成
int get_sb_single(struct file_system_type *fs_type,
int flags, void *data,
int (*fill_super)(struct super_block *, void *, int),
struct vfsmount *mnt)
{
struct super_block *s;
int error;
s = sget(fs_type, compare_single, set_anon_super, NULL);
if (IS_ERR(s))
return PTR_ERR(s);
if (!s->s_root) {
s->s_flags = flags;
error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
if (error) {
deactivate_locked_super(s);
return error;
}
s->s_flags |= MS_ACTIVE;
} else {
do_remount_sb(s, flags, data, 0);
}
simple_set_mnt(mnt, s);
return 0;
}
打开文件的过程实际上就是将file,dentry,inode关联起来的过程,每打开一个文件,就创建一个file结构
具体实现的文件系统里面的函数结构:
static struct inode *wzjfs_make_inode(struct super_block *sb, int mode)
static int wzjfs_open(struct inode *inode, struct file *filp)
static ssize_t wzjfs_read_file(struct file *filp, char *buf,size_t count, loff_t *offset)
static ssize_t wzjfs_write_file(struct file *filp, const char *buf,size_t count, loff_t *offset)
static struct file_operations wzjfs_file_ops= {
.open = wzjfs_open,
.read = wzjfs_read_file,
.write = wzjfs_write_file,
};
static struct dentry *wzjfs_create_file (struct super_block *sb,struct dentry *dir, const char *name,atomic_t *counter)
static struct dentry *wzjfs_create_dir (struct super_block *sb,struct dentry *parent, const char *name)
static atomic_t counter, subcounter;
static void wzjfs_create_files (struct super_block *sb, struct dentry *root)
static struct super_operations wzjfs_s_ops;
static int wzjfs_fill_super (struct super_block *sb, void *data, int silent)
static int wzjfs_get_super;
static struct file_system_type wzjfs_type = {
.owner = THIS_MODULE,
.name = "wzjfs",
.get_sb = wzjfs_get_super,
.kill_sb = kill_litter_super,
};
static int __init wzjfs_init(void)
{
struct file_system_type * tmp;
printk("wzjfs_init ok\n");
return register_filesystem(&wzjfs_type);
}
static void __exit wzjfs_exit(void)
{
unregister_filesystem(&wzjfs_type);
printk("wzjfs_exit ok\n");
}
VFS与实际文件系统的交互
通过struct找到磁盘inode节点对象:
一个进程打开的文件用struct file结构表示,这是VFS可访问的(file中的file_operations)。在file结构中可找到这个文件对应的dentry对象,如果两个进程打开同一个文件,那它们的file就指向同一个dentry。通过dentry就可以找到这个文件对应的inode对象,到了inode就与特定文件系统(如ext3/ext4)相关了,这个inode有读写文件的file_operations。那么,如果一个dentry是另一个dentry的硬链接,那这两个dentry就指向同一个inode对象。
VFS中的通用read/write调用实际文件系统的read/write:
在程序open()一个文件时,inode的file_operations就会被填到供VFS使用的file结构的file_operations,这样实际文件和VFS就建立了联系,就可以开始实际操作这个文件了。
通过address_space接触磁盘:
在open文件之后进行read/write时,读写操作(file_operations)并不是直接跟硬盘交互,而是会经过address_space。每个inode有自己的一个address_space(inode的i_mapping字段),address_space中的address_space_operations(如readpage/writepage/readpages/writepages等)才会跟磁盘打交道进行读写。
虚拟文件系统与Ext4文件系统
文件系统在内核中的读写过程
文件系统在内核中的读写过程是在 sys_write( ) 中定义的,其定义在 include/linux/syscalls.h 中:asmlinkage long sys_write(unsigned int fd, const char __user *buf, 568 size_t count);
sys_write( )的具体实现在 fs/read_write.c 中:如下所示
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
size_t, count)
{
struct fd f = fdget_pos(fd);
ssize_t ret = -EBADF;
if (f.file) {
loff_t pos = file_pos_read(f.file);
ret = vfs_write(f.file, buf, count, &pos);
if (ret >= 0)
file_pos_write(f.file, pos);
fdput_pos(f);
}
return ret;
}
sys_write过程分析:
1、根据打开文件号 fd找到该已打开文件file结构 struct fd f = fdget_pos(fd);<br />2、读取当前文件的读写位置
loff_t pos = file_pos_read(f.file);
3、写入(最重要的一部分 ) ret = vfs_write(f.file, buf, count, &pos);<br />4、根据读文件结果,更新文件读写位置
file_pos_write(f.file, pos);
sys_write( ) 的核心部分 vfs_write( )
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos){
ssize_t ret;
if (!(file->f_mode & FMODE_WRITE))
return -EBADF;
if (!(file->f_mode & FMODE_CAN_WRITE))
return -EINVAL;
if (unlikely(!access_ok(VERIFY_READ, buf, count)))
return -EFAULT;
ret = rw_verify_area(WRITE, file, pos, count);//合法性检查
if (ret >= 0) {
count = ret;
file_start_write(file);
if (file->f_op->write)
ret = file->f_op->write(file, buf, count, pos);
else if (file->f_op->aio_write)
ret = do_sync_write(file, buf, count, pos);
else
ret = new_sync_write(file, buf, count, pos);
if (ret > 0) {
fsnotify_modify(file);
add_wchar(current, ret);
}
inc_syscw(current);
file_end_write(file);
}
return ret;
}
首先函数在 rw_verify_area(WRITE, file, pos, count); 检查文件是否从当前位置 pos 开始的 count 字节是否对写操作加上了 “强制锁”,这是通过调用函数完成的。
通过合法性检查后,就调用具体文件系统 file_operations中 write 的方法。对于ext4文件系统,file_operations方法定义在 fs/ext4/file.c 中。从定义中可知 write 方法实现函数为 do_sync_write( )。
ext4中file_operation的定义:
const struct file_operations ext4_file_operations = {
.llseek = generic_file_llseek,
.read = do_sync_read,
.write = do_sync_write,//这部分的代码在下面
.aio_read = generic_file_aio_read,
.aio_write = ext4_file_write,
.unlocked_ioctl = ext4_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = ext4_compat_ioctl,
#endif
.mmap = ext4_file_mmap,
.open = ext4_file_open,
.release = ext4_release_file,
.fsync = ext4_sync_file,
.splice_read = generic_file_splice_read,
.splice_write = generic_file_splice_write,
};
const struct inode_operations ext4_file_inode_operations = {
.truncate = ext4_truncate,
.setattr = ext4_setattr,
.getattr = ext4_getattr,
#ifdef CONFIG_EXT4_FS_XATTR
.setxattr = generic_setxattr,
.getxattr = generic_getxattr,
.listxattr = ext4_listxattr,
.removexattr = generic_removexattr,
#endif
.check_acl = ext4_check_acl,
.fallocate = ext4_fallocate,
.fiemap = ext4_fiemap,
};
但是具体的实现do_sync_write( )的代码在fs/read_write.c里面,这个是个通用的文件,不是ext4特定的,所有文件系统的公用目录里面存在,因此这部分在rtems里面是否已经有实现的方式,在哪?
下面是do_sync_write( )的具体代码,也在fs/read_write.c中:
ssize_t do_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
struct iovec iov = { .iov_base = (void __user *)buf, .iov_len = len };
struct kiocb kiocb;
ssize_t ret;
init_sync_kiocb(&kiocb, filp);
kiocb.ki_pos = *ppos;
kiocb.ki_nbytes = len;
ret = filp->f_op->aio_write(&kiocb, &iov, 1, kiocb.ki_pos);
if (-EIOCBQUEUED == ret)
ret = wait_on_sync_kiocb(&kiocb);
*ppos = kiocb.ki_pos;
return ret;
}
EXPORT_SYMBOL(do_sync_write);
异步I/O允许用户空间来初始化操作而不必等待它们的完成,因此,一个应用程序可以在他的I/O处理进行中做其他的处理。
块和网络驱动在整个时间是完全异步的,因此只有字符驱动对于明确的异步I/O支持是候选的。实现异步I/O操作的file_operations方法,都使用I/O Control Block,其定义在 include/linux/aio.h中
定义了一个临时变量iov,这个变量记录了用户空间缓冲区地址buf和所要写的字节数len,用户空间的缓冲区地址buf是保存在iov中的。初始化异步I/O数据结构后,就用file_operations 中的aio_write方法。拓展到ext4文件中的时,该方法就是ext4_file_operations结构体中的ext4_file_write( )。
下面就具体到ext4的文件系统,这个函数也是aio_write( ) 的延展。
下面两个链接,讲linux里面文件系统结构的部分比较详细,各个结构之间的关系,在linux源码中的位置,以及读写过程在整个系统中的运行过程都很详细的介绍了
https://www.cnblogs.com/hanyan225/archive/2011/07/29/2120658.html
https://www.cnblogs.com/hanyan225/archive/2011/07/29/2121192.html