一、文件系统位图操作
文件系统位图用于控制inode和文件块的占用情况,标记哪些块被占用了。涉及如下四个函数:
idx_t balloc(dev_t dev) // 分配一个文件块
{
super_block_t *sb = get_super(dev); // 获取设备的超级块
buffer_t *buf = NULL;
idx_t bit = EOF;
bitmap_t map;
for (size_t i = 0; i < ZMAP_NR; i++)
{
buf = sb->zmaps[i]; // 循环逐个获取文件位图占用的块
bitmap_make(&map, buf->data, BLOCK_SIZE, i * BLOCK_BITS + sb->desc->firstdatazone - 1); // 将整个缓冲区作为位图
bit = bitmap_scan(&map, 1); // 从位图中扫描一位
if (bit != EOF)
{
assert(bit < sb->desc->zones);
buf->dirty = true; // 如果扫描成功,则 标记缓冲区脏,中止查找
break;
}
}
bwrite(buf); // todo 调试期间强同步
return bit;
}
void bfree(dev_t dev, idx_t idx) // 释放一个文件块, idx是被释放块的索引
{
super_block_t *sb = get_super(dev); // 获取设备的超级块
buffer_t *buf;
bitmap_t map;
for (size_t i = 0; i < ZMAP_NR; i++)
{
if (idx > BLOCK_BITS * (i + 1)) // 跳过开始的块
{
continue;
}
buf = sb->zmaps[i];
bitmap_make(&map, buf->data, BLOCK_SIZE, BLOCK_BITS * i + sb->desc->firstdatazone - 1); // 将整个缓冲区作为位图
assert(bitmap_test(&map, idx)); // 将 idx 对应的位图置位 0
bitmap_set(&map, idx, 0);
buf->dirty = true; // 标记缓冲区脏
break;
}
bwrite(buf); // todo 调试期间强同步
}
idx_t ialloc(dev_t dev) // 分配一个文件系统 inode
void ifree(dev_t dev, idx_t idx) // 释放一个文件系统 inode
如上所示,四个函数分别用于申请和释放文件块和inode。这四个函数比较简单,通过读取超级块在内存中修改位图来实现文件块或者inode的申请和释放。这里展示的代码只贴了其中一对儿函数的实现,另外一堆逻辑基本是类似的。
二、文件系统 inode
在磁盘中,一个inode描述符占32字节,因此一个inode块(1kb)可以存储32个inode。
#define INODE_NR 64 // 内核同时能“打开”或“缓存”的 inode 数量是64个
static inode_t inode_table[INODE_NR]; // 定义一个inode表, 这里的inode是内存中的inode结构,而不是磁盘上的inode描述符
void inode_init() // 初始化inode, 所有inode的设备初始化为EOF
{
for (size_t i = 0; i < INODE_NR; i++)
{
inode_t *inode = &inode_table[i];
inode->dev = EOF;
}
}
static inode_t *get_free_inode() // 从inode表中,申请一个空闲 inode
{
for (size_t i = 0; i < INODE_NR; i++)
{
inode_t *inode = &inode_table[i];
if (inode->dev == EOF)
{
return inode;
}
}
panic("no more inode!!!");
}
static void put_free_inode(inode_t *inode) // 释放一个 inode,这里省略了断言,即释放的inode引用计数需要为0,且不能释放根inode
{
inode->dev = EOF;
}
inode_t *get_root_inode() // 获取根 inode
{
return inode_table;
}
static inline idx_t inode_block(super_block_t *sb, idx_t nr) // 计算 inode nr 对应的块号, nr是inode的索引
{
return 2 + sb->desc->imap_blocks + sb->desc->zmap_blocks + (nr - 1) / BLOCK_INODES; // inode 编号 从 1 开始,即1号inode是根
}
static inode_t *find_inode(dev_t dev, idx_t nr) // 从已有 inode 中查找编号为 nr 的 inode
{
super_block_t *sb = get_super(dev); // 获取超级块
list_t *list = &sb->inode_list;
for (list_node_t *node = list->head.next; node != &list->tail; node = node->next) // 遍历inode链表
{
inode_t *inode = element_entry(inode_t, node, node);
if (inode->nr == nr)
{
return inode;
}
}
return NULL;
}
inode_t *iget(dev_t dev, idx_t nr) // 获得设备 dev 的 nr inode
{
inode_t *inode = find_inode(dev, nr);
if (inode)
{
inode->count++;
inode->atime = time();
return inode;
}
super_block_t *sb = get_super(dev); // 如果已有inode没找到,则新申请一个inode
inode = get_free_inode();
inode->dev = dev;
inode->nr = nr;
inode->count = 1;
list_push(&sb->inode_list, &inode->node); // 加入超级块 inode 链表
idx_t block = inode_block(sb, inode->nr);
buffer_t *buf = bread(inode->dev, block);
inode->buf = buf;
// 将缓冲视为一个 inode 描述符数组,获取对应的指针;
inode->desc = &((inode_desc_t *)buf->data)[(inode->nr - 1) % BLOCK_INODES];
inode->ctime = inode->desc->mtime;
inode->atime = time();
return inode;
}
void iput(inode_t *inode) // 释放 inode
{
if (!inode)
return;
inode->count--; // 引用计数减1
if (inode->count)
{
return;
}
brelse(inode->buf); // 释放 inode 对应的缓冲
list_remove(&inode->node); // 从超级块链表中移除
put_free_inode(inode); // 释放 inode 内存
}
如上所示,为inode的管理,在内存中使用一个数组,最大管理64个inode。
最后还有一个很重要的函数,获取 inode 指定块的索引值,我们还是结合代码来分析:
// 获取 inode 第 block 块的索引值, 如果不存在 且 create 为 true,则创建
idx_t bmap(inode_t *inode, idx_t block, bool create)
{
assert(block >= 0 && block < TOTAL_BLOCK); // block是逻辑块号, 确保 block 合法
u16 index = block; // 数组索引
u16 *array = inode->desc->zone; // inode的描述符对应的zone。zone决定了inde使用了哪些文件块
buffer_t *buf = inode->buf; // 缓冲区
buf->count += 1; // 用于下面的 brelse,传入参数 inode 的 buf 不应该释放
int level = 0; // 当前处理级别
int divider = 1; // 当前子级别块数量
if (block < DIRECT_BLOCK) // 直接块, 如果小于7块,那就是使用直接块。
{
goto reckon;
}
block -= DIRECT_BLOCK; // 如果大于7, 那就是间接块
if (block < INDIRECT1_BLOCK) // 判断是否是1级间接块
{
index = DIRECT_BLOCK;
level = 1;
divider = 1;
goto reckon;
}
block -= INDIRECT1_BLOCK; // 否则就是二级间接块
index = DIRECT_BLOCK + 1;
level = 2;
divider = BLOCK_INDEXES;
reckon:
for (; level >= 0; level--)
{
if (!array[index] && create) // 如果不存在 且 create 则申请一块文件块
{
array[index] = balloc(inode->dev); // 分配一个物理块
buf->dirty = true;
}
brelse(buf);
if (level == 0 || !array[index]) // 如果 level == 0 或者 索引不存在,直接返回
{
return array[index];
}
buf = bread(inode->dev, array[index]); // level 不为 0,处理下一级索引
index = block / divider; // 计算在下一级块中的索引
block = block % divider; // 余数留给更下一级
divider /= BLOCK_INDEXES; // 每向下一级,divider缩小
array = (u16 *)buf->data; // array改为指向下一级索引块的数据区
}
}
如上所示,我们假设申请第三个逻辑块,这里属于直接块。这里会直接申请一个块并返回。并给zone[3]这里赋值为申请到的逻辑块号。这里提一点,zone是u16的数字,因此逻辑块最多65536个,也就是一个文件系统最大支持到64MB。间接块的逻辑可自行调试。
三、文件系统的状态
文件系统的状态指的是文件的访问权限,文件的属主,已经文件的类型等信息。也就是在linux上使用 ls -l 输出的内容。例如 rwx 表示文件权限,d代表目录,这里我们不做过多的介绍。minux 的文件信息存储在 inode.mode 字段中,总共有 16 位,其中:
- 高 4 位用于表示文件类型
- 中 3 位用于表示特殊标志
- 低 9 位用于表示文件权限
这里需要做一些宏定义,用来表示文件类型,权限等内容。如:
// 文件类型
#define IFMT 00170000 // 文件类型(8 进制表示)
#define IFREG 0100000 // 常规文件
#define IFBLK 0060000 // 块特殊(设备)文件,如磁盘 dev/fd0
#define IFDIR 0040000 // 目录文件
#define IFCHR 0020000 // 字符设备文件
#define IFIFO 0010000 // FIFO 特殊文件
#define IFSYM 0120000 // 符号连接
在这里高4为表示文件类型,这里罗列一些文件类型的宏定义,权限的宏定义就不再展开说明。
四、umask系统调用
umask用来设置系统的默认文件权限,这里需要通过系统调用来实现。在创建任务时设置默认 task->umask = 0022。 如下为uamsk的函数实现:
mode_t sys_umask(mode_t mask)
{
task_t *task = running_task();
mode_t old = task->umask;
task->umask = mask & 0777;
return old;
}
当用户态程序执行umask()时,就会修改当前进程的默认umask。
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付
