linux 0.11 文件系统总结

linux 0.11 文件系统总结

文件系统

详见:操作系统文件系统。

操作系统中的文件系统可以分为两部分:操作系统内核中或者在硬盘软盘虚拟盘中。一个物理设备可以分为多个逻辑设备,比如一个物理硬盘可以分为多个逻辑硬盘。而一个逻辑设备只有一个文件系统,一个文件系统只包含一个i结点的树结构。一个逻辑设备只能有一个根i结点。

image-20231203234247319

未安装文件系统的磁盘称之为生磁盘,生磁盘也可以作为文件读写,linux中一切皆文件。

生磁盘可以被分区,分区中可以安装文件系统,常见的文件系统有fat32、ext2、ext4等。

MINIX 文件系统与标准 UNIX 的文件系统基本相同。它由 6 个部分组成。分区内可以安装指定文件系统,同一磁盘多个分区文件系统不要求相同。MINIX文件系统布局如下:(下述部分是在磁盘上的)

MINIX文件系统布局
  • 引导块:若作为引导分区,将存放操作系统的引导程序代码,否则空置。

  • 超级块:用于存放磁盘设备上文件系统结构的信息,说明各部分的大小。

  • i节点位图:标记i节点数据元素是否被使用

  • 逻辑块位图:标记磁盘数据块是否被使用

  • i节点区:用于存放inode节点数据,一个文件对应一个inode节点,inode节点存储文件属性数据。

  • 数据区:以固定大小盘块(1k)为单位进行动态分配和回收,用于存储数据,类似内存分页。

    位图:一个比特对应一个逻辑块,0,1代表是否被占用

    删除文件:清理数据块关系清掉,对应逻辑块位图清0,清理i结点和i结点对应位图。

    如果一个物理块有多个逻辑块,上述就罗列着摆放:

    image-20231211195002220

    超级块结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct super_block {
unsigned short s_ninodes;
unsigned short s_nzones;
unsigned short s_imap_blocks;
unsigned short s_zmap_blocks;
unsigned short s_firstdatazone;
unsigned short s_log_zone_size;
unsigned long s_max_size;
unsigned short s_magic;
/* These are only in memory */
struct buffer_head * s_imap[8];//位图信息存到了缓冲块里面
struct buffer_head * s_zmap[8];
unsigned short s_dev;
struct m_inode * s_isup;//文件系统的根i结点
struct m_inode * s_imount;//文件系统挂载到的结点
unsigned long s_time;
struct task_struct * s_wait;
unsigned char s_lock;
unsigned char s_rd_only;
unsigned char s_dirt;
};
超级块结构

inode结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct m_inode {
unsigned short i_mode;
unsigned short i_uid;
unsigned long i_size;
unsigned long i_mtime;
unsigned char i_gid;
unsigned char i_nlinks;
unsigned short i_zone[9];
/* these are in memory also */
struct task_struct * i_wait;
unsigned long i_atime;
unsigned long i_ctime;
unsigned short i_dev;
unsigned short i_num;
unsigned short i_count;
unsigned char i_lock;
unsigned char i_dirt;
unsigned char i_pipe;
unsigned char i_mount;
unsigned char i_seek;
unsigned char i_update;
};
inode结构

i_zone数组

unsigned short i_zone[9];

i_zone数组包含直接盘块号、一次间接盘块号和二次间接盘块号。一次盘块号可视为单级页表,一次间接盘块号可视为二级页表、二次间接盘块号可视为三级页表。

这种处理方式的好处在于,对于小文件,通过直接块号可快速定位数据块;对于中等类型的文件,一次间接块可以维护较多数据块的同时,具有较快的访问速度;对于大型文件,二次间接盘块号可以维护大量磁盘块,但访问速度较慢。

内存多级页表与i_zone直接区别:不同进程具有固定大小的虚地址空间,并且对其整个虚地址空间的内存,都有可能访问到,因此使用多级页表。文件系统内存在很多大小不一的文件,综合考虑对不同大小文件的特点,使用1-3级磁盘块表可以分别处理小、中、大文件。

izone

文件系统树形结构

所有文件的i结点最终会挂成一个树形结构,树根i结点就是文件系统的根i结点,

加载文件系统就是把一个文件系统的根i结点挂接在另一个文件系统的i结点上,按照这个设计,一个文件系统必须要挂在另一个文件系统上面,最后最根部那个文件系统就是根文件系统

加载根文件系统

根文件系统挂在super_block[8]上。超级块:有一个超级块数组super_block[8]里面每一个元素是一个超级块,只要一个文件系统加载到内核了这个文件系统的根i结点会依次加载到这个数组里面。最多加载8个文件系统

总体效果图

文件系统用i结点来管理,一个i结点管理一个文件,目录文件也是文件,也有i结点来管理。

文件系统与i结点

加载文件过程举例

通过文件inode节点,可以定位文件数据块,那如何通过文件路径定位到具体文件?

文件系统主要包含文件和目录两种文件,目录是一种特殊的文件,其文件内容存储其目录下文件名->inode节点号的映射信息。文件查找开始于根目录,根目录号固定为0,不需要查找即可直接打开。

举例说明文件查找过程,给定存在路径/name1/name2/name3查找具体文件过程:

1)通过根节点inode号,打开根目录,读取其文件内容,即目录下文件名->inode节点号映射表,找到name1目录inode节点号n1

2)通过name1的inode号n1,打开name1目录,读取其文件内容,即目录下文件名->inode节点号映射表,找到name1目录inode节点号n2

3)通过name2的inode号n2,打开name2目录,读取其文件内容,即目录下文件名->inode节点号映射表,找到name3目录inode节点号n3

4)通过name3的inode号n3,打开name3文件

怎么打开文件:

通过文件查找找到文件inode节点号,然后打开文件,即读取inode至内存。

定位数据块:通过文件inode节点,访问其i_zone数组,进一步可以定位具体的数据所在磁盘块号。

以c语言open和close返回的是什么解释文件系统

1
2
3
4
5
6
7
8
9
10
 struct file file_table[NR_FILE];//20
struct super_block super_block[NR_SUPER];//8

struct file {
unsigned short f_mode;
unsigned short f_flags;
unsigned short f_count;
struct m_inode * f_inode;
off_t f_pos;
};
  • 操作系统只有一个super_blocks数组,每个数组元素是一个超级块,一个超级块管理一个逻辑设备,因此最多挂载8个逻辑设备,其中只有一个根设备。

  • inode_table[32]每一个元素就是一个i结点,是在操作系统中所有打开的i结点

  • file_table 里面装了file结构体,struct super_block super_block[NR_SUPER]

​ f_inode指针指向inode_table里面的元素

  • task struct里面的filp struct file * filp[NR_OPEN];/: 指针数组,每个元素都是file类型的指针

linux 0.11一个进程最多只能打开20个文件(文件是可以重复打开的)可以同一个文件占多个file_table的表项

filp归进程管。进程打开一个文件,首先在filp里面找空闲项, 将这个空闲的位置指向file_table其中的一项,这一项里面的f_inode指针指向inode_table。 c语言里面打开文件返回的句柄就是这个指向的inode_table位置对应的下标索引,例如下图就是0.打开文件就是建立这个指针链接的过程,对应的close文件就是把这个关系链断掉。

file对应的是用户的需求,inode对应的是内核管理

文件系统

打开同一个文件,指向的inode_table是一个,file_table新开了一个

==file_table【64】是整个kernel只有一个,file_table[32]也是整个操作系统只有一个,每个元素是一个file对象==

打开同一个文件

目录跟结点也要放到inode——table,当路径找完了就把结点pop了

image-20231205200445043

i结点是如何管理文件的

1
2
3
4
5
struct m_inode {
...
unsigned short i_zone[9];
....
};
izone
izone

izone数组是unsigned short类型,两个字节。前7个是直接的指向7个块。一级间接块大小1k个字节,包含了1k/2 个unsigned short索引

可以指向512个块,二级索引同理。有512*512个数据块。

因此一个inode管理的极限是:(7+512+512*512)KB

根文件系统

更换根设备(进程1格式化虚拟盘并更换跟设备为虚拟盘)

之前第二章设置了虚拟盘并初始化,但是当时没有进行格式化还不能作为块设备使用。格式化的信息存在boot操作系统的软盘上

进程1调用rd_load();函数格式化虚拟盘调用

rd_load()是虚拟盘根文件加载函数。在系统初始化阶段,该函数被用于尝试从启动引导盘 上指定的磁盘块位置开始处把一个根文件系统加载到虚拟盘中。在函数中,这个起始磁盘块位置被定为256。当然你也可以根据自己的具体要求修改这个值,只要保证这个值所规定的磁盘容量能容纳内核映象 文件即可。这样一个由内核引导映象文件(Bootimage)加上根文件系统映象文件(Rootiamge)组合而 成的“二合一”磁盘,就可以象启动 DOS 系统盘那样来启动 Linux 系统。在进行正常的根文件系统加载之前,系统会首先执行 rd_load()函数,试图从磁盘的第 257 块中读取 根文件系统超级块。若成功,就把该根文件映象文件读到内存虚拟盘中,并把根文件系统设备标志

ROOT_DEV 设置为虚拟盘设备(0x0101),否则退出 rd_load(),系统继续从别的设备上执行根文件加载 操作。 之前根设备是软盘:bootsect.s里面指定的

==把虚拟盘指定为根设备,读硬盘是有中断的。软盘因为比较快就在内存里。所以读软盘不用中断读软盘要用do_rd_request==

==rd虚拟盘,虚拟的是软盘==,相当于把软盘的内容映射过来,然后把虚拟盘替软盘成为根设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
void rd_load(void)
{
struct buffer_head *bh;
struct super_block s;
int block = 256; /* Start at block 256 */
int i = 1;
int nblocks;
char *cp; /* Move pointer */

if (!rd_length)
return;
printk("Ram disk: %d bytes, starting at 0x%x\n", rd_length,
(int) rd_start);
if (MAJOR(ROOT_DEV) != 2)//判断是不是软盘
return;
//block:引导块,block+1超级块//见上面文件系统的格式图
bh = breada(ROOT_DEV,block+1,block,block+2,-1);
if (!bh) {
printk("Disk error while looking for ramdisk!\n");
return;
}
*((struct d_super_block *) &s) = *((struct d_super_block *) bh->b_data);
brelse(bh);
//判断文件系统是不是minux文件系统
if (s.s_magic != SUPER_MAGIC)
/* No ram disk image present, assume normal floppy boot */
return;
nblocks = s.s_nzones << s.s_log_zone_size;
if (nblocks > (rd_length >> BLOCK_SIZE_BITS)) {
printk("Ram disk image too big! (%d blocks, %d avail)\n",
nblocks, rd_length >> BLOCK_SIZE_BITS);
return;
}
printk("Loading %d bytes into ram disk... 0000k",
nblocks << BLOCK_SIZE_BITS);
cp = rd_start;
while (nblocks) {
if (nblocks > 2)
bh = breada(ROOT_DEV, block, block+1, block+2, -1);
else
bh = bread(ROOT_DEV, block);
if (!bh) {
printk("I/O error on block %d, aborting load\n",
block);
return;
}
(void) memcpy(cp, bh->b_data, BLOCK_SIZE);
brelse(bh);
printk("\010\010\010\010\010%4dk",i);
cp += BLOCK_SIZE;
block++;
nblocks--;
i++;
}
printk("\010\010\010\010\010done \n");
ROOT_DEV=0x0101;//主设备号换为100:虚拟盘
}

加载根文件系统

进程1调用mount_root在根设备虚拟盘上加载根文件系统

1
2
3
4
5
6
struct super_block {
...
struct m_inode * s_isup;//文件系统的根i结点
struct m_inode * s_imount;//文件系统挂载到的结点
...
};
文件系统加载结点

首先挂载super_block数组和file_table数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void mount_root(void)
{
int i,free;
struct super_block * p;
struct m_inode * mi;

if (32 != sizeof (struct d_inode))
panic("bad i-node size");
//file_table初始化引用计数清0
for(i=0;i<NR_FILE;i++)
file_table[i].f_count=0;
//判断是否为软盘
if (MAJOR(ROOT_DEV) == 2) {
printk("Insert root floppy and press ENTER");
wait_for_keypress();
}
//初始化super_block
for(p = &super_block[0] ; p < &super_block[NR_SUPER] ; p++) {
p->s_dev = 0;
p->s_lock = 0;
p->s_wait = NULL;
}
...
}
image-20231211092132558

read_super加载文件系统超级块

整体流程图:

readsuper
1
2
3
4
5
6
7
void mount_root(void)
{
...
if (!(p=read_super(ROOT_DEV)))
panic("Unable to mount root");
...
}

read_super()用于把指定设备的文件系统的==超级块==读入到==缓冲区==中,并登记到超级块数组中,同时也 把文件系统的 i 节点位图和逻辑块位图读入内存超级块结构的相应数组中。最后并返回该超级块结构的 指针。

首先检查这个要读的超级块是不是已经在super_block[8]中,如果有直接使用不用在加载一次了(和缓冲区看有没有现成的一个意思)

1
2
3
4
5
6
7
8
9
10
11
12
13
static struct super_block * read_super(int dev)
{
struct super_block * s;
struct buffer_head * bh;
int i,block;

if (!dev)
return NULL;
check_disk_change(dev);//检查是否换过盘
if (s = get_super(dev))
return s;
...
}

get_super

查这个要读的超级块是不是已经在super_block[8]中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct super_block * get_super(int dev)
{
struct super_block * s;

if (!dev)
return NULL;
s = 0+super_block;
while (s < NR_SUPER+super_block)
if (s->s_dev == dev) {
wait_on_super(s);
if (s->s_dev == dev)
return s;
s = 0+super_block;
} else
s++;
return NULL;
}

超级块上锁等待(别的进程加载了这个文件系统)

1
2
3
4
5
6
7
8
static void wait_on_super(struct super_block * sb)
{
cli();
while (sb->s_lock)
sleep_on(&(sb->s_wait));
sti();
}

在super_block里面找到空项

在super_block中找到一项空的并加锁。这里加载根文件系统,第一项就是空的所以是选了第一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static struct super_block * read_super(int dev)
{
...
for (s = 0+super_block ;; s++) {
if (s >= NR_SUPER+super_block)
return NULL;
if (!s->s_dev)
break;
}
s->s_dev = dev;
s->s_isup = NULL;
s->s_imount = NULL;
s->s_time = 0;
s->s_rd_only = 0;
s->s_dirt = 0;
lock_super(s);
...
}
image-20231211095203241

把超级块加载到缓冲区,再加载到super block

调用bread读取超级块,这里的设备是rd虚拟盘。块号是1.因此在do request的时候是do_rd_request.虚拟盘虽然是内存模拟的盘,但是读取的操作完全模仿了外设,但是他毕竟是内存不是外设,因此和读硬盘不同的是:不会发生类似硬盘中断的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static struct super_block * read_super(int dev)
{
...
if (!(bh = bread(dev,1))) {
s->s_dev=0;
free_super(s);//释放超级块
return NULL;
}
//将缓冲区的超级块复制到刚才找到的super_block【0】中
*((struct d_super_block *) s) =
*((struct d_super_block *) bh->b_data);
brelse(bh);//释放缓冲块
if (s->s_magic != SUPER_MAGIC) {
s->s_dev = 0;
free_super(s);
return NULL;
}
...
}
image-20231211100257070

完善super blok中i结点位图逻辑位图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
static struct super_block * read_super(int dev)
{
...
//首先初始化imap和zmap
for (i=0;i<I_MAP_SLOTS;i++)
s->s_imap[i] = NULL;
for (i=0;i<Z_MAP_SLOTS;i++)
s->s_zmap[i] = NULL;
block=2;//虚拟盘的第一块是超级块,第二块开始是i结点位图和逻辑块位图所以这里是2
//把虚拟盘上的逻辑位图加载到缓冲区中,并都挂载到s_imap上
for (i=0 ; i < s->s_imap_blocks ; i++)
if (s->s_imap[i]=bread(dev,block))
block++;
else
break;
//挂完i结点的位图挂载逻辑块位图
for (i=0 ; i < s->s_zmap_blocks ; i++)
if (s->s_zmap[i]=bread(dev,block))
block++;
else
break;
//如果块数量不对说明操作系统出问题了释放之前的缓冲块和超级块
if (block != 2+s->s_imap_blocks+s->s_zmap_blocks) {
for(i=0;i<I_MAP_SLOTS;i++)
brelse(s->s_imap[i]);
for(i=0;i<Z_MAP_SLOTS;i++)
brelse(s->s_zmap[i]);
s->s_dev=0;
free_super(s);
return NULL;
}
//牺牲第一个i结点,防止查找算法返回0
s->s_imap[0]->b_data[0] |= 1;
s->s_zmap[0]->b_data[0] |= 1;
free_super(s);//解锁超级块
return s;
}
1
2
3
4
5
6
7
static void free_super(struct super_block * sb)
{
cli();
sb->s_lock = 0;
wake_up(&(sb->s_wait));
sti();
}
image-20231211102021214

将根设备的根i结点挂载super block上

调用iget从虚拟盘上读取i结点。有了i结点,可以通过根i结点找到文件系统中的任意指定i结点

1
2
3
4
5
6
7
8
9
void mount_root(void)
{
...
//i number :i结点位图的序号,iget:给定结点,给定设备,这里在找虚拟盘的根i结点
if (!(mi=iget(ROOT_DEV,ROOT_INO)))
panic("Unable to read root i-node");
...


iget:get_empty_inode

首先在记载所有打开的i结点的数组中申请一个空闲的

1
2
3
4
5
6
7
8
9
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_table[32]中申请一个空闲的i结点
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
struct m_inode * get_empty_inode(void)
{
struct m_inode * inode;
static struct m_inode * last_inode = inode_table; // last_inode 指向 i 节点表第一项
int i;

do {
// 扫描 i 节点表。
inode = NULL;
for (i = NR_INODE; i ; i--) {
// 如果 last_inode 已经指向 i 节点表的最后 1 项之后,则让其重新指向 i 节点表开始处。
if (++last_inode >= inode_table + NR_INODE)
last_inode = inode_table;
// 如果 last_inode 所指向的 i 节点的计数值为 0,则说明可能找到空闲 i 节点项。让 inode 指向
// 该 i 节点。如果该 i 节点的已修改标志和锁定标志均为 0,则我们可以使用该 i 节点,于是退出循环。
if (!last_inode->i_count) {
inode = last_inode;
if (!inode->i_dirt && !inode->i_lock)
break;
}
}
// 如果没有找到空闲 i 节点(inode=NULL),则将整个 i 节点表打印出来供调试使用,并死机。
if (!inode) {
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 节点解锁(如果又被上锁的话)。
wait_on_inode(inode);
// 如果该 i 节点已修改标志被置位的话,则将该 i 节点刷新,并等待该 i 节点解锁。
while (inode->i_dirt) {
write_inode(inode);
wait_on_inode(inode);
}
} while (inode->i_count);//// 如果 i 节点又被其它占用的话,则重新寻找空闲 i 节点。
// 已找到空闲 i 节点项。则将该 i 节点项内容清零,并置引用标志为 1,返回该 i 节点指针。
memset(inode,0,sizeof(*inode));
inode->i_count = 1;
return inode;
}

iget

inode_table 初始化的时候:

1
struct m_inode inode_table[NR_INODE]={{0,},}; // 内存中 i 节点表(NR_INODE=32 项)。

这是对数组进行初始化的语法。它使用了一个嵌套的大括号,将数组中的每个元素初始化为 {0,},这将初始化结构体中的所有成员为零(或NULL,具体取决于结构体的定义)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
struct m_inode * iget(int dev,int nr)
{
...
// 扫描 i 节点表。寻找指定节点号的 i 节点。并递增该节点的引用次数。
inode = inode_table;
while (inode < NR_INODE+inode_table) {
if (inode->i_dev != dev || inode->i_num != nr) {
inode++;
continue;
}
wait_on_inode(inode);
// 如果当前扫描的 i 节点的设备号不等于指定的设备号或者节点号不等于指定的节点号,则继续扫描。
//MOUNT ROOT调用的时候if (!(mi=iget(ROOT_DEV,ROOT_INO)))。dev=0.nr=1)
//所以刚初始化完的都是0,找不到所以会跳完这个while
if (inode->i_dev != dev || inode->i_num != nr) {
inode = inode_table;
continue;
}
inode->i_count++;
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);
dev = super_block[i].s_dev;
nr = ROOT_INO;
inode = inode_table;
continue;
}
if (empty)
iput(empty);
return inode;
}
if (!empty)
return (NULL);
//第一次初始化的话会跳到这个地方执行,而不会在上面的循环中return 因为有continue
//将当前的跟设备和块号赋值给找出来的inode table里面空闲的inode
inode=empty;
inode->i_dev = dev;
inode->i_num = nr;

read_inode(inode);//从虚拟盘上读出i结点
return inode;
}

read_inode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void read_inode(struct m_inode * inode)
{
struct super_block * sb;
struct buffer_head * bh;
int block;

lock_inode(inode);//首先对这个选出来的结点加锁,在解锁之前这个结点都不会被其它进程占用
if (!(sb=get_super(inode->i_dev)))//获得该节点所在设备的超级块
panic("trying to read inode without dev");
// 该 i 节点所在的逻辑块号 = (启动块+超级块) + i 节点位图占用的块数 + 逻辑块位图占用的块数 +
// (i 节点号-1)/每块含有的 i 节点数。
block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks +
(inode->i_num-1)/INODES_PER_BLOCK;
//将inode的块读到缓冲中
if (!(bh=bread(inode->i_dev,block)))
panic("unable to read i-node block");
//将读入的数据从缓冲块赋值给inode
*(struct d_inode *)inode =
((struct d_inode *)bh->b_data)
[(inode->i_num-1)%INODES_PER_BLOCK];
brelse(bh);//释放缓冲块
unlock_inode(inode);//解锁
}
image-20231211131847124

将根文件系统与进程1关联,设置root和pwd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    void mount_root(void)
{
...
if (!(mi=iget(ROOT_DEV,ROOT_INO)))
panic("Unable to read root i-node");
/* 该 i 节点引用次数递增 3 次。因为下面
p->s_isup = p->s_imount = mi;
p->s_isup = p->s_imount = mi;
current->pwd = mi;
也引用了该 i 节点。*/
mi->i_count += 3 ; /* NOTE! it is logically used 4 times, not 1 */
p->s_isup = p->s_imount = mi;//他是最根的i结点。自己挂自己,这句话加载了跟设备的根文件系统,非常重要
current->pwd = mi;//当前进程的工作目录(当前进程是进程1)进程1的工作目录是根文件系统的根i结点,从进程1开始才有文件系统
//进程0没有,绝对路径:从根文件系统往下撸和相对路径,根据pwd往下撸
current->root = mi; //后面由于父子进程创建遗传机制,后面的进程也会继承这个特征
...
}

计算虚拟盘空闲块信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    void mount_root(void)
{
...
// 统计该设备上空闲块数。首先令 i 等于超级块中表明的设备逻辑块总数。
free=0;
i=p->s_nzones;
// 然后根据逻辑块位图中相应比特位的占用情况统计出空闲块数。这里宏函数 set_bit()只是在测试
// 比特位,而非设置比特位。"i&8191"用于取得 i 节点号在当前块中的偏移值。"i>>13"是将 i 除以
// 8192,也即除一个磁盘块包含的比特位数。
while (-- i >= 0)
if (!set_bit(i&8191,p->s_zmap[i>>13]->b_data))
free++;
printk("%d/%d free blocks\n\r",free,p->s_nzones);
// 统计设备上空闲 i 节点数。首先令 i 等于超级块中表明的设备上 i 节点总数+1。加 1 是将 0 节点
// 也统计进去。
free=0;
i=p->s_ninodes+1;
// 然后根据 i 节点位图中相应比特位的占用情况计算出空闲 i 节点数
while (-- i >= 0)
if (!set_bit(i&8191,p->s_imap[i>>13]->b_data))
free++;
// 显示设备上可用的空闲 i 节点数/i 节点总数。
printk("%d/%d free inodes\n\r",free,p->s_ninodes);
}

至此mount_root执行完,同时返回后sys_setip函数也执行完了:

1
2
3
4
5
6
7
int sys_setup(void * BIOS) //首先解决硬盘的驱动器问题
{
...
rd_load();
mount_root();
return (0);
}

返回到之前调用system_call的地方,一路返回到一开始的init函数。

更通用的安装文件系统

安装文件系统分为三步:

  1. 把超级块读出来挂载到super block[8]上
  2. 将作为挂载结点的虚拟盘上的i结点挂在inode_table上
  3. 将硬盘上的需要被挂载的超级块挂在inode table上

在shell下输入 mount /dev/hd1 /mnt 的命令可以安装文件系统:将设备hd1的文件系统挂载到mnt目录文件下。此时shell会创建一个新的进程调用mount函数最终映射到sys_mount系统调用函数执行加载文件系统的过程

sys_mount

挂载流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 参数 dev_name 是设备文件名,dir_name 是安装到的目录名,rw_flag 被安装文件的读写标志。
int sys_mount(char * dev_name, char * dir_name, int rw_flag)
{
struct m_inode * dev_i, * dir_i;
struct super_block * sb;
int dev;

if (!(dev_i=namei(dev_name)))//获取设备i结点,通过设备名获取
return -ENOENT;
// 对于块特殊设备文件,设备号在 i 节点的 i_zone[0]中。
dev = dev_i->i_zone[0];//通过i结点获取设备号
//根据mode判断结点类型,如果不是块设备类型的结点则释放
if (!S_ISBLK(dev_i->i_mode)) {
iput(dev_i);//释放
return -EPERM;
}
iput(dev_i);//因为dev i结点的挂载的目的就是获得设备号,已经得到了,没有留着的用了就释放掉这个i结点
if (!(dir_i=namei(dir_name)))//找要挂接的目录的i结点
return -ENOENT;
//判断目录i结点是不是根结点
//inum是inode在位图里面的下标,第一个i结点:根i结点
//// 如果该 i 节点的引用计数不为 1(仅在这里引用),或者该 i 节点的节点号是根文件系统的节点
// 号 1,则释放该 i 节点,返回出错码。不能挂载在根结点上面
if (dir_i->i_count != 1 || dir_i->i_num == ROOT_INO) {
iput(dir_i);
return -EBUSY;
}
// 如果该节点不是一个目录文件节点,则也释放该 i 节点,返回出错码。

if (!S_ISDIR(dir_i->i_mode)) {
iput(dir_i);
return -EPERM;
}
// 读取将安装文件系统的超级块,如果失败则也释放该 i 节点,返回出错码。根据设备号就可以加载超级块
if (!(sb=read_super(dev))) {
iput(dir_i);
return -EBUSY;
}
// 如果将要被安装的文件系统已经安装在其它地方,则释放该 i 节点,返回出错码
//s_imount 是挂载的inode指针
if (sb->s_imount) {
iput(dir_i);
return -EBUSY;
}
// 如果将要安装到的 i 节点已经安装了文件系统(安装标志已经置位),则释放该 i 节点,返回出错码。
if (dir_i->i_mount) {
iput(dir_i);
return -EPERM;
}

sb->s_imount=dir_i;
//dir inode 已经安装了文件系统。i_dirt置1 说明i结点已经被修改
dir_i->i_mount=1;
dir_i->i_dirt=1; /* NOTE! we don't iput(dir_i) */
return 0; /* we do that in umount */
}

文件系统加载结点
//看imode区分inode类型,判断是不是目录文件。设备文件i结点找到设备号,第一个就是根i结点
//read super :先找有没有现成的
//simap 8个缓冲块,每个缓冲块1k,一字节8bit:64kbit,有64ki结点
//64M文件
//超级块里面有两个8个的指针数组。每个指向一个块,
1
2
3
4
5
6
7
struct super_block {
...
/* These are only in memory */
struct buffer_head * s_imap[8];
struct buffer_head * s_zmap[8];
...
};

超级块在内存的信息里面用于存放i结点位图用的buffer head *类型的数组。一个buffer head 1k 8个8k字节。一字节8位,所以对应了64K个inode。一个inode对应一个文件一个文件系统最多可以有64k个文件。同理一共64k个逻辑块,注意逻辑块也是块大小是1k,所以一共逻辑系统最多有64M大小的文件。

打开文件

打开文件的本质就是建立*filep【20】,file_table【64】 和inode_table【32】之间的联系

taskstruct 里面有:struct file * filp[NR_OPEN]; 每个进程都有一个filep,而file_table和inode_table是内核持有的,只有一个。

image-20231211225204563

打开文件建立连接的过程:使用open函数打开文件,最终映射到系统调用sys_open

sys_open

总导图:

open文件流程

将进程的*filp和file_table挂接

分别找filp和file_table里面的空闲项,并建立他俩的连接关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int sys_open(const char * filename,int flag,int mode)
{
struct m_inode * inode;
struct file * f;
int i,fd;

mode &= 0777 & ~current->umask;
//打开文件的进程是当前进程,在filep里面找一个空闲项
for(fd=0 ; fd<NR_OPEN ; fd++) // #define NR_OPEN 20
if (!current->filp[fd])
break;
if (fd>=NR_OPEN)
return -EINVAL;
current->close_on_exec &= ~(1<<fd);
f=0+file_table;
//找完找file table,引用计数为0的空闲项
for (i=0 ; i<NR_FILE ; i++,f++)//#define NR_FILE 64
if (!f->f_count) break;
if (i>=NR_FILE)
return -EINVAL;
(current->filp[fd]=f)->f_count++;//当前进程的空闲的filep的哪一项指向空闲的file table里面的项,引用计数++

...

}

获取文件的i结点:opennamei

例如要打开的文件路径是:/mnt/user/user1/user2/hellow.txt如何找到hellow.txt文件的i结点?

寻找过程

主要有目录文件结点和最终的文件结点。通过目录文件结点可以找到目录文件,里面的目录项指向了下一级的结点

1
2
3
4
5
6
7
8
9
10
11
12
	int sys_open(const char * filename,int flag,int mode)
{
...
//读i结点,filename:sysopen的参数const char*类型,返回给inode
//如果open_namei成功返回0,失败返回1
if ((i=open_namei(filename,flag,mode,&inode))<0) {
current->filp[fd]=NULL;
f->f_count=0;
return i;
}
...
}

这里调用了open_namei实现上述的寻找过程:

流程图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int open_namei(const char * pathname, int flag, int mode,
struct m_inode ** res_inode)
{
const char * basename;
int inr,dev,namelen;
struct m_inode * dir, *inode;
struct buffer_head * bh;
struct dir_entry * de;//目录项的数据结构,目录文件里面的目录项可多可少

if ((flag & O_TRUNC) && !(flag & O_ACCMODE))
flag |= O_WRONLY;
mode &= 0777 & ~current->umask;
mode |= I_REGULAR;
//返回枝梢i结点
if (!(dir = dir_namei(pathname,&namelen,&basename)))
return -ENOENT;
...
}

首先找到枝梢结点,也就是例子里面的user2目录文件inode结点

dir_namei

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static struct m_inode * dir_namei(const char * pathname,
int * namelen, const char ** name)
{
char c;
const char * basename;
struct m_inode * dir;

if (!(dir = get_dir(pathname)))//分析路径,获取i节点的执行函数
return NULL;
basename = pathname;
//一个一个解析paathname,一次过一个字符串常量字符
while (c=get_fs_byte(pathname++))
if (c=='/')
basename=pathname;
*namelen = pathname-basename-1;
*name = basename;
return dir;
}
get_dir
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
static struct m_inode * get_dir(const char * pathname)
{
char c;
const char * thisname;
struct m_inode * inode;
struct buffer_head * bh;
int namelen,inr,idev;
struct dir_entry * de;

if (!current->root || !current->root->i_count)
panic("No root inode");
if (!current->pwd || !current->pwd->i_count)
panic("No cwd inode");
//如果第一个是/:绝对路径,如果第一个不是/也不是空:相对路径
if ((c=get_fs_byte(pathname))=='/') {
inode = current->root;
pathname++;
} else if (c)
inode = current->pwd;
else
return NULL; /* empty name is bad */
inode->i_count++;//该i节点的引用计数+1
while (1) {//寻找枝梢 结点
thisname = pathname;//后面pathname++遍历,thisname没变
if (!S_ISDIR(inode->i_mode) || !permission(inode,MAY_EXEC)) {
iput(inode);
return NULL;
}
//不为空“\0"且没遇到/
for(namelen=0;(c=get_fs_byte(pathname++))&&(c!='/');namelen++)
/* nothing */ ;
if (!c)
return inode;//返回前一个inode,正常的情况下是在这里结束的,返回枝末梢i结点
//c不为空,说明还没到末梢,是目录项结点,在目录项文件中寻找包含目录项的块
//pathname动,this name不动
if (!(bh = find_entry(&inode,thisname,namelen,&de))) {//找到含有目录项的块
iput(inode);
return NULL;
}
inr = de->inode;//从目录项中提取i结点号 inumber
idev = inode->i_dev;//从i结点中提取设备号
brelse(bh);
iput(inode);
if (!(inode = iget(idev,inr)))//获取下一级的inode
return NULL;
}
}

find_entry
findentry
1
2
3
4
struct dir_entry {
unsigned short inode;//下一级的inode结点在硬盘上,没有指针。用的是位图的index。i节点位图的偏移,inode number
char name[NAME_LEN];//这一段的目录文件名
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
static struct buffer_head * find_entry(struct m_inode ** dir,
const char * name, int namelen, struct dir_entry ** res_dir)
{
int entries;
int block,i;
struct buffer_head * bh;
struct dir_entry * de;
struct super_block * sb;

#ifdef NO_TRUNCATE
if (namelen > NAME_LEN)
return NULL;
#else
if (namelen > NAME_LEN)
namelen = NAME_LEN;
#endif
entries = (*dir)->i_size / (sizeof (struct dir_entry));//目录项文件大小/每个目录项的大小->获得目录项的数目
*res_dir = NULL;
if (!namelen)
return NULL;
/* check for '..', as we might have to do some "magic" for it */
if (namelen==2 && get_fs_byte(name)=='.' && get_fs_byte(name+1)=='.') {
/* '..' in a pseudo-root results in a faked '.' (just change namelen) */
if ((*dir) == current->root)
namelen=1;
else if ((*dir)->i_num == ROOT_INO) {
/* '..' over a mount-point results in 'dir' being exchanged for the mounted
directory-inode. NOTE! We set mounted, so that we can iput the new dir */
sb=get_super((*dir)->i_dev);
if (sb->s_imount) {
iput(*dir);
(*dir)=sb->s_imount;
(*dir)->i_count++;
}
}
}
//目录文件第一个块不能是逻辑块0,不能为空
if (!(block = (*dir)->i_zone[0]))
return NULL;
//不空的话把这个block读入缓冲区
if (!(bh = bread((*dir)->i_dev,block)))
return NULL;
i = 0;
de = (struct dir_entry *) bh->b_data;
while (i < entries) {
if ((char *)de >= BLOCK_SIZE+bh->b_data) {
//如果一个缓冲区全部搜索完还没找到指定的目录项
brelse(bh);
bh = NULL;
if (!(block = bmap(*dir,i/DIR_ENTRIES_PER_BLOCK)) ||
!(bh = bread((*dir)->i_dev,block))) {
i += DIR_ENTRIES_PER_BLOCK;
continue;
}
de = (struct dir_entry *) bh->b_data;
}
if (match(namelen,name,de)) {
*res_dir = de;
return bh;
}
de++;
i++;
}
brelse(bh);
return NULL;
}
iget

iget根据目录项中提供的设备号i结点号获取i结点。

首先在inode_table里面看有没有现成的,如果找不到再加载

整体流程图:

iget
返回dir_namei
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static struct m_inode * dir_namei(const char * pathname,
int * namelen, const char ** name)
{
char c;
const char * basename;
struct m_inode * dir;

if (!(dir = get_dir(pathname)))//分析路径,获取i节点的执行函数
return NULL;
basename = pathname;
//一个一个解析paathname,一次过一个字符串常量字符
while (c=get_fs_byte(pathname++))
if (c=='/')
basename=pathname;
*namelen = pathname-basename-1;
*name = basename;
return dir;
}

image-20231212095622262

opennamei:获取目标文件i节点

上文已经讲述了获得枝梢结点的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
int open_namei(const char * pathname, int flag, int mode,
struct m_inode ** res_inode)
{
...
//返回枝梢i结点
if (!(dir = dir_namei(pathname,&namelen,&basename)))
return -ENOENT;
//如果目标文件名字长度为0
if (!namelen) { /* special case: '/usr/' etc */
if (!(flag & (O_ACCMODE|O_CREAT|O_TRUNC))) {
*res_inode=dir;
return 0;
}
iput(dir);
return -EISDIR;
}
//根据末梢结点和文件名,寻找目标文件再目录项块中的块
bh = find_entry(&dir,basename,namelen,&de);
if (!bh) {
if (!(flag & O_CREAT)) {
iput(dir);
return -ENOENT;
}
//检查用户权限
if (!permission(dir,MAY_WRITE)) {
iput(dir);
return -EACCES;
}
inode = new_inode(dir->i_dev);
if (!inode) {
iput(dir);
return -ENOSPC;
}
inode->i_uid = current->euid;
inode->i_mode = mode;
inode->i_dirt = 1;
bh = add_entry(dir,basename,namelen,&de);
if (!bh) {
inode->i_nlinks--;
iput(inode);
iput(dir);
return -ENOSPC;
}
de->inode = inode->i_num;
bh->b_dirt = 1;
brelse(bh);
iput(dir);
*res_inode = inode;
return 0;
}
inr = de->inode;//i number,这个是双向找的。得到i结点号
dev = dir->i_dev;
brelse(bh);
iput(dir);
if (flag & O_EXCL)
return -EEXIST;
if (!(inode=iget(dev,inr)))
return -EACCES;
if ((S_ISDIR(inode->i_mode) && (flag & O_ACCMODE)) ||
!permission(inode,ACC_MODE(flag))) {
iput(inode);
return -EPERM;
}
inode->i_atime = CURRENT_TIME;
if (flag & O_TRUNC)
truncate(inode);
*res_inode = inode;//最终的inode,将inode传回sys_open
return 0;
}

将文件i结点与file_table[64]挂接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int sys_open(const char * filename,int flag,int mode)
{
...
if ((i=open_namei(filename,flag,mode,&inode))<0) {
current->filp[fd]=NULL;
f->f_count=0;
return i;
}
/* ttys are somewhat special (ttyxx major==4, tty major==5) */
if (S_ISCHR(inode->i_mode))//如果是字符设备文件
if (MAJOR(inode->i_zone[0])==4) {
if (current->leader && current->tty<0) {
current->tty = MINOR(inode->i_zone[0]);
tty_table[current->tty].pgrp = current->pgrp;
}
} else if (MAJOR(inode->i_zone[0])==5)
if (current->tty<0) {
iput(inode);
current->filp[fd]=NULL;
f->f_count=0;
return -EPERM;
}
/* Likewise with block-devices: check for floppy_change */
if (S_ISBLK(inode->i_mode))//如果是块设备文件
check_disk_change(inode->i_zone[0]);
//设置文件属性,与file_table[64] 挂接
f->f_mode = inode->i_mode;
f->f_flags = flag;
f->f_count = 1;
f->f_inode = inode;
f->f_pos = 0;
return (fd);
}

namei

将给定的文件路径名映射 到其 i 节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct m_inode * namei(const char * pathname)
{
const char * basename;
int inr,dev,namelen;
struct m_inode * dir;
struct buffer_head * bh;
struct dir_entry * de;

if (!(dir = dir_namei(pathname,&namelen,&basename)))
return NULL;
if (!namelen) /* special case: '/usr/' etc */
return dir;
bh = find_entry(&dir,basename,namelen,&de);
if (!bh) {
iput(dir);
return NULL;
}
inr = de->inode;
dev = dir->i_dev;
brelse(bh);
iput(dir);
dir=iget(dev,inr);
if (dir) {
dir->i_atime=CURRENT_TIME;
dir->i_dirt=1;
}
return dir;
}

izone :;一个块1k。一个号unsigned short 2字节,一个块:512个号-》512k

inode_table里面的inode只有一个,但是允许别人重复打开,因此有conunt引用计数

读文件

sys_read

读文件由用户调用read完成

read函数最终映射到sys_read()系统调用执行

读文件的offset在file结构体里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int sys_read(unsigned int fd,char * buf,int count)//count是要读取的字节数
{
//偏置等信息在file结构体里面,可以通过fd找filep里面的指针找到file_table里面的file
struct file * file;
struct m_inode * inode;
//检查fd和conunt的范围是否合理
if (fd>=NR_OPEN || count<0 || !(file=current->filp[fd]))
return -EINVAL;
if (!count)//读取字符为0直接返回
return 0;
verify_area(buf,count);//对buf所在页面的属性进行验证。如果页面是只读的则复制该页面
inode = file->f_inode;
//针对不同文件类型读
if (inode->i_pipe)
return (file->f_mode&1)?read_pipe(inode,buf,count):-EIO;
if (S_ISCHR(inode->i_mode))
return rw_char(READ,inode->i_zone[0],buf,count,&file->f_pos);
if (S_ISBLK(inode->i_mode))
return block_read(inode->i_zone[0],&file->f_pos,buf,count);
//目录文件
if (S_ISDIR(inode->i_mode) || S_ISREG(inode->i_mode)) {
if (count+file->f_pos > inode->i_size)
count = inode->i_size - file->f_pos;
if (count<=0)
return 0;
return file_read(inode,file,buf,count);
}
printk("(Read)inode->i_mode=%06o\n\r",inode->i_mode);
return -EINVAL;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void verify_area(void * addr,int size)
{
unsigned long start;

start = (unsigned long) addr;
size += start & 0xfff;
start &= 0xfffff000;
start += get_base(current->ldt[2]);
while (size>0) {
size -= 4096;
write_verify(start);
start += 4096;
}
}
void write_verify(unsigned long address)
{
unsigned long page;
// 判断指定地址所对应页目录项的页表是否存在(P),若不存在(P=0)则返回。4M对齐
if (!( (page = *((unsigned long *) ((address>>20) & 0xffc)) )&1))
return;
// 取页表的地址,加上指定地址的页面在页表中的页表项偏移值,得对应物理页面的页表项指针。
page &= 0xfffff000;
page += ((address>>10) & 0xffc);//高10位置零,低十位置零只留了中间十位,后两位置零了起到乘4的效果,因为每一个偏移量是以4k为单位的,避免后面取到了index再乘4的操作
// 如果该页面不可写(标志 R/W 没有置位),则执行共享检验和复制页面操作(写时复制)。
if ((3 & *(unsigned long *) page) == 1) /* non-writeable, present */
un_wp_page((unsigned long *) page);
return;
}

file_read

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int file_read(struct m_inode * inode, struct file * filp, char * buf, int count)
{
int left,chars,nr;
struct buffer_head * bh;

if ((left=count)<=0)
return 0;
while (left) {
//每次按块读,每次读一个块
if (nr = bmap(inode,(filp->f_pos)/BLOCK_SIZE)) {
if (!(bh=bread(inode->i_dev,nr)))
break;
} else
bh = NULL;
// 计算文件读写指针在数据块中的偏移值 nr,则该块中可读字节数为(BLOCK_SIZE-nr),然后与还需
// 读取的字节数 left 作比较,其中小值即为本次需读的字节数 chars。若(BLOCK_SIZE-nr)大则说明
// 该块是需要读取的最后一块数据,反之则还需要读取一块数据。
nr = filp->f_pos % BLOCK_SIZE;
chars = MIN( BLOCK_SIZE-nr , left );
// 调整读写文件指针。指针前移此次将读取的字节数 chars。剩余字节计数相应减去 chars。
filp->f_pos += chars;
left -= chars;
if (bh) {
char * p = nr + bh->b_data;
while (chars-->0)
put_fs_byte(*(p++),buf++);//从缓冲区往用户拷贝
brelse(bh);
} else {
while (chars-->0)
put_fs_byte(0,buf++);
}
}
inode->i_atime = CURRENT_TIME;
return (count-left)?(count-left):-ERROR;
}

_bmap

1
2
3
4
int bmap(struct m_inode * inode,int block)
{
return _bmap(inode,block,0);//注意这里写死了一个0,0表示用已有的,1表示没有就创建一个(izone有块没有就补一下)
}

新建文件

示意图

creat函数,最终映射到sys_create函数,调用sys_open新建文件,和之前打开文件调用open不一样,这回opennamei没有这个i结点会返回空

1
2
3
4
5
6
7
8
9
10
int sys_open(const char * filename,int flag,int mode)
{
...
if ((i=open_namei(filename,flag,mode,&inode))<0) {
current->filp[fd]=NULL;
f->f_count=0;
return i;
}
...
}
1
2
3
4
5
6
7
8
int open_namei(const char * pathname, int flag, int mode,
struct m_inode ** res_inode)
{
...
if (!(dir = dir_namei(pathname,&namelen,&basename)))
return -ENOENT;
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static struct buffer_head * find_entry(struct m_inode ** dir,
const char * name, int namelen, struct dir_entry ** res_dir)
{
...
while (i < entries) {
...
if (match(namelen,name,de)) {//匹配当前的名字和目录项里面的名字
*res_dir = de;
return bh;
}
de++;
i++;
}
brelse(bh);
return NULL;
}

最终find_entry会返回null

因此opennamei执行:if (!bh) 里面的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int open_namei(const char * pathname, int flag, int mode,
struct m_inode ** res_inode)
{
...
bh = find_entry(&dir,basename,namelen,&de);
if (!bh) {
//没有找到文件不一定是要新建文件,可能是用户输错了名字
//检查flag的O_CREAT标志位是否置位。如果置位了,说明确实是要新建文件
if (!(flag & O_CREAT)) {
iput(dir);
return -ENOENT;
}
//检查用户再该目录是否有写权限
if (!permission(dir,MAY_WRITE)) {
iput(dir);
return -EACCES;
}
//新建inode
inode = new_inode(dir->i_dev);
if (!inode) {
iput(dir);
return -ENOSPC;
}
inode->i_uid = current->euid;
inode->i_mode = mode;
inode->i_dirt = 1;
bh = add_entry(dir,basename,namelen,&de);//新建目录项
if (!bh) {
inode->i_nlinks--;
iput(inode);
iput(dir);
return -ENOSPC;
}
de->inode = inode->i_num;
bh->b_dirt = 1;
brelse(bh);
iput(dir);
*res_inode = inode;
return 0;
}
...
}

new_inode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct m_inode * new_inode(int dev)
{
struct m_inode * inode;
struct super_block * sb;
struct buffer_head * bh;
int i,j;
//在inode_tale里面寻找空闲i结点
if (!(inode=get_empty_inode()))
return NULL;
//获取设备超级块
if (!(sb = get_super(dev)))
panic("new_inode with unknown device");
j = 8192;//根据超级块里面的i结点位图信息设置i结点位图
for (i=0 ; i<8 ; i++)
if (bh=sb->s_imap[i])
if ((j=find_first_zero(bh->b_data))<8192)
break;
if (!bh || j >= 8192 || j+i*8192 > sb->s_ninodes) {
iput(inode);
return NULL;
}
if (set_bit(j,bh->b_data))
panic("new_inode: bit already set");
bh->b_dirt = 1;
inode->i_count=1;
inode->i_nlinks=1;
inode->i_dev=dev;
inode->i_uid=current->euid;
inode->i_gid=current->egid;
inode->i_dirt=1;
inode->i_num = j + i*8192;
inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
return inode;
}

add_entry

如果在目录文件中找到空闲项,则在此处加载

目录文件示意图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
static struct buffer_head * add_entry(struct m_inode * dir,
const char * name, int namelen, struct dir_entry ** res_dir)
{
int block,i;
struct buffer_head * bh;
struct dir_entry * de;

*res_dir = NULL;
#ifdef NO_TRUNCATE
if (namelen > NAME_LEN)
return NULL;
#else
if (namelen > NAME_LEN)
namelen = NAME_LEN;
#endif
if (!namelen)
return NULL;
if (!(block = dir->i_zone[0]))
return NULL;
if (!(bh = bread(dir->i_dev,block)))
return NULL;
i = 0;
de = (struct dir_entry *) bh->b_data;
while (1) {
// 如果当前判别的目录项已经超出当前数据块,则释放该数据块,重新申请一块磁盘块 block。如果
// 申请失败,则返回 NULL,退出。
if ((char *)de >= BLOCK_SIZE+bh->b_data) {
//整个数据块都没有空闲则加载新的block
brelse(bh);
bh = NULL;
block = create_block(dir,i/DIR_ENTRIES_PER_BLOCK);
if (!block)
return NULL;
if (!(bh = bread(dir->i_dev,block))) {
i += DIR_ENTRIES_PER_BLOCK;
continue;
}
de = (struct dir_entry *) bh->b_data;
}
// 如果当前所操作的目录项序号 i*目录结构大小已经超过了该目录所指出的大小 i_size,则说明该第 i
// 个目录项还未使用,我们可以使用它。于是对该目录项进行设置(置该目录项的 i 节点指针为空)。并
// 更新该目录的长度值(加上一个目录项的长度,设置目录的 i 节点已修改标志,再更新该目录的改变

// 间为当前时间。
//在末端找到空闲项
if (i*sizeof(struct dir_entry) >= dir->i_size) {
de->inode=0;
dir->i_size = (i+1)*sizeof(struct dir_entry);
dir->i_dirt = 1;
dir->i_ctime = CURRENT_TIME;
}
//在中间找到空闲项
if (!de->inode) {
dir->i_mtime = CURRENT_TIME;
for (i=0; i < NAME_LEN ; i++)
de->name[i]=(i<namelen)?get_fs_byte(name+i):0;
bh->b_dirt = 1;
*res_dir = de;
return bh;
}
de++;
i++;
}
brelse(bh);
return NULL;
}


int create_block(struct m_inode * inode, int block)
{
return _bmap(inode,block,1);
}

_bmap

直接索引部分 (block<7)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static int _bmap(struct m_inode * inode,int block,int create)
{
struct buffer_head * bh;
int i;
// 如果块号小于 0,则死机。
if (block<0)
panic("_bmap: block<0");
// 如果块号大于直接块数 + 间接块数 + 二次间接块数,超出文件系统表示范围,则死机。
if (block >= 7+512+512*512)
panic("_bmap: block>big");
if (block<7) {
// 如果创建标志置位,并且 i 节点中对应该块的逻辑块(区段)字段为 0,则向相应设备申请一磁盘
// 块(逻辑块,区块),并将盘上逻辑块号(盘块号)填入逻辑块字段中。然后设置 i 节点修改时间,
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];
}
block -= 7;
...
}
bmap

一级索引部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static int _bmap(struct m_inode * inode,int block,int create)
{
...
if (block<512) {
// 如果是创建,并且该 i 节点中对应间接块字段为 0,表明文件是首次使用间接块,则需申请
// 一磁盘块用于存放间接块信息,并将此实际磁盘块号填入间接块字段中。然后设置 i 节点
// 已修改标志和修改时间。
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;
}
if (!inode->i_zone[7])
return 0;
if (!(bh = bread(inode->i_dev,inode->i_zone[7])))
return 0;
i = ((unsigned short *) (bh->b_data))[block];
if (create && !i)
if (i=new_block(inode->i_dev)) {
((unsigned short *) (bh->b_data))[block]=i;
bh->b_dirt=1;
}
brelse(bh);
return i;
}
block -= 512;
...
}
bmap1

二级索引部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static int _bmap(struct m_inode * inode,int block,int create)
{
...
if (create && !inode->i_zone[8])
if (inode->i_zone[8]=new_block(inode->i_dev)) {
inode->i_dirt=1;
inode->i_ctime=CURRENT_TIME;
}
if (!inode->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];
if (create && !i)
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;
if (!(bh=bread(inode->i_dev,i)))
return 0;
i = ((unsigned short *)bh->b_data)[block&511];
if (create && !i)
if (i=new_block(inode->i_dev)) {
((unsigned short *) (bh->b_data))[block&511]=i;
bh->b_dirt=1;
}
brelse(bh);
return i;
}

new block

new block
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
int new_block(int dev)
{
struct buffer_head * bh;
struct super_block * sb;
int i,j;

if (!(sb = get_super(dev)))
panic("trying to get new block from nonexistant device");
j = 8192;
// 扫描逻辑块位图,寻找首个 0 比特位,寻找空闲逻辑块,获取放置该逻辑块的块号。
for (i=0 ; i<8 ; i++)
if (bh=sb->s_zmap[i])
if ((j=find_first_zero(bh->b_data))<8192)
break;
// 如果全部扫描完还没找到(i>=8 或 j>=8192)或者位图所在的缓冲块无效(bh=NULL)则 返回 0,
// 退出(没有空闲逻辑块)。
if (i>=8 || !bh || j>=8192)
return 0;
if (set_bit(j,bh->b_data))
panic("new_block: bit already set");
bh->b_dirt = 1;
// 置对应缓冲区块的已修改标志。如果新逻辑块大于该设备上的总逻辑块数,则说明指定逻辑块在
// 对应设备上不存在。申请失败,返回 0,退出。
j += i*8192 + sb->s_firstdatazone-1;
if (j >= sb->s_nzones)
return 0;
// 读取设备上的该新逻辑块数据(验证)。如果失败则死机。
if (!(bh=getblk(dev,j)))
panic("new_block: cannot get block");
if (bh->b_count != 1)
panic("new block: count is != 1");
// 将该新逻辑块清零,并置位更新标志和已修改标志。然后释放对应缓冲区,返回逻辑块号。
clear_block(bh->b_data);
bh->b_uptodate = 1;
bh->b_dirt = 1;
brelse(bh);
return j;
}

写文件

写入文件最终映射到sys_write,sys_write判断要写的文件的类型,对于目录文件调用file_write

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
int file_write(struct m_inode * inode, struct file * filp, char * buf, int count)
{
off_t pos;
int block,c;
struct buffer_head * bh;
char * p;
int i=0;

/*
* ok, append may not work when many processes are writing at the same time
* but so what. That way leads to madness anyway.
*/
if (filp->f_flags & O_APPEND)//如果设置了文件尾部添加标志,将pos移动到文件末尾
pos = inode->i_size;
else
pos = filp->f_pos;
while (i<count) {
if (!(block = create_block(inode,pos/BLOCK_SIZE))) //创建逻辑块并反回块号
break;
if (!(bh=bread(inode->i_dev,block)))
break;
// 求出文件读写指针在数据块中的偏移值 c,将 p 指向读出数据块缓冲区中开始读取的位置。置该
// 缓冲区已修改标志。
c = pos % BLOCK_SIZE;
p = c + bh->b_data;
bh->b_dirt = 1;
// 从开始读写位置到块末共可写入 c=(BLOCK_SIZE-c)个字节。若 c 大于剩余还需写入的字节数
// (count-i),则此次只需再写入 c=(count-i)即可。
c = BLOCK_SIZE-c;
if (c > count-i) c = count-i;
// 文件读写指针前移此次需写入的字节数。如果当前文件读写指针位置值超过了文件的大小,则
// 修改 i 节点中文件大小字段,并置 i 节点已修改标志。
pos += c;
if (pos > inode->i_size) {
inode->i_size = pos;
inode->i_dirt = 1;
}
// 已写入字节计数累加此次写入的字节数 c。从用户缓冲区 buf 中复制 c 个字节到高速缓冲区中 p
// 指向开始的位置处。然后释放该缓冲区。
i += c;
while (c-->0)
*(p++) = get_fs_byte(buf++);
brelse(bh);
}
inode->i_mtime = CURRENT_TIME;
// 如果此次操作不是在文件尾添加数据,则把文件读写指针调整到当前读写位置,并更改 i 节点修改
// 时间为当前时间。
if (!(filp->f_flags & O_APPEND)) {
filp->f_pos = pos;
inode->i_ctime = CURRENT_TIME;
}
return (i?i:-1);
}

关闭文件

关闭文件

注意file_table[64]是所有进程共享的,所以这里是减小引用计数而不是清零。当引用计数为0的时候空闲

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int sys_close(unsigned int fd)
{
struct file * filp;

if (fd >= NR_OPEN)
return -EINVAL;
current->close_on_exec &= ~(1<<fd);
if (!(filp = current->filp[fd]))
return -EINVAL;
current->filp[fd] = NULL;
if (filp->f_count == 0)
panic("Close: file count is 0");
if (--filp->f_count)
return (0);
iput(filp->f_inode);//释放文件i结点。减小在inode_table[32]里面的引用计数。当引用计数为0的时候空闲
return (0);
}

删除文件

删除文件

linux文件可以建立链接。文件没多被链接一次文件的的inode的i_nlinks会加1。也就是多个目录项链接到一个文件inode

i_nlinks

检查文件是否能被删除。函数整个流程和打开文件很类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
int sys_unlink(const char * name)
{
const char * basename;
int namelen;
struct m_inode * dir, * inode;
struct buffer_head * bh;
struct dir_entry * de;

if (!(dir = dir_namei(name,&namelen,&basename)))
return -ENOENT;
if (!namelen) {
iput(dir);
return -ENOENT;
}
if (!permission(dir,MAY_WRITE)) {
iput(dir);
return -EPERM;
}
bh = find_entry(&dir,basename,namelen,&de);
if (!bh) {
iput(dir);
return -ENOENT;
}
if (!(inode = iget(dir->i_dev, de->inode))) {
iput(dir);
brelse(bh);
return -ENOENT;
}
if ((dir->i_mode & S_ISVTX) && !suser() &&
current->euid != inode->i_uid &&
current->euid != dir->i_uid) {
iput(dir);
iput(inode);
brelse(bh);
return -EPERM;
}
if (S_ISDIR(inode->i_mode)) {
iput(inode);//释放文件结点
iput(dir); //释放枝梢结点
brelse(bh);
return -EPERM;
}
if (!inode->i_nlinks) {
printk("Deleting nonexistent file (%04x:%d), %d\n",
inode->i_dev,inode->i_num,inode->i_nlinks);
inode->i_nlinks=1;
}
//具体的文件删除操作:
de->inode = 0;
bh->b_dirt = 1;
brelse(bh);
inode->i_nlinks--;//修改i_nlinks
inode->i_dirt = 1;
inode->i_ctime = CURRENT_TIME;
iput(inode);//释放文件结点
iput(dir);//释放枝梢结点
return 0;
}

### iput

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void iput(struct m_inode * inode)
{
if (!inode)
return;
wait_on_inode(inode);
if (!inode->i_count)
panic("iput: trying to free free inode");
if (inode->i_pipe) {
wake_up(&inode->i_wait);
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;
}
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;
}
//当链接数目等于0的时候,释放逻辑块
if (!inode->i_nlinks) {
truncate(inode);//释放文件在硬盘上占据的逻辑块
free_inode(inode);//将i结点位图对应的位清空,将inode_table里面对应的表现清空
return;
}
//将i结点内容同步到外设
if (inode->i_dirt) {
write_inode(inode); /* we can sleep - so do again */
wait_on_inode(inode);
goto repeat;
}
inode->i_count--;
return;
}

truncate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void truncate(struct m_inode * inode)
{
int i;
//如果该文件不是目录文件或者普通文件则直接返回
if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode)))
return;

for (i=0;i<7;i++)
if (inode->i_zone[i]) {
free_block(inode->i_dev,inode->i_zone[i]);//将izone的前7位对应的逻辑块在逻辑位图上对应清零
inode->i_zone[i]=0;
}
free_ind(inode->i_dev,inode->i_zone[7]);//释放一级
free_dind(inode->i_dev,inode->i_zone[8]);//释放二级
inode->i_zone[7] = inode->i_zone[8] = 0;
inode->i_size = 0;
inode->i_dirt = 1;
inode->i_mtime = inode->i_ctime = CURRENT_TIME;
}

free_inode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void free_inode(struct m_inode * inode)
{
struct super_block * sb;
struct buffer_head * bh;

if (!inode)//结点为空返回
return;
//设备号为0
if (!inode->i_dev) {
memset(inode,0,sizeof(*inode));
return;
}
//结点被多次引用
if (inode->i_count>1) {
printk("trying to free inode with count=%d\n",inode->i_count);
panic("free_inode");
}
//结点的nlinks不为0
if (inode->i_nlinks)
panic("trying to free inode with links");
//超级块不存在
if (!(sb = get_super(inode->i_dev)))
panic("trying to free inode on nonexistent device");
//检查i结点号
if (inode->i_num < 1 || inode->i_num > sb->s_ninodes)
panic("trying to free inode 0 or nonexistant inode");
//结点i结点位图不存在
if (!(bh=sb->s_imap[inode->i_num>>13]))
panic("nonexistent imap in superblock");
//清空对应的位
if (clear_bit(inode->i_num&8191,bh->b_data))
printk("free_inode: bit already cleared.\n\r");
bh->b_dirt = 1;
memset(inode,0,sizeof(*inode));
}