文件open在手册中有两个函数原型,如下所示:
#include
#include
#include
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
//返回值:成功返回新分配的文件描述符,失败返回-1并设置errno
参数解释:
pathname :要打开或创建的文件名,和fopen一样,pathname既可以是绝对路径也可以是相对路径;
flags :用于指示打开文件的选项,常用的有O_RDONLY、O_WRONLY、O_RDWR。这三个选项必须有一个且只能有一个被指定。除以上三个选项,Linux平台还支持更多的选项,可在fcntl.h文件中找到
open函数原型:
当我们调用open函数时,实际上调用的是glibc封装的函数,然后由glibc通过自陷指令,进行真正的系统调用。也就是说所有的系统调用都要先经过glibc才会进入操作系统。这样的话是祭祀夯实glibc提供了一个变参函数open来满足两个函数原型,然后通过glibc的变参函数open实现真正的系统调用来调用open函数,open函数的声明如下所示:
extern int open (__const char *__file, int __oflag, ...) __nonnull ((1));
在内核sys_open源码中sys_open->do_sys_open源码(/linux-2.6.30/fs/open.c)如下所示:
long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
char *tmp = getname(filename);
int fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
fd = get_unused_fd_flags(flags);
if (fd >= 0) {
struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fsnotify_open(f->f_path.dentry);
fd_install(fd, f);
}
}
putname(tmp);
}
return fd;
}
打开文件时,内核主要消耗了两种资源:文件描述符与内核管理文件结构file
根据POSIX标准,当获取一个新的文件描述符时,要返回最低的未使用的文件描述符。在Linux中,通过do_sys_open->get_unused_fd_flags_->alloc_fd(0, (flags))来选择文件描述符,代码(/linux-2.6.30/fs/open.c)如下所示:
/*
* allocate a file descriptor, mark it busy.
*/
int alloc_fd(unsigned start, unsigned flags)
{
struct files_struct *files = current->files;
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->fds_bits,
fdt->max_fds, fd);
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;
FD_SET(fd, fdt->open_fds);
if (flags & O_CLOEXEC)
FD_SET(fd, fdt->close_on_exec);
else
FD_CLR(fd, fdt->close_on_exec);
error = fd;
#if 1
/* Sanity check */
if (rcu_dereference(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;
}
内核使用fd_install将文件管理结构file与fd组合起来,具体的操作如下代码(/linux-2.6.30/fs/open.c)所示:
/*
* Install a file pointer in the fd array.
*
* The VFS is full of places where we drop the files lock between
* setting the open_fds bitmap and installing the file in the file
* array. At any such point, we are vulnerable to a dup2() race
* installing a file in the array before us. We need to detect this and
* fput() the struct file we are about to overwrite in this case.
*
* It should never happen - if we allow dup2() do it, _really_ bad things
* will follow.
*/
void fd_install(unsigned int fd, struct file *file)
{
struct files_struct *files = current->files;
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);
spin_unlock(&files->file_lock);
}
当用户使用fd与内核交互时,内核可以使用fd从fdt->fd[fd]中得到内部管理文件的结构struct file
creat函数用于创建一个新的文件,其等价于open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode).
creat函数引入的原因:由于历史原因,早期的Unix版本中,open的第二个参数只能是0(O_RDONLY)、1(O_WRONLY)或者2(O_RDWR),这样就没有办法打开一个不存在的文件,因此一个独立的系统调用creat被引入,用于创建新文件。现在的open函数通过使用O_CREAT和O_TRUNC选项,可以实现creat的功能,因此creat已经不是必要的了。
内核creat的实现代码(/linux-2.6.30/fs/open.c)如下所示:
/*
* For backward compatibility? Maybe this should be moved
* into arch/i386 instead?
*/
SYSCALL_DEFINE2(creat, const char __user *, pathname, int, mode)
{
return sys_open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
}
这样就确定了creat无非是open的一种封装实现。