目录与文件系统


前言

文件, 抽象一个磁盘块集合,这是第三层抽象,从文件到文件系统:文件系统, 抽象整个磁盘(第四层抽象)

提示:以下是本篇文章正文内容

一、文件系统

在使用磁盘的时候,用户眼里的磁盘是一堆树结构的有组织的文件,文件系统就是实现文件到盘块的映射
在这里插入图片描述

目录树的优点:
在这里插入图片描述

但是有了这样的映射,怎么才能使用 /my/data/a中a文件("/" 表示根目录)?
通过文件路径名找到文件 ,也就是先通过文件路径找到文件a的FCB

因为需要通过比较文件名才能找到文件,所以目录中需要存放子目录的文件名,又还需要在磁盘中操作该文件,因此还需要存子目录的FCB。

但实际上我们只需要比较一个文件名,却读入了大量的FCB,系统效率笔记低,因此可以在目录中存放子目录名+该目录对应的FCB地址。目录里存的就是<文件名:索引值>,实现这一索引结构,需要磁盘配合,需要磁盘划分一块连续的区域来存放FCB块,这样就能建立索引值到FCB地址的映射

因为根目录没有上一级目录来保存它的索引值,因此需要在磁盘中找一个固定的地址存放根目录

所以操作系统需要完成以下两个任务:

1.目录中存放子目录的文件名和子目录FCB的索引值
2.磁盘块要划分一段连续的区域专门存放FCB块,并定义一个初始地址作为根目录的索引

磁盘块如下:
在这里插入图片描述

(1) inode位图:哪些inode空闲,哪些被占用
(2)盘块位图:哪些盘块是空闲的,硬盘大小不同这个图的大小也不同
(3)引导块:磁盘启动和初始化
(4)超级块:记录两个位图有多大等信息

空闲位图(位向量):0011110011101
表示:表示磁盘块2、 3、 4、 5、8、 9、 10、 12空闲

完成全部映射下的磁盘使用过程
在这里插入图片描述

二、文件系统代码实现

只要把文件系统如何映像到磁盘上对应扇区再加上前面学过的知识不就是文件系统的实现吗?

目录解析:
在open()函数里面找到对应文件并打开,而open()调用了sys_open()

在linux/fs/open.c中
int sys_open(const char* filename, int flag)
{
    
     
	i=open_namei(filename,flag,&inode); //解析路径
	... 
}

int open_namei(...)
{
    
     
	dir=dir_namei(pathname,&namelen,&basename);
	....
}

static struct m_inode *dir_namei()
{
    
     dir=get_dir(pathname); }

经过一系列的调用真正完成目录解析的是get_dir

static struct m_inode *get_dir(const char *pathname)
{
    
     
	if((c=get_fs_byte(pathname))==/) //根目录
	{
    
    
		inode=current->root; pathname++;
	}
	else if(c) inode=current->pwd; //解析从此处开始
	while(1)
	{
    
    
		if(!c) return inode; //函数的正确出口
		bh=find_entry(&inode,thisname,namelen,&de);
		int inr=de->inode; 
		int idev=inode->i_dev;
		inode=iget(idev,inr); //根据目录项读取下一层inode
	}
}

(1)root: 找到根目录;
(2)find_entry: 从目录中读取目录项;
(3)inr: 是目录项中的索引节点号;
(4)iget: 再读下一层目录

目录解析首先要找到一个解析的起点,如果路径名从 / 开始,就从根目录的inode 开始,否则要从当前目录的 inode 开始

根目录inode的解析

inode=current->root;

void init(void)
{
    
     
	setup((void *) &drive_info);
	...
}

sys_setup(void * BIOS)//在kernel/hd.c中
{
    
     
	hd_info[drive].head = *(2+BIOS);
	hd_info[drive].sect = *(14+BIOS);
	mount_root(); 
	... 
}

void mount_root(void)//在fs/super.c中
{
    
    
	mi=iget(ROOT_DEV,ROOT_INO));
	current->root = mi;
	...
}

所有进程的 root 都是从 1 号进程继承来的,在 1 号进程 init() 函数中要执行 mount_root()函数,用来将根目录的 inode读入到内存中,并且关联到 1 号进程的 PCB 中

有了起点目录文件的 inode,接下来读出目录文件内容,然后用文件路径上的文件名和和目录中的目录项逐个比对,不断向下解析,直到路径名被全部处理完成, 最终找到目标文件的inode

读取inode (iget()函数)

struct m_inode * iget(int dev, int nr)
{
    
     
	struct m_inode * inode = get_empty_inode();
	inode->i_dev=dev; inode->i_num=nr;
	read_inode(inode); return inode;
}

static void read_inode(struct m_inode *inode)
{
    
     
	struct super_block *sb=get_super(inode->i_dev);;
	lock_inode(inode);
	block=2+sb->s_imap_blocks+sb->s_zmap_blocks+
	(inode->i_num-1)/INODES_PER_BLOCK;
	bh=bread(inode->i_dev,block);
	inode=bh->data[(inode->i_num-1)%INODES_PER_BLOCK];
	unlock_inode(inode); 
}

以上都是根据磁盘的划分来实现的
在这里插入图片描述

从上图可以看到inode 数组的起始位置在引导块,超级块以及两个位图数组之后

inode 数组在磁盘上的起始位置 = 引导块大小 + 超级块大小 + s_imap_blocks大小 + s_zmap_blocks大小

开始解析目录 find_entry()

函数 find_entry 根据目录文件的 inode 读取目录项数组,然后逐个目录项进行匹配,即 while(i<entries) if(match(namelen,name,de))

在fs/namei.c中
static struct buffer_head *find_entry(struct m_inode
**dir, char *name, ..., struct dir_entry ** res_dir)
{
    
     
	int entries=(*dir)->i_size/(sizeof(struct dir_entry));
	int block=(*dir)->i_zone[0];
	*bh=bread((*dir)->i_dev, block);
	struct dir_entry *de =bh->b_data;
	while(i<entries) //entries是目录项数
	{
    
     	//#define BLOCK_SIZE  1024   两个扇区
		if((char*)de> = BLOCK_SIZE+bh->b_data)
		{
    
    
			brelse(bh);
			block=bmap(*dir,i/DIR_ENTRIES_PER_BLOCK);
			bh=bread((*dir)->i_dev,block);
			de=(struct dir_entry*)bh->b_data;
		} 
		//读入下一块上的目录项继续match
		if(match(namelen,name,de))
		{
    
    
			*res_dir=de;return bh;
		}
			de++; i++; 
	}
}

de: directory entry(目录项)

#define NAME_LEN 14

struct dir_entry
{
    
    
	unsigned short inode; //i节点号
	char name[NAME_LEN]; //文件名 
}

iget 用来读取索引节点,根据 inode 编号(iget 的参数)和 inode 数组的初始位置计算出该 inode 所在的磁盘块号,再用 bread 发送请求,放入“电梯” 队列,就可以完成磁盘的读写。

总结

提示:这里对文章进行总结:

Guess you like

Origin blog.csdn.net/qq_53144843/article/details/120628979