1. 引言
从事Linux环境工作2年有余,一直懵懵懂懂,1年前拜读了《莱昂氏UNIX源代码分析》一书,感觉自己的学习道路漫漫且修远。最近受chinaunix的精华文帖启发,拟将近来的部分内核调用分析笔记拿出来与各前辈先进共同探讨学习,以壮个人学习之路。
本部分主要讲述的是文件I/O操作的2.6.11内核版本实现,包括了主要的数据结构、宏定义和函数流程。以下分别讲述open,create,close,read,write,lseek系统调用。
2. 主要参考
《莱昂氏UNIX源代码分析》
《UNIX环境高级编程》
www.kernel.org
3. 主要数据结构
3.1. FD
对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。
当读、写一个文件时,用open或creat返回的文件描述符fd标识该文件,将其作为参数传送给read或write。在POSIX.1应用程序中,文件描述符为常数0、1和2分别代表STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,意即标准输入,标准输出和标准出错输出,这些常数都定义在头文件<unistd.h>;中。
文件描述符的范围是0~OPEN_MAX,在目前常用的linux系统中,是32位整形所能表示的整数,即65535,64位机上则更多。
3.2. File
struct file {
struct list_head f_list; //文件链表指针
struct dentry *f_dentry; // 文件对应的目录结构
struct vfsmount *f_vfsmnt; // 虚拟文件系统挂载点
struct file_operations *f_op; // 文件操作函数指针
atomic_t f_count;
unsigned int f_flags;
mode_t f_mode; // 文件模式
int f_error;
loff_t f_pos; // 文件offset
struct fown_struct f_owner; //文件owner 结构
unsigned int f_uid, f_gid;
struct file_ra_state f_ra; // 跟踪上次文件操作状态的结构指针
size_t f_maxcount; // 文件大小
unsigned long f_version;
void *f_security; // hook 文件操作的security结构指针
void *private_data; // tty 驱动器所需数据
#ifdef CONFIG_EPOLL
struct list_head f_ep_links; // EPOLL 机制检测所需链表结构
spinlock_t f_ep_lock; // 兼容早期gcc bug 的标志
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping; // 地址映射表
}
3.3. File_struct
File_struct结构保存了进程打开的所有文件表数据。
struct files_struct {
atomic_t count; // 自动增量
spinlock_t file_lock; // 低位成员保护标识
int max_fds; // 最大文件句柄数目
int max_fdset; // 最大的fd集合容量
int next_fd; // 下一个空闲fd
struct file ** fd; // 当前fd对应的文件结构指针列表
fd_set *close_on_exec; // 可执行close的fd集合
fd_set *open_fds; // 打开的fd集合
fd_set close_on_exec_init; //
fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT]; // 默认打开的fd队列
};
4. open 函数
4.1. 原型与参数
int open(const char * pathname, int oflag, .../*, mode_t mode * / ) -1代表错误。
这里的oflag是一个整形,主要供open 函数使用,部分fcntl函数也会使用。详细的说明请用 man 2 open就可以看到了。以下列出了2.6内核定义的open和fcntl函数所使用的flag宏定义,说明的格式如宏定义名称<实际常数值>;: 描述。
O_ACCMODE <0003>;: 读写文件操作时,用于取出flag的低2位。
O_RDONLY<00>;: 只读打开
O_WRONLY<01>;: 只写打开
O_RDWR<02>;: 读写打开
O_CREAT<0100>;: 文件不存在则创建,需要mode_t,not fcntl
O_EXCL<0200>;: 如果同时指定了O_CREAT,而文件已经存在,则出错, not fcntl
O_NOCTTY<0400>;: 如果pathname指终端设备,则不将此设备分配作为此进程的控制终端。O_TRUNC<01000>;: 如果此文件存在,而且为只读或只写成功打开,则将其长度截短为0。 O_APPEND<02000>;: 每次写时都加到文件的尾端
O_NONBLOCK<04000>;: 如果p a t h n a m e指的是一个F I F O、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I / O操作设置非阻塞方式。
O_NDELAY<O_NONBLOCK>;;
O_SYNC<010000>;: 使每次write都等到物理I/O操作完成。
FASYNC<020000>;: 兼容BSD的fcntl同步操作
O_DIRECT<040000>;: 直接磁盘操作标识
O_LARGEFILE<0100000>;: 大文件标识
O_DIRECTORY<0200000>;: 必须是目录
O_NOFOLLOW<0400000>;: 不获取连接文件
O_NOATIME<01000000>;: 暂无
当新创建一个文件时,需要指定mode 参数,以下说明的格式如宏定义名称<实际常数值>;: 描述。
S_IRWXU<00700>;:文件拥有者有读写执行权限
S_IRUSR (S_IREAD)<00400>;:文件拥有者仅有读权限
S_IWUSR (S_IWRITE)<00200>;:文件拥有者仅有写权限
S_IXUSR (S_IEXEC)<00100>;:文件拥有者仅有执行权限
S_IRWXG<00070>;:组用户有读写执行权限
S_IRGRP<00040>;:组用户仅有读权限
S_IWGRP<00020>;:组用户仅有写权限
S_IXGRP<00010>;:组用户仅有执行权限
S_IRWXO<00007>;:其他用户有读写执行权限
S_IROTH<00004>;:其他用户仅有读权限
S_IWOTH<00002>;:其他用户仅有写权限
S_IXOTH<00001>;:其他用户仅有执行权限
4.2. 实现分析
4.2.1. 主要函数调用关系图
sys_open( 见4.2.2 节)
| ----------- getname( 见4.2.3 节 )
| ----------- filp_open( 见4.2.4节 )
| | ------------ open_namei( 见4.2.4.1节 )
| | | ----------- may_open
| | ------------ dentry_open( 见4.2.4.2节 )
4.2.2. 主调用函数sys_open,函数定义在/fs/open.c文件
asmlinkage long sys_open(const char __user * filename, int flags, int mode){
char * tmp;
int fd, error;
// 如果不是32位处理器,则增加大文件标识
#if BITS_PER_LONG != 32
flags |= O_LARGEFILE;
#endif
// 为了提高使用效率,在使用之前先将文件名拷贝到内核数据区。见3.2.2说明
tmp = getname(filename);
// 获取到返回值,如果出错,则返回,否则执行打开操作。
fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
// 从进程的文件表中找出一个空闲的文件表指针,如果出错,则返回
fd = get_unused_fd();
if (fd >= 0) {
// 执行打开操作。见3.2.3说明
struct file *f = filp_open(tmp, flags, mode);
// 获取返回结果,如果出错,则跳转至out_error,否则执行fd_install
error = PTR_ERR(f);
if (IS_ERR(f))
goto out_error;
// 添加打开的文件表 f 到当前进程的文件表队列中。见3.2.4说明
fd_install(fd, f);
}
out:
// 释放getname分配的内存空间
putname(tmp);
}
return fd;
out_error:
// 将文件表指针返回到进程的文件表中,并返回错误代码。
put_unused_fd(fd);
fd = error;
goto out;
}
4.2.3. sys_open子函数getname
getname函数主要功能是在使用文件名之前将其拷贝到内核数据区,正常结束时返回内核分配的空间首地址,出错时返回错误代码。其调用了函数do_getname来实现。
static inline int do_getname(const char __user *filename, char *page){
int retval;
unsigned long len = PATH_MAX; // 内核允许的最大路径长度
// 如果进程的地址限制是否和KERNEL_DS相等,则检查文件名是否小于用户进程空间
if (!segment_eq(get_fs(), KERNEL_DS)) {
// 文件名地址大于用户进程空间,则返回错误-EFAULT
if ((unsigned long) filename >= TASK_SIZE)
return -EFAULT;
// 获取较小的地址长度
if (TASK_SIZE - (unsigned long) filename < PATH_MAX)
len = TASK_SIZE - (unsigned long) filename;
}
// 将filename拷贝len长度到page中,返回实际拷贝长度
retval = strncpy_from_user(page, filename, len);
if (retval >0) {
// 如果retval大于等于len,则返回-ENAMETOOLONG
if (retval < len)
return 0;
return -ENAMETOOLONG;
} else if (!retval)
// filename 为空,则返回-ENOENT
retval = -ENOENT;
return retval;
}
char * getname(const char __user * filename){
char *tmp, *result;
result = ERR_PTR(-ENOMEM);
// 从内核缓存中分配空间,如果成功,则执行do_getname
tmp = __getname();
if (tmp) {
// 执行文件名拷贝操作
int retval = do_getname(filename, tmp);
result = tmp;
if (retval < 0) {
// do_getname出错,则释放空间,并返回错误代码
__putname(tmp);
result = ERR_PTR(retval);
}
}
// 如果前面操作成功,且audit_context不为空,则将当前文件名添加到audit列表中
if (unlikely(current->;audit_context) && !IS_ERR(result) && result)
audit_getname(result);
// 返回处理结果
return result;
}
4.2.4. sys_open子函数filp_open
在较高版本的内核里函数名改为do_filp_open,但是函数内容一样。
这后面的函数使用了一个nameidata的数据结构来描述文件相关的操作数据。
struct nameidata {
struct dentry *dentry; // 目录数据
struct vfsmount *mnt; // 虚拟文件挂载点数据
struct qstr last; // hash值
unsigned int flags; // 文件操作标识
int last_type; // 类型
unsigned depth;
char *saved_names[MAX_NESTED_LINKS + 1];
union {
struct open_intent open;
} intent; // 专用数据
};
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++; // 如果flags有O_WRONLY,则增加O_RDONLY
if (namei_flags & O_TRUNC)
namei_flags |= 2; // 如果有O_TRUNC,则增加O_RDWR
error = open_namei(filename, namei_flags, mode, &nd); // 如3.2.3.1 描述
if (!error)
return dentry_open(nd.dentry, nd.mnt, flags); // 如3.2.3.2描述
return ERR_PTR(error); // 返回错误代码
}
4.2.4.1. filp_open子函数open_namei
open_namei函数主要执行文件操作的inode部分的打开等操作。
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); // 取出低2位操作标识
if (flag & O_APPEND) // 取出O_APPEND操作标识
acc_mode |= MAY_APPEND;
//赋值open函数的专用数据
nd->;intent.open.flags = flag;
nd->;intent.open.create_mode = mode;
// 如果不需要创建文件,则在进程目录文件表搜索已有文件,并把结果拷贝到nd中
if (!(flag & O_CREAT)) {
error = path_lookup(pathname, lookup_flags(flag)|LOOKUP_OPEN, nd);
if (error) // 错误代码有ENOENT,ENOTDIR,EAGAIN,ESTALE,
return error;
goto ok; // 否则执行打开函数,更新inode数据
}
// 在进程文件表中搜索该文件,如果不存在,则创建,结果由nd保存
error = path_lookup(pathname, LOOKUP_PARENT|LOOKUP_OPEN|LOOKUP_CREATE, nd);
if (error)
return error;
// 检测nd的结果是否是一个目录文件,是则返回
error = -EISDIR;
if (nd->;last_type != LAST_NORM || nd->;last.name[nd->;last.len])
goto exit;
// 获取文件的相关目录数据,结果返回到dentry中。
dir = nd->;dentry;
nd->;flags &= ~LOOKUP_PARENT;
down(&dir->;d_inode->;i_sem);
dentry = __lookup_hash(&nd->;last, nd->;dentry, nd);
do_last:
// 如果dentry是一个错误值,则返回
error = PTR_ERR(dentry);
if (IS_ERR(dentry)) {
up(&dir->;d_inode->;i_sem);
goto exit;
}
// 如果dentry不存在,则创建他
if (!dentry->;d_inode) {
if (!IS_POSIXACL(dir->;d_inode))
mode &= ~current->;fs->;umask;
error = vfs_create(dir->;d_inode, dentry, mode, nd); // 创建inode
up(&dir->;d_inode->;i_sem);
dput(nd->;dentry);
nd->;dentry = dentry;
if (error)
goto exit;
acc_mode = 0;
flag &= ~O_TRUNC;
goto ok;
}
up(&dir->;d_inode->;i_sem);
error = -EEXIST; // 如果指定了O_EXCL和O_CREAT,文件存在时,出错
if (flag & O_EXCL)
goto exit_dput;
if (d_mountpoint(dentry)) { // 检测文件是否是连接文件
error = -ELOOP;
if (flag & O_NOFOLLOW) // 如果指定不遍历连接文件,则返回
goto exit_dput;
// 检测dentry挂载点
while (__follow_down(&nd->;mnt,&dentry) && d_mountpoint(dentry));
}
error = -ENOENT;
if (!dentry->;d_inode) // inode 不存在,则返回
goto exit_dput;
if (dentry->;d_inode->;i_op && dentry->;d_inode->;i_op->;follow_link)
goto do_link; // 允许遍历连接文件,则手工找到连接文件对应的文件
// 将处理后的dentry复制到nd结构中,并判断其是否是目录,是则返回错误
dput(nd->;dentry);
nd->;dentry = dentry;
error = -EISDIR;
if (dentry->;d_inode && S_ISDIR(dentry->;d_inode->;i_mode))
goto exit;
ok:
error = may_open(nd, acc_mode, flag); // 打开文件,返回处理结果代码。如3.2.3.1.1描述
if (error)
goto exit;
return 0;
exit_dput:
dput(dentry); // 释放dentry
exit:
path_release(nd); // 释放nd结构
return error; // 返回错误代码
do_link:
error = -ELOOP;
if (flag & O_NOFOLLOW)
goto exit_dput; // 不允许遍历连接文件,则返回错误
// 以下代码是手工找到连接文件对应的文件dentry数据
nd->;flags |= LOOKUP_PARENT;
error = security_inode_follow_link(dentry, nd);
if (error)
goto exit_dput;
error = __do_follow_link(dentry, nd);
dput(dentry);
if (error)
return error;
nd->;flags &= ~LOOKUP_PARENT;
if (nd->;last_type == LAST_BIND) {
dentry = nd->;dentry;
goto ok;
}
error = -EISDIR;
if (nd->;last_type != LAST_NORM)
goto exit;
if (nd->;last.name[nd->;last.len]) {
putname(nd->;last.name);
goto exit;
}
error = -ELOOP;
if (count++==32) {
putname(nd->;last.name);
goto exit;
}
dir = nd->;dentry;
down(&dir->;d_inode->;i_sem);
dentry = __lookup_hash(&nd->;last, nd->;dentry, nd);
putname(nd->;last.name);
goto do_last;
}
4.2.4.1.1. filp_open子函数may_open
在较高版本的内核里作为open_namei的子函数。。。
may_open执行权限检测和文件打开,和truncate的操作。
int may_open(struct nameidata *nd, int acc_mode, int flag){
struct dentry *dentry = nd->;dentry;
struct inode *inode = dentry->;d_inode;
int error;
if (!inode) return -ENOENT; // inode为空,则返回错误
if (S_ISLNK(inode->;i_mode)) // 连接文件,返回错误
return -ELOOP;
if (S_ISDIR(inode->;i_mode) && (flag & FMODE_WRITE))
return -EISDIR; // 是目录且仅有写权限,返回错误
error = permission(inode, acc_mode, nd); // 见擦inode的accmode
if (error)
return error;
if (S_ISFIFO(inode->;i_mode) || S_ISSOCK(inode->;i_mode)) {
flag &= ~O_TRUNC; // 如果是FIFO文件,则不允许truncate
} else if (S_ISBLK(inode->;i_mode) || S_ISCHR(inode->;i_mode)) {
if (nd->;mnt->;mnt_flags & MNT_NODEV)
return -EACCES; // 如果是设备,则不允许truncate,否则返回错误
flag &= ~O_TRUNC;
} else if (IS_RDONLY(inode) && (flag & FMODE_WRITE))
return -EROFS; 如果flag标识和inode权限冲突,则返回错误
// 如果inode只允许append方式写入,则不允许truncate和非append写入方式。
if (IS_APPEND(inode)) {
if ((flag & FMODE_WRITE) && !(flag & O_APPEND))
return -EPERM;
if (flag & O_TRUNC)
return -EPERM;
}
// O_NOATIME方式仅在inode用户是文件拥有者或者超级用户情况下才被允许
if (flag & O_NOATIME)
if (current->;fsuid != inode->;i_uid && !capable(CAP_FOWNER))
return -EPERM;
// 检查是否有其他进程在使用该文件
error = break_lease(inode, flag);
if (error)
return error;
if (flag & O_TRUNC) {
error = get_write_access(inode); // 获取一次inode写操作权限
if (error)
return error;
// 锁定inode
error = locks_verify_locked(inode);
if (!error) {
DQUOT_INIT(inode); // 对inode执行配额初始化
error = do_truncate(dentry, 0); // truncate dentry
}
put_write_access(inode); // 释放当前写操作权限
if (error)
return error;
} else
if (flag & FMODE_WRITE) // 如果有写标识,则对inode执行配额初始化
DQUOT_INIT(inode);
return 0;
}
4.2.4.2. open_namei子函数dentry_open
dentry_open函数主要实现文件表的对应打开等操作,返回文件指针。
struct file *dentry_open(struct dentry *dentry, struct vfsmount *mnt, int flags){
struct file * f;
struct inode *inode;
int error;
error = -ENFILE;
f = get_empty_filp(); // 从进程文件表中获取一个未使用的文件结构指针,空则出错返回
if (!f)
goto cleanup_dentry;
// 设置文件的flags和mode标识
f->;f_flags = flags;
f->;f_mode = ((flags+1) & O_ACCMODE) | FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE;
inode = dentry->;d_inode;
if (f->;f_mode & FMODE_WRITE) {
error = get_write_access(inode); // 获取一次inode写操作权限
if (error)
goto cleanup_file;
}
// 初始化文件结构
f->;f_mapping = inode->;i_mapping;
f->;f_dentry = dentry;
f->;f_vfsmnt = mnt;
f->;f_pos = 0;
f->;f_op = fops_get(inode->;i_fop);
file_move(f, &inode->;i_sb->;s_files);
// 调用文件驱动模块初始化物理磁盘
if (f->;f_op && f->;f_op->;open) {
error = f->;f_op->;open(inode,f);
if (error)
goto cleanup_all;
}
f->;f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
// 初始化上次读取状态
file_ra_state_init(&f->;f_ra, f->;f_mapping->;host->;i_mapping);
// 如果设置了O_DIRECT,则检测文件结构中是否有驱动的操作函数指针
if (f->;f_flags & O_DIRECT) {
if (!f->;f_mapping->;a_ops || !f->;f_mapping->;a_ops->;direct_IO) {
fput(f);
f = ERR_PTR(-EINVAL);
}
}
return f; // 返回文件结构
cleanup_all: // 出错,则释放资源并返回
fops_put(f->;f_op);
if (f->;f_mode & FMODE_WRITE)
put_write_access(inode);
file_kill(f);
f->;f_dentry = NULL;
f->;f_vfsmnt = NULL;
cleanup_file:
put_filp(f);
cleanup_dentry:
dput(dentry);
mntput(mnt);
return ERR_PTR(error);
}
4.3. 总结
open函数的主要操作就是为文件初始化inode,初始化文件结构,刷新进程文件链表。
5. creat 函数
asmlinkage long sys_creat(const char __user * pathname, int mode){
return sys_open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
}
从上面的简短的实现代码可以看出,creat的系统调用直接以O_CREAT|O_WRONLY|O_TRUNC标识调用open函数实现的。
文章出处:飞诺网(www.diybl.com):http://www.diybl.com/course/6_system/linux/Linuxjs/20101230/550127.html
Linux中打开文件是通过open系统调用实现,其函数中调用了do_sys_open()函数完成打开功能,所以下面主要分析do_sys_open()函数,首先先看下open系统调用的入口函数,再具体看do_sys_open()函数:
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
{
long ret;
if (force_o_largefile())
flags |= O_LARGEFILE;
ret = do_sys_open(AT_FDCWD, filename, flags, mode);
/* avoid REGPARM breakage on x86: */
asmlinkage_protect(3, ret, filename, flags, mode);
return ret;
}
long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
/*获取文件名称,由getname()函数完成,其内部首先创建存取文件名称的空间,然后*从用户空间把文件名拷贝过来*/
char *tmp = getname(filename);
int fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
/*获取一个可用的fd,此函数调用alloc_fd()函数从fd_table中获取一个可用fd,并做些简单初始化,此函数内部实现比较简单,此次分析不细看*/
fd = get_unused_fd_flags(flags);
if (fd >= 0) {
/*fd获取成功则开始打开文件,此函数是主要完成打开功能的函数,在此先放一放,下面详细分析*/
struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);
if (IS_ERR(f)) {
/*打开失败,释放fd*/
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
/*文件如果已经被打开了,调用fsnotify_open()函数*/
fsnotify_open(f->f_path.dentry);
/*将文件指针安装在fd数组中*/
fd_install(fd, f);
}
}
/*释放放置从用户空间拷贝过来的文件名的存储空间*/
putname(tmp);
}
return fd;
}
接下来即将进入到打开功能的真正实现功能的函数do_filp_open()函数:
struct file *do_filp_open(int dfd, const char *pathname,
int open_flag, int mode, int acc_mode)
{
/*
*…若干变量声明
*/
/*改变参数flag的值,具体做法是flag+1*/
int flag = open_to_namei_flags(open_flag);
int force_reval = 0;
/*根据__O_SYNC标志来设置O_DSYNC 标志,用以防止恶意破坏程序*/
if (open_flag & __O_SYNC)
open_flag |= O_DSYNC;
/*设置访问权限*/
if (!acc_mode)
acc_mode = MAY_OPEN | ACC_MODE(open_flag);
/*根据 O_TRUNC标志设置写权限 */
if (flag & O_TRUNC)
acc_mode |= MAY_WRITE;
/* 设置O_APPEND 标志*/
if (flag & O_APPEND)
acc_mode |= MAY_APPEND;
/*如果不是创建文件*/
if (!(flag & O_CREAT)) {
/*返回特定的file结构体指针*/
filp = get_empty_filp();
if (filp == NULL)
return ERR_PTR(-ENFILE);
/*填充nameidata 结构*/
nd.intent.open.file = filp;
filp->f_flags = open_flag;
nd.intent.open.flags = flag;
nd.intent.open.create_mode = 0;
/*当内核要访问一个文件的时候,第一步要做的是找到这个文件,而查找文件的过程在vfs里面是由path_lookup或者path_lookup_open函数来完成的。这两个函数将用户传进来的字符串表示的文件路径转换成一个dentry结构,并建立好相应的inode和file结构,将指向file的描述符返回用户。用户随后通过文件描述符,来访问这些数据结构*/ error = do_path_lookup(dfd, pathname,
lookup_flags(flag)|LOOKUP_OPEN, &nd);
if (IS_ERR(nd.intent.open.file)) {
if (error == 0) {
error = PTR_ERR(nd.intent.open.file);
/*减少dentry和vsmount得计数*/
path_put(&nd.path);
}
} else if (error)
/*如果查找失败则释放一些资源*/
release_open_intent(&nd);
if (error)
return ERR_PTR(error);
goto ok;
}
/*到此则是要创建文件*/
reval:
/* path-init为查找作准备工作,path_walk真正上路查找,这两个函数联合起来根据一段路径名找到对应的dentry */
error = path_init(dfd, pathname, LOOKUP_PARENT, &nd);
if (error)
return ERR_PTR(error);
if (force_reval)
nd.flags |= LOOKUP_REVAL;
/*这个函数相当重要,就如源代码注释的那样,是整个NFS的名字解析函数,其实也是NFS得以构筑的函数。这里作一重点分析。这里先作一个综述。该函数采用一个for循环,对name路径根据目录的层次,一层一层推进,直到终点或失败。在推进的过程中,一步步建立了目录树的dentry和对应的inode */
error = path_walk(pathname, &nd);
if (error) {
if (nd.root.mnt)
path_put(&nd.root);
return ERR_PTR(error);
}
if (unlikely(!audit_dummy_context()))
/*保存inode节点信息*/
audit_inode(pathname, nd.path.dentry);
/*父节点信息*/
error = -EISDIR;
if (nd.last_type != LAST_NORM || nd.last.name[nd.last.len])
goto exit_parent;
error = -ENFILE;
/*获取文件指针*/
filp = get_empty_filp();
if (filp == NULL)
goto exit_parent;
/*填充nameidata 结构*/
nd.intent.open.file = filp;
filp->f_flags = open_flag;
nd.intent.open.flags = flag;
nd.intent.open.create_mode = mode;
dir = nd.path.dentry;
nd.flags &= ~LOOKUP_PARENT;
nd.flags |= LOOKUP_CREATE | LOOKUP_OPEN;
if (flag & O_EXCL)
nd.flags |= LOOKUP_EXCL;
mutex_lock(&dir->d_inode->i_mutex);
/*从哈希表中查找nd对应的dentry*/
path.dentry = lookup_hash(&nd);
path.mnt = nd.path.mnt;
do_last:
error = PTR_ERR(path.dentry);
if (IS_ERR(path.dentry)) {
mutex_unlock(&dir->d_inode->i_mutex);
goto exit;
}
if (IS_ERR(nd.intent.open.file)) {
error = PTR_ERR(nd.intent.open.file);
goto exit_mutex_unlock;
}
/*如果此dentry结构没有对应的inode节点,说明是无效的,应该创建文件节点 */
if (!path.dentry->d_inode) {
/*write权限是必需的*/
error = mnt_want_write(nd.path.mnt);
if (error)
goto exit_mutex_unlock;
/*按照namei格式的flag open*/
error = __open_namei_create(&nd, &path, flag, mode);
if (error) {
mnt_drop_write(nd.path.mnt);
goto exit;
}
/*根据nameidata 得到相应的file结构*/
filp = nameidata_to_filp(&nd);
/*放弃写权限*/
mnt_drop_write(nd.path.mnt);
if (nd.root.mnt)
/*计数减一*/
path_put(&nd.root);
if (!IS_ERR(filp)) {
error = ima_file_check(filp, acc_mode);
if (error) {
fput(filp);
filp = ERR_PTR(error);
}
}
return filp;
}
/*要打开的文件已经存在*/
mutex_unlock(&dir->d_inode->i_mutex);
/*保存inode节点*/
audit_inode(pathname, path.dentry);
/*
*省略若干flag标志检查代码
*/
/*路径装化为相应的nameidata 结构*/
path_to_nameidata(&path, &nd);
error = -EISDIR;
/*如果是文件夹*/
if (S_ISDIR(path.dentry->d_inode->i_mode))
goto exit;
ok:
/*检测是否截断文件标志*/
will_truncate = open_will_truncate(flag, nd.path.dentry->d_inode);
if (will_truncate) {
/*要截断的话就要获取写权限*/
error = mnt_want_write(nd.path.mnt);
if (error)
goto exit;
}
//may_open执行权限检测、文件打开和truncate的操作
error = may_open(&nd.path, acc_mode, flag);
if (error) {
if (will_truncate)
mnt_drop_write(nd.path.mnt);
goto exit;
}
filp = nameidata_to_filp(&nd);
if (!IS_ERR(filp)) {
error = ima_file_check(filp, acc_mode);
if (error) {
fput(filp);
filp = ERR_PTR(error);
}
}
if (!IS_ERR(filp)) {
if (acc_mode & MAY_WRITE)
vfs_dq_init(nd.path.dentry->d_inode);
if (will_truncate) {
//处理截断
error = handle_truncate(&nd.path);
if (error) {
fput(filp);
filp = ERR_PTR(error);
}
}
}
//安全的放弃写权限
if (will_truncate)
mnt_drop_write(nd.path.mnt);
if (nd.root.mnt)
path_put(&nd.root);
return filp;
exit_mutex_unlock:
mutex_unlock(&dir->d_inode->i_mutex);
exit_dput:
path_put_conditional(&path, &nd);
exit:
if (!IS_ERR(nd.intent.open.file))
release_open_intent(&nd);
exit_parent:
if (nd.root.mnt)
path_put(&nd.root);
path_put(&nd.path);
return ERR_PTR(error);
//允许遍历连接文件,则手工找到连接文件对应的文件
do_link:
error = -ELOOP;
if (flag & O_NOFOLLOW)
//不允许遍历连接文件,返回错误
goto exit_dput;
/*
以下是手工找到链接文件对应的文件dentry结构代码
*/
// 设置查找LOOKUP_PARENT标志
nd.flags |= LOOKUP_PARENT;
//判断操作是否安全
error = security_inode_follow_link(path.dentry, &nd);
if (error)
goto exit_dput;
// 处理符号链接
error = __do_follow_link(&path, &nd);
path_put(&path);
if (error) {
release_open_intent(&nd);
if (nd.root.mnt)
path_put(&nd.root);
if (error == -ESTALE && !force_reval) {
force_reval = 1;
goto reval;
}
return ERR_PTR(error);
}
nd.flags &= ~LOOKUP_PARENT;
// 检查最后一段文件或目录名的属性情况
if (nd.last_type == LAST_BIND)
goto ok;
error = -EISDIR;
if (nd.last_type != LAST_NORM)
goto exit;
if (nd.last.name[nd.last.len]) {
__putname(nd.last.name);
goto exit;
}
error = -ELOOP;
// 出现回环标志: 循环超过32次
if (count++==32) {
__putname(nd.last.name);
goto exit;
}
dir = nd.path.dentry;
mutex_lock(&dir->d_inode->i_mutex);
// 更新路径的挂接点和dentry
path.dentry = lookup_hash(&nd);
path.mnt = nd.path.mnt;
__putname(nd.last.name);
goto do_last;
}
分析完上述主要函数以后,我们来看一下整个打开流程是如何做到的:
在内核中要打开一个文件,首先应该找到这个文件,而查找文件的过程在vfs里面是由do_path_lookup或者path_lookup_open函数来完成的。这两个函数将用户传进来的字符串表示的文件路径转换成一个dentry结构,并建立好相应的inode和file结构,将指向file的描述符返回用户。用户随后通过文件描述符,来访问这些数据结构。
基本函数流程及调用方式如下所示:
打开过程首先是open系统调用访问SYSCALL_DEFINE3函数,然后调用do_sys_open 函数完成主要功能,再调用函数do_filp_open完成主要的打开功能,下面详细看下do_filp_open中调用的do_path_lookup主要过程:
staic int do_path_lookup(int dfd,const char
*name,unsigned int flags,strucy nameidata *nd)
{
int retval=path_init(dfd,name,flags,nd);
//设置nd->root=根路径(绝对地址)或者当前工作目录(相对地址) 。
//这一步做完了后,内核会建立一些数据结构(dentry,inode)来初始化查找的起点
if(!retval)
retval = path_walk(name,nd);
//path_walk,会遍历路径的每一份量,也就是用“/”分隔开的每一部分,
//最中找到name指向的文件,walk的意思就是walk path的每一个组分(component)
}
我们进一步看看path_walk
int path_walk(const char *name,struct nameidata *nd)
{
return link_path_walk(name,nd);
//path_walk其实相当于直接调用link_path_walk来完成工作
}
link_path_walk的主要工作是有其内部函数__link_path_walk 来完成的
result = __link_path_walk(name,nd)
至此我们转向最重要的代码__link_walk_path,该函数把传进来的字符串name,也就是用户指定的路径,按路径分隔符分解成一系列小的component。比如用户说,我要找/path/to/dest这个文件,那么我们的文件系统就会按path,to,dest一个一个来找,知道最后一个分量是文件或者查找完成。他找的时候,会先用path_init初始化过的根路径去找第一个分量,也就是path。然后用path的dentry->d_inode去找to,这样循环到最后一个。注意,内核会缓存找到的路径分量,所以往往只有第一次访问一个路径的时候,才会去访问磁盘,后面的访问会直接从缓存里找,下面会看到,很多与页告诉缓存打交道的代码。但不管怎样,第一遍查找总是会访问磁盘的。
static int __link_path_walk(const char *name,strucy
nameidata *nd)
{
}
至此,按照每一个component查找完成之后,就会找到相应的文件,然后相应的打开工作就基本完成了。