Linux 是近年来发展起来的一种新型的操作系统,其最重要的特征之一就是支持多种文件系统,使其更加灵活,从而与许多其它的操作系统共存。Linux支持ext,ext2,xia,minix,umsdos,msdes,fat32 ,ntfs,proc,stub,ncp,hpfs,affs 以及 ufs 等多种文件系统。为了实现这一目的,Linux 对所有的文件系统采用统一的文件界面,用户通过文件的操作界面来实现对不同文件系统的操作。对于用户来说,我们不要去关心不同文件系统的具体操作过程,而只是对一个虚拟的文件操作界面来进行操作,这个操作界面就是 Linux 的虚拟文件系统(VFS ) 。
本文讲述的虚拟文件系统 API 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解文件系统操作。
open()
creat()
openat() 遵循 POSIX.1-2008
openat2(2) 遵循 Linux 标准
标准 c 库,libc, -lc
-
- int open(const char *pathname, int flags);
- int open(const char *pathname, int flags, mode_t mode);
-
- int creat(const char *pathname, mode_t mode);
-
- int openat(int dirfd, const char *pathname, int flags);
- int openat(int dirfd, const char *pathname, int flags, mode_t mode);
-
- /* Documented separately, in openat2(2): */
- int openat2(int dirfd, const char *pathname,
- const struct open_how *how, size_t size);
openat() 依赖的宏:
glibc 2.10 后,_POSIX_C_SOURCE >= 200809L
glibc 2.10 前,_ATFILE_SOURCE
open() 系统调用用来打开 pathname 指定的文件。如果指定的文件不存在,open() 有可能会创建该文件(flags 标记中指定了 O_CREAT)。
open() 的返回值是一个文件描述符,它是一个指向进程打开文件描述符符表入口的小非负数。文件描述符会在接下来的各种操作(read(2)、write(2)、lseek(2)、fcntl(2) 等)种用来指向这个打开的文件。返回的文件描述符是当前进程中没有打开的描述符号码最小的那个。
默认情况下,文件描述符被设置为在 execve(2) 前后保持打开(fcntl(2) 中描述的 FD_CLOEXEC 标记初始被设置为禁止),不过可以通过下面的 O_CLOEXEC 标记来修改这个设置。文件偏移值被设置为文件的开始(参考 lseek(2))。
open() 调用会创建一个新的打开文件描述(open file description),系统打开文件表中的条目(entry)。打开文件描述记录文件的偏移以及文件状态标记(参考下面)。一个文件描述符是一个打开文件描述的索引,这个索引不会受到 pathname 删除或者指向另一个文件影响,更多关于打开文件描述参考后面注意章节。
flags 参数必须包含下面访问模式中的一种:O_RDONLY、O_WRONLY、O_RDWR。这些请求以只读、只写或者读写方式打开相应文件。
此外,可以在 flags 里指定文件创建标记或者文件状态标记,每个标记进行位或。文件创建标记包括:O_CLOEXEC、O_CREAT、O_DIRECTORY、O_EXCL、O_NOCTTY、O_NOFOLOOW、O_TMPFILE 和 O_TRUNC。文件状态标记包括下面列表中所有其他标记。这两组标记的区别是创建标记影响打开操作本身的语义,而文件状态标记影响接下来所有 I/O 操作。文件状态标记能够通过 fcntl(2) 得到或者修改(一些情况下)。
所有文件创建标记和文件状态标记如下:
O_APPEND
文件以追加模式打开。在每次 write(2) 前,文件偏移都是被指定到文件的末尾的,和 lseek(2) 一样。文件偏移的修改以及写操作以单原子步骤进行。
在 NFS 文件系统中,如果多个进程向同一个文件通过 O_APPEND 追加数据,那么有可能会导致文件破坏。因为 NFS 不支持向文件追加数据,所以内核就需要模拟,而这种模拟只有在存在数据竞争情况下才能实现。
O_ASYNC
开启信号驱动的 I/O 操作:当文件描述符输入
输出数据可用时生成一个信号(默认是 SIGIO,但是可以通过 fcntl(2) 修改)。这个特性只在终端、伪终端、套接字以及管道、FIFO (Linux 2.6 后)有效。参考 fcntl(2) 查阅更多内容,也可以查看后面 BUGS 章节。
O_CLOEXEC(Linux 2.6.23 后)
使能新文件描述符的执行关闭标记(close-on-exec),设置这个标记省去了通过 fcntl(2) F_SETFD 操作来设置 FD_CLOEXEC 标记。
多线程场景下这个标记是很常用的,比如一个线程通过 fcntl(2) F_SETFD 设置 FD_CLOEXEC 标记而另一个线程打开了文件描述符并且通过 fork(2) 和 execve(2) 传教子进程,这就可能会存在竞争问题。根据执行的不同顺序,新打开的文件描述符可能会无意的泄露给 fork(2) 创建的子进程。
O_CREAT
如果 pathname 不存在,以常规文件创建它。
新文件的所有者(用户 ID)是进程的有效用户 ID。
新文件的用户组 ID 是进程的有效组 ID (System V 语义)或者父目录的组 ID(BSD 语义)。Linux 上主要依赖父目录的设置组 ID 模式位是否被设置:如果设置了,那么就适用 BSD 语义,否则适用于 System V 语义。对于一些文件系统,行为依赖于 mount(8) 中描述的 bsdgroups 和 sysvgroups 的挂载选项
mode 参数指定应用在新创建文件上的文件模式位。如果 O_CREAT 和 O_TMPFILE 都没有指定,那么 mode 参数会被忽略。对于 这两个选项必须提供 mode 参数,如果没有提供,那么就会使用栈上对应的随机值用作模式(很危险了)。
有效模式通过进程的 umask 修改:在没有 ACL 访问控制表的情况下,创建文件的模式为 mode&~umask。
下面这些符号常量可以用于 mode:
S_IRWXU 00700 用户(文件拥有者)具有读写以及执行权限
S_IRUSR 00400 用户具有读权限
S_IWUSR 00200 用户具有写权限
S_IXUSR 00100 用户具有执行权限
S_IRWXG 00070 用户组具有读写和执行权限
S_IRGRP 00040 用户组具有读权限
S_IWGRP 00020 用户组具有写权限
S_IXGRP 00010 用户组具有执行权限
S_IRWXO 00007 其他人具有读写执行权限
S_IROTH 00004 其他人具有读权限
S_IWOTH 00002 其他人具有写权限
S_IXOTH 00001 其他人具有执行权限
根据 POSIX 要求,mode 中如果指定其他位,那么行为是未定义的。Linux 上也可以设置下面这些位:
S_ISUID 0004000 设置用户 ID 位
S_ISGID 0002000 设置用户组 ID (参考 inode(7))
S_ISVTX 0001000 黏贴位(参考 inode(7))
成功时,open()、openat()、creat() 会返回一个新的文件描述符(一个非负数)。错误时返回 -1,并设置 errno 提示相应的错误。
在 Linux 上,O_NOBLOCK 标记有时也有一些特殊用途,比如指向打开一个文件而不会对其读写。比如,可以使用这个来打开一个设备的文件描述符,后面会使用 ioctl(2) 来操作设备。
- // C program to illustrate
- // open system call
- #include
- #include
- #include
- #include
-
- extern int errno;
-
- int main()
- {
- // if file does not have in directory
- // then file foo.txt is created.
- int fd = open("foo.txt", O_RDONLY | O_CREAT);
-
- printf("fd = %d\n", fd);
-
- if (fd == -1) {
- // print which type of error have in a code
- printf("Error Number % d\n", errno);
-
- // print program detail "Success or failure"
- perror("Program");
- }
- return 0;
- }