3.1 introduction 介绍
先介绍unix 系统的 五个I/O 函数- open , read, write, lseek, close . 然后 检查 不同 buffer sizes 对 read, write 函数的 效率的影响。
在这章中 函数 相比standard I/O (5章)的是 unbuffered I/O, 术语 unbuffered 指的 是 每个 read, write 调用内核的系统调用。这些 unbuffered I/O function 不是 ISO C 一部分,但是是 是POSIX.1 和 single Unix Specification 的一部分。
多进程之家 共享资源,这里的 原子操作 概念 变的很重要。从这个角度 检查下 I/O 函数。
最后 介绍下 dup, fcntl, sync, fsync, ioctl 函数。
3.2 File Descriptors 文件描述符
对于内核,所有打开的文件 都被 file descriptors 来引用,file descriptor 是 非负整数。
当我们打开(open)一个已存在的文件或 创建(create)一个新file, 内核会返回一个file descriptor 给 调用进程。
方便起见, unix 系统 shell 把 file descriptor 0 和 进程的标准输入 关联起来, file descriptor 1 和 标准输出 关联起来,file descriptor 2 和 进程的标准错误关联起来。这种方便的做法,通常 是 shell 和 大多数applications 所采用的。 注意,它不是unix 内核的功能。然而,许多应用也可能不遵守 此约定。
尽管 POSIX.1 对0,1,2 的含义是标准化的,应该使用 symbolic constants STDIN_FILENO, STDOUT_FILENO, 和 STDERR_FILENO 来提高可读性。这几个常量 被定义在<unistd.h> 中
file descriptors 的范围 是 从0 到 OPEN_MAX-1 .
对于FreeBSD8.0 ,Linux 3.2.0, Mac OS X 10.6.8 和 Solaris 10, 这个限制是 基本上是无限的(essentially infinite), 取决于 系统的内存量, integer 的size , 由管理员设置的硬件和软件的 限制。
3.3 open 和 openat 函数
#include <fcntl.h>
int open(const char *path, int oflag, ... /* mode_t mode */ );
int openat(int fd, const char *path, int oflag, ... /* mode_t mode */ );
Both return: file descriptor if OK, -1 on error
... 是ISO C 的方式 用以 指定 剩余的 可能的 多个参数。
/* mode_t mode*/最后参数, 来指定模式。
path 是 文件名
flag 这个使用 一个或多个 如下的常量 的 OR 来定义, 这些常量被定义在<fcntl.h> 头中。
O_RDONLY 打开只为了 读
O_WRONLY 打开 只为了 写
O_RDWR 打开 为 读 和 写
O_EXEC 打开 只为了 execute
O_SEARCH 打开 只为了 search ( 应用到 目录)
注意 在本书上的,还没有任何版本的操作系统 支持这个选项。
以上的 五个 必须 指定一个, 下面的选项 是可选的。
O_APPEND 对于每个write 都追加到文件的末尾。
O_CLOEXEC 3.14部分会讨论
O_CREAT 如果文件不存在就创建, open需要第三个参数, openat 需要第四个参数。这个参数指 mode
O_DIRECTORY 如果 path 指向的不是目录,就会产生一个error
O_EXCL 如果O_CREAT 也被指定,且 file 已经存在,就会产生一个 error. 这用来测试 是否文件已经存在 并且 当不存在时创建一个文件,这个过程是个原子操作。 在3.11 部分会描述更多的原子操作。
O_EXCL 一般不单独存在, 如果指定了 O_CREAT 和 O_EXCL , 那么就会检查文件是否存在,不存在就创建 ,这是个原子操作,目的是 当 多个进程同事 open 相同的filename(不存在) 时 只有一个进程能够创建成功,其他进程得到失败(你想 如果没有O_EXCL 那都能open 成功,每个进程都写点东西 不就相互覆盖,乱套了吗)。如果 O_EXCL 和O_CREAT 被设置,pathname 是个 symbolic link ,那么open 将失败并且为 EEXIST, 不管symbolic link 指向的内容是啥。 如果O_EXCL 被设置,O_CREAT没有被设置,结果是未定义的。
O_NOCTTY 如果path 指向 一个 terminal device, 不要分配这个 device 做为进程的 controlling terminal. 9.6部分会讨论 controlling terminals
O_NOFOLLOW 如果path 指向一个 symbolic link 就会产生error。 4.17部分会讨论symbolic link
O_NONBLOCK 如果path 指向 FIFO, 块特殊文件(block special file), 字符特殊文件(character special file), 这个选项设置 nonblocking mode , 对打开文件及 接下来的操作I/O 都是。 在14.2 部分会描述这种模式。
O_SYNC 使 每个write 操作 都wait 到 物理I/O 完成, 这其中包括必要的 更新文件的属性。 3.14部分 会使用这个选项。
O_TRUNC 如果文件存在 并且 它被成功打开 并且 是 write-only 或 read-write , 则truncate 文件的长度为 0
O_TTY_INIT 在18章会讨论 terminal I/O
接下来的 两个flags 是 可选的。 他们是 synchronized input 和output 的 一部分,是POSIX.1的规范
O_DSYNC 使 write 操作 都wait 到 物理 I/O 完成, 但不会等文件属性被更新。
O_DSYNC和O_SYNC flags是相似的,但是有微妙(subtly)的不同。
O_DSYNC flag 影响 文件的属性 仅当 他们需要更新属性以 反映文件中数据的改变(例如: update文件的size 以反映 有更多的数据). 对带有O_SYNC flag,数据和属性总是被同步地更新。 当覆盖了 文件的 已存在的一部分数据,如果是O_DSYNC flag ,file times 将不会被同步地更新。对比地如果是 O_SYNC flag 每个write 操作都会 在write return之前 更新文件的times,且 不管 我们 是 覆盖了文件的一部分字节还是 追加到文件的末尾。
O_RSYNC : 使每个读操作 等待 直到 任何的 对文件的相同部分的写 的 pending 完成了
Linux3.2.0 支持O_DSYNC flag ,但把它当O_SYNC 来对待。
fd 参数 区分了 openat 和 open 函数。这有3个可能:
- path 参数 是 绝对路径, 这种情况 fd 参数 被忽略, openat的 函数行为就像 open 函数。
- path 参数 指定了 相对路径名,并且 fd参数 是一个文件描述符它指定了 文件系统中pathname 参考的starting location . fd 可以通过打开 目录(pathname 的参考目录)来获取。
- path 参数指定了 相对路径名(relative pathname),并且 fd参数 是 特殊的 AT_FDCWD。这种情况 pathname 的 参考目录 是指定的当前的工作目录,并且openat 函数的行为就像open 那样。
openat 解决了2个问题。首先 它给 线程 一种 可以使用相对路径来打开文件(而不是当前工作目录), 在11章,我们看到 在相同进程的所有线程 共享当前的工作目录, 使多个线程 在相同时刻,工作在不同的目录是 困难的。 第二,它提供了一种避免 TOCTTOU errors (time-of-check-to-time-of-use)的方式.
一个程序是脆弱的,如果它 发起了 两个 基础的file 函数调用,在这种情况, 第二个调用 依赖与 第一个调用的结果。 由于两个调用 不是原子的, file 可能在 两次调用之间的间隙时刻 被 改变, 因此 第一次的不合法的调用结果 ,就会导致 问题 error. 文件系统的 TOCTTOU errors ,通常。。。。。
Filename 和 Pathname Truncation
NAME_MAX : 如果文件名 超过了 NAME_MAX 会出现什么。
对于POSIX.1 常量 _POSIX_NO_TRUNC 决定了 当 filename 太长是被 truncated 还是 返回一个error。 我们可以使用 fpathconf 或 pathconf 来查询一个目录,去看看哪种行为是被支持。
Linux 总是返回error.
If _POSIX_NO_TRUNC 是有效的, errno 被设置为ENAMETOOLONG, 并且 error status 被返回。
大多数file systems 支持 最大 255个字符。
3.4 creat 函数
#include <fcntl.h>
int creat(const char *path, mode_t mode);
Returns: file descriptor opened for write-only if OK, -1 on error
注意这个函数 等效于:
open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
create 的一个缺点(deficiency) 是 ,它打开文件 仅是为了 writing。 在open的新版本提供之前,如果我们想 创建一个临时的文件并且 去写,并且稍后读,我们就不得不调用 create, close, 然后打开 open 。 一个更好的方式是 使用 open 函数 如下:
open(path, O_RDWR | O_CREAT | O_TRUNC, mode);
3.5 close 函数
#include <unistd.h>
int close(int fd);
Returns: 0 if OK, −1 on error
关闭 文件 也会引起 释放 进程施加给文件的 任何的 record locks 。我们将在14.3 部分讨论这一点。
当一个进程终止, 所有它打开的文件 都会被 内核 自动地 closed 。 许多程序利用这一个优点,不去调用close 关闭文件。
3.6 lseek 函数
字符“l” 意味着 long integer.
每个打开的文件 都 关联着 一个 “current file offset”, 正常的是 一个非负数。 Read和 write 操作通常开始 在 “current file offset”, 并且 通过read或written一些字节会引起 这个offset的 增大。 默认地 ,这个offset 当file 被打开时, 被初始化为 0, 除非 指定了 O_APPEND 选项。
一个打开的文件的offset 可以被 lseek 来设置
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
Returns: new file offset if OK, −1 on error
参数offset 的解释取决于 whence 参数:
如果whence 是 SEEK_SET, 则 文件的offset 被设置到 从文件开始偏移 参数offset bytes 的位置。
如果whence 是 SEEK_CUR ,则 文件的offset 被设置为 文件的current value 加上 参数offset。 注意参数offset 可以是正数,也可以是负数。
如果whence 被SEEK_END, 文件的offset 被设置为 file的size 加上 参数offset。 参数offset 可以是 正数 或 负数。
如lseek 调用成功就会 返回新的文件offset ,我们可以 设置 参数offset=0 , 且SEEK_CUR 来检测 当前offset。
off_t currpos;
currpos = lseek(fd, 0, SEEK_CUR);
这个技术 也被用于 检测 一个文件 是否 是 可以被seeking 的。如果file descriptor 指向pipe, FIFO,或socket , 则 lseek就会 设置 errno 为 ESPIPE, 并且 return -1。
例子3.1 测试 他的标准输入 是否可以被 seeking
1.#include "apue.h"
2.
3.int
4.main(void)
5.{
6. if (lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
7. printf("cannot seek\n");
8. else
9. printf("seek OK\n");
10. exit(0);
11.}
12.
13./**
14. [xingqiji@work78 fileio]$ ./seek
15.cannot seek
16.[xingqiji@work78 fileio]$ ./seek < /etc/passwd
17.seek OK
18. *
19. */
正常地 一个文件的当前offset 必须是 一个 非负integer, 然而特定的设备 可能允许 负数offsets。 但对于常规文件,offset 必须是 非负数。因为 负数的offsets 是可能的,我们应该小心 地处理 lseek return 的value : 我们应该把 return value 和 等于或不等于-1 来比较,而不是去测量它是否是 小于 0。
文件的offset 可以 大于 文件当前的 size, 这种情况 下一步的write to file将会 扩展file. 这被。这被 称作 在文件里创建了一个 hole.
在文件中的 hole ,是否存回到disk ,取决于 file system的实现。
例子3.2 创建一个文件 ,它里边有hole
fileio/hole.c
#include "apue.h"
#include <fcntl.h>
char buf1[] = "abcdefghij";
char buf2[] = "ABCDEFGHIJ";
int
main(void)
{
int fd;
if ((fd = creat("file.hole", FILE_MODE)) < 0)
err_sys("creat error");
if (write(fd, buf1, 10) != 10)
err_sys("buf1 write error");
/* offset now = 10 */
if (lseek(fd, 16384, SEEK_SET) == -1)
err_sys("lseek error");
/* offset now = 16384 */
if (write(fd, buf2, 10) != 10)
err_sys("buf2 write error");
/* offset now = 16394 */
exit(0);
}
/**
*
[xingqiji@work78 fileio]$ ls -l file.hole
-rw-r--r-- 1 xingqiji xingqiji 16394 12月 24 17:49 file.hole
[xingqiji@work78 fileio]$ od -c file.hole
0000000 a b c d e f g h i j \0 \0 \0 \0 \0 \0
0000020 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0040000 A B C D E F G H I J
0040012
*
[xingqiji@work78 fileio]$ ls -ls file.hole file.nohole
ls: 无法访问file.nohole: 没有那个文件或目录
8 -rw-r--r-- 1 xingqiji xingqiji 16394 12月 24 17:49 file.hole
*/
为了证明在这个文件中确实有一个 hole, 让我们比较这个文件 同 我们刚刚用相同size创建的文件。
ls -ls file.hole file.nohole
说明分析: 尽管两个文件有相同的size,没有空洞的文件消费 20 disk blocks, 有空洞的文件仅仅消费 8 disk blocks.
Operating system | CPU architecture | _FILE_OFFSET_BITS value | ||
Undefined | 32 | 64 | ||
FreeBSD 8.0 | x86 32-bit | 8 | 8 | 8 |
Linux 3.2.0 | x86 64-bit | 8 | 8 | 8 |
Mac OS X 10.6.8 | x86 64-bit | 8 | 8 | 8 |
Solaris 10 | SPARC 64-bit | 8 | 4 | 8 |
Figure 3.4 Size in bytes of off_t for different platforms
注意,尽管你启动了64-bits file offset. 你是否能创建的文件大于 2 GB (231 - 1 bytes) 取决于 the underlying file system type
3.7 read 函数
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
Returns: number of bytes read, 0 if end of file, -1 on error
这里有几种情况,read 到真实数据的数量 小于要求的数量:
- 从 常规文件(regular file)中读, 例如 到文件末尾还有30个字节, 我们尝试读取100个字节,这时read就返回30, 下次再调用read, 它将返回0 (到文件末尾了)
- 从 terminal device 中读, 通常地, 一次读取一行(18章,可以看到如何改变这种默认行为)
- 从 network 中读
- 从 pipe 或 FIFO 中读
- 从 record-oriented device 中读
- 被信号中断了(interrupted)
3.8 write 函数
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);
Returns: number of bytes written if OK, -1 on error
通常引起错误的是: 磁盘满了,或者达到了文件的大小限制。
3.9 I/O Efficiency
3.5 程序: copy 一个文件,仅仅就是使用 read, write 函数。
#include "apue.h"
#define BUFFSIZE 4096
int
main(void)
{
int n;
char buf[BUFFSIZE];
while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
if (write(STDOUT_FILENO, buf, n) != n)
err_sys("write error");
if (n < 0)
err_sys("read error");
exit(0);
}
/**
*
[xingqiji@work78 fileio]$ ./mycat
ni hao
ni hao
hello world
hello world
^C
[xingqiji@work78 fileio]$
*
*/
Figure 3.6 Timing results for reading with different buffer sizes on Linux
在3.14部分,我们展示了 同步写(synchronous writes); 在5.8部分,我们比较 这些 unbuffered I/O 同 标准I/O 库(standard I/O library)
注意,当去测量 程序(read and write files)的性能时。操作系统将尝试去 缓存文件 incore。 所以 重复测量, 很可能结果比第一次的结果 好。 incore :含义是 in main memory。
3.6的测试报告,每次run都使用了不同的 file copy,目的是 当前run 不会找到 先前run 的缓存。
3.10 File Sharing 文件共享
unix 系统支持 在多个进程间共享 打开的文件。在描述dup之前需要先描述 sharing.
先来看看内核 关于I/O 用到的数据结构:
注意,如下的描述是 概念性的, 它可能,也可能不 匹配一个特定的实现。
内核使用3个数据结构 来代表 一个 打开的文件。
Figure 3.7 Kernel data structures for open files
注意: Linux 没有 v-node, 它使用 generic i-node , 尽管实现是不同的, v-node 和 generic i-node 概念上是相同的。
如果两个 独立的(independent)进程s 有相同的 file open, 我们能看到 3.8 的安排图
说明:每个进程都有一个它自己的 file table entry 的其中一个原因是: 使每个进程都有它自己的 current file offset。
看着这些数据结构,我们再来专门的讨论下,特定的操作下,会发生什么:
a: 在每个write完成后, 在file table entry 中的 the current file offset 会被增加 所写内容的字节数。如果这 引起 the current file offset 超过 current file size (i-node entry 中的),则the current file size 被设置到跟 the current file offset 一样。
b: 如果 打开一个文件时使用了O_APPEND flag, 对应的 这个flag信息被设置在 file table entry 的 file status flags 中。每次write完成后, 首先更新 i-node table entry 的 the current file size .
d: lseek 函数 仅仅修改 file table entry 中的 current file offset, 不是I/O 发生时 才做这样的事。
多个 file descriptor 对应 同一个 file table entry 是可能的, 这发生在 dup 函数, 这也发生在 fork 之后, 这时 parent 和 child 共享相同的 file table entry。
注意: file descriptor flags 和 file status flags 的作用域(scope) 是不同的。
3.11 Atomic Operations 原子操作
往文件中追加:
老版本unix 系统不支持 open的 O_APPEND 选项,所以程序看起来如下:
if (lseek(fd, 0L, 2) < 0) /* position to EOF */
err_sys("lseek error");
if (write(fd, buf, 100) != 100) /* and write */
err_sys("write error");
假定 有两个独立的进程: A, B, 正往同一个文件里追加。这种情况 和3.8中的图一样。
假定A 使用 lseek , 把进程的 the current offset 设置为 1500 (文件的末尾)。--》
然后内核切换进程,进程B 运行,B 然后 lseek,它也设置 the current offset 为 1500(文件的末尾)--》 B 然后write , 它 把B的current file offset 增加到 1600。由于file的size已经被 扩展,内核也会更新 在v-node 的 current file size 到 1600。---》然后 内核切换进程,假定进程A运行。A write , 这时他的 current file offset 是1500,这就会覆盖B写到文件中的数据。
出现这个问题的原因就是 定位到文件末尾 和 write 是两个函数。解决方案就是 时这两个操作变成 原子操作。多说一句,任何操作需要超过一个函数的 都不可能是 原子操作。
unix 系统提供了 进行这种原子操作的方式。就是使用O_APPEND flag . 这个会,在每次write前内核都会设置文件的位置,因此我们就不用在每次write之前都进行lseek了。
pread 和 pwrite 函数
unix 规范包含了 2个函数 ,他们允许 应用可以 seek 并且 执行I/O 是原子性地,他们就是:pread ,pwrite
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
Returns: number of bytes read, 0 if end of file, -1 on error
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
Returns: number of bytes written if OK, -1 on error
注意pread, pwrite : 当前的current file offset 不会被更新。
创建文件
检查文件的是否存在, (如果不存在就)创建文件。 如果不用原子操作,可能是如下的写法:
if ((fd = open(path, O_WRONLY)) < 0) {
if (errno == ENOENT) {
if ((fd = creat(path, mode)) < 0)
err_sys("creat error");
} else {
err_sys("open error");
}
}
问题会产生:在open和create之间可能会产生问题。如果这个文件在open 和create 之间被另一个进程创建,并且另一个进程往文件写了些内容, 则当本进程执行create后 数据就会被擦出。 解决方案是 测试文件的存在性 和创建文件 合并成一个原子操作。
3.12 dup ,dup2 函数
一个已存在的 file descriptor 可被 复制(duplicated ),使用如下两个函数:
#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);
Both return: new file descriptor if OK, -1 on error
dup(1) 后 新的 new file descriptor 和 fd(参数)共享相同的 file table entry. 如下图:
说明:
在这个图中,我们假定,进程执行了
newfd = dup(1);
我们假定 下一个可用的 descriptor 是 3 (这是很可能的,由于0,1,2 被shell打开了)。两个描述符 指向共同的 file table entry, 它们共享相同的 file status flags -read, write, append 等等,并且有相同的 current file offset.
每个描述符 有他自己的 file descriptor flags 集。 像在3.14部分描述的, 新的 file descriptor 的 close-on-exec file descriptor flag 总是在 dup 函数中 被 清除掉。
另一个可以 复制 描述符的函数是 fcntl 函数:
dup(fd); 等效于 fcntl(fd, F_DUPFD, 0);
类似地:dup2(fd, fd2); 等效于 close(fd2); fcntl(fd, F_DUPFD, fd2);
在最后一个例子,dup2 不是 完全精确地 和 close, 后跟fcntl 相同。不同的地方如下:
dup2 是原子操作,dup2 和 fcntl 的errno 是不同的。
3.13 sync, fsync, fdatasync 函数
在对硬盘进行 I/O 操作时,unix 系统 内核 通常都 使用 buffer cache 或 page cache 。当我们往file中写数据时,数据正常地 被copy 到 内核它自己的 缓冲区,并且 放到队列排队,等待着稍后被写到硬盘。这叫做 delayed write。
为了确保 在硬盘上的file 和 buffer cache中内容一致,提供了 sync, fsync, fdatasync 函数。
#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
Returns: 0 if OK, -1 on error
void sync(void);
sync 函数 简单queue 所有 被修改的 block buffers 来等着write 并且return; 它不会等到 write disk 才return 。
sync 正常被系统 daemon 周期地(通常 30秒) 调用。命令sync(1) 也调用sync 函数。
fsync 指向单个file, 由参数fd 来指定, 等待 硬盘write 完成 之后才 return。这个函数通常由应用使用,如果数据库,因为需要 确保 修改的blocks 被write 到disk。
fdatasync 和 fsync 类型,但它 影响一个文件的一部分。使用fsync, 文件的attributes也会被同步地更新。
3.14 fcntl 函数
fcntl 函数可以改变文件的properties 。
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* int arg */ );
Returns: depends on cmd if OK (see following), -1 on error
fcntl 函数被通常用于以下 5个不同的目的:
- 复制 一个已存在的描述符(cmd= F_DUPFD or F_DUPFD_CLOEXEC)
- Get/Set file descriptor flags ( cmd= F_GETFD or F_SETFD)
- Get/Set file status flags (cmd= F_GETFL or F_SETFL)
- Get/Set 异步I/O (asynchronous) ownership (cmd=F_GETOWN or F_SETOWN)
- Get/Set 记录锁(record locks) (cmd=F_GETLK, F_SETLK, or F_SETLKW)
先描述 11 cmd value的前8个。
F_DUPFD : 复制文件描述符 fd. 新的 文件描述符 被做为函数的返回值 返回。 它是 最小的且没有被open的数字,并且大于或等于 第三个参数。 新的文件描述符 和 fd 共享相同的 file table entry (参考3.9图)。 但新的描述符 有他自己的 file descriptor flags, 并且他的 F_CLOEXEC file descriptor flag 会被清除掉。
F_DUPFD_CLOEXEC: 复制文件描述符,并且设置 new descriptor 的 FD_CLOEXEC file descriptor flag 。
F_GETFD : 返回 fd的 file descriptor flags 。当前 仅仅只有一个 file descriptor flag 被定义就是: FD_CLOEXEC flag
F_SETFD : 设置 file descriptor flags . 由第三个参数指定。
F_GETFL 返回fd的 file status flags . 这些flags描述了打开了文件后的状态。他们含义如下列表:
File status flag | Description |
O_RDONLY | open for reading only |
O_WRONLY | open for writing only |
O_RDWR | open for reading and writing |
O_EXEC | open for execute only |
O_SEARCH | open directory for searching only |
O_APPEND | append on each write |
O_NONBLOCK | nonblocking mode |
O_SYNC | wait for writes to complete (data and attributes) |
O_DSYNC | wait for writes to complete ( data only) |
O_RSYNC | synchronize reads and writes |
O_ASYNC | asynchronous I/O (FreeBSD and Mac OS X only) |
O_FSYNC | wait for writes to complete (FreeBSD and Mac OS X only) |
不幸的,5个 access-mode 是相互独立的。 一个文件仅仅只能启用 他们中的一个。因此我们必须使用 O_ACCMODE mask 去获取 access-mode bits 然后 得到的结果和这五个值进行对比。
F_SETFL : 设置 file status flags, 只有这些flags可以被改变,这些flags:O_APPEND, O_NONBLOCK, O_SYNC, O_DSYNC, O_RSYNC, O_FSYNC, O_ASYNC.
F_GETOWN : 收到 SIGIO, SIGURG 信号,Get 进程ID 或进程组 ID。14.5.2会描述。
F_SETOWN : 设置 进程ID, 进程组ID, 为 收到SIGIO, SIGURG 信号 。正数arg 是进程ID, 负数 arg 应用于进程组ID。
返回什么取决于 command, 发生错误返回-1 ,如果OK 返回其他 值。
F_DUPFD 返回 new file descriptor,
F_GETFD, F_GETFL, 返回对应的 flags,
F_GETOWN 返回 正数 进程ID, 负数 进程组ID.
例子:
接收一个命令行参数, 这个参数是fd, 打印 fd的 file flags。
fileio/fileflags.c
1.#include "apue.h"
2.#include <fcntl.h>
3.
4.int
5.main(int argc, char *argv[])
6.{
7. int val;
8.
9. if (argc != 2)
10. err_quit("usage: a.out <descriptor#>");
11.
12. if ((val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0)
13. err_sys("fcntl error for fd %d", atoi(argv[1]));
14.
15. switch (val & O_ACCMODE) {
16. case O_RDONLY:
17. printf("read only");
18. break;
19.
20. case O_WRONLY:
21. printf("write only");
22. break;
23.
24. case O_RDWR:
25. printf("read write");
26. break;
27.
28. default:
29. err_dump("unknown access mode");
30. }
31.
32. if (val & O_APPEND)
33. printf(", append");
34. if (val & O_NONBLOCK)
35. printf(", nonblocking");
36. if (val & O_SYNC)
37. printf(", synchronous writes");
38.
39.#if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC) && (O_FSYNC != O_SYNC)
40. if (val & O_FSYNC)
41. printf(", synchronous writes");
42.#endif
43.
44. putchar('\n');
45. exit(0);
46.}
47.
48./**
49. *
50. [xingqiji@bogon fileio]$ ./fileflags 0
51.read write
52.[xingqiji@bogon fileio]$ ./fileflags 0 < /dev/tty
53.read only
54.[xingqiji@bogon fileio]$ ./fileflags 1
55.read write
56.[xingqiji@bogon fileio]$ ./fileflags 1 > temp.foo
57.[xingqiji@bogon fileio]$ cat temp.foo
58.write only
59.[xingqiji@bogon fileio]$ ./fileflags 2 2>> temp.foo
60.write only, append
61.[xingqiji@bogon fileio]$ ./fileflags 5 5<> temp.foo
62.read write
63.[xingqiji@bogon fileio]$
64.
65.5<>temp.foo 在描述符5 打开 temp.foo ,以 reading ,writing
66. *
*/
例子: 当修改 file descriptor flags 或 file status flags ,要 当心。
3.12 图 打开一个或多个 file status flags
fileio/setfl.c
1.#include "apue.h"
2.#include <fcntl.h>
3.
4.void
5.set_fl(int fd, int flags) /* flags are file status flags to turn on */
6.{
7. int val;
8.
9. if ((val = fcntl(fd, F_GETFL, 0)) < 0)
10. err_sys("fcntl F_GETFL error");
11.
12. val |= flags; /* turn on flags */
13.
14. if (fcntl(fd, F_SETFL, val) < 0)
15. err_sys("fcntl F_SETFL error");
16.}
对应的 关闭flags:
val &= ~flags;
3.15 ioctl 函数
#include <unistd.h> /* System V */
#include <sys/ioctl.h> /* BSD and Linux */
int ioctl(int fd, int request, ...);
Returns: -1 on error, something else if OK
3.16 /dev/fd
新的系统 提供了 一个目录 叫 /dev/fd 它的entries 是 0, 1,2 等等。打开 /dev/fd/n 等效于 复制描述符 n, (假定 n 是open).
fd = open(“/dev/fd/0”, mode);
注意,mode 是 原始的0 的打开模式的 子集。
例如: 0 被打开 read-only。 即便是如下:
fd = open(“/dev/fd/0”, O_RDWR);
成功后,仍然不能 write to fd.
Linux 对 /dev/fd 的实现 是异类。它映射 file descriptors 到 symbolic links (它指向底层的物理文件)。
也可以在create中 通过指定 O_CREAT ,pathname参数是 /dev/fd 来 创建。但是注意!在Linux ,因为Linux 实现 使用 symbolic 链接到 real files, 使用create 参数是/dev/fd 将导致 底层的文件 被 truncated.
一些系统 提供了 pathnames 如: /dev/stdin, /dev/stdout, /dev/stderr.
这些 等效于 /dev/fd/0, /dev/fd/1, /dev/fd/2
/dev/fd 的用途主要是 shell在使用, 例如:
filter file2 | cat file1 - file3 | lpr // - 代表 standard input
如果 支持 /dev/fd ,则可以如下写:
filter file2 | cat file1 /dev/fd/0 file3 | lpr