/*
* linux/fs/inode.c
*
* (C) 1991 Linus Torvalds
*/
#include <string.h>
#include <sys/stat.h> // 文件状态头文件
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <asm/system.h>
struct m_inode inode_table[NR_INODE]={{0,},}; // 内存中i 节点表
static void read_inode(struct m_inode * inode);
static void write_inode(struct m_inode * inode);
/* 等待指定的i 节点可用 */
static inline void wait_on_inode(struct m_inode * inode)
{
cli();
while (inode->i_lock) // // 如果i 节点已被锁定
sleep_on(&inode->i_wait); // 不可中断的等待状态
sti();
}
/* 对指定的i 节点上锁 */
static inline void lock_inode(struct m_inode * inode)
{
cli();
while (inode->i_lock)
sleep_on(&inode->i_wait);
inode->i_lock=1; // 置锁定标志
sti();
}
/* 对指定的i 节点解锁 */
static inline void unlock_inode(struct m_inode * inode)
{
inode->i_lock=0; // 复位i 节点的锁定标志
wake_up(&inode->i_wait); // 唤醒等待此i 节点的进程
}
/* 释放内存中设备dev 的所有i 节点 */
void invalidate_inodes(int dev)
{
int i;
struct m_inode * inode;
inode = 0+inode_table;
for(i=0 ; i<NR_INODE ; i++,inode++)
{
wait_on_inode(inode); // 等待该i 节点可用
if (inode->i_dev == dev)
{
if (inode->i_count) // 如果其引用数不为0
printk("inode in use on removed disk\n\r");
inode->i_dev = inode->i_dirt = 0; // 释放该i 节点,只是一个标记
}
}
}
/* 同步内存(inode数组)与设备上的所有i 节点信息 */
void sync_inodes(void)
{
int i;
struct m_inode * inode;
inode = 0+inode_table; // 第一项
for(i=0 ; i<NR_INODE ; i++,inode++)
{
wait_on_inode(inode); // 等待该i 节点可用
// 如果该i 节点已修改且不是管道节点
if (inode->i_dirt && !inode->i_pipe)
write_inode(inode); // 写盘
}
}
/* 文件数据块映射到盘块的处理操作,可能需要建立新的逻辑块 */
static int _bmap(struct m_inode * inode,int block,int create)
{
// inode 文件的i 节点;block (相对于文件而言)文件中的数据块号;create 创建标志
// block是相对于文件而言,但是i_zone[block]则是相对于设备而言的。相对于文件的
// block可能不止i_zone数组大小,所以如果block的值大于7时,需要使用间接来寻址。
// 如下的映射 :
// |-----------| |------------------------------|
// |file block | ---> | disk block |
// |-----------| |------------------------------|
// 如果create = 1,需要建立新的逻辑块,
struct buffer_head * bh;
int i;
if (block<0) // 块号小于0 ?
panic("_bmap: block<0");
if (block >= 7+512+512*512) // 超出文件系统表示范围
panic("_bmap: block>big");
/*
* 文件在磁盘上存储时,并不是在连续空间存储的。一个文件可能对应的是
* 几个设备逻辑块号(设备的逻辑块号同样决定了系统支持的最大的文件长度)
* 这些信息存储在i_zone中。
*/
// 如果该块号小于7,则使用直接块表示
if (block<7)
{
// 创建标志置位, 并且i 节点中对应该块的逻辑块(区段)字段为0
if (create && !inode->i_zone[block])
// 向相应设备申请一磁盘块
if (inode->i_zone[block]=new_block(inode->i_dev))
{
inode->i_ctime=CURRENT_TIME; // 设置i 节点修改时间
inode->i_dirt=1; // 置i 节点已修改标志
}
return inode->i_zone[block]; // 返回逻辑块号
}
// 如果该块号>=7,并且小于7+512,则说明是一次间接块
block -= 7;
if (block<512)
{
// 如果是创建,并且该i 节点中对应间接块字段为0
if (create && !inode->i_zone[7]) // 首次使用间接块
// 需申请一磁盘块用于存放间接块信息,inode->i_zone[7]
// 中存储的是相对于软盘的逻辑块号,在以后读取该块时,
// 使用下面的据对软盘号
if (inode->i_zone[7]=new_block(inode->i_dev))
{
// 设置i 节点已修改标志和修改时间
inode->i_dirt=1;
inode->i_ctime=CURRENT_TIME;
}
// 若此时i 节点间接块字段中为0,表明申请磁盘块失败
if (!inode->i_zone[7])
return 0;
/* 读取设备上的一次间接块 */
/*
* i_zone[7] -- 逻辑块号 -- 将该逻辑块号对应内容读入到
* 内存中 -- 在内存中查找(间接)查找逻辑块号
*
*/
if (!(bh = bread(inode->i_dev,inode->i_zone[7])))
return 0;
// 取该间接块上第block 项中的逻辑块号(盘块号)
i = ((unsigned short *) (bh->b_data))[block];
// 如果是创建并且间接块的第block 项中的逻辑块号为0 的话
// 该初始化是在函数new_block中使用函数clear_block来讲整个
// 分配的缓冲区域置0
if (create && !i)
if (i=new_block(inode->i_dev)) // 申请一磁盘块(逻辑块)
{
// 间接块中的第block 项等于该新逻辑块块号
((unsigned short *) (bh->b_data))[block]=i;
bh->b_dirt=1; // 置位间接块的已修改标志
}
brelse(bh); // 最后释放该间接块
return i; // 返回磁盘上新申请的对应block 的逻辑块的块号
}
// 程序运行到此,表明数据块是二次间接块
block -= 512;
// 如果是新创建并且i 节点的二次间接块字段为0
if (create && !inode->i_zone[8])
// 需申请一磁盘块用于存放二次间接块的一级块信息
// 将此实际磁盘块号填入二次间接块字段中
if (inode->i_zone[8]=new_block(inode->i_dev))
{
// 置i 节点已修改编制和修改时间
inode->i_dirt=1;
inode->i_ctime=CURRENT_TIME;
}
// 若此时i 节点二次间接块字段为0,表明申请磁盘块失败
if (!inode->i_zone[8])
return 0;
// 读取该二次间接块的一级块(读取设备上的数据)。即是说明的是间接
// 寻找逻辑块的信息时存储在设备上
if (!(bh=bread(inode->i_dev,inode->i_zone[8])))
return 0;
// 取该二次间接块的一级块上第(block/512)项中的逻辑块号
i = ((unsigned short *)bh->b_data)[block>>9];
// 如果是创建并且二次间接块的一级块上第(block/512)项中的逻辑块号为0 的话
if (create && !i)
// 申请一磁盘块(逻辑块)作为二次间接块的二级块
if (i=new_block(inode->i_dev))
{
((unsigned short *) (bh->b_data))[block>>9]=i;
bh->b_dirt=1; // 置位二次间接块的一级块已修改标志
}
brelse(bh); // 释放二次间接块的一级块
// 如果二次间接块的二级块块号为0,表示申请磁盘块失败
if (!i)
return 0;
// 读取二次间接块的二级块
if (!(bh=bread(inode->i_dev,i)))
return 0;
// 取该二级块上第block 项中的逻辑块号
i = ((unsigned short *)bh->b_data)[block&511];
// 如果是创建并且二级块的第block 项中的逻辑块号为0 的话,则申请一磁盘块(逻辑块)
if (create && !i)
if (i=new_block(inode->i_dev)) {
((unsigned short *) (bh->b_data))[block&511]=i;
bh->b_dirt=1;
}
brelse(bh); // 最后释放该二次间接块的二级块
// 返回磁盘上新申请的对应block 的逻辑块的块号
return i;
}
/* 根据文件的块偏移量来得到设备上的逻辑块号 */
/* 根据文件的m_inode节点信息,和在文件中的偏移量block来影射到实际的 */
/* 逻辑块号,有返回值int返回 */
int bmap(struct m_inode * inode,int block)
{
/*
* 在该函数调用时,create = 0,该函数只是返回的是return inode->i_zone[block];
* 只是返回相应的信息
*/
return _bmap(inode,block,0);
}
/* 创建文件数据块block 在设备上对应的逻辑块,并返回设备上对应的逻辑块号 */
int create_block(struct m_inode * inode, int block)
{
return _bmap(inode,block,1);
}
/* 释放一个i 节点(从内存数组回写入设备文件) */
void iput(struct m_inode * inode)
{
if (!inode)
return;
wait_on_inode(inode); // 等待inode 节点解锁
if (!inode->i_count)
panic("iput: trying to free free inode");
if (inode->i_pipe) // 管道i 节点
{
wake_up(&inode->i_wait); // 唤醒等待该管道的进程
if (--inode->i_count) // 引用次数减1, 还有引用则返回
return;
// 释放管道占用的内存页面
free_page(inode->i_size);
// 复位该节点的引用计数值
inode->i_count=0;
inode->i_dirt=0; // 已修改标志
inode->i_pipe=0; // 管道标志
return;
}
if (!inode->i_dev) // 如果i 节点对应的设备号0
{
inode->i_count--; // 此节点的引用计数递减1
return;
}
if (S_ISBLK(inode->i_mode)) // 块设备文件的i 节点?
{
sync_dev(inode->i_zone[0]); // 刷新该设备
wait_on_inode(inode); // 等待i 节点解锁
}
repeat:
if (inode->i_count>1) // // 如果i 节点的引用计数大于1
{
inode->i_count--; // 递减1
return;
}
if (!inode->i_nlinks) // 如果i 节点的链接数为0
{
truncate(inode); // 释放该i 节点的所有逻辑块
free_inode(inode); // 释放该i 节点
return;
}
if (inode->i_dirt) // 该i 节点已作过修改,没有修改的话,不需要写入文件
{
// 更新该i 节点
write_inode(inode); /* we can sleep - so do again */
wait_on_inode(inode); // 等待该i 节点解锁
goto repeat;
}
// i 节点引用计数递减1
inode->i_count--;
return;
}
/* 从i 节点表(inode_table)中获取一个空闲i 节点项 */
struct m_inode * get_empty_inode(void)
{
struct m_inode * inode;
// last_inode 指向i 节点表第一项
static struct m_inode * last_inode = inode_table;
int i;
do {
inode = NULL;
for (i = NR_INODE; i ; i--)
{
// 循环
if (++last_inode >= inode_table + NR_INODE)
last_inode = inode_table;
// 计数值为0,则说明可能找到空闲i 节点项
if (!last_inode->i_count)
{
inode = last_inode;
// 如果该i 节点的已修改标志和锁定标志均为0,则我们可以使用该i 节点
if (!inode->i_dirt && !inode->i_lock)
break;
}
}
// 此时inode就是上面寻找到的空闲的inode项
if (!inode) // 如果没有找到空闲i 节点(inode=NULL)
{
for (i=0 ; i<NR_INODE ; i++)
printk("%04x: %6d\t",inode_table[i].i_dev,
inode_table[i].i_num);
panic("No free inodes in mem");
}
wait_on_inode(inode); // 等待该i 节点解锁
// 如果该i 节点已修改标志被置位的话
while (inode->i_dirt)
{
write_inode(inode); // 将该i 节点刷新
wait_on_inode(inode); // 等待该i 节点解锁
}
// 如果i 节点又被其它占用的话,则重新寻找空闲i 节点
} while (inode->i_count);
memset(inode,0,sizeof(*inode)); // 该i 节点项内容清零
inode->i_count = 1; // 引用标志为1
return inode;
}
/* 获取管道节点 */
struct m_inode * get_pipe_inode(void)
{
struct m_inode * inode;
// 寻找一个空闲i 节点项
if (!(inode = get_empty_inode()))
return NULL;
// 节点的i_size 字段指向缓冲区
if (!(inode->i_size=get_free_page()))
{
inode->i_count = 0;
return NULL;
}
inode->i_count = 2; /* sum of readers/writers */
// 复位管道头尾指针
PIPE_HEAD(*inode) = PIPE_TAIL(*inode) = 0;
inode->i_pipe = 1; // 置节点为管道使用的标志
return inode;
}
/* 从设备上dev读取指定节点号的i 节点nr */
struct m_inode * iget(int dev,int nr)
{
struct m_inode * inode, * empty;
if (!dev)
panic("iget with dev==0");
empty = get_empty_inode();
inode = inode_table;
// 扫描i 节点表。寻找指定节点号的i 节点
while (inode < NR_INODE+inode_table)
{
if (inode->i_dev != dev || inode->i_num != nr)
{
inode++;
continue;
}
// 此时inode->i_dev == dev && inode->i_num == nr
wait_on_inode(inode); // 等待该节点解锁
// 在等待该节点解锁的阶段,节点表可能会发生变化
if (inode->i_dev != dev || inode->i_num != nr)
{
// 如果发生了变化,则再次重新扫描整个i 节点表。
inode = inode_table;
continue;
}
// 没有变化
inode->i_count++; // 将该i 节点引用计数增1
/**************************************************************/
/* 是目录inode? */
if (inode->i_mount) // 如果该i 节点是其它文件系统的安装点
{
int i;
// 在超级块表中搜寻安装在此i 节点的超级块
for (i = 0 ; i<NR_SUPER ; i++)
if (super_block[i].s_imount==inode)
break;
if (i >= NR_SUPER) // 没有找到
{
// 显示出错信息
printk("Mounted inode hasn't got sb\n");
if (empty) // 如果empty不空,清空inode内存数组
iput(empty);
return inode;
}
iput(inode); // 将该i 节点写盘
dev = super_block[i].s_dev; // 从安装在此i 节点文件系统的超级块上取设备号
// 扫描整个i 节点表,取该被安装文件系统的根节点
nr = ROOT_INO; // nr = ROOT_INO = 1
inode = inode_table;
continue;
}
/***************************************************************/
// 已经找到相应的i 节点,因此放弃临时申请的空闲节点,返回该找到的i 节点
if (empty)
iput(empty);
return inode;
}
// 程序运行到这,说明在i 节点表中没有找到指定的i 节点
if (!empty)
return (NULL);
inode=empty;
inode->i_dev = dev;
inode->i_num = nr;
// 从相应设备上读取该i 节点信息
read_inode(inode);
return inode; // 返回该i节点的值
}
/* 从设备上读取指定i 节点的信息到内存中(缓冲区中) */
static void read_inode(struct m_inode * inode)
{
struct super_block * sb;
struct buffer_head * bh;
int block;
// 首先锁定该i 节点,取该节点所在设备的超级块
lock_inode(inode);
if (!(sb=get_super(inode->i_dev)))
panic("trying to read inode without dev");
// 计算inode的位置(在设备文件中存在存放inode的地方,
// 此位置是在设备上存放inode的位置)
block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks +
(inode->i_num-1)/INODES_PER_BLOCK;
if (!(bh=bread(inode->i_dev,block)))
panic("unable to read i-node block");
*(struct d_inode *)inode =
((struct d_inode *)bh->b_data)
[(inode->i_num-1)%INODES_PER_BLOCK];
// 最后释放读入的缓冲区,并解锁该i 节点
brelse(bh);
unlock_inode(inode);
}
/* 将指定i 节点信息写入设备 */
static void write_inode(struct m_inode * inode)
{
struct super_block * sb;
struct buffer_head * bh;
int block;
lock_inode(inode);
if (!inode->i_dirt || !inode->i_dev) // 无须写
{
unlock_inode(inode);
return;
}
if (!(sb=get_super(inode->i_dev)))
panic("trying to write inode without device");
// 同上,计算该inode在设备上inode存储区域上的位置
block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks +
(inode->i_num-1)/INODES_PER_BLOCK;
// 从设备上读取该i 节点所在的逻辑块
if (!(bh=bread(inode->i_dev,block)))
panic("unable to read i-node block");
// 将i节点的信息暂时的写入bh->data中,并设置b_dirty
// 标志,内核在合适的时间将bh的内容刷洗到实际的设备上
((struct d_inode *)bh->b_data)
[(inode->i_num-1)%INODES_PER_BLOCK] =
*(struct d_inode *)inode;
bh->b_dirt=1;
inode->i_dirt=0;
brelse(bh);
unlock_inode(inode);
}
/*
* 这个文件主要是实现函数iget和函数iput。函数iget的主要作用是
* 从设备上读取指定的inode节点号,就是实现从设备(存数介质)到
* 内存inode数组的转移。函数iput则是相反的函数,该函数实现的是
* 将内存inode的信息写入到设备中,在函数中调用write_inode函数
* 写入。另外一个重要的函数时bmap函数,该函数实现的是根据文件
* 的block来计算得到设备上的绝对逻辑块号
*/
参考《linux内核完全注释》和网上相关文章