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

相关文章

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、