Linux 2.6.11内核文件IO系统调用详解 (2)
ZDNet 软件频道 更新时间:2007-10-18 作者:赛迪网技术社区 来源:赛迪网技术社区
本文关键词:
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指终端设备,则不将此设备分配作为此进程的控制终端。not
fcntl O_TRUNC<01000>;: 如果此文件存在,而且为只读或只写成功打开,则将其长度截短为0。not fcntl
O_APPEND<02000>;: 每次写时都加到文件的尾端
O_NONBLOCK<04000>;: 如果p a t h n a m e指的是一个F I F O、一个块特殊文件或一个字符特殊文件,则此选择项为此文件的本次打开操作和后续的I / O操作设置非阻塞方式。
O_NDELAY;;
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( 见4.2.4.1.1节 ) | | ------------ dentry_open( 见4.2.4.2节 ) |
4.2.2.主调用函数sys_open
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
这后面的函数使用了一个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
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,初始化文件结构,刷新进程文件链表。
阅读(2972) | 评论(0) | 转发(0) |