linux dup函数源码剖析

2024-08-22 04:38
文章标签 linux 源码 函数 剖析 dup

本文主要是介绍linux dup函数源码剖析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

好了,先说说我为什么要写这样一个专栏,其实研究源码的朋友都应该经历过这个过程,特别是研究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)ret
T_PSEUDO_END (SYSCALL_SYMBOL)

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

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

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

(gdb) disassemble dup
Dump of assembler code for function dup:0x00007ffff7b06b60 <+0>:	mov    $0x20,%eax0x00007ffff7b06b65 <+5>:	syscall 0x00007ffff7b06b67 <+7>:	cmp    $0xfffffffffffff001,%rax0x00007ffff7b06b6d <+13>:	jae    0x7ffff7b06b70 <dup+16>0x00007ffff7b06b6f <+15>:	retq   0x00007ffff7b06b70 <+16>:	mov    0x2cc2f1(%rip),%rcx        # 0x7ffff7dd2e680x00007ffff7b06b77 <+23>:	neg    %eax0x00007ffff7b06b79 <+25>:	mov    %eax,%fs:(%rcx)0x00007ffff7b06b7c <+28>:	or     $0xffffffffffffffff,%rax0x00007ffff7b06b80 <+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_node	fu_llist;struct rcu_head 	fu_rcuhead;} f_u;struct path		f_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_t		f_lock;atomic_long_t		f_count;unsigned int 		f_flags;fmode_t			f_mode;struct mutex		f_pos_lock;loff_t			f_pos;struct fown_struct	f_owner;const struct cred	*f_cred;struct file_ra_state	f_ra;u64			f_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_head	f_ep_links;struct list_head	f_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);
}


这篇关于linux dup函数源码剖析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1095263

相关文章

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

MySQL高级查询之JOIN、子查询、窗口函数实际案例

《MySQL高级查询之JOIN、子查询、窗口函数实际案例》:本文主要介绍MySQL高级查询之JOIN、子查询、窗口函数实际案例的相关资料,JOIN用于多表关联查询,子查询用于数据筛选和过滤,窗口函... 目录前言1. JOIN(连接查询)1.1 内连接(INNER JOIN)1.2 左连接(LEFT JOI

Linux ls命令操作详解

《Linuxls命令操作详解》通过ls命令,我们可以查看指定目录下的文件和子目录,并结合不同的选项获取详细的文件信息,如权限、大小、修改时间等,:本文主要介绍Linuxls命令详解,需要的朋友可... 目录1. 命令简介2. 命令的基本语法和用法2.1 语法格式2.2 使用示例2.2.1 列出当前目录下的文

MySQL中FIND_IN_SET函数与INSTR函数用法解析

《MySQL中FIND_IN_SET函数与INSTR函数用法解析》:本文主要介绍MySQL中FIND_IN_SET函数与INSTR函数用法解析,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一... 目录一、功能定义与语法1、FIND_IN_SET函数2、INSTR函数二、本质区别对比三、实际场景案例分

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

C语言函数递归实际应用举例详解

《C语言函数递归实际应用举例详解》程序调用自身的编程技巧称为递归,递归做为一种算法在程序设计语言中广泛应用,:本文主要介绍C语言函数递归实际应用举例的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录前言一、递归的概念与思想二、递归的限制条件 三、递归的实际应用举例(一)求 n 的阶乘(二)顺序打印

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

C/C++错误信息处理的常见方法及函数

《C/C++错误信息处理的常见方法及函数》C/C++是两种广泛使用的编程语言,特别是在系统编程、嵌入式开发以及高性能计算领域,:本文主要介绍C/C++错误信息处理的常见方法及函数,文中通过代码介绍... 目录前言1. errno 和 perror()示例:2. strerror()示例:3. perror(

Linux中的计划任务(crontab)使用方式

《Linux中的计划任务(crontab)使用方式》:本文主要介绍Linux中的计划任务(crontab)使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言1、linux的起源与发展2、什么是计划任务(crontab)二、crontab基础1、cro

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将