上一篇文章记录了对文件的读写操作,那么文件操作到目前为止,已经完成了创建和读写,还剩下的常用操作就是删除文件了。这篇文章就来记录删除文件的实现以及总结一下为文件系统添加系统调用的步骤。
删除文件
删除是添加的反过程,所以要删除文件,我们需要做以下工作:
- 释放 inode-map 中的相应位。
- 释放 sector-map 中的相应位。
- 删除根目录中的目录项。
注意我们不需要在inode_array中释放相应的i-node,因为释放inode-map中的相应位已经将inode_array中的位置标记为“未使用”了,不过在接下来的代码中我们仍然将i-node清空。
另外从最简单的删除功能出发,我们并不需要释放为文件分配的扇区,因为sector-map中的1和0已经清楚地表明了扇区是否可以使用。
现在我们来看一下删除文件的代码。
代码 fs/link.c,do_unlink,这是新建的文件。
/**
* Remove a file.
*
* @note We clear the i-node in inode_array[] although it is not really needed.
* We don't clear the data bytes so the file is recoverable.
*
* @return On success, zero is returned. On error, -1 is returned.
*/
PUBLIC int do_unlink()
{
char pathname[MAX_PATH];
/* get parameters from the message */
int name_len = fs_msg.NAME_LEN; /* length of filename */
int src = fs_msg.source; /* caller proc nr. */
assert(name_len < MAX_PATH);
phys_copy((void*)va2la(TASK_FS, pathname),
(void*)va2la(src, fs_msg.PATHNAME),
name_len);
pathname[name_len] = 0;
if (strcmp(pathname, "/") == 0) {
printl("FS:do_unlink():: cannot unlink the root\n");
return -1;
}
int inode_nr = search_file(pathname);
if (inode_nr == INVALID_INODE) { /* file not found */
printl("FS::do_unlink()::search_file() returns invalid inode: %s\n", pathname);
return -1;
}
char filename[MAX_PATH];
struct inode * dir_inode;
if (strip_path(filename, pathname, &dir_inode) != 0) {
return -1;
}
struct inode * pin = get_inode(dir_inode->i_dev, inode_nr);
if (pin->i_mode != I_REGULAR) { /* can only remove regular files */
printl("cannot remove file %s, because it is not a regular file.\n", pathname);
return -1;
}
if (pin->i_cnt > 1) { /* the file was opened */
printl("cannot remove file %s, because pin->i_cnt is %d.\n", pathname, pin->i_cnt);
return -1;
}
struct super_block * sb = get_super_block(pin->i_dev);
/* free the bit in i-map */
int byte_idx = inode_nr / 8;
int bit_idx = inode_nr % 8;
assert(byte_idx < SECTOR_SIZE); /* we have only one i-map sector */
/* read sector 2 (skip bootsect and superblk): */
RD_SECT(pin->i_dev, 2);
assert(fsbuf[byte_idx % SECTOR_SIZE] & (1 << bit_idx));
fsbuf[byte_idx % SECTOR_SIZE] &= ~(1 << bit_idx);
WR_SECT(pin->i_dev, 2);
/* free the bits in s-map */
/*
* bit_idx: bit idx in the entire i-map
* ... ____|____
* \ .-- byte_cnt: how many bytes between
* \ | the first and last byte
* +-+-+-+-+-+-+-+-+ V +-+-+-+-+-+-+-+-+
* ... | | | | | |*|*|*|...|*|*|*|*| | | | |
* +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
* 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
* ...__/
* byte_idx: byte idx in the entire i-map
*/
bit_idx = pin->i_start_sect - sb->n_1st_sect + 1;
byte_idx = bit_idx / 8;
int bits_left = pin->i_nr_sects;
int byte_cnt = (bits_left - (8 - (bit_idx % 8))) / 8;
/* current sector nr. */
int s = 2 /* 2: bootsect + superblk */
+ sb->nr_imap_sects + byte_idx / SECTOR_SIZE;
RD_SECT(pin->i_dev, s);
int i;
/* clear the first byte */
for (i = bit_idx % 8; (i < 8) && bits_left; i++, bits_left--) {
assert((fsbuf[byte_idx % SECTOR_SIZE] >> i & 1) == 1);
fsbuf[byte_idx % SECTOR_SIZE] &= ~(1 << i);
}
/* clear bytes from the second bytes to the second to last */
int k;
i = (byte_idx % SECTOR_SIZE) + 1; /* the second byte */
for (k = 0; k < byte_cnt; k++, i++, bits_left-=8) {
if (i == SECTOR_SIZE) {
i = 0;
WR_SECT(pin->i_dev, s);
RD_SECT(pin->i_dev, ++s);
}
assert(fsbuf[i] == 0xFF);
fsbuf[i] = 0;
}
/* clear the last byte */
if (i == SECTOR_SIZE) {
i = 0;
WR_SECT(pin->i_dev, s);
RD_SECT(pin->i_dev, ++s);
}
unsigned char mask = ~((unsigned char)(~0) << bits_left);
assert((fsbuf[i] & mask) == mask);
fsbuf[i] &= (~0) << bits_left;
WR_SECT(pin->i_dev, s);
/* clear the i-node itself */
pin->i_mode = 0;
pin->i_size = 0;
pin->i_start_sect = 0;
pin->i_nr_sects = 0;
sync_inode(pin);
/* release slot in inode_table[] */
put_inode(pin);
/* set the inode-nr to 0 in the directory entry */
int dir_blk0_nr = dir_inode->i_start_sect;
int nr_dir_blks = (dir_inode->i_size + SECTOR_SIZE) / SECTOR_SIZE;
int nr_dir_entries = dir_inode->i_size / DIR_ENTRY_SIZE; /* including unused slots (the file has been deleted but the slot is still there) */
int m = 0;
struct dir_entry * pde = 0;
int flg = 0;
int dir_size = 0;
for (i = 0; i < nr_dir_blks; i++) {
RD_SECT(dir_inode->i_dev, dir_blk0_nr + i);
pde = (struct dir_entry *)fsbuf;
int j;
for (j = 0; j < SECTOR_SIZE / DIR_ENTRY_SIZE; j++, pde++) {
if (++m > nr_dir_entries) {
break;
}
if (pde->inode_nr == inode_nr) {
memset(pde, 0, DIR_ENTRY_SIZE);
WR_SECT(dir_inode->i_dev, dir_blk0_nr + i);
flg = 1;
break;
}
if (pde->inode_nr != INVALID_INODE) {
dir_size += DIR_ENTRY_SIZE;
}
}
if (m > nr_dir_entries || /* all entries have been iterated OR */
flg) { /* file is found */
break;
}
}
assert(flg);
if (m == nr_dir_entries) { /* the file is the last one in the dir */
dir_inode->i_size = dir_size;
sync_inode(dir_inode);
}
return 0;
}
代码分四部分:
- 释放 inode-map 中的相应位;
- 释放 sector-map 中的相应位;
- 将 inode_array 中的 i-node 清零;
- 删除根目录中的目录项。
第46行我们对pin->i_cnt进行了判断,只有当i_cnt等于1时我们才能继续下去。也就是说,此时的i-node应该只被引用了一次。只要i_cnt大于1,函数就直接返回代表不成功的-1,因为此时一定还有别的文件描述符(file descriptor)引用了i-node。这时文件还在使用中,我们当然不能就这样删除它。
由于代码第39行调用了get_inode(),所以一定不要忘记在使用完pin之后调用put_inode()来释放i_cnt,不然inode_table[]中的相应项就永远不会被覆盖,这就造成内存泄漏了。
在删除根目录的目录项时,我们面临一个选择:是让删除后的目录项空洞留在那里还是重新布置根目录。我们选择了前者,因为这样比较省事。这样做的坏处在于,一旦系统中有过文件删除操作,那么根目录文件的i_size就可能无法准备反应根目录中文件的多少。比如下图所示的情况,虽然根目录中连表示自身的“.”都算上也只有7个文件,但根目录文件的i_size却是(7+N)×DIR_ENTRY_SIZE,因为中间有N个之前存在的文件被删掉,此刻留下了大小为N×DIR_ENTRY_SIZE的空洞。
不过这个坏处其实无关紧要,只是要注意两个地方。一是当我们创建文件时,有空洞就先利用空洞,没有任何空洞时再扩展根目录文件的大小;二是空洞之后的文件都已被删除之后,我们最好改变根目录文件的i_size。
好了,删除文件的核心代码有了,我们在task_fs中将删除文件的消息处理添加上,代码如下所示。
代码 fs/main.c,task_fs。
/**
* <Ring 1> The main loop of TASK FS.
*/
PUBLIC void task_fs()
{
...
switch (fs_msg.type) {
case OPEN:
fs_msg.FD = do_open();
break;
case CLOSE:
fs_msg.RETVAL = do_close();
break;
case READ:
case WRITE:
fs_msg.CNT = do_rdwt();
break;
case UNLINK:
fs_msg.RETVAL = do_unlink();
break;
default:
dump_msg("FS::unknown message:", &fs_msg);
assert(0);
break;
}
...
}
接下来,我们来写一个对用户的接口函数,代码如下所示。
代码 lib/unlink.c,unlink(),这是新建的文件。
/**
* Delete a file.
*
* @param pathname The full path of the file to delete.
*
* @return Zero if successful, otherwise -1.
*/
PUBLIC int unlink(const char * pathname)
{
MESSAGE msg;
msg.type = UNLINK;
msg.PATHNAME = (void*)pathname;
msg.NAME_LEN = strlen(pathname);
send_recv(BOTH, TASK_FS, &msg);
return msg.RETVAL;
}
然后就可以在用户进程中调用它了,代码如下所示。
代码 kernel/main.c,删除文件。
void TestA()
{
...
char * filenames[] = {"/foo", "/bar", "/baz"};
int i;
/* create files */
for (i = 0; i < sizeof(filenames) / sizeof(filenames[0]); i++) {
fd = open(filenames[i], O_CREAT | O_RDWR);
assert(fd != -1);
printf("File create: %s (fd %d)\n", filenames[i], fd);
close(fd);
}
char * rfilenames[] = {"/foo", "/bar", "/baz", "/dev_tty0"};
/* remove files */
for (i = 0; i < sizeof(rfilenames) / sizeof(rfilenames[0]); i++) {
if (unlink(rfilenames[i]) == 0) {
printf("File removed: %s\n", rfilenames[i]);
} else {
printf("Failed to remove file: %s\n", rfilenames[i]);
}
}
spin("TestA");
}
现在我们可以make并运行一下,看一下运行结果了,由于我们增加了C文件,所以不要忘记更改Makefile。运行结果如下图所示。
我们创建了“foo”、“bar”和“baz”三个文件,然后又以不同的顺序删除它们。另外我们还试图删除特殊文件“/dev_tty0”,这也被及时地“制止”了。
为文件系统添加系统调用的步骤
如今我们已经有了open()、close()、read()、write()以及unlink()等系统调用,如果对比Linux的系统调用,这显然还不够,不过更多的系统调用也不会有太多的新意了,有兴趣的话,可以自行添加需要的系统调用。为文件系统添加一个系统调用xxx()的步骤大致上有这些:
- 定义一种消息,比如MM(可参照 include/const.h 中 UNLINK 的定义方法)。
- 写一个函数来处理MM消息(可参照 fs/link.c 中 do_unlink() 的代码)。
- 修改task_fs(),增加对消息MM的处理。
- 写一个用户接口函数xxx()(可参照 lib/unlink.c 中 unlink() 的代码)。
另外诸如增加函数声明的琐碎事宜,在此不再赘述。
欢迎关注我的公众号
公众号中对应文章附有当前文章代码下载说明。