linux dup函数源码剖析

来源:互联网 发布:unity3d序列帧动画 编辑:程序博客网 时间:2024/06/03 11:15

这是我新的专栏的第一篇,其实我这种水平的人还写专栏,实在是没有自知之明,夜郎自大。这个专栏只是希望起到一个抛砖引玉的作用,欢迎大家对我的文章多提宝贵的意见。

好了,先说说我为什么要写这样一个专栏,其实研究源码的朋友都应该经历过这个过程,特别是研究linux内核源码的朋友,一开始可能都是从几本经典的讲解内核的书籍开始,什么《linux内核设计与实现》、《深入理解linux内核》,但这几本书看下来还是一头雾水,特别是后一本书,每次都是看完这一章前一章就忘了,更有甚者这一节没看完前面的内容就忘了。这类书籍在我看来都有这样一个问题——离源码太远了,这里的太远了有两个方面的含义,其一是这类书往往上来就给出原理,而理论结合源码的内容少之有少。好吧你没有结合的内容,我自己看可以吧,这里就引出了“太远”的第二个方面,书中写的内容与当前的实际情况相距甚远,书籍的撰写往往需要很长的时间,等到书籍出版,书中作为参考的内核版本早就不知道被丢到哪里去了,还有这些书,特别是《深入理解linux内核》,很少给出原理对应的源码在哪(貌似我就没看到过)。举个我本人的例子,以上两本书用的内核基本都是2.6内核,而我用的ubuntu 15.04内核采用的内核版本是3.19,书中很多内容和实际情况根本就对不上。当然在此绝不是批评这类书籍,这些书籍中的原理我还认为是非常重要的参考资料,我在分析内核的时候也以这些书籍作为参考。最后对于这类书籍的问题,我还要谈一点我的想法——给出内容太过直接,但对于这些作者是怎么分析得出的只字未提,但对于刚刚入门的同学来说,我个人认为这其实才是最关键的部分。如果我们能掌握一种方法,能够自己分析内核,即使对于完全陌生的源码,我们也具备这样的能力那就是最好不过的了。

当然分析源码需要经验的积累,小弟虽不才,愿意将我的方法与大家分享一二,也欢迎大家一起交流分享研究源码的心得体会。

这里就给大家说一下我的思路:如果能够借助最少的外部参考资料,仅凭程序的运行现象就能够由浅入深,层层深入分析源码的功能就是极好的了。因为程序的运行结果就摆在那里,不多不少。所以我分析内核源码的思路就是“动静结合”,“动者”程序的运行结果,“静者”程序的源码,由动入静,也就是由程序的运行结果步步深入,直到将内核源码分析的一干二净。

这个系列的专栏(如果我能写完的话,很多内容在我脑中都是构想,还未付诸实践,即使付诸实践也不一定能够乘够),打算沿着三条线进行。第一条线就是计算机的启动线,这条线主要就是分析机器从上电之后所经历的一系列变化。第二条线就是程序运行线,这条线主要分析一个程序从shell中启动之后,到程序执行结束所经历的过程。还有第三条线,也是最轻松愉快的线路,就是简单的从系统调用出发,对内核中执行部分功能的内核源码进行分析。今天咱们要分析的内容就属于这条线路,这条线路中的内容没有什么特别的思路,遇到什么就分析什么吧。

好了,闲聊了这么多,开始今天的主题——dup系统调用。

先从程序的执行开始分析,源码如下:

#include <unistd.h>int main(){int newfd;newfd = dup(STDIN_FILENO);return 0;}

启动gdb,关于如何使用gdb调试glibc,请见我的博文:http://blog.csdn.net/u012927281/article/details/51289608大笑

好了,通过step命令调试程序,并加载所需要的文件,结果程序运行到此处,以下程序位于../sysdeps/unix/syscall-template.S。

T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)retT_PSEUDO_END (SYSCALL_SYMBOL)

关于这部分源码的解释请见这篇blog:http://blog.csdn.net/caspiansea/article/details/39022377

这三条语句中涉及系统调用的相关知识,再给大家安利一下,还是我的博客:http://blog.csdn.net/u012927281/article/details/51540447

这几条语句都是宏函数,其定义、调用十分复杂,而且相同的定义很多,不通过研究生成的脚本根本无法区分。所以来点简单粗暴的,直接反汇编,结果如下:

(gdb) disassemble dupDump of assembler code for function dup:   0x00007ffff7b06b60 <+0>:mov    $0x20,%eax   0x00007ffff7b06b65 <+5>:syscall    0x00007ffff7b06b67 <+7>:cmp    $0xfffffffffffff001,%rax   0x00007ffff7b06b6d <+13>:jae    0x7ffff7b06b70 <dup+16>   0x00007ffff7b06b6f <+15>:retq      0x00007ffff7b06b70 <+16>:mov    0x2cc2f1(%rip),%rcx        # 0x7ffff7dd2e68   0x00007ffff7b06b77 <+23>:neg    %eax   0x00007ffff7b06b79 <+25>:mov    %eax,%fs:(%rcx)   0x00007ffff7b06b7c <+28>:or     $0xffffffffffffffff,%rax   0x00007ffff7b06b80 <+32>:retq   End of assembler dump.

此处0x20,十进值为32,正好为dup函数的系统调用号。不过我觉得此处源码不是很全面,缺少了参数传递的相关内容,先不管了,不影响我们分析内核源码。

好了此时正式进入linux内核源码,使用understand直接搜索“SYSCALL_DEFINE1(dup”,发现dup相关的内核源码位于./fs/file.c。源码如下:

SYSCALL_DEFINE1(dup, unsigned int, fildes){int ret = -EBADF;struct file *file = fget_raw(fildes);if (file) {ret = get_unused_fd_flags(0);if (ret >= 0)fd_install(ret, file);elsefput(file);}return ret;}

好了,开始进行分析:

1.先将返回码设定为“EBADF”,EBADF代表The argument s is an invalid descriptor,参数是一个非法的描述符。

2.先来看看struct file,这个结构体的定义位于/include/linux/fs.h中,你问我怎么知道的?在file.c中包含有这个文件,从名称上来看就这一个头文件与文件系统的内容相关,所以我推测是位于这个头文件中,查看了一下,果然位于这个头文件中。struct file的定义如下:

struct file {union {struct llist_nodefu_llist;struct rcu_head fu_rcuhead;} f_u;struct pathf_path;struct inode*f_inode;/* cached value */const struct file_operations*f_op;/* * Protects f_ep_links, f_flags. * Must not be taken from IRQ context. */spinlock_tf_lock;atomic_long_tf_count;unsigned int f_flags;fmode_tf_mode;struct mutexf_pos_lock;loff_tf_pos;struct fown_structf_owner;const struct cred*f_cred;struct file_ra_statef_ra;u64f_version;#ifdef CONFIG_SECURITYvoid*f_security;#endif/* needed for tty driver, and maybe others */void*private_data;#ifdef CONFIG_EPOLL/* Used by fs/eventpoll.c to link all the hooks to this file */struct list_headf_ep_links;struct list_headf_tfile_llink;#endif /* #ifdef CONFIG_EPOLL */struct address_space*f_mapping;} __attribute__((aligned(4)));/* lest something weird decides that 2 is OK */

现在还没有什么办法把struct file中的内容全部理清,先留在这个地方吧,如果以后有需要可以再一点一点的研究。再来研究一下“fget_raw”函数,这个函数的定义同样是位于file.c中,具体定义如下:

struct file *fget_raw(unsigned int fd){return __fget(fd, 0);}EXPORT_SYMBOL(fget_raw);

继续向下研究“__fget”函数,定义如下:

static struct file *__fget(unsigned int fd, fmode_t mask){struct files_struct *files = current->files;struct file *file;rcu_read_lock();file = fcheck_files(files, fd);if (file) {/* File object ref couldn't be taken */if ((file->f_mode & mask) ||    !atomic_long_inc_not_zero(&file->f_count))file = NULL;}rcu_read_unlock();return file;}

通过其参数我们可以获知,__fget函数的第一个参数是文件描述符,第二个参数通过参数的类型名可以猜到一二,是文件状态标志,通过fs.h文件中的相关定义也可以大致印证这一点:

/* file is open for reading */#define FMODE_READ((__force fmode_t)0x1)/* file is open for writing */#define FMODE_WRITE((__force fmode_t)0x2)/* file is seekable */#define FMODE_LSEEK((__force fmode_t)0x4)/* file can be accessed using pread */#define FMODE_PREAD((__force fmode_t)0x8)/* file can be accessed using pwrite */#define FMODE_PWRITE((__force fmode_t)0x10)/* File is opened for execution with sys_execve / sys_uselib */#define FMODE_EXEC((__force fmode_t)0x20)/* File is opened with O_NDELAY (only set for block devices) */#define FMODE_NDELAY((__force fmode_t)0x40)/* File is opened with O_EXCL (only set for block devices) */#define FMODE_EXCL((__force fmode_t)0x80)/* File is opened using open(.., 3, ..) and is writeable only for ioctls   (specialy hack for floppy.c) */#define FMODE_WRITE_IOCTL((__force fmode_t)0x100)/* 32bit hashes as llseek() offset (for directories) */#define FMODE_32BITHASH         ((__force fmode_t)0x200)/* 64bit hashes as llseek() offset (for directories) */#define FMODE_64BITHASH         ((__force fmode_t)0x400)/* * Don't update ctime and mtime. * * Currently a special hack for the XFS open_by_handle ioctl, but we'll * hopefully graduate it to a proper O_CMTIME flag supported by open(2) soon. */#define FMODE_NOCMTIME((__force fmode_t)0x800)/* Expect random access pattern */#define FMODE_RANDOM((__force fmode_t)0x1000)/* File is huge (eg. /dev/kmem): treat loff_t as unsigned */#define FMODE_UNSIGNED_OFFSET((__force fmode_t)0x2000)/* File is opened with O_PATH; almost nothing can be done with it */#define FMODE_PATH((__force fmode_t)0x4000)/* File needs atomic accesses to f_pos */#define FMODE_ATOMIC_POS((__force fmode_t)0x8000)/* Write access to underlying fs */#define FMODE_WRITER((__force fmode_t)0x10000)/* Has read method(s) */#define FMODE_CAN_READ          ((__force fmode_t)0x20000)/* Has write method(s) */#define FMODE_CAN_WRITE         ((__force fmode_t)0x40000)/* File was opened by fanotify and shouldn't generate fanotify events */#define FMODE_NONOTIFY((__force fmode_t)0x4000000)
都是有关于文件状态标志的定义。

好,还是回到我们要研究的函数——__fget函数,还是一句一句的看:

struct files_struct *files = current->files;
先来看第一个数据结构“files_struct”,这个数据结构可是大有来头,“struct files_struct”是“task_struct”的一部分,这回明白了吧,知道“struct files_struct”的重要地位了吧。

struct files_struct的定义如下(位于/include/linux/fdtable.h):

struct files_struct {  /*   * read mostly part   */atomic_t count;struct fdtable __rcu *fdt;struct fdtable fdtab;  /*   * written part on a separate cache line in SMP   */spinlock_t file_lock ____cacheline_aligned_in_smp;int next_fd;unsigned long close_on_exec_init[1];unsigned long open_fds_init[1];struct file __rcu * fd_array[NR_OPEN_DEFAULT];};
这里还有一个比较重要的数据结构——struct fdtable,其定义如下(位于/include/linux/fdtable.h):

struct fdtable {unsigned int max_fds;struct file __rcu **fd;      /* current fd array */unsigned long *close_on_exec;unsigned long *open_fds;struct rcu_head rcu;};

其中“struct file __rcu **fd”变量不知道是什么,与“struct file __rcu * fd_array[NR_OPEN_DEFAULT]”是否是一个东西?

这里current的定义没有找到,不过我估计应该是当前进程的task_struct,这一句的功能就非常简单了,使用一个files指针指向当前进程的files指针。

struct file *file;

这一句没什么说的,就是声明一个“struct file”类型的file指针,留待后用。

rcu_read_lock();

定义位于/include/linux/rcupdate.h,应该是某种读锁,锁的具体功能就不深入研究了,有时间回头再详细研究吧。

file = fcheck_files(files, fd);

“fcheck_files”定义位于/include/linux/fdtable.h中,根据函数的命名、函数的参数以及返回值就可以大概推测函数的作用,首先fd是被复制的文件描述符,files是当前进程有关于打开文件的信息,所以我推测这一句的作用的就是取文件描述符指定的文件表项并返回。

函数的定义也印证了我的推测:

static inline struct file *__fcheck_files(struct files_struct *files, unsigned int fd){struct fdtable *fdt = rcu_dereference_raw(files->fdt);if (fd < fdt->max_fds)return rcu_dereference_raw(fdt->fd[fd]); //这一句实际是执行功能的语句,返回fd指定的struct file指针。return NULL;}static inline struct file *fcheck_files(struct files_struct *files, unsigned int fd){rcu_lockdep_assert(rcu_read_lock_held() ||   lockdep_is_held(&files->file_lock),   "suspicious rcu_dereference_check() usage");return __fcheck_files(files, fd);}

好,继续回到__fget函数,此时执行到了

if (file) {/* File object ref couldn't be taken */if ((file->f_mode & mask) ||    !atomic_long_inc_not_zero(&file->f_count))file = NULL;}

若file不为空,则进行下一步的判断,由于mask是0,所以判断进行到前半段就已经跳出了,再来是最后两句:

rcu_read_unlock();return file;

解锁,并返回file,此处要重申的一点是struct file中保存有文件的状态标志等信息,相当于是APUE中那个概念图中的文件表项。

此时函数“fget_raw”返回,还是返回这个struct file指针,此时程序已经回到dup函数的主题部分:

if (file) {ret = get_unused_fd_flags(0);if (ret >= 0)fd_install(ret, file);elsefput(file);}

若file指针不为空,再进行下面的操作,首先是“get_unused_fd_flags”函数(定义同样位于fs/file.c中),这个函数的功能通过其命名就可以知道,返回第一个可用的文件描述符,这一点通过dup函数的功能也可以验证。函数定义如下,关于这个函数的运行过程就不详细分析了。

int __alloc_fd(struct files_struct *files,       unsigned start, unsigned end, unsigned flags){unsigned int fd;int error;struct fdtable *fdt;spin_lock(&files->file_lock);repeat:fdt = files_fdtable(files);fd = start;if (fd < files->next_fd)fd = files->next_fd;if (fd < fdt->max_fds)fd = find_next_zero_bit(fdt->open_fds, fdt->max_fds, fd);/* * N.B. For clone tasks sharing a files structure, this test * will limit the total number of files that can be opened. */error = -EMFILE;if (fd >= end)goto out;error = expand_files(files, fd);if (error < 0)goto out;/* * If we needed to expand the fs array we * might have blocked - try again. */if (error)goto repeat;if (start <= files->next_fd)files->next_fd = fd + 1;__set_open_fd(fd, fdt);if (flags & O_CLOEXEC) //flags是0,所以一定会执行__clear_close_on_exec(fd, fdt);__set_close_on_exec(fd, fdt);else__clear_close_on_exec(fd, fdt);__clear_close_on_exec函数的功能通过函数名就可以知道,清除相应的文件描述符标志。error = fd;#if 1/* Sanity check */if (rcu_access_pointer(fdt->fd[fd]) != NULL) {printk(KERN_WARNING "alloc_fd: slot %d not NULL!\n", fd);rcu_assign_pointer(fdt->fd[fd], NULL);}#endifout:spin_unlock(&files->file_lock);return error;}


若返回值大于0,则执行fd_install:

void __fd_install(struct files_struct *files, unsigned int fd,struct file *file){struct fdtable *fdt;spin_lock(&files->file_lock);fdt = files_fdtable(files);BUG_ON(fdt->fd[fd] != NULL);rcu_assign_pointer(fdt->fd[fd], file); //执行功能的语句应该就是这一句,将file指针所指向内容赋给fdt->fd[fd]。spin_unlock(&files->file_lock);}void fd_install(unsigned int fd, struct file *file){__fd_install(current->files, fd, file);}EXPORT_SYMBOL(fd_install);

好了,dup函数的执行过程就先给大家分析到这里,之所以要分析这个函数,主要有两个方面的考虑,一方面是要搞清楚task_struct中与文件相关的内容是如何定义的,另一方面是希望搞清楚dup函数在何时清楚close_on_exec标志。

最后给大家把linux下相关数据结构的关系进行了简单整理



有了上面的基础,再来看看dup2函数,源码如下:

SYSCALL_DEFINE2(dup2, unsigned int, oldfd, unsigned int, newfd){if (unlikely(newfd == oldfd)) { /* corner case */ //此处就是newfd与old相等的情况,若两个相等,则直接返回而不清除FD_CLOEXEC标志struct files_struct *files = current->files;int retval = oldfd;rcu_read_lock();if (!fcheck_files(files, oldfd))retval = -EBADF;rcu_read_unlock();return retval;}return sys_dup3(oldfd, newfd, 0);}


0 0