分类: LINUX
2007-10-09 10:11:02
Linux 2.6.11内核文件IO系统调用
1. 引言
从事Linux环境工作2年有余,一直懵懵懂懂,1年前拜读了《莱昂氏UNIX源代码分析》一书,感觉自己的学习道路漫漫且修远。最近受chinaunix的精华文帖启发,拟将近来的部分内核调用分析笔记拿出来与各前辈先进共同探讨学习,以壮个人学习之路。
本部分主要讲述的是文件I/O操作的2.6.11内核版本实现,包括了主要的数据结构、宏定义和函数流程。以下分别讲述open,create,close,read,write,lseek系统调用。
2. 主要参考
《莱昂氏UNIX源代码分析》
《UNIX环境高级编程》
3. 主要数据结构
3.1. FD
对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。
当读、写一个文件时,用open或creat返回的文件描述符标识该文件,将其作为参数传送给read或write。在POSIX.1应用程序中,文件描述符为常数0、1和2分别代表STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,意即标准输入,标准输出和标准出错输出,这些常数都定义在头文件
文件描述符的范围是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指终端设备,则不将此设备分配作为此进程的控制终端。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;
}