Linux内核分析:dup、dup2的实现
一、首先需要看一下这两个函数的作用:
1 #include <unistd.h>
2
3 int dup(int oldfd);
4 int dup2(int oldfd, int newfd);
根据manual的解释:
dup:创建一份oldfd的拷贝,使用最小的文件描述符作为新的文件描述符。
dup2:创建一份oldfd的拷贝,使用指定的newfd作为新的文件描述符。
要看这两个函数是怎么实现的,首先得知道Linux对于文件描述符是怎么处理的,参考这篇文章。
二、分析dup
1 static inline long
2 dup (int fd)
3 {
4 return sys_dup(fd);
5 }
这里看到dup调用了函数sys_dup。
1 asmlinkage long sys_dup(unsigned int fildes)
2 {
3 int ret = -EBADF;
4 struct file * file = fget(fildes);
5
6 if (file)
7 ret = dupfd(file, 0);
8 return ret;
9 }
在sys_dup函数中,关键的就是两步,fget获取指定文件描述符的struct file指针,然后调用dupfd,至于dupfd的具体实现,我们接着往下走。
1 struct file fastcall *fget(unsigned int fd)
2 {
3 struct file *file;
4 struct files_struct *files = current->files;
5
6 spin_lock(&files->file_lock);
7 file = fcheck_files(files, fd);
8 if (file)
9 get_file(file);
10 spin_unlock(&files->file_lock);
11 return file;
12 }
可以看到fget函数的实现就是首先获取一个files_struct指针,我们知道files_struct保存了所有打开文件信息(其中current是当前进程的struct task_struct指针),然后加锁,调用fcheck_files,获取file指针,如果file不为空,则调用get_file,下面我们看下这两个函数的实现。
1 static inline struct file * fcheck_files(struct files_struct *files, unsigned int fd)
2 {
3 struct file * file = NULL;
4
5 if (fd < files->max_fds)
6 file = files->fd[fd];
7 return file;
8 }
9
10 #define get_file(x) atomic_inc(&(x)->f_count)
现在已经可以知道,fcheck_files函数的具体步骤是首先判断给定的文件描述符fd是否小于最大文件描述符max_fds,如果小于,则返回fd数组中对应该fd下标的指针。
get_file的作用是原子的增加f_count,也就是该文件的引用计数(在close的时候会减这个值)。
现在再回到sys_dup中,看一下dumfd的实现。
1 static int dupfd(struct file *file, unsigned int start)
2 {
3 struct files_struct * files = current->files;
4 int fd;
5
6 spin_lock(&files->file_lock);
7 fd = locate_fd(files, file, start);
8 if (fd >= 0) {
9 FD_SET(fd, files->open_fds);
10 FD_CLR(fd, files->close_on_exec);
11 spin_unlock(&files->file_lock);
12 fd_install(fd, file);
13 } else {
14 spin_unlock(&files->file_lock);
15 fput(file);
16 }
17
18 return fd;
19 }
该函数的具体步骤如下:
1、通过current->files获取struct files_struct指针。
2、加锁,完成后会解锁。
3、调用locate_fd函数获取一个fd,具体获取规则下面再看。
4、如果获取到的fd>=0,则调用FD_SET、FD_CLR、解锁、fd_install。关键在于fd_install;否则调用解锁、fput。
下面再看一下locate_fd、fd_install、fput的实现。
1 /*
2 * locate_fd finds a free file descriptor in the open_fds fdset,
3 * expanding the fd arrays if necessary. Must be called with the
4 * file_lock held for write.
5 */
6
7 static int locate_fd(struct files_struct *files,
8 struct file *file, unsigned int orig_start)
9 {
10 unsigned int newfd;
11 unsigned int start;
12 int error;
13
14 error = -EINVAL;
15 if (orig_start >= current->signal->rlim[RLIMIT_NOFILE].rlim_cur)
16 goto out;
17
18 repeat:
19 /*
20 * Someone might have closed fd's in the range
21 * orig_start..files->next_fd
22 */
23 start = orig_start;
24 if (start < files->next_fd)
25 start = files->next_fd;
26
27 newfd = start;
28 if (start < files->max_fdset) {
29 newfd = find_next_zero_bit(files->open_fds->fds_bits,
30 files->max_fdset, start);
31 }
32
33 error = -EMFILE;
34 if (newfd >= current->signal->rlim[RLIMIT_NOFILE].rlim_cur)
35 goto out;
36
37 error = expand_files(files, newfd);
38 if (error < 0)
39 goto out;
40
41 /*
42 * If we needed to expand the fs array we
43 * might have blocked - try again.
44 */
45 if (error)
46 goto repeat;
47
48 if (start <= files->next_fd)
49 files->next_fd = newfd + 1;
50
51 error = newfd;
52
53 out:
54 return error;
55 }
根据该函数的注释即可知道它的所用就是:找到一个没有被使用的文件描述符,从start开始(这里就是dup和dup2的区别所在)。
1 /*
2 * Install a file pointer in the fd array.
3 *
4 * The VFS is full of places where we drop the files lock between
5 * setting the open_fds bitmap and installing the file in the file
6 * array. At any such point, we are vulnerable to a dup2() race
7 * installing a file in the array before us. We need to detect this and
8 * fput() the struct file we are about to overwrite in this case.
9 *
10 * It should never happen - if we allow dup2() do it, _really_ bad things
11 * will follow.
12 */
13
14 void fastcall fd_install(unsigned int fd, struct file * file)
15 {
16 struct files_struct *files = current->files;
17 spin_lock(&files->file_lock);
18 if (unlikely(files->fd[fd] != NULL))
19 BUG();
20 files->fd[fd] = file;
21 spin_unlock(&files->file_lock);
22 }
fd_install的作用就是把fd指针数组对应fd下标的指针赋值为file。
到此回到dupfd,返回获取的fd,这就是新的拷贝,然后sys_dup就返回该值。
三、dup2
dup2的实现跟dup类似,关键的差别就是dup2返回指定的newfd,dup返回最小可用的fd。