leevis.com
leevis.com copied to clipboard
linux-0.11打开文件
概述
linux中一切兼文件,本文分析的就是存在块设备,准确的说是磁盘中的文件打开过程,也就是open
函数的实现。
代码分析
上一篇中就说明了文件系统结构。磁盘块2是超级块,存放了保存了i 节点个数、逻辑块个数、i节点位图所占用的数据块个数、逻辑块位图所占用的数据块个数、第一个数据逻辑块等等。具体参考fs.h中定义的d_super_block
结构体。
磁盘上一个文件或目录,对应一个i节点和1个或多个数据块,而目录的数据块保存的就是目录下文件和对应的i节点号,参考fs.h定义的dir_entry
结构体。
在任务1中调用的初始化init
函数,调用了 setup((void *) &drive_info);
函数,该函数调用了 mount_root
函数挂载根文件系统。
挂载要做的一个工作就是把磁盘超级块读到内存中 super_block[NR_SUPER] 数组中。
初始化工作完成后,我们从open
函数跟踪一次文件打开,因为open
是用户态的系统调用,因此对应内核函数是sys_open
。
先看下进程打开一个文件对应的结构体
fs.h
struct file {
unsigned short f_mode; // 文件操作模式
unsigned short f_flags; // 文件打开和控制标志
unsigned short f_count; // 文件引用计数值
struct m_inode * f_inode; // 对应的i节点
off_t f_pos; // 文件位置
};
struct dir_entry {
unsigned short inode; // 目下文件对应的I节点号
char name[NAME_LEN]; // 目录下文件的名称
};
open.c 文件
// 打开一个文件,对应用户态open的三个参数。
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;
// 默认是清除执行时关闭标志位。该标志用一个无符号长整型标识,每bit标志一个打开的文件。
current->close_on_exec &= ~(1<<fd);
// 从系统打开文件数组获取一个文件表项,也就是一个空闲的file结构体。
f=0+file_table;
for (i=0 ; i<NR_FILE ; i++,f++)
if (!f->f_count) break;
if (i>=NR_FILE)
return -EINVAL;
// fd是进程中文件指针数组的下标,也就是用户态open返回的文件句柄。
(current->filp[fd]=f)->f_count++;
// 根据文件名,从磁盘获取对应的i节点
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; // 打开文件的i节点
f->f_pos = 0;
return (fd);
}
namei.c 文件
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;
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;
}
// 新建i节点
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;
// 获取文件i 节点
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;
}
// 返回最后一级目录的i节点和 文件名,文件名通过name和namelen指定。
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;
}
// 根据参数路径名开始搜索直到达到最顶端目录
// 根据文件名是绝对路径还是相对路径从进程对应i节点开始查找。
// 调用find_entry函数从当前目录i节点 查找下一级目录的目录项。
// 根据目录项中的i节点号,调用iget获取对应i节点号的i节点。
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++;
while (1) {
thisname = pathname;
// 如果不是目录, 没有对目录的访问权限直接返回null。
if (!S_ISDIR(inode->i_mode) || !permission(inode,MAY_EXEC)) {
iput(inode);
return NULL;
}
for(namelen=0;(c=get_fs_byte(pathname++))&&(c!='/');namelen++)
/* nothing */ ;
if (!c)。// 已经是最顶端目录了,直接返回对应的inode
return inode;
// 从目录下查找子目录
if (!(bh = find_entry(&inode,thisname,namelen,&de))) {
iput(inode);
return NULL;
}
inr = de->inode;
idev = inode->i_dev;
brelse(bh);
iput(inode);
// 读取设备上对应i节点号的i节点
if (!(inode = iget(idev,inr)))
return NULL;
}
}
我们以`/etc/rc`为例,再执行一次该函数。
首先判断当前进程是否指定了根目录和工作目录,如果没有则系统异常。
文件是绝对路径,则从当前进程根目录`current->root`指定的i节点遍历。
进入循环后,先进行权限判断。 然后解析文件路径,`thisname`和`namelen` 是下一级目录,例如`etc`、`rc`等。
如果已经解析完文件路径,则返回。在上例中返回的实际是etc对应的i节点。
调用`find_entry`函数从当前目录查找下一级目录, 例子中从根目录查找`etc/`
然后调用`iget`函数获取下一级目录的i 节点,继续循环。
// 从指定目录查找指定文件的目录项,
// 找到对应文件则,res_dir为文件目录项,返回包含文件的数据块的缓冲块。否则返回NULL。
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++;
}
}
}
// 数据块
if (!(block = (*dir)->i_zone[0]))
return NULL;
// 读取第一个数据块内容
if (!(bh = bread((*dir)->i_dev,block)))
return NULL;
i = 0;
// 目录节点数据块保存的就是目录项和i节点,强制转换成对应的结构体。
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;
}
总结
最后总结一下,用户程序调用glibc提供的open
函数打开一个文件时,会触发int 0x80
中断,调用内核函数sys_open
。
sys_open
函数从进程打开文件数组filp
中获取一个空项,空项数组下标就是open
函数返回的文件句柄fd
。然后再从全局打开文件数组file_table
中获取一个结构体,作为进程打开文件数组filp[fd]
的值。然后调用open_namei
函数查找磁盘文件的i节点,赋给打开文件结构体的f_inode
,把进程打开文件数组下标fd
返回,作为open
函数的返回值。