| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 系统运维 -> File I/O (unbufferd) -APUE第三版 -> 正文阅读 |
|
[系统运维]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_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个可能:
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
正常地?一个文件的当前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
为了证明在这个文件中确实有一个?hole, 让我们比较这个文件?同?我们刚刚用相同size创建的文件。 ls?-ls file.hole ?file.nohole ? 说明分析: 尽管两个文件有相同的size,没有空洞的文件消费?20 disk blocks, 有空洞的文件仅仅消费?8 disk blocks.
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?到真实数据的数量?小于要求的数量:
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?Efficiency3.5 程序: copy 一个文件,仅仅就是使用?read, write 函数。
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个不同的目的:
先描述?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描述了打开了文件后的状态。他们含义如下列表:
不幸的,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
例子: 当修改?file?descriptor flags 或?file?status?flags ,要?当心。 3.12 ?图?打开一个或多个?file status flags fileio/setfl.c
对应的?关闭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 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/16 5:43:15- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |