前言
文件系统根目录下的所有文件名信息则保持在指定 i 节点(即1号节点)的数据块中。每个目录项只包括一个长度为 14 字节的文件名字字符串和该文件名对应的 2 字节的 i 节点号。有关文件的其它信息则被保存在该 i 节点号指定的 i 节点结构中,该结构中主要包括文件访问属性、宿主、长度、访问保存时间以及所在磁盘块等信息。
#define NAME_LEN 14 // 名字长度值 #define ROOT_INO 1 // 根 i 节点 struct dir_entry { unsigned short inode; // i 节点号(0.11版本可理解为存储序号) char name[NAME_LEN]; // 文件名 };
在打开一个文件时,文件系统会根据给定的文件名找到其 i 节点号,从而找到文件所在的磁盘块位置。例如对于要查找文件名 /usr/bin/vi 的 i 节点号,文件系统首先会从具有固定 i 节点号(1)的根目录开始操作,即从 i 节点号 1 的数据块中查找到名称为 usr 的目录项,从而得到文件 /usr 的 i 节点号。根据该 i 节点号文件系统可以顺利地取得目录 /usr,并在其中可以查找到文件名 bin 的目录项。这样也就知道了 /usr/bin 的 i 节点号,因而我们可以知道目录 /usr/bin 的目录所在位置,并在该目录中查找到 vi 文件的目录项。最终我们获得了文件路径名 /usr/bin/vi 的 i 节点号,从而可以从磁盘上得到该 i 节点号的 i 节点结构信息。
一、由来
在文件 init/main.c 的 init 函数中调用了 open 函数:
void init(void) { // ... (void)open("/dev/tty0", O_RDWR, 0); (void)dup(0); (void)dup(0); // ... }
接下来我们分析下其调用过程。
1、open函数
// lib/open.c int open(const char * filename, int flag, ...) { register int res; va_list arg; va_start(arg,flag); __asm__("int $0x80" :"=a" (res) :"0" (__NR_open),"b" (filename),"c" (flag), "d" (va_arg(arg,int))); if (res>=0) return res; errno = -res; return -1; }
从上面可知,这是一个系统调用,其中断响应为 kernel/system_call.s 文件中 system_call 函数,最终调用 sys_open 函数(具体可参考 Linux 0.11 fork 函数(二))。
二、sys_open 函数
函数位于文件 fs/open.c 中。
// fs/open.c int sys_open(const char * filename,int flag,int mode) { struct m_inode * inode; struct file * f; int i,fd; mode &= 0777 & ~current->umask; for(fd=0 ; fd<NR_OPEN ; fd++) if (!current->filp[fd]) break; if (fd>=NR_OPEN) return -EINVAL; current->close_on_exec &= ~(1<<fd); f=0+file_table; for (i=0 ; i<NR_FILE ; i++,f++) if (!f->f_count) break; if (i>=NR_FILE) return -EINVAL; (current->filp[fd]=f)->f_count++; 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]); f->f_mode = inode->i_mode; f->f_flags = flag; f->f_count = 1; f->f_inode = inode; f->f_pos = 0; return (fd); }
1、open_namei 函数
函数位于文件 fs/namei.c 中。
// fs/namei.c // 文件打开函数 // pathname是文件名,flag是打开文件标志,可为:O_RDONLY(只读)、O_WRONLY(只写)或 // O_RDWR(读写),以及O_CREAT(创建)、O_EXCL(被创建文件必须不存在)、O_APPEND(在文件尾添加数据) // 等其他一些标志的组合。 // 成功返回0,否则返回出错码,res_node返回对应文件路径名的i节点指针 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; if (!(dir = dir_namei(pathname,&namelen,&basename))) return -ENOENT; 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; 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; return 0; }
dir_namei 函数
函数位于文件 fs/namei.c 中。
该函数返回指定目录名的 i 节点指针,以及在最顶层目录的名称。
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))) return NULL; basename = pathname; while ((c=get_fs_byte(pathname++))) if (c=='/') basename=pathname; *namelen = pathname-basename-1; *name = basename; return dir; }
get_dir 函数
函数位于文件 fs/namei.c 中。
该函数根据给出的路径名进行搜索,直到达到最顶端的目录。
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; // 搜索操作会从当前进程任务结构中设置的根(或伪根)i 节点或当前工作目录 i 节点开始。 // 因此首先需要判断进程的根 i 节点指针和当前工作目录 i 节点指针是否有效。如果当前进程 // 没有设定根 i 节点,或者该进程根 i 节点指向是一个空闲i节点(引用为0),则系统出错停机。 // 如果进程的当前工作目录i节点指针为空,或者当前工作目录 i 节点指向是一个空闲i节点, // 这也是系统有问题,停机。 if (!current->root || !current->root->i_count) panic("No root inode"); if (!current->pwd || !current->pwd->i_count) panic("No cwd inode"); // 如果用户指定的路径名的第1个字符是'/',则说明路径是绝对路径名。则从根i节点开始操作。 // 否则表示给定的是相对路径名。应从进程的当前工作目录开始操作。则取进程当前工作目录的 // i节点。 if ((c=get_fs_byte(pathname))=='/') { inode = current->root; pathname++; } else if (c) inode = current->pwd; else return NULL; /* empty name is bad */ // 然后针对路径名中的各个目录名部分和文件名进行循环处理。首先把得到的i节点引用计数增1, // 表示我们正在使用。在循环处理过程中,我们先要对当前正在处理的目录名部分(或文件名)的i // 节点进行有效性判断,并且把变量thisname指向当前正在处理的目录名部分(或文件名)。 // 如果该i节点不是目录类型的i节点,或者没有可进入该目录的访问许可,则放回该i节点,并返回 // NULL退出。当然,刚进入循环时,当前的i节点就是进程根i节点或者是当前工作目录的i节点。 // 注意!如果路径名中最后一个名称也是一个目录名,但其后面没有加上'/'字符,则函数不会 // 返回该最后目录的i节点!例如:对于路径名/usr/src/linux,该函数将只返回src目录名的i节点。 inode->i_count++; while (1) { thisname = pathname; if (!S_ISDIR(inode->i_mode) || !permission(inode,MAY_EXEC)) { iput(inode); return NULL; } // 每次循环我们处理路径名中一个目录名(或文件名)部分。因此在每次循环中我们都要从路径 // 名字符串中分离出一个目录名(或文件名)。方法是从当前路径名指针pathname开始处搜索 // 检测字符,直到字符是一个结尾符(NULL)或者是一个'/'字符。此时变量namelen正好是当前 // 处理目录名部分的长度,而变量thisname正指向该目录名部分的开始处。此时如果字符是结尾符 // NULL,则表明已经搜索到路径名末尾,并已到达最后指定目录名或文件名,则返回该i节点指针退出 for(namelen=0;(c=get_fs_byte(pathname++))&&(c!='/');namelen++) /* nothing */ ; if (!c) return inode; // 在得到当前目录名部分(或文件名)后,我们调用查找目录项函数find_entry在当前处理的目录中 // 寻找指定名称的目录项。如果没找到,则返回NULL退出。否则在找到的目录项中取出其i节点号inr // 和设备号idev,然后取节点号inr的i节点inode,并以该目录项为当前目录继续循环处理路径名中的 // 下一目录名部分(或文件名) if (!(bh = find_entry(&inode,thisname,namelen,&de))) { iput(inode); return NULL; } inr = de->inode; idev = inode->i_dev; brelse(bh); iput(inode); if (!(inode = iget(idev,inr))) return NULL; } }
find_entry 函数
/* 查找指定目录和文件名的目录项。 * dir 指定目录i节点的指针,name 文件名,namelen文件名长度。 * 返回:成功则返回函数告诉缓冲区指针,并在*res_dir处返回的目录项结构指针。失败则返回空指针。 */ static struct buffer_head * find_entry(struct m_inode ** dir, const char * name, int namelen, struct dir_entry ** res_dir) { //... }