以Linux 0.11为实例,兴趣所至,个人总结,不保证正确性。。。
[i节点]
i节点是表征文件的方式,因此在linux 0.11内核中,有一系列专门操作i节点的函数,在fs/inode.c中。与i节点进程同步相关的函数,主要是wait_on_inode、lock_inode和unlock_inode,这几个函数都比较简单。以lock_inode函数为例,
static
inline
void
lock_inode(
struct
m_inode * inode)
{
cli();
while
(inode->i_lock)
sleep_on(&inode->i_wait);
inode->i_lock=1;
sti();
}
在进入函数时,先用cli关闭中断。如果这个i节点已经被加锁,那让当前进程睡眠在这个i节点上。等待这个i节点被解锁,然后当前进程将这个i节点加锁。最后退出函数之前,打开中断。unlock_inode与lock_inode执行的动作相反,将这个i节点解锁,然后唤醒等待在这个i节点上的进程。
系统在启动的时候,会初始化一个i节点的数组inode_table,这个数组中的i节点就是系统全部可用的i节点。iget是根据设备号和i节点号获取i节点的入口函数,get_empty_inode是从inode_table数组中获取一个空的i节点的入口。get_pipe_inode是获取一个管道设备的i节点,可以把管道看成一个虚拟的设备,所以也需要一个i节点来表示。读写i节点的入口函数是read_inode和write_inode,在进行i节点的读写之前,要先设置好待读写的i节点的设备号和i节点号。对于文件的i节点,还需要保存相应的文件数据,通过_bmap来实现。
[get_empty_inode]
get_empty_inode的作用是从inode_table中获取一个空的inode。如果有必要,就将数据同步到磁盘上去。并且设置i节点各个字段的初值。实现原理就是对inode_table做一次遍历,找到空闲的i节点。代码如下
struct
m_inode * get_empty_inode(
void
)
{
struct
m_inode * inode;
static
struct
m_inode * last_inode = inode_table; //inode_table在inode.c中定义,
struct
m_inode inode_table[NR_INODE]={{0,},};
int
i;
do
{
inode = NULL;
for
(i = NR_INODE; i ; i--) {
/*
如果last_inode已经指向了inode_table数组的最后一项,就让它重新指向inode_table的第一项
保证外围的for语句在最坏的情况下能够将数组整个遍历一遍
*/
if
(++last_inode >= inode_table + NR_INODE)
last_inode = inode_table;
/*
虽然说,找到一个空闲的i节点只需要i_count为0即可。i_dirt和i_lock都为0的话,就更好了。i_dirt为0,表明在
使用这个i节点之前不需要就i节点数据写入磁盘。i_lock为0,说明不需要等待其它进程释放这个i节点
*/
if
(!last_inode->i_count) {
inode = last_inode;
if
(!inode->i_dirt && !inode->i_lock)
break
;
}
}
if
(!inode) { //遍历完之后没有找到空闲的i节点,表明系统中的i节点已经全部被占用
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"
);
}
/*
能运行到这里说明能找到空闲的i节点,剩下的工作就是看是否需要先把i节点数据写入磁盘,等待解锁了
*/
wait_on_inode(inode);
while
(inode->i_dirt) {
write_inode(inode);
wait_on_inode(inode);
}
}
while
(inode->i_count); //在进程睡眠期间,如果这个i节点又被其它进程所用。那就需要重新寻找了
/*
到此,找到了一个空的i节点,没有被加锁,也不再需要写入磁盘了。剩下就做一些初始化字段值的工作即可
*/
memset(inode,0,
sizeof
(*inode));
inode->i_count = 1;
return
inode;
}
[iget]
iget函数需要两个参数,设备号和i节点号。它的作用是根据设备号和i节点号,获取相应的i节点信息。在寻找时,如果存在设备和i节点号的i节点,就使用这个i节点。否则,就使用一个空的i节点。
struct
m_inode * iget(
int
dev,
int
nr)
{
struct
m_inode * inode, * empty;
if
(!dev)
panic(
"iget with dev==0"
);
empty = get_empty_inode(); //先获取一个空的i节点
inode = inode_table;
while
(inode < NR_INODE+inode_table) {
if
(inode->i_dev != dev || inode->i_num != nr) {
inode++;
continue
;
}
/*
如果能找到已有的i节点的设备号和i节点号都对应,那么就等待这个i节点解锁
在等待解锁的过程中,这个i节点的内容可能发生变化
所以解锁后,需要再次确认i节点的设备号和i节点号是否就是我们所需要的
*/
wait_on_inode(inode);
if
(inode->i_dev != dev || inode->i_num != nr) {
inode = inode_table;
continue
;
}
inode->i_count++;
//here!找到了设备号和i节点号都对应的i节点,就是所需要的
/*
i_mout为1表示这个i节点是否是其它设备/文件系统的挂载节点。如果是其它设备/文件系统的挂载节点,就需要用这个设备超级块
上的设备号来重新获取i节点,并且这是要获取的i节点号为1,获取这个文件系统的根节点。
*/
if
(inode->i_mount) {
int
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)
iput(empty);
return
inode;
}
iput(inode); //前面找到的i节点被释放,重新寻找
dev = super_block[i].s_dev;
nr = ROOT_INO; // #define ROOT_INO 1
inode = inode_table;
continue
;
//
}
//end of if
if
(empty)
// 如果
找到了设备号和i节点号都对应的i节点,最开始获取的空的i节点就要被释放
iput(empty);
return
inode;
}
//end of while
/*
没有找到设备号和i节点号都对应的i节点,就是用那个空的i节点,设置好初值
*/
if
(!empty)
return
(NULL);
inode=empty;
inode->i_dev = dev;
inode->i_num = nr;
read_inode(inode); //从磁盘读取i节点信息
return
inode;
}
[iput]
iput释放一个i节点。根据实际情况,可能需要将数据写回磁盘,或者释放磁盘空间
void
iput(
struct
m_inode * inode)
{
if
(!inode)
return
;
wait_on_inode(inode);
if
(!inode->i_count)
panic(
"iput: trying to free free inode"
);
/*
管道i节点比较特殊,它涉及到两个进程。读进程和写进程。由于管道是一个虚拟设备,管道i节点的i_size字段表示的是这个管道i节点实际使
用的物理页的其实地址。
*/
if
(inode->i_pipe) {
wake_up(&inode->i_wait);
/*
管道i节点涉及到两个进程,所以引用次数减一之后,如果不为零,就不用释放内存空间
*/
if
(--inode->i_count)
return
;
free_page(inode->i_size);
inode->i_count=0;
inode->i_dirt=0;
inode->i_pipe=0;
return
;
}
if
(!inode->i_dev) {
inode->i_count--;
return
;
}
/*
S_ISBLC宏表示这个i节点是否是设备的i节点。即这个i节点用来表示一个“设备文件”。如果是,那么i_zone[0]中就存放的是设备号
*/
if
(S_ISBLK(inode->i_mode)) {
sync_dev(inode->i_zone[0]);
wait_on_inode(inode);
}
repeat:
if
(inode->i_count>1) {
inode->i_count--;
return
;
}
/*
i_nlinks为0,就要释放i节点,如果有需要就要释放磁盘空间(当这个i节点代表的是一般的文件或目录时。存储文件数据和目录数据的逻辑块需要被释放掉。truncate函数实现释放磁盘的功能。它会首先进行是否为一般文件或目录的i节点检查
*/
if
(!inode->i_nlinks) {
truncate(inode);
free_inode(inode);
return
;
}
/*
i节点的内容被修改,那么就需要把内容同步到磁盘上
*/
if
(inode->i_dirt) {
write_inode(inode);
wait_on_inode(inode);
goto
repeat;
}
inode->i_count--;
return
;
}
[读写i节点]
读写i节点的函数分别是read_inode和write_inode。它们的作用分别是根据i节点的设备号和i节点号,将磁盘上的数据读入到i节点中,或者把i节点的数据写入磁盘。当然,都是通过中间的高速缓存来实现的。
在对一个i节点进行read_inode调用之前,需要首先设定好这个i节点的设备号和i节点号。同理也是write_inode。
static
void
read_inode(
struct
m_inode * inode)
{
struct
super_block * sb;
struct
buffer_head * bh;
int
block;
lock_inode(inode);
/*
get_super是根据设备号,获取这个设备的超级快的数据。
linux 0.11中定义了一个超级快数据
struct
super_block super_block[NR_SUPER];
设备在挂载的时候,会把超级快的数据加入到这个数组中。get_super的实现就是遍历这个数组,找到所需的超级块为止
*/
if
(!(sb=get_super(inode->i_dev)))
panic(
"trying to read inode without dev"
);
/*
这里是根据i节点号计算这个i节点所在的逻辑块的块号。因为磁盘是块设备,所以,对磁盘数据的读写都是以块为单位的。在逻逻辑块的编号上,从小到大是:引导块、超级块、i节点位图区、逻辑块节点位图区。然后才是i节点区。所以是2(超级块+引导块)+s_imap_blocks(i节点位图区所占的逻辑块的快数)+s_zmap_blocks(逻辑块位图区所占的逻辑块的块数)+(i_num-1)/每个逻辑块的i节点的个数
*/
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"
);
/*
磁盘上存放的i节点数据是磁盘上i节点,对应的数据结构是d_inode。而内存中的i节点要比磁盘上i节点的数据结构多一些数据项,但在前面部分是一样的。将整个磁盘块看成磁盘i节点数组,根据i节点号计算索引,读取数据。最后,数据读取完成后,释放缓存,解锁这个i节点
*/
*(
struct
d_inode *)inode =
((
struct
d_inode *)bh->b_data)
[(inode->i_num-1)%INODES_PER_BLOCK];
brelse(bh);
unlock_inode(inode);
}
/*
*/
static
void
write_inode(
struct
m_inode * inode)
{
struct
super_block * sb;
struct
buffer_head * bh;
int
block;
lock_inode(inode);
/*
i_dirt为0表明i节点读入内存后,内容没有被修改,所以不需要重新写回磁盘
*/
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"
);
/*
计算i节点所在的逻辑块的编号
*/
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"
);
/*
写入i节点数据,实际上是写到高速缓存
*/
((
struct
d_inode *)bh->b_data)
[(inode->i_num-1)%INODES_PER_BLOCK] =
*(
struct
d_inode *)inode;
/*
b_dirt设为1,表明内容被修改。如果有需要,就将数据同步到磁盘
*/
bh->b_dirt=1;
inode->i_dirt=0;
brelse(bh);
unlock_inode(inode);
}