inode 译成中文就是索引节点。每个存储设备或存储设备的分区(存储设备是硬盘、软盘、U盘 ... ... )被格式化为文件系统后,应该有两部份,一部份是inode,另一部份是Block。Block是用来存储数据用的。而inode呢,就是用来存储这些数据的信息,这些信息包括文件大小、属主、归属的用户组、读写权限等。inode为每个文件进行信息索引,所以就有了inode的数值。操作系统根据指令,能通过inode值最快的找到相对应的文件。
inode本质上就是一个结构体,所以通过对Linux下的ROMFS文件系统代码inode.c进行分析能够让我们了解一个文件系统的执行过程需要做什么样的工作,从而对操作系统有更深入的了解.
Linux下的ROMFS文件系统源码 inode.c 解析(2)
不用再去翻一遍目录了,图片可以点击放大。我本来打算取消粉丝可见的设置。不过,我想想算了。
因为:
你可以通过粉丝数来判定,有多少和你选一样作业的朋友,目前90+个同学和你选的题目一样
题外话:
这是我 2015 的大学实验,倒是没想到操作系统大作业,这都六七年了还没换,好好理解一遍,答辩的时候能够把你的理解说出来,得分也挺高的。大三了吧,祝你们一帆风顺,所行即所愿。
/*2-1-1*/
/*
* linux/fs/inode.c
*
* (C) 1991 Linus Torvalds
*/
#include // 字符串头文件。主要定义了一些有关字符串操作的嵌入函数。
#include // 文件状态头文件。含有文件或文件系统状态结构stat{}和常量。
#include // 调度程序头文件,定义了任务结构task_struct、初始任务0 的数据,还有一些有关描述符参数设置和获取的嵌入式汇编函数宏语句。
#include // 内核头文件。含有一些内核常用函数的原形定义。
#include // 内存管理头文件。含有页面大小定义和一些页面释放函数原型。
#include // 系统头文件。定义了设置或修改描述符/中断门等的嵌入式汇编宏。
struct m_inode inode_table[NR_INODE] = { {0,}, }; // 内存中i_node表(NR_INODE=32 项)
思考:file_table可以打开64个文件(可以考虑一下NR_INODE是否太小,容易产生当机的情况)。
static void read_inode (struct m_inode *inode);
static void write_inode (struct m_inode *inode);
/*2-1-2*代码解释及流程图/
/*等待指定的i_node可用 */
//如果i_node已被锁定则将当前任务置为不可中断的等待状态。直到该i_node解锁
锁定的意思为:正在被一个用户使用,执行读或写,此时等待的是一个ID(UID),上一个用户使用完毕后,发出指令后,系统会释放i_node//
static inline void wait_on_inode (struct m_inode *inode)
{
cli (); //关中断,先使用可以避免想要的执行被中断(后边有比较详细的解读)
while (inode->i_lock) // 如果i_node已被锁定
sleep_on (&inode->i_wait); // 不可中断的等待状态,进入睡眠等待
sti (); //开中断
}
/*2-1-3*代码解释及流程图/
/* 对指定的i 节点上锁 */
// 先判断i_node的可用性,如果i_node已被锁定 则将当前任务置为不可中断的等待状态。
// 直到该i_node解锁 然后对其上锁。否则对指定的i_node上锁锁定指定的i_node//
static inline void lock_inode (struct m_inode *inode)
{
cli ();
while (inode->i_lock)
sleep_on (&inode->i_wait);
inode->i_lock = 1; // 置锁定标志。
sti ();
}
/*2-1-4*代码解释及流程图/
/* 对指定的i_node解锁 */
// 重置i_node的锁定标志并直接唤醒等待此i节点的进程。
static inline void unlock_inode (struct m_inode *inode)
{
inode->i_lock = 0; // 重置i_node的锁定标志
wake_up (&inode->i_wait); // 唤醒等待此i_node的进程
}
/*2-1-5*代码解释及流程图/
/* 释放内存中设备dev 的所有i_node*/
在类Unix操作系统中,设备文件系统允许软件通过标准输入输出系统调用与驱动程序交互,从而简化了许多任务。
设备文件系统包括设备文件、设备节点、设备特定文件,它们是驱动程序的接口,而在文件系统中,它们就像是普通文件。
在微软的MS-DOS和 Windows等操作系统中,也有专门的设备文件。
设备dev包括:字符设备、块设备、伪设备,伪设备又包括的如:
/dev/full、/dev/null、/dev/loop、/dev/zero、/dev/random、/dev/urandom
// 扫描内存中的i_node表数组如果是指定设备使用的i_node就释放。
void invalidate_inodes (int dev)
{
int i; //定义int型数据i
struct m_inode *inode; //定义指定结构体节点i_node
inode = 0 + inode_table; //指针指向i_node表指针数组的首项
for (i = 0; i < NR_INODE; i++, inode++) // 扫描i_node表指针数组中的所有i节点
{
wait_on_inode (inode); // 等待该i_node可用(解锁)
if (inode->i_dev == dev) // 如果是指定设备的i_node则进入执行
{
if (inode->i_count) // 如果其引用数不为0则给出警告(设备还在调用)
printk ("inode in use on removed disk\n\r");
inode->i_dev = inode->i_dirt = 0; // 释放该i_node(置设备号为0等)
}
}
}
/*2-1-6*代码解释及流程图/
/* 同步内存(i_node数组)与设备上的所有i_node的信息 */
void sync_inodes (void)
{
int i;
struct m_inode *inode; //定义
inode = 0 + inode_table; // 指针指向i_node表指针数组的首项
for (i = 0; i < NR_INODE; i++, inode++) // 扫描i_node表指针数组中的所有i_node
{
wait_on_inode (inode); // 等待该i_node可用(解锁)
if (inode->i_dirt && !inode->i_pipe) // 如果该i_node已修改且不是管道节点
(管道流量和流向发生改变的点)
write_inode (inode); // 所有i_node信息写入磁盘
}
}
管道是linux提供的一种常见的进程通信工具,也是很多shell命令能够灵活组合产生强大用途的一个重要工具。管道,顾名思义就是个管子,里面可以流过去很多东西。举个栗子 ls | morels输出列出来的文件目录就通过‘|’这个管道流向了more这个文本浏览器。相同的功能我们也可以通过ls > tmp ; tmp > more来完成。实际上管道的功能和第二个方法也很像。管道也是一个文件ls的输出送到这个文件,more再从这个文件将东西拿走。所不同的是管道不同于普通的文件,是一套特殊的文件pipefs,在磁盘中没有映像,只在内存中存在,而且只存在于存在亲缘关系的进程之间。然后省略若干和文件系统。
/*2-1-7*代码解释及流程图/
/* 文件数据块映射到盘块的处理操作,可能需要建立新的逻辑块,获取逻辑块号*/
//1、 块映射处理操作。(block位图处理函数bmap全名:block map)
//2、 参数:inode为i_node指针block为数据块号create为创建标志
//3、 如果创建标志置位,则在对应逻辑块不存在时就申请新磁盘块
//4、 返回block数据块对应在设备上的逻辑块号
//5、 block是相对于文件而言,但是i_zone[block]则是相对于设备而言的。
//6、 block可能不止i_zone数组大小,所以如果block的值大于7时,需要使用间接来寻址。
//7、 如下的映射:
// |-----------| |------------------------------|
// |file block | ---> | disk block |
// |-----------| |------------------------------|
// 如果create = 1,需要建立新的逻辑块,
static int_bmap(struct m_inode *inode, int block, int create)
{
struct buffer_head *bh; //定义缓冲区头部对象
!每一个缓冲区都有一个缓冲区头部来唯一地标识与描述该缓冲区。
Linux通过数据结构buffer_head来定义缓冲区头部。
这一部分用于缓存物理磁盘上的磁盘块,从而加快对磁盘上数据的访问。
int i;
if (block < 0) // 如果数据块号小于0则结束程序,同时给出块号小于0的警告
panic("_bmap: block<0");
if (block >= 7 + 512 + 512 * 512) // 如果块号大于直接块数+单级块数+两级块数
//超出文件系统表示范围 则结束程序并给出块数过大的警告
panic("_bmap: block>big");
!文件在磁盘上存储时,并不是在连续的空间存储的。
一个文件可能对应的是几个设备逻辑块号(设备的逻辑块号同样决定了系统支持的最大的文件长度)
这些信息存储在i_zone中。
if (block < 7) // 如果该块号小于7则使用直接块表示
{
if (create && !inode->i_zone[block]) // 创建标志置位, 并且i_node中对应该块的逻辑块(区段)字段为0
if (inode->i_zone[block] = new_block(inode->i_dev))
// 向相应设备申请一磁盘块
{
inode->i_ctime = CURRENT_TIME; // 设置i_node修改时间
inode->i_dirt = 1; // 置i_node已修改标志
}
return inode->i_zone[block]; // 返回逻辑块号
}
block -= 7; // 将block减去直接块所容纳的块数(7)
if (block < 512) // 如果该块号>=7,并且小于7+512,则说明是单级块。下面对单级块进行处理。
{
if (create && !inode->i_zone[7]) // 如果是创建,并且该i_node中对应单级块字段为0,
//i_zone[7]指向单级块,存储的是相对于软盘的逻辑块号,在以后读取该块时,
//使用下面的据对软盘号。注意:首次使用间接块需申请一磁盘块用于存放间接块信息。
if (inode->i_zone[7] = new_block(inode->i_dev))
{
// 同上设置i_node已修改标志和修改时间
inode->i_dirt = 1;
inode->i_ctime = CURRENT_TIME;
}
if (!inode->i_zone[7]) // 若此时i 节点间接块字段中为0,表明申请磁盘块失败
return 0;
/* 读取设备上的单级块 */
// 1、了解到i_zone[7]对应逻辑块号
// 2、将该逻辑块号对应内容读入到内存中
// 3、在内存中查找(间接)查找逻辑块号
if (!(bh = bread(inode->i_dev, inode->i_zone[7])))
return 0; //读取该单级块的直接块(读取设备上的数据)
i = ((unsigned short *)(bh->b_data))[block]; // 取该间接块上第block 项中的逻辑块号(盘块号)
if (create && !i) // 如果是创建并且间接块的第block 项中的逻辑块号
为0 if (i = new_block(inode->i_dev)) // 申请一磁盘块(逻辑块)
{
((unsigned short *)(bh->b_data))[block] = i; // 间接块中的第block项等于该新逻辑块块号
bh->b_dirt = 1; // 置位间接块的已修改标志
}
brelse(bh); // 释放该间接块
return i; // 返回磁盘上新申请的对应block 的逻辑块的块号
}
block -= 512; // 将block再减去间接块所容纳的块数(512)
if (create && !inode->i_zone[8]) // 如果是新创建并且i_node的两级间接块字段为0,
//则需申请一磁盘块用于存放两级间接块的单级块信息,
//并将此实际磁盘块号填入两级间接块字段中。之后置i_node已修改编制和修改时间。
if (inode->i_zone[8] = new_block(inode->i_dev))
{
// 置i_node已修改编制和修改时间
inode->i_dirt = 1;
inode->i_ctime = CURRENT_TIME;
}
if (!inode->i_zone[8]) // 若此时i节点两级间接块字段为0表明申请磁盘块失败,
//返回0退出。i_zone[8] 指向软盘的二级逻辑块。 return 0;
if (!(bh = bread(inode->i_dev, inode->i_zone[8])))
return 0; // 读取该两级间接块的单级块(读取设备上的数据)。即是说明的是间接寻找逻辑块的信息时存储在设备上
i = ((unsigned short *)bh->b_data)[block >> 9]; // 取该二次间接块的一级块上第(block/512)项中的逻辑块号
if (create && !i)// 如果是创建并且二次间接块的一级块上第(block/512)项中的逻辑块号为0 的话
if (i = new_block(inode->i_dev)) // 申请一磁盘块(逻辑块)作为二次间接块的二级块
{
((unsigned short *)(bh->b_data))[block >> 9] = i;
bh->b_dirt = 1; // 置位两级间接块的单级块已修改标志
}
brelse(bh); // 释放二次间接块的一级块
if (!i)
return 0; // 如果二次间接块的二级块块号为0,表示申请磁盘块失败
if (!(bh = bread(inode->i_dev, i)))
return 0; // 读取二次间接块的二级块
i = ((unsigned short *)bh->b_data)[block & 511]; // 取该二级块上第block 项中的逻辑块号
if (create && !i) // 如果是创建并且二级块的第block 项中的逻辑块号为0 的话,则申请一磁盘块(逻辑块)
if (i = new_block(inode->i_dev))
{
((unsigned short *)(bh->b_data))[block & 511] = i;
bh->b_dirt = 1; // 置位两级间接块的两级块已修改标志
}
brelse(bh); // 最后释放该二次间接块的二级块
return i; // 返回磁盘上新申请的对应block 的逻辑块的块号
}
/* 根据文件的块偏移量来得到设备上的逻辑块号 */
/* 根据文件的m_inode节点信息,和在文件中的偏移量block来影射到实际的 */
/* 逻辑块号,有返回值int返回 */
/*2-1-8*/
// 在该函数调用时,create = 0,该函数只是返回的是return
int bmap (struct m_inode *inode, int block)
{
inode->i_zone[block]; //只是返回相应的信息
return _bmap (inode, block, 0);
}
/*2-1-9*/
//创建文件数据块block在设备上对应的逻辑块,并返回设备上对应的逻辑块号
int create_block (struct m_inode *inode, int block)
{
return _bmap (inode, block, 1);
}