File I/O (unbufferd) -APUE第三版

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个可能:

  1. path  参数 是 绝对路径, 这种情况 fd 参数 被忽略, openat的 函数行为就像 open 函数。
  2.  path 参数 指定了 相对路径名,并且 fd参数 是一个文件描述符它指定了  文件系统中pathname 参考的starting location .  fd  可以通过打开 目录(pathname 的参考目录)来获取。
  3. 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 到真实数据的数量 小于要求的数量:

  1. 从 常规文件(regular file)中读, 例如 到文件末尾还有30个字节, 我们尝试读取100个字节,这时read就返回30, 下次再调用read, 它将返回0 (到文件末尾了)
  2. 从 terminal device 中读, 通常地, 一次读取一行(18章,可以看到如何改变这种默认行为)
  3.  从 network 中读
  4.  从 pipe  或 FIFO 中读
  5. 从 record-oriented device 中读
  6.  被信号中断了(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个不同的目的:

  1. 复制 一个已存在的描述符(cmd= F_DUPFD or F_DUPFD_CLOEXEC)
  2. Get/Set file descriptor flags ( cmd= F_GETFD or  F_SETFD)
  3. Get/Set file status flags (cmd= F_GETFL or F_SETFL)
  4.  Get/Set 异步I/O (asynchronous) ownership (cmd=F_GETOWN  or F_SETOWN)
  5.  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

3.17 Summary

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值