目录
1.打开文件
1.1 函数原型介绍
1.1.1 open函数
1.1.2 creat函数
1.1.2 openat函数
1.2 内核源码分析
1.3 函数原型区别
2.关闭文件
2.1 函数原型介绍
2.1.1 close函数
2.2 内核源码实现
1.打开文件
1.1 函数原型介绍
1.1.1 open函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
函数简介:open函数用于打开一个文件,并返回一个文件描述符,应用程序可以通过文件描述符对文件进行读写等操作。open函数可以打开已经存在的文件,也可以创建一个新文件。
函数参数:
pathname:是文件的路径名。
flags:打开文件时的标志,可以设置为以下值之一或多个值的按位或:
- O_RDONLY:只读打开
- O_WRONLY:只写打开
- O_RDWR:读写打开
- O_CREAT:如果文件不存在,则创建文件
- O_TRUNC:如果文件存在且以写方式打开,则截断文件为零长度
- O_APPEND:以追加方式打开文件
mode:只有在O_CREAT标志被设置时才有效,用于指定文件的访问权限,可以设置为以下值之一:
#define S_IRWXU 00700 //用户可读,可写,可执行
#define S_IRUSR 00400 //用户可读
#define S_IWUSR 00200 //用户可写
#define S_IXUSR 00100 //用户可执行
#define S_IRWXG 00070 //组可读,可写,可执行
#define S_IRGRP 00040 //组可读
#define S_IWGRP 00020 //组可写
#define S_IXGRP 00010 //组可执行
#define S_IRWXO 00007 //其他用户可读,可写,可执行
#define S_IROTH 00004 //其他用户可读
#define S_IWOTH 00002 //其他用户可写
#define S_IXOTH 00001 //其他用户可执行
mode可以通过八进制赋值,例如设置为0777,0777表示所有权限按位或。
需要注意的是mode需要与系统umask值共同作用才能得到最终的文件权限,umask作用去掉哪些权限,比如umask值为0022(八进制),标识去掉S_IWGRP和S_IWOTH权限。
文件最终权限计算方法为: mode &~ umask.
举例说明:
mode设置为0777,umask值为0022,文件最终权限为0755。
函数返回值:
成功:返回文件描述符,文件描述符为非负整数。
失败:返回-1,并设置errno。
示例代码
#define TEST_FILE "/tmp/test.txt"
int open_test() {
int fd = open(TEST_FILE, O_RDWR | O_CREAT | O_TRUNC, 0777);
if (fd == -1) {
perror("open error");
return -1;
}
return 0;
}
1.1.2 creat函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat(const char *pathname, mode_t mode);
函数简介:creat函数用于创建并打开一个新文件,并返回一个文件描述符,如果文件已经存在,则会将其截断为0字节,应用程序可以通过文件描述符对文件进行读写等操作。
函数参数:
pathname:文件路径名。
mode:是新文件的权限(参考open函数)。
函数返回值:
成功:返回文件描述符,文件描述符为非负整数。
失败:返回-1,并设置errno。
示例代码:
int creat_test() {
int fd = creat(TEST_FILE, 0644);
if (fd == -1) {
perror("creat error");
return -1;
}
return 0;
}
1.1.2 openat函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int openat(int dirfd, const char *pathname, int flags);
int openat(int dirfd, const char *pathname, int flags, mode_t mode);
函数简介:openat函数用于在指定目录下打开文件并返回文件描述符。它与open函数类似,但可以指定相对路径来打开文件,而不需要提供完整的文件路径。
函数参数:
dirfd:已打开的文件目录描述符。
pathname:是指定文件名的字符串。
flags:打开文件时的标志(参考open函数)。
mode:是新文件的权限(参考open函数)。
openat函数dirfd和pathname参数组合关系:
- 组合1:pathname为绝对路径时,按绝对路径方式打开文件,dirfd失效。
- 组合2:pathname为相对路径时,由dirfd(文件目录)和pathname(相对路径)共同决定打开的文件名。
- 组合3:pathname为相对路径时,如果dirfd设置为AT_FDCWD(当前工作目录),由dirfd(当前工作目录)和pathname(相对路径)共同决定打开的文件名。
函数返回值:
成功:返回文件描述符,文件描述符为非负整数。
失败:返回-1,并设置errno。
示例代码:
#define DIR_PATH "/tmp"
#define FILE "test.txt"
int openat_test() {
int dirfd = open(DIR_PATH, O_RDONLY);
if (dirfd == -1) {
perror("open dir error");
return -1;
}
int fd = openat(dirfd, FILE, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("openat error");
return -1;
}
return 0;
}
1.2 内核源码分析
图 1-1 open函数内核源码分析
open,creat,openat通过系统调用再调用一个共同的函数do_sys_open函数,do_sys_open函数有四个参数:dfd,path,flags,mode。这四个参数对应openat函数原型四个参数。
open函数没有dfd参数,所以在调用do_sys_open函数之前dfd设置为默认值AT_FDCWD(当前工作目录)。
creat函数没有dfd参数和flags参数,所以在调用do_sys_open函数之前dfd设置为默认值AT_FDCWD(当前工作目录),以及flags设置为默认值O_WRONLY | O_CREAT | O_TRUNC,creat因为flags默认值的关系,所以只能新建文件或者截断文件长度至0来打开文件。
openat函数需要传递四个参数至do_sys_open函数。
打开文件描述符系列函数源码调用路径如图,这里就不再赘述。
1.3 函数原型区别
(1)open函数和creat函数区别
- 区别1:open函数可以打开已存在的文件,也可以创建新文件,而creat函数只能创建新文件。如果文件已存在,creat函数会将其截断为零长度。
- 区别2:open函数的标志参数可以更加灵活地指定文件的打开方式,包括只读、只写、读写、追加等选项。而creat函数只能以写方式打开文件,并且具有固定的标志(O_WRONLY | O_CREAT | O_TRUNC)。
(2)open函数和openat函数区别
- 区别1:open函数接受一个文件路径名作为参数,该路径名可以是绝对路径或相对路径。 openat函数需要传递一个已打开的目录文件描述符(dirfd)和一个相对于该目录的路径名作为参数。这种方式可以更加灵活地控制文件的打开位置。
- 区别2:目录解析方式: open函数的文件路径名会根据当前进程的工作目录进行解析。 openat函数的路径名是相对于提供的目录文件描述符(dirfd)进行解析。
- 区别3:安全性考虑: open函数在解析文件路径时,依赖于进程的当前工作目录。这可能会导致安全性问题,特别是在多线程环境下,因为当前工作目录是共享的。 openat函数提供了更安全的方式,可以避免依赖于进程的当前工作目录,而是通过提供目录文件描述符来指定相对路径。
2.关闭文件
2.1 函数原型介绍
2.1.1 close函数
#include <unistd.h>
int close(int fd);
函数简介:close函数的作用是关闭文件描述符并释放相关资源。
函数参数:
fd:表示需要关闭的文件描述符。
函数返回值:
成功:返回0.
失败:返回-1,并设置errno,一些常见的错误码包括:
- EBADF:无效的文件描述符,即文件描述符未打开或已经关闭。
- EINTR:操作被信号中断。
- EIO:I/O错误。
2.2 内核源码实现
图 2-1 close函数内核源码分析
close函数通过系统调用sys_close函数进入内核空间,close通过fd找到struct file对象并清除和释放该对象,struct file的f_op成员会根据文件系统和文件类型不一样而设置不同的值,关闭文件时,也会调用f_op对应的一些具体函数清除资源,典型的情况如socket关闭,socket关闭需要完成三次握手,需要通过f_op->release函数实现。