利用体系——L10-经过文件使用磁盘
Linux0.11 经过文件来使用磁盘的历程触及到了很多看法,以及看法之间繁复庞杂的干系,容易让人怀疑,本章主要是对 Linux0.11 经过文件来使用磁盘的历程举行梳理。《Linux内核完全分析——基于0.12内核》的第12章:文件体系,对这些看法及其之间的干系举行了相当出色的分析,保举各位阅读
1、从生磁盘到文件
上一章分析了生磁盘的使用办法,即经过盘块号使用磁盘。但很多人连扇区都不晓得是什么? 要求他们依据盘块号来拜候磁盘显然是不切合的。我们必要在盘块上引入更高一条理的笼统看法——文件,人们经过文件来拜候磁盘中的字符序列就会以为天然多了。用户在掀开一个文件前,先向利用体系提供该文件的文件名,然后利用体系依据文件名找到文件存放的盘块地点,最初利用体系将文件内容(字符序列)表如今用户的眼前。
至此用户眼中的文件就变成了一串字符序列,用户可以随意拜候修正这串字符序列,并且用户也不必去体贴这串字符序列是怎样存放的。而利用体系眼中的文件就变成了字符序列到盘块聚集的映射,即文件创建起了字符序列到盘块聚集的映射干系。
2、文件名与盘块的映射
引入文件是对使用磁盘的笼统。为了完成这层笼统,利用体系必要依据用户提供的文件名找到文件存放的盘块地点,因此利用体系必要创建一个可以完成从文件名到盘块映射的数据布局。临时把这个数据布局叫做 FCB 吧。
2.1 一连布局
一连布局是一种最天然想到的布局,就像必要存放一堆数据的时分,我们起首会想到用数组是一样的。排序布局如下:
从图中可以看出用户要掀开文件名为 test.c 的文件,该文件存放在6、7、8号盘块,共占用3个盘块。创建如此的布局体也不繁复,这里不再多说。
2.2 索引布局
相反是掀开 test.c ,索引布局如下:
索引布局必要在磁盘中留出一个单独的索引块,用于存放索引,而这些索引指向的就是文件存放在磁盘中的盘块地点。从图中可以看出 test.c 文件按排序存放在磁盘的第9号、17号、1号、10号盘块中。创建如此的FCB的布局体相反也不繁复。
2.3 多级索引布局
多级索引布局是 Linux0.11 实践使用的一种布局。这里起首必要先容一个“鼎鼎学名”的数据布局——inode(index node)。inode 是利用体系中界说的一个布局体,inode 中有一个数构成员 short i_zone[9] 内里纪录了文件所占用的盘块号的信息,利用体系会将 inode 的前32个字节存放磁盘中, short i_zone[9] 就在这32个字节之中:
从图2.3中可以看出,i_zone[0 - 6] 为直接块号,它们直接指向数据块(这里我们暂且把数据块了解为真正存放着文件数据的盘块吧,把数据块与索引块区分开来)。i_zone[7] 为一次直接块号,它指向了一个一次直接块(没错就是索引布局中的谁人索引块),一次直接块又指向了一堆数据块。可以猜一下一次直接块可以存放几多个索引呢?由于 i_zone[n] 为 short 典范占2字节,而一个盘块的轻重为 1kB, 因此一次直接块可以存放 512 个索引。i_zone[8] 为二次直接块号,原理与 i_zone[7] 差不多,只不外多了一级。当文件的轻重小于7个盘块时,只必要使用 i_zone[0 - 6] 就好,若大于则必要使用 i_zone[7] 乃至 i_zone[8] 了。如此我们就可以表现很大的文件了,并且关于很小的文件也可以高效拜候,关于中等轻重的文件拜候速率也不慢。这里只是深刻的先容了一下 inode ,关于 inode 更多的先容可以参考《Linux内核完全分析——基于0.12内核》的第12章:文件体系。
的确 i_zone[ ] 就以前构成多级索引布局了,但我们必要经过文件名来获取文件。我们的 FCB 还没有创建。多级索引布局以前有了,我们只必要把多级索引布局和文件名关联起来,我们的 FCB 就建好了:
把上图做成布局体
struct dir_entry {
unsigned short inode; // i节点号
char name[NAME_LEN]; // 文件名
};
没错,这个就是“目次项”布局体!
再次回忆一下基于多级索引布局经过文件使用磁盘的历程。用户在掀开一个文件前,先向利用体系提供该文件的文件名,然后利用体系依据文件名找文件对应的目次项。目次项中存放着 i 节点号,因此利用体系可以从磁盘中将文件的 inode 读入内存,然后经过 inode 中的 i_zone[] 字段找到文件对应的盘块号,最初经过盘块号拜候磁盘,获取文件内容,并将文件内容表如今用户眼前。
3、文件磁盘的完成
本节以对文件举行写利用为线索,分析 Linux0.11 经过文件使用磁盘局部步骤。
(1)调用 open() 掀开文件
在读写一个文件前,都必要先调用 open() 掀开文件,open() 的中心是依据文件名,找到对应的 inode ,并读入 inode。
int open(const char * filename, int flag, ...)
内核使用文件布局 file ,文件表 file_table,和内存中 i 节点表 inode_table 来办理对文件的拜候利用。open() 会找出 inode_table[NR_INODE] 的一个空闲项,并将读入的 inode 存放在空闲项中 。然后在找出 file_table[NR_FILE] 的空闲项,添补该空闲项,并让空闲项的 f_inode 字段指向读入的 inode 。最初寻觅 filp[NR_OPEN] 中的空闲项,并让该空闲项指向前方刚添补好的file_table[NR_FILE] 中的谁人项。open() 的前往值就是刚刚谁人 filp[NR_OPEN] 的空闲项的索引。
struct file {
unsigned short f_mode;
unsigned short f_flags;
unsigned short f_count;
struct m_inode * f_inode; // 文件 inode
off_t f_pos; // 文件如今读写指针地点(文件光标地点)
};
struct task_struct {
......
struct file * filp[NR_OPEN]; // PCB中的文件指针数组,此中 NR_OPEN = 20
......
};
struct file file_table[NR_FILE]; // 文件表,NR_FILE = 64
struct m_inode inode_table[NR_INODE]; // i 节点表, NR_INODE = 32
历程掀开文件时使用的内核数据布局如下:
到此 open() 的事情就完毕了。
(2)调用 write()向文件写
依据体系调用,实践上 write() 的功效时经过 sys_write() 完成的。sys_write() 将使用前方 open() 的前往值,取得文件的 inode。
// 参数 fd 是 open() 的前往值
int sys_write(unsigned int fd,char * buf,int count)
{
struct file * file;
struct m_inode * inode;
if (fd>=NR_OPEN || count <0 || !(file=current->filp[fd])) //获取 file
return -EINVAL;
if (!count)
return 0;
inode=file->f_inode; // 获取文件的 inode
......
if (S_ISREG(inode->i_mode)) // 推断文件典范对否是平凡文件
return file_write(inode,file,buf,count); // 该文件为平凡文件,调用 file_write() 举行处理
printk("(Write)inode->i_mode=%06o\n\r",inode->i_mode);
return -EINVAL;
}
sys_write() 并没有直接处理平凡文件,而是交给了 file_write() 举行处理。
(3)调用 file_write() 持续剖析
file_write() 共有4个参数,此中 inode 中有最紧张的文件地点的盘块地点信息,filp 中存放着文件光标地点,经过它可以确定文件修正的地点,buf 为写入的内容,count 为写入内容的字节数。经过 filp 和 count 可以确认文件修正的范围。必要注意的是,file_write() 并不是直接将数据写入盘块中,而是写入缓冲区中。
int file_write(struct m_inode * inode, struct file * filp, char * buf, int count)
{
off_t pos;
int block,c;
struct buffer_head * bh;// struct buffer_head 是缓冲开头布局体
char * p;
int i=0;
if (filp->f_flags & O_APPEND)
pos = inode->i_size; //假如是“追加” 则从文件的末了开头写入
else
pos = filp->f_pos; //不然从文件光标地点开头修正,filp->f_pos中纪录了文件光标的地点
while (i<count) {
//BLOCK_SIZE = 1024(一个盘块的轻重),pos/BLOCK_SIZE 就是如今修正地点地点的盘块干系于文件第一个盘块的盘块地点
//create_block() 用于获取盘块号,create_block() 的前往值是 pos 地点盘块的盘块号
if (!(block = create_block(inode,pos/BLOCK_SIZE)))
break;
// bread() 会调用 ll_rw_block(), 而 ll_rw_block()又会调用 make_request() ,然后就是上章讲的内容了。
if (!(bh=bread(inode->i_dev,block)))
break;
c = pos % BLOCK_SIZE;//盘算出 pos 在盘块中的相对地点
p = c + bh->b_data; //p指向缓冲块中开头写入数据的地点
bh->b_dirt = 1;
c = BLOCK_SIZE-c;
if (c > count-i) c = count-i;
pos += c; //修正 pos
if (pos > inode->i_size) {
inode->i_size = pos;
inode->i_dirt = 1;
}
i += c;
while (c-->0)
*(p++) = get_fs_byte(buf++); //获取要写入的内容
brelse(bh);
}
......
}
(4)create_block() 分析
create_block() 中调用了 _bmap() 。_bmap() 的第3个参数为 1 时表现没有映射时则创建映射,这里的映射是指缓冲区。
int create_block(struct m_inode * inode, int block)
{
return _bmap(inode,block,1);
}
static int _bmap(struct m_inode * inode,int block,int create)
{
struct buffer_head * bh;
int i;
......
if (block<7) { // 若block< 7 直接取得一个数据块
if (create && !inode->i_zone[block])
if ((inode->i_zone[block]=new_block(inode->i_dev))) {
inode->i_ctime=CURRENT_TIME;
inode->i_dirt=1;
}
return inode->i_zone[block];
}
// 若减去7个数据块后剩余的数据块数小于512,则分析该文件仅有一次直接块号。inode->i_zone[7]是一次直接块号
block -= 7;
if (block<512) {
if (create && !inode->i_zone[7])
if ((inode->i_zone[7]=new_block(inode->i_dev))) {
inode->i_dirt=1;
inode->i_ctime=CURRENT_TIME;
}
......
}
// 存在二次直接块号,那么持续处理
block -= 512;
if (create && !inode->i_zone[8])
......
}
至此,约莫还会有些疑问,好比利用体系怎样经过 inode 号找到 磁盘中文件的 inode 的呢?目次项是存在磁盘中照旧在体系启动后利用体系在内存中创建目次项的呢?假如是在内存中创建的那么 OS 启动时是怎样晓得磁盘中有哪些文件的呢?另有写文件时写入的缓冲区是怎样回事?等等一系列的细节成绩。但是不必纠结于利用体系对这些细节是怎样完成的,正如李治军教师课程中提到的一种头脑:我们能处理这些细节成绩吗?我们可以猜一下利用体系怎样完成这些细节。
下一章将分析目次与文件体系。