歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> linux dup函數源碼剖析

linux dup函數源碼剖析

日期:2017/3/1 11:46:10   编辑:關於Linux

這是我新的專欄的第一篇,其實我這種水平的人還寫專欄,實在是沒有自知之明,夜郎自大。這個專欄只是希望起到一個拋磚引玉的作用,歡迎大家對我的文章多提寶貴的意見。

好了,先說說我為什麼要寫這樣一個專欄,其實研究源碼的朋友都應該經歷過這個過程,特別是研究linux內核源碼的朋友,一開始可能都是從幾本經典的講解內核的書籍開始,什麼《linux內核設計與實現》、《深入理解linux內核》,但這幾本書看下來還是一頭霧水,特別是後一本書,每次都是看完這一章前一章就忘了,更有甚者這一節沒看完前面的內容就忘了。這類書籍在我看來都有這樣一個問題——離源碼太遠了,這裡的太遠了有兩個方面的含義,其一是這類書往往上來就給出原理,而理論結合源碼的內容少之有少。好吧你沒有結合的內容,我自己看可以吧,這裡就引出了“太遠”的第二個方面,書中寫的內容與當前的實際情況相距甚遠,書籍的撰寫往往需要很長的時間,等到書籍出版,書中作為參考的內核版本早就不知道被丟到哪裡去了,還有這些書,特別是《深入理解linux內核》,很少給出原理對應的源碼在哪(貌似我就沒看到過)。舉個我本人的例子,以上兩本書用的內核基本都是2.6內核,而我用的ubuntu 15.04內核采用的內核版本是3.19,書中很多內容和實際情況根本就對不上。當然在此絕不是批評這類書籍,這些書籍中的原理我還認為是非常重要的參考資料,我在分析內核的時候也以這些書籍作為參考。最後對於這類書籍的問題,我還要談一點我的想法——給出內容太過直接,但對於這些作者是怎麼分析得出的只字未提,但對於剛剛入門的同學來說,我個人認為這其實才是最關鍵的部分。如果我們能掌握一種方法,能夠自己分析內核,即使對於完全陌生的源碼,我們也具備這樣的能力那就是最好不過的了。

當然分析源碼需要經驗的積累,小弟雖不才,願意將我的方法與大家分享一二,也歡迎大家一起交流分享研究源碼的心得體會。

這裡就給大家說一下我的思路:如果能夠借助最少的外部參考資料,僅憑程序的運行現象就能夠由淺入深,層層深入分析源碼的功能就是極好的了。因為程序的運行結果就擺在那裡,不多不少。所以我分析內核源碼的思路就是“動靜結合”,“動者”程序的運行結果,“靜者”程序的源碼,由動入靜,也就是由程序的運行結果步步深入,直到將內核源碼分析的一干二淨。

這個系列的專欄(如果我能寫完的話,很多內容在我腦中都是構想,還未付諸實踐,即使付諸實踐也不一定能夠乘夠),打算沿著三條線進行。第一條線就是計算機的啟動線,這條線主要就是分析機器從上電之後所經歷的一系列變化。第二條線就是程序運行線,這條線主要分析一個程序從shell中啟動之後,到程序執行結束所經歷的過程。還有第三條線,也是最輕松愉快的線路,就是簡單的從系統調用出發,對內核中執行部分功能的內核源碼進行分析。今天咱們要分析的內容就屬於這條線路,這條線路中的內容沒有什麼特別的思路,遇到什麼就分析什麼吧。

好了,閒聊了這麼多,開始今天的主題——dup系統調用。

先從程序的執行開始分析,源碼如下:

#include 

int main()
{
	int newfd;
	newfd = dup(STDIN_FILENO);
	return 0;
}

啟動gdb,

好了,通過step命令調試程序,並加載所需要的文件,結果程序運行到此處,以下程序位於../sysdeps/unix/syscall-template.S。

T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
	ret
T_PSEUDO_END (SYSCALL_SYMBOL)

這幾條語句都是宏函數,其定義、調用十分復雜,而且相同的定義很多,不通過研究生成的腳本根本無法區分。所以來點簡單粗暴的,直接反匯編,結果如下:

(gdb) disassemble dup
Dump of assembler code for function dup:
   0x00007ffff7b06b60 <+0>:	mov    $0x20,%eax
   0x00007ffff7b06b65 <+5>:	syscall 
   0x00007ffff7b06b67 <+7>:	cmp    $0xfffffffffffff001,%rax
   0x00007ffff7b06b6d <+13>:	jae    0x7ffff7b06b70 
   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);
		else
			fput(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_SECURITY
	void			*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中保存有文件的狀態標志等信息,相當於是概念中的文件表項。

此時函數“fget_raw”返回,還是返回這個struct file指針,此時程序已經回到dup函數的主題部分:

if (file) {
		ret = get_unused_fd_flags(0);
		if (ret >= 0)
			fd_install(ret, file);
		else
			fput(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);
	}
#endif

out:
	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下相關數據結構的關系進行了簡單整理

\

Copyright © Linux教程網 All Rights Reserved