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

相关文章

ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法

《ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法》本文介绍了Elasticsearch的基本概念,包括文档和字段、索引和映射,还详细描述了如何通过Docker... 目录1、ElasticSearch概念2、ElasticSearch、Kibana和IK分词器部署

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

Python itertools中accumulate函数用法及使用运用详细讲解

《Pythonitertools中accumulate函数用法及使用运用详细讲解》:本文主要介绍Python的itertools库中的accumulate函数,该函数可以计算累积和或通过指定函数... 目录1.1前言:1.2定义:1.3衍生用法:1.3Leetcode的实际运用:总结 1.1前言:本文将详

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

轻松上手MYSQL之JSON函数实现高效数据查询与操作

《轻松上手MYSQL之JSON函数实现高效数据查询与操作》:本文主要介绍轻松上手MYSQL之JSON函数实现高效数据查询与操作的相关资料,MySQL提供了多个JSON函数,用于处理和查询JSON数... 目录一、jsON_EXTRACT 提取指定数据二、JSON_UNQUOTE 取消双引号三、JSON_KE

MySQL数据库函数之JSON_EXTRACT示例代码

《MySQL数据库函数之JSON_EXTRACT示例代码》:本文主要介绍MySQL数据库函数之JSON_EXTRACT的相关资料,JSON_EXTRACT()函数用于从JSON文档中提取值,支持对... 目录前言基本语法路径表达式示例示例 1: 提取简单值示例 2: 提取嵌套值示例 3: 提取数组中的值注意

Linux环境变量&&进程地址空间详解

《Linux环境变量&&进程地址空间详解》本文介绍了Linux环境变量、命令行参数、进程地址空间以及Linux内核进程调度队列的相关知识,环境变量是系统运行环境的参数,命令行参数用于传递给程序的参数,... 目录一、初步认识环境变量1.1常见的环境变量1.2环境变量的基本概念二、命令行参数2.1通过命令编程

Linux之进程状态&&进程优先级详解

《Linux之进程状态&&进程优先级详解》文章介绍了操作系统中进程的状态,包括运行状态、阻塞状态和挂起状态,并详细解释了Linux下进程的具体状态及其管理,此外,文章还讨论了进程的优先级、查看和修改进... 目录一、操作系统的进程状态1.1运行状态1.2阻塞状态1.3挂起二、linux下具体的状态三、进程的

Linux编译器--gcc/g++使用方式

《Linux编译器--gcc/g++使用方式》文章主要介绍了C/C++程序的编译过程,包括预编译、编译、汇编和链接四个阶段,并详细解释了每个阶段的作用和具体操作,同时,还介绍了调试和发布版本的概念... 目录一、预编译指令1.1预处理功能1.2指令1.3问题扩展二、编译(生成汇编)三、汇编(生成二进制机器语