> For the complete documentation index, see [llms.txt](https://hansimov.gitbook.io/csapp/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hansimov.gitbook.io/csapp/part3/ch10-system-level-io/10.3-opening-and-closing-files.md).

# 10.3 打开和关闭文件

进程是通过调用 open 函数来打开一个已存在的文件或者创建一个新文件的：

```c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(char *filename, int flags, mode_t mode);

// 返回：若成功则为新文件描述符，若出错为 -1。
```

open 函数将 filename 转换为一个文件描述符，并且返回描述符数字。返回的描述符总是在进程中当前没有打开的最小描述符。flags 参数指明了进程打算如何访问这个文件：

* O\_RDONLY：只读。
* O\_WRONLY：只写。
* O\_RDWR：可读可写。

例如，下面的代码说明如何以读的方式打开一个已存在的文件：

```c
fd = Open("foo.txt", O_RDONLY, 0);
```

flags 参数也可以是一个或者更多位掩码的或，为写提供给一些额外的指示：

* O\_CREAT：如果文件不存在，就创建它的一个**截断的**（truncated）（空）文件。
* O\_TRUNC：如果文件已经存在，就截断它。
* O\_APPEND：在每次写操作前，设置文件位置到文件的结尾处。

例如，下面的代码说明的是如何打开一个已存在文件，并在后面添加一些数据：

```c
fd = Open("foo.txt", O_WRONLY|O_APPEND, 0);
```

mode 参数指定了新文件的访问权限位。这些位的符号名字如图 10-2 所示。

| 掩码       | 描述                |
| -------- | ----------------- |
| S\_IRUSR | 使用者（拥有者）能够读这个文件   |
| S\_IWUSR | 使用者（拥有者）能够写这个文件   |
| S\_IXUSR | 使用者（拥有者）能够执行这个文件  |
| S\_IRGRP | 拥有者所在组的成员能够读这个文件  |
| S\_IWGRP | 拥有者所在组的成员能够写这个文件  |
| S\_IXGRP | 拥有者所在组的成员能够执行这个文件 |
| S\_IROTH | 其他人（任何人）能够读这个文件   |
| S\_IWOTH | 其他人（任何人）能够写这个文件   |
| S\_IXOTH | 其他人（任何人）能够执行这个文件  |

> 图 10-2 访问权限位。在 sys/stat.h 中定义

作为上下文的一部分，每个进程都有一个 umask，它是通过调用 umask 函数来设置的。当进程通过带某个 mode 参数的 open 函数调用来创建一个新文件时，文件的访问权限位被设置为 **mode & \~umask**。例如，假设我们给定下面的 mode 和 umask 默认值：

```c
#define DEF_MODE   S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH
#define DEF_UMASK  S_IWGRP|S_IWOTH
```

接下来，下面的代码片段创建一个新文件，文件的拥有者有读写权限，而所有其他的用户都有读权限：

```c
umask(DEF_UMASK);
fd = Open("foo.txt", O_CREAT|O_TRUNC|O_WRONLY, DEF_MODE);
```

最后，进程通过调用 close 函数关闭一个打开的文件。

```c
#include <unistd.h>

int close(int fd);

// 返回：若成功则为 0，若出错则为 -1。
```

关闭一个已关闭的描述符会出错。

### 练习题 10.1

{% tabs %}
{% tab title="练习题 10.1" %}
下面程序的输出是什么？

```c
#include "csapp.h"

int main()
{
    int fd1, fd2;

    fd1 = Open("foo.txt", O_RDONLY, 0);
    Close(fd1);
    fd2 = Open("baz.txt", O_RDONLY, 0);
    printf("fd2 = %d\n", fd2);
    exit(0);
}
```

{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="答案" %}
Unix 进程生命周期开始时，打开的描述符赋给了 stdin（描述符 0）、stdout（描述符 1）和 stderr（描述符 2）。open 函数总是返回最低的未打开的描述符，所以第一次调用。pen 会返回描述符 3。调用 close 函数会释放描述符 3。最后对 open 的调用会返回描述符 3，因此程序的输出是 “fd2=3”。
{% endtab %}
{% endtabs %}
