--------------------------------------------------------
创建一个文件并打开(一)
--------------------------------------------------------
上一节我们已经有个文件系统在硬盘上了,这节我们就为我们的文件系统创建新建文件的功能,因为 【文件系统 = 布局 + 操作】,在我们这样的布局下,怎样才能将一个文件写进磁盘,这是我们这节要考虑的内容(设计文件系统 API)...
·search_file 函数
PUBLIC int search_file(char *path)
{
int i, j;
char filename[MAX_PATH]; /* 【应为 MAX_FILENAME_LEN 】 */
memset(filename, 0, MAX_FILENAME_LEN);
struct inode *dir_inode;
// 【逻辑有问题】
// 从路径中解析出文件名
if (strip_path(filename, path, &dir_inode) != 0)
return 0;
// 如果是 '/'
if (filename[0] == 0) /* path: "/" */
return dir_inode->i_num;
// 在根目录的目录项中找是否有这个文件
int dir_blk0_nr = dir_inode->i_start_sect; /* 父目录的 dir entry们 所在的起始扇区 */
int nr_dir_blks = (dir_inode->i_size + SECTOR_SIZE - 1) / SECTOR_SIZE; /* 父目录的 dir entry们 占据多少扇区 */
int nr_dir_entries = dir_inode->i_size / DIR_ENTRY_SIZE; /* 父目录有多少个 dir entry */
int m = 0;
struct dir_entry *pde;
for (i = 0; i < nr_dir_blks; i++) /* 以扇区为单位遍历 */
{
RD_SECT(dir_inode->i_dev, dir_blk0_nr + i);
pde = (struct dir_entry *)fsbuf; /* 读出来的都是 dir entry */
for (j = 0; j < SECTOR_SIZE / DIR_ENTRY_SIZE; j++, pde++) /* 以 dir entry 为单位遍历 */
{
if (memcmp(filename, pde->name, MAX_FILENAME_LEN) == 0) /* 比较目录项中的名字和参数的名字 */
return pde->inode_nr;
if (++m > nr_dir_entries) /* 遍历完了所有的 dir entry */
break;
}
if (m > nr_dir_entries) /* all entries have been iterated */
break;
}
/* file not found */
return 0;
}
- 说是在路径指定的父目录下查找该文件是否存在,其实是从 root 目录查找,也没有判断指定的目录是否存在!
- 从参数指定的路径中判断文件是否存在,如果不存在就返回 0 ,如果存在就返回此文件的 inode 号(在此文件 dir entry 中)
·strip_path 函数
PUBLIC int strip_path(char *filename, const char *pathname,
struct inode **ppinode)
{
const char *s = pathname;
char *t = filename;
if (s == 0)
return -1;
if (*s == '/')
s++;
while (*s)
{ /* check each character */
if (*s == '/')
return -1;
*t++ = *s++;
/* if filename is too long, just truncate it */
if (t - filename >= MAX_FILENAME_LEN)
break;
}
*t = 0;
// 返回根目录文件的 inode 号
*ppinode = root_inode;
return 0;
}
- 这个函数就是把路径名 '/' 后面的部分取出来,如果后面的部分遇见 '/' 的话就报错(应该要报错的,但是这里逻辑有错误,变为了有 '/' 就创建)
- 象征性地返回父目录 inode (假定父目录一定存在)
·get_inode 函数
PUBLIC struct inode *get_inode(int dev, int num)
{
if (num == 0)
return 0;
struct inode *p;
struct inode *q = 0;
// 每一个在 inode_table 中的 inode 都要知道自己的 inode_number!
for (p = &inode_table[0]; p < &inode_table[NR_INODE]; p++)
{
if (p->i_cnt)
{ /* not a free slot */
if ((p->i_dev == dev) && (p->i_num == num))
{
/* this is the inode we want */
p->i_cnt++;
return p;
}
}
else
{ /* a free slot */
if (!q) /* q hasn't been assigned yet */
q = p; /* q <- the 1st free slot */
}
}
if (!q)
panic("the inode table is full");
q->i_dev = dev;
q->i_num = num;
q->i_cnt = 1;
// 从硬盘加载 inode 入 inode_table
struct super_block *sb = get_super_block(dev);
int blk_nr = 1 + 1 + sb->nr_imap_sects + sb->nr_smap_sects + /* inode 所在的扇区 */
((num - 1) / (SECTOR_SIZE / INODE_SIZE));
RD_SECT(dev, blk_nr); /* 读 */
struct inode *pinode = /* 取 */
(struct inode *)((u8 *)fsbuf +
((num - 1) % (SECTOR_SIZE / INODE_SIZE)) * INODE_SIZE);
// 只给内存中的 inode 赋值几个成员
q->i_mode = pinode->i_mode;
q->i_size = pinode->i_size;
q->i_start_sect = pinode->i_start_sect;
q->i_nr_sects = pinode->i_nr_sects;
return q;
}
——通过 inode 号找 inode,如果 inode 在内存中,直接返回,否则将此 inode 从硬盘加载到内存,因为前面我们已经通过文件名到 dir_entry 中获得了 inode 号,所以之后从 inode_array 中找此 inode 很容易!
·do_open 函数
PUBLIC int do_open()
{
int fd = -1; /* return value */
char pathname[MAX_PATH];
// 拷贝进程传递的路径名
int flags = fs_msg.FLAGS; /* 访问标志 */
int name_len = fs_msg.NAME_LEN; /* 文件名的长度 */
int src = fs_msg.source; /* 哪个进程要打开文件 */
assert(name_len < MAX_PATH); /* 小于的原因是最后一位要放结束字符 */
phys_copy((void *)va2la(TASK_FS, pathname),
(void *)va2la(src, fs_msg.PATHNAME),
name_len);
pathname[name_len] = 0; /* pathname[name_len] 就是第 name_len + 1 个字符 */
// 在文件描述符指针数组中找到一个没有指向文件描述符的指针
int i;
for (i = 0; i < NR_FILES; i++)
{
if (pcaller->filp[i] == 0)
{
fd = i;
break;
}
}
if ((fd < 0) || (fd >= NR_FILES)) /* 这里 fd 怎么会 >= NR_FILES 呢??? */
panic("filp[] is full (PID:%d)", proc2pid(pcaller));
// 在文件描述符数组中找到一个空闲项
for (i = 0; i < NR_FILE_DESC; i++)
if (f_desc_table[i].fd_inode == 0)
break;
if (i >= NR_FILE_DESC)
panic("f_desc_table[] is full (PID:%d)", proc2pid(pcaller));
// 取得文件的 inode 号,可能返回 0 ,糟糕的是返回 0 有两种情况:
// 1,文件名中包含了 '/' 2,文件确实不存在
int inode_nr = search_file(pathname);
struct inode *pin = 0;
if (flags & O_CREAT) /* 如果想创建这个文件 */
{
if (inode_nr) /* 文件已存在 */
{
printl("file exists.\n");
return -1;
}
else /* 文件不存在 */
{
pin = create_file(pathname, flags);
}
}
else /* 如果想读写这个文件 */
{
assert(flags & O_RDWR);
char filename[MAX_PATH];
struct inode *dir_inode;
if (strip_path(filename, pathname, &dir_inode) != 0)
return -1;
// 如果 inode_nr == 0(文件不存在),get_inode 直接返回 0!
pin = get_inode(dir_inode->i_dev, inode_nr);
}
if (pin) /* 到这里内存中的 inode 要么是新创建的,要么从磁盘加载的,要么是原先就在内存的 */
{
// 重要的代码
pcaller->filp[fd] = &f_desc_table[i];
f_desc_table[i].fd_inode = pin;
f_desc_table[i].fd_mode = flags;
f_desc_table[i].fd_pos = 0;
// 打开
int imode = pin->i_mode & I_TYPE_MASK;
if (imode == I_CHAR_SPECIAL)
{
// 设备文件
MESSAGE driver_msg;
driver_msg.type = DEV_OPEN;
int dev = pin->i_start_sect;
driver_msg.DEVICE = MINOR(dev);
assert(MAJOR(dev) == 4);
assert(dd_map[MAJOR(dev)].driver_nr != INVALID_DRIVER);
send_recv(BOTH,
dd_map[MAJOR(dev)].driver_nr,
&driver_msg);
}
else if (imode == I_DIRECTORY) /* 目录文件 */
assert(pin->i_num == ROOT_INODE);
else
assert(pin->i_mode == I_REGULAR); /* 普通文件 */
}
else
return -1;
return fd;
}
一:OPEN 的动作可以有
- 创建并打开:如果文件已经存在,返回错误,如果文件不存在,就创建文件,并将创建的 inode 加载进内存
- 读写打开:文件一定要存在,如果不存在,将显示打开失败!
二:函数的流程
首先通过文件名到父目录的目录项数组中查找目录项,如果能在目录项数组中匹配到文件名,就证明文件是存在滴,然后取出 inode 号(文件不存在返回 0 inode 号),inode 号取出来了之后判断是创建还是读写,只有文件不存在才能创建,只有文件存在才能读写!创建和读写都要将 inode 从磁盘加载到内存(如果已经在内存那就直接返回 inode 地址就行,当然创建的时候不可能已经存在于内存),最后建立 【文件句柄】、【文件描述符】、【inode】三者之间的指向关系,建立了关联,就意味着文件已经打开了,返回文件句柄就可以了(设备文件还要做点处理)!
三:注意点
- 关于文件名有 '/' 的问题
- 不要重复打开一个文件(目前并没有防范机制)
OK,第一步先做到这里,剩下的任务下一节完成!