linux man page:open(翻译和注释)

文章目录

  • 总览
  • 描述
  • flags
  • return
  • errors
  • 注意事项
    • file description - open file description 注意用户表-内核表的区别
    • 同步I/O 没用过
    • NFS
    • FIFO 管道
    • 文件访问模式 O_RDONLY | O_WRONLY != O_RDWR
    • openat() -- 不懂
    • O_DIRECT -- 调控内存对齐 不懂
  • bugs

总览

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);

描述

open()系统调用打开由pathname指定的文件。如果指定的文件不存在,它可以选择性地(如果在flags中指定了O_CREAT)由open()创建。

open()的返回值是文件描述符,一个小的非负整数,在后续的系统调用(read(2)、write(2)、lseek(2)、fcntl(2)等)中用于引用打开的文件。成功调用返回的文件描述符将是该进程当前未打开的最小编号的文件描述符

默认情况下,新的文件描述符设置为跨过execve保持打开(即,描述在fcntl(2)中描述的FD_CLOEXEC文件描述符标志最初被禁用);O_CLOEXEC标志可用于更改此默认设置。文件偏移设置为文件开头(参见lseek(2))。

对open()的调用创建一个新的打开文件描述,这是system-wide table(即内核表)中的一个条目。打开文件描述记录了文件偏移量和文件状态标志(参见下文)。文件描述符是对打开文件描述的引用;如果路径名后续被删除或修改为引用不同的文件,则此引用不受影响。有关打开文件描述的更多详细信息,请参阅注释。(注:进程表 - 内核表 - inode,进程表中的fd是索引内核表的下标,内核表的inode是对应磁盘中文件位置用的。根据路径创建内核表后,以后用fd索引内核表即可找到inode,此时文件路径已经不再重要。

flags

参数flags必须包括以下访问模式之一:O_RDONLY、O_WRONLY或O_RDWR。这些请求以只读、只写或读/写的方式打开文件。

此外,flags中可以按位或零个或多个文件创建标志和文件状态标志。文件创建标志是O_CLOEXEC、O_CREAT、O_DIRECTORY、O_EXCL、O_NOCTTY、O_NOFOLLOW、O_TMPFILE和O_TRUNC。文件状态标志是下面列出的所有其余标志。这两组标志的区别在于文件创建标志影响open()操作本身的语义,而文件状态标志影响后续I/O操作的语义。可以检索和(在某些情况下)修改文件状态标志;有关详细信息,请参阅fcntl(2)。

文件创建标志和文件状态标志的完整列表如下:

   O_APPEND
       文件以追加模式打开。在每次write(2)之前,文件偏移量定位到文件末尾,就像lseek(2)一样。文件偏移量的修改和写入操作作为一个原子步骤执行。

       在多进程同时向一个文件追加数据的情况下,O_APPEND可能会导致NFS文件系统上的文件损坏。这是因为NFS不支持追加到文件,所以客户端内核必须模拟它,而这无法在没有竞争条件的情况下完成。

   O_ASYNC
       启用信号驱动的I/O:当输入或输出变为可能时,产生一个信号(默认为SIGIO,但可以通过fcntl(2)更改)。此功能仅适用于终端、伪终端、套接字,以及(从Linux 2.6开始)管道和FIFO。参见fcntl(2)以获取更多详细信息。另请参阅下面的BUGS。

   O_CLOEXEC (从Linux 2.6.23开始)
       为新的文件描述符启用close-on-exec标志。指定此标志允许程序避免为设置FD_CLOEXEC标志执行额外的fcntl(2) F_SETFD操作。

       请注意,在某些多线程程序中使用此标志是必要的,因为使用单独的fcntl(2) F_SETFD操作来设置FD_CLOEXEC标志不足以避免竞争条件,其中一个线程打开文件描述符并尝试同时使用fcntl(2)设置其close-on-exec标志,而另一个线程执行fork(2)加上execve(2)。根据执行顺序的不同,这种竞争可能导致open()返回的文件描述符意外泄漏到fork(2)创建的子进程执行的程序中。(原则上,对于需要设置close-on-exec标志的文件描述符的任何系统调用都存在这种竞争,Linux的其他各种系统调用提供了等效于O_CLOEXEC的标志来处理此问题。)

   O_CREAT
       如果pathname不存在,将其作为常规文件创建。

       新文件的所有者(用户ID)设置为进程的有效用户ID。

       新文件的组所有权(组ID)设置为进程的有效组ID(System V语义)或父目录的组ID(BSD语义)。在Linux上,行为取决于父目录上是否设置了组ID模式位:如果设置了该位,则应用BSD语义;否则应用System V语义。在某些文件系统上,行为还取决于描述在mount(8)中描述的bsdgroups和sysvgroups挂载选项。

       mode参数指定应用在创建新文件时的文件模式位。当在flags中指定O_CREAT或O_TMPFILE时,必须提供此参数;如果既不指定O_CREAT也不指定O_TMPFILE,则忽略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上,mode中还遵循以下位:

       S_ISUID 0004000 设置用户ID位

       S_ISGID 0002000 设置组ID位(参见inode(7))。

       S_ISVTX 0001000 粘滞位(参见inode(7))O_DIRECT (从Linux 2.4.10开始)
       尝试最小化I/O对文件缓存的影响。通常这会降低性能,但在某些特殊情况下很有用,例如应用程序进行自己的缓存时。文件I/O直接从用户空间缓冲区完成。仅O_DIRECT标志本身努力同步传输数据,但不能保证数据和所需元数据的传输。要保证同步I/O,除了O_DIRECT之外还必须使用O_SYNC。参见下面的注释以获取进一步的讨论。

       块设备的语义类似(但已弃用)的接口在raw(8)中描述。

return

返回值
open()、openat()和creat()返回新的文件描述符,或者在发生错误时返回-1(在这种情况下,errno被适当设置)。

errors

   EACCES 请求的文件访问被拒绝,或者pathname前缀目录的搜索权限被拒绝,或者文件还不存在而写父目录的权限也被拒绝。
   
   EDQUOT 其中O_CREAT被指定,文件不存在,用户在文件系统上的磁盘块或索引节点配额已耗尽。
   
   EEXIST pathname已经存在,且指定了O_CREAT和O_EXCL。
   
   EFAULT pathname指向不可访问的地址空间之外。
   
   EFBIG  参见EOVERFLOW。
   
   EINTR   在阻塞等待完成对慢速设备(例如FIFO;参见fifo(7))的打开时,调用被信号处理程序中断;参见signal(7)。
   
   EINVAL 文件系统不支持O_DIRECT标志。有关更多信息,请参阅注释。
   
   EINVAL flags中的无效值。
   
   EINVAL 在flags中指定了O_TMPFILE,但没有指定O_WRONLY或O_RDWR。
   
   EINVAL 在flags中指定了O_CREAT,新文件路径名的最后一个组件(“基名”)无效(例如,它包含文件系统不允许的字符)。
   
   EISDIR pathname引用目录,请求的访问涉及写入(即设置了O_WRONLY或O_RDWR)。
   
   EISDIR pathname引用现有目录,在flags中指定了O_TMPFILE和O_WRONLY或O_RDWR之一,但此内核版本不提供O_TMPFILE功能。
   
   ELOOP   在解析pathname时遇到太多符号链接。 
   
   ELOOP  pathname是一个符号链接,并且标志指定了O_NOFOLLOW但没有O_PATH。
   
   EMFILE  每个进程对打开文件的描述符数目限制已达到(参见getrlimit(2)中RLIMIT_NOFILE的描述)。
   
   ENAMETOOLONG
          pathname太长。
   
   ENFILE 系统范围内的打开文件的总数限制已达到。
   
   ENODEV pathname引用设备特殊文件,对应设备不存在。(这是Linux内核的bug;在这种情况下必须返回ENXIO。)
   
   ENOENT 没有指定O_CREAT,命名的文件不存在。或者,pathname中的目录分量不存在或是一个悬挂符号链接。
   
   ENOENT pathname引用一个不存在的目录,在flags中指定了O_TMPFILE和O_WRONLY或O_RDWR之一,但此内核版本不提供O_TMPFILE功能。
   
   ENOMEM pathname引用一个FIFO,但是由于管道的用户硬限制已经达到,无法为FIFO缓冲区分配内存;参见pipe(7)。
   
   ENOMEM 内核内存不足。
   
   ENOSPC 打算要创建pathname,但包含它的设备上没有空间了。
   
   ENOTDIR
          pathname的一个组成部分被用作目录,实际上不是目录,或者指定了O_DIRECTORY,而pathname不是目录。
   
   ENXIO  O_NONBLOCK | O_WRONLY已设置,命名的文件是一个FIFO,没有进程已打开FIFO用于读取。
   
   ENXIO  文件是一个设备特殊文件,对应设备不存在。
   
   EOPNOTSUPP
          包含pathname的文件系统不支持O_TMPFILE。
   
   EOVERFLOW
          pathname引用一个太大,无法打开的常规文件。这里的典型场景是在没有-D_FILE_OFFSET_BITS=6432位平台上编译的应用程序试图打开大小超过(1<<31)-1字节的文件;另请参阅上面的O_LARGEFILE。这是POSIX.1指定的错误;2.6.24之前的内核中,Linux为此案例返回EFBIG。
   
   EPERM  指定了O_NOATIME,但调用进程的有效用户ID与文件所有者不匹配,调用者也没有特权。
   
   EPERM  操作被文件封印阻止;参见fcntl(2)。
   
   EROFS  pathname引用只读文件系统上的文件,请求写访问。
   
   ETXTBSY
          pathname引用当前正在执行的可执行映像,请求写访问。
   
   EWOULDBLOCK
          指定了O_NONBLOCK,且文件上存在不兼容的租约(参见fcntl(2))

注意事项

  1. 在Linux下,O_NONBLOCK标志表示希望打开文件但不一定有意向读写。这通常用于打开设备以获取用于ioctl(2)的文件描述符。
  2. O_RDONLY | O_TRUNC的(未定义)效果在不同实现中有所不同。在许多系统上,文件实际被截断。
  3. 请注意open()可以打开设备特殊文件,但creat()不能创建它们;请使用mknod(2)。
  4. 如果文件是新创建的,它的st_atime、st_ctime、st_mtime字段(分别是最后访问时间、最后状态改变时间和最后修改时间;参见stat(2))被设置为当前时间,父目录的st_ctime和st_mtime字段也被设置为当前时间。否则,如果由于O_TRUNC标志文件被修改,它的st_ctime和st_mtime字段被设置为当前时间。
  5. /proc/[pid]/fd目录中的文件显示pid进程打开的文件描述符。/proc/[pid]/fdinfo目录中的文件显示有关这些文件描述符的更多信息。有关这两个目录的更多详细信息,请参阅proc(5)。

file description - open file description 注意用户表-内核表的区别

注意:前者是文件描述符fd,后者是内核表ystem-wide table,内核表的别称还有:"open file object", a "file handle", an "open file table entry"。
  1. 打开文件描述符这个词是POSIX用来指代系统范围打开文件表中的条目(system-wide table)。在其他上下文中,这个对象也分别被称为“打开的文件对象”、“文件句柄”、“打开的文件表条目”,或者在内核开发人员术语中称为struct file(“open file object”, a “file handle”, an “open file table entry”)
  2. 当文件描述符被复制(使用dup(2)或类似的方法)时,副本引用与原始文件描述符相同的内核表,因此两个文件描述符随后共享文件偏移量和文件状态标志。这种共享也可以在进程之间发生:通过fork(2)创建的子进程继承其父进程的文件描述符副本,那些副本引用同一个内核表。
  3. 每次打开一个文件都会创建一个新的打开的文件描述符;因此,可能有多个打开的文件描述符对应一个文件inode。
  4. 在Linux上,可以使用kcmp(2) KCMP_FILE操作来测试两个文件描述符(在同一进程或两个不同进程中)是否引用同一个打开的文件描述符。

同步I/O 没用过

  1. POSIX.1-2008“同步I/O”选项指定了不同的同步I/O变体,并指定了open()标志O_SYNC、O_DSYNC和O_RSYNC来控制行为。无论实现是否支持此选项,它必须至少支持对常规文件使用O_SYNC。 Linux实现了O_SYNC和O_DSYNC,但没有实现O_RSYNC。(有点不正确的是,glibc将O_RSYNC定义为与O_SYNC相同的值。)

  2. O_SYNC提供同步I/O文件完整性完成,这意味着写入操作将刷新数据和所有相关元数据到底层硬件。O_DSYNC提供同步I/O数据完整性完成,这意味着写入操作将刷新数据到底层硬件,但只刷新后续读操作成功完成所需的元数据更新。数据完整性完成可以减少不需要文件完整性保证的应用程序所需的磁盘操作数量。

    要了解这两种完成类型之间的区别,请考虑两个文件元数据:文件上次修改时间戳(st_mtime)和文件长度。所有写入操作都将更新最后文件的修改时间戳,但只有向文件末尾添加数据的写入才会改变文件长度。最后修改时间戳不是确保读成功所必需的,但文件长度是必需的。因此,O_DSYNC只保证刷新对文件长度元数据的更新(而O_SYNC还总是刷新最后修改时间戳元数据)。

    在Linux 2.6.33之前,Linux只为open()实现了O_SYNC标志。但是,指定该标志时,大多数文件系统实际上提供同步I/O数据完整性完成(即,O_SYNC实际上实现为等效于O_DSYNC)。

    从Linux 2.6.33开始,提供适当的O_SYNC支持。但是,为了向后兼容,O_DSYNC被定义为历史O_SYNC的值,O_SYNC被定义为一个新的(两位)标志值,包含O_DSYNC标志值。这可以确保针对新头文件编译的应用程序在2.6.33之前的内核上至少获取O_DSYNC语义。

NFS

NFS基础协议中存在许多影响O_SYNC和O_NDELAY等的问题。
在启用了UID映射的NFS文件系统上,open()可能会返回一个文件描述符,但例如read(2)请求会被拒绝EACCES。这是因为客户端通过检查权限执行open(),但是UID映射是在读写请求上由服务器执行的。

FIFO 管道

打开有名管道的读取或写入端将阻塞,直到另一端也被另一个进程或线程打开。更多详细信息请参阅fifo(7)。

文件访问模式 O_RDONLY | O_WRONLY != O_RDWR

与flags中可以指定的其他值不同,访问模式值O_RDONLY、O_WRONLY和O_RDWR不指定单个位。相反,它们分别定义flags的低2位,定义为0、1和2。换句话说,组合O_RDONLY | O_WRONLY是一个逻辑错误,当然不具有与O_RDWR相同的含义

Linux在flags中保留了特殊的非标准访问模式3(二进制11),意思是:检查文件读写权限,并返回一个不能用于读写的文件描述符。这种非标准访问模式被一些Linux驱动程序用来返回仅用于特定设备的ioctl(2)操作的文件描述符。

openat() – 不懂

首先,openat()允许应用程序避免使用open()在当前工作目录之外打开文件时可能发生的竞争条件。这些竞争条件来自于给定open()的目录前缀的某些组件可能与open()调用并行更改的事实。例如,假设我们希望在文件dir1/dir2/xxx存在时创建文件dir1/dir2/xxx.dep。问题是在存在性检查和文件创建步骤之间,dir1或dir2(可能是符号链接)可能会被修改为指向不同的位置。通过为目标目录打开文件描述符,然后将该文件描述符指定为例如fstatat(2)和openat()的dirfd参数,可以避免这种竞争。使用dirfd文件描述符还有其他好处:

文件描述符是对目录的稳定引用,即使目录被重命名;
打开的文件描述符可防止底层文件系统被卸载,就像进程在文件系统上有当前工作目录时一样。

O_DIRECT – 调控内存对齐 不懂

注意:没看懂,没用过。附上linus的创意发言:
"The  thing  that  has  always  disturbed me about O_DIRECT is that the whole interface is just stupid, and was probably designed by a deranged monkey on some serious mind-controlling substances."—Linus

O_DIRECT标志可能对用户空间缓冲区的长度和地址以及I/O的文件偏移施加对齐限制。在Linux中,对齐限制因文件系统和内核版本的不同而异,也可能完全不存在。但是,当前没有文件系统独立的接口允许应用程序发现给定文件或文件系统的这些限制。一些文件系统提供了自己的接口,例如xfsctl(3)中的XFS_IOC_DIOINFO操作。

在Linux 2.4下,传输大小以及用户缓冲区的对齐和文件偏移量必须都是文件系统逻辑块大小的倍数。从Linux 2.6.0开始,对基础存储的逻辑块大小(通常是512字节)对齐就足够了。可以使用ioctl(2) BLKSSZGET操作或从shell使用以下命令确定逻辑块大小:

blockdev --getss

如果内存缓冲区是私有映射(即,使用mmap(2) MAP_PRIVATE标志创建的任何映射,这包括堆上分配的内存和静态分配的缓冲区),则不应该同时运行O_DIRECT I/O和fork(2)系统调用。不论是否通过异步I/O接口提交,或来自进程中的其他线程,所有这些I/O都应在调用fork(2)之前完成。不遵守此限制可能导致父进程和子进程中的数据损坏和未定义的行为。当O_DIRECT I/O的内存缓冲区使用shmat(2)或使用MAP_SHARED标志的mmap(2)创建时,此限制不适用。当内存缓冲区已使用madvise(2)标记为MADV_DONTFORK,确保它在fork(2)后不可用于子进程时,此限制也不适用。

O_DIRECT标志最初在SGI IRIX中引入,其中对齐限制类似于Linux 2.4。IRIX还有一个fcntl(2)调用可以查询适当的对齐和大小。FreeBSD 4.x引入了同名的标志,但没有对齐限制。

O_DIRECT支持在Linux内核2.4.10版本中添加。旧的Linux内核简单地忽略此标志。某些文件系统可能不实现此标志,在这种情况下,如果使用则open()会失败并返回EINVAL。

应用程序应该避免在同一文件上混合使用O_DIRECT和普通I/O,特别是对文件中重叠的字节区域混合使用。即使文件系统正确处理这种情况下的一致性问题,总体I/O吞吐量也可能比仅使用一种模式慢。同样,应用程序应避免混合使用对同一文件的mmap(2)和直接I/O。

O_DIRECT与NFS的行为将不同于本地文件系统。旧内核或以某些方式配置的内核可能不支持此组合。NFS协议不支持将标志传递给服务器,因此O_DIRECT I/O将仅在客户端上绕过页面缓存;服务器可能仍缓存I/O。客户端要求服务器使I/O同步以保留O_DIRECT的同步语义。在这些情况下,某些服务器的性能会很差,特别是如果I/O大小很小。一些服务器也可能被配置为在I/O达到稳定存储时对客户端说谎;这会以某些数据完整性风险为代价避免性能损失。Linux NFS客户端对O_DIRECT I/O不施加对齐限制。

bugs

  1. 当前,无法通过在调用open()时指定O_ASYNC来启用信号驱动的I/O;请使用fcntl(2)启用此标志。
  2. 在试图确定内核是否支持O_TMPFILE功能时,必须检查两个不同的错误代码EISDIR和ENOENT。
  3. 当在flags中同时指定O_CREAT和O_DIRECTORY,而pathname指定的文件不存在时,open()将创建一个常规文件(即,忽略O_DIRECTORY)。

你可能感兴趣的:(linux,网络)