就是将open弄明白
open.c的核心就是sys_open()函数 就是找到inode,将inode读出来
当用户空间程序用open系统调用打开一个文件的时候,内核对应的处理是sys_open函数:
fs/open.c
asmlinkage long sys_open(const char __user * filename, int flags, int mode)
{
char * tmp;
int fd, error;
#if BITS_PER_LONG != 32
flags |= O_LARGEFILE;
#endif
tmp = getname(filename);//从进程地址空间读取该文件的路径名
fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
fd = get_unused_fd();//调用get_unused_fd()在current->files->fd中查找一个空的位置。相应的索引存放在fd局部变量中
if (fd >= 0) {
//打开文件,将文件名转换成文件结构
struct file *f = filp_open(tmp, flags, mode);
//调用file_open()函数,传递他的参数为路径名、访问模式标志、以及许可权位掩码
error = PTR_ERR(f);
if (IS_ERR(f))
goto out_error;
fd_install(fd, f);
}
}
对于filp_open()文件打开函数,filp_open()的定义也在open.c文件中
struct file *filp_open(const char * filename, int flags, int mode)
{
int namei_flags, error;
struct nameidata nd; //这个结构存放了查找操作的结果
namei_flags = flags;
if ((namei_flags+1) & O_ACCMODE)
namei_flags++;
if (namei_flags & O_TRUNC)
namei_flags |= 2;
error = open_namei(filename, namei_flags, mode, &nd); //顺着文件名打开操作
if (!error)
return dentry_open(nd.dentry, nd.mnt, flags); //如果成功的话,根据返回的数据填充file结构体:
return ERR_PTR(error);
}
EXPORT_SYMBOL(filp_open);
这个函数执行以下操作:
1. 把访问模式标志复制到namei_flags标志中
2. 调用open_namei(),传递他的参数为路径名、修改的访问模式以及局部nameidata数据结构的地址
3. 调用dentry_open()函数,传递他的参数为访问模式标志、目录项对象的地址以及由查找操作确定的已安装文件系统对象
接下来重点是open_namei()函数,实现了函数名到inode的转换
open_namei()定义在fs/namei.c文件中
int open_namei(const char * pathname, int flag, int mode, struct nameidata *nd)
{
int acc_mode, error = 0;
struct dentry *dentry;
struct dentry *dir;
int count = 0;
acc_mode = ACC_MODE(flag);//从flags得到打开模式
/* Allow the LSM permission hook to distinguish append
access from general write access. */
if (flag & O_APPEND) //总是在文件末尾写
acc_mode |= MAY_APPEND;
/* 填充nameidata的intent字段,即将打开文件的信息放在nd的intent中*/
nd->intent.open.flags = flag;
nd->intent.open.create_mode = mode;
/* 不需要创建文件*/
//不需要创建文件如果没有设置的话,则在未找到文件的时候不用创建文件,直接通过查找来打开文件
if (!(flag & O_CREAT)) {
//直接找pathname的dentry和挂载点,结果填充在nd中
error = path_lookup(pathname, lookup_flags(flag)|LOOKUP_OPEN, nd);
if (error)
return error;
goto ok; //成功查找到了目标文件的话,就跳转到ok去执行后续操作
}error = path_lookup(pathname, LOOKUP_PARENT|LOOKUP_OPEN|LOOKUP_CREATE, nd);//如果创建文件的话,我们需要知道他的父目录,所以加上LOOKUP_PARENT
if (error)
return error;
error = -EISDIR;
if (nd->last_type != LAST_NORM || nd->last.name[nd->last.len])//如果最后返回的结果不是一个普通文件的话,就把错误返回。
goto exit;
dir = nd->dentry; //获取父目录
nd->flags &= ~LOOKUP_PARENT;//取消LOOKUP_PARENT的设置
down(&dir->d_inode->i_sem); //释放索引节点信号量
dentry = __lookup_hash(&nd->last, nd->dentry, nd);//在父目录下查找last名字的dentry是否存在,如果不存在就进行创建一个新的目录项
}
再看一下path_lookup()函数——路径名查找函数。
这个函数的作用是将用户传过来的字符串表示的路径转换成一个dentry结构。
它的执行步骤是:
- 如下初始化nd参数的某些字段:a. 把 last_type字段置为LAST_ROOT b. 把flags字段置为参数的flags的值 c. 把depth字段置为0
- 未进行读操作而获取当前进程的current->fs->lock读写信号量
- 如果路径名的第一个字符是“/”,那么查找操作必须从当前根目录开始
- 如果不是‘/’ 则查找操作必须从当前目录开始:获得相应已安装文件对象和目录项对象的地址
- 释放当前进程的读写信号量
- 当前进程描述符中的total_link_count字段置0
- 调用link_path_walk()函数处理正在进行的查找操作
具体代码为
int fastcall path_lookup(const char *name, unsigned int flags, struct nameidata *nd)
{
int retval;
//初始化nd参数的某些字段
nd->last_type = LAST_ROOT; /* if there are only slashes... */
nd->flags = flags;
nd->depth = 0;
//未进行读操作而获取当前进程的current->fs->lock读写信号量
read_lock(¤t->fs->lock);
if (*name=='/') {
if (current->fs->altroot && !(nd->flags & LOOKUP_NOALT)) {
nd->mnt = mntget(current->fs->altrootmnt);
nd->dentry = dget(current->fs->altroot);
read_unlock(¤t->fs->lock);
if (__emul_lookup_dentry(name,nd))
return 0;
read_lock(¤t->fs->lock);
}
//获取相应已安装文件对象和目录项对象的地址
nd->mnt = mntget(current->fs->rootmnt);
nd->dentry = dget(current->fs->root);
} else {
//如果不是‘/’ 则查找操作必须从当前目录开始:获得相应已安装文件对象和目录项对象的地址
nd->mnt = mntget(current->fs->pwdmnt);
nd->dentry = dget(current->fs->pwd);
}
//释放当前进程的读写信号量
read_unlock(¤t->fs->lock);
//当前进程描述符中的total_link_count字段置0
current->total_link_count = 0;
//调用link_path_walk()函数处理正在进行的查找操作
retval = link_path_walk(name, nd);
if (unlikely(current->audit_context
&& nd && nd->dentry && nd->dentry->d_inode))
audit_inode(name,
nd->dentry->d_inode->i_ino,
nd->dentry->d_inode->i_rdev);
return retval;
}
当path_lookup()返回时,nd指向的nameidata结构用与路径名查找操作有关的数据来填充。