APUE——Chapter 3:文件I/O

2024-05-06 14:58
文章标签 chapter apue

本文主要是介绍APUE——Chapter 3:文件I/O,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

open、read、write、lseek、close。

本章所说明的函数被称为不带缓冲的I/O(unbuffered I/O)。不带缓冲:指的是每个read和write都调用内核中的一个系统调用。这些不带缓冲的I/O函数是POSIX.1和Single UNIX Specification的组成部分。

所谓不带缓冲并不是指内核不提供缓冲,而是只单纯的系统调用,不是函数库的调用。系统内核对磁盘的读写都会提供一个块缓冲(在有些地方也被称为内核高速缓存),当用write函数对其写数据时,直接调用系统调用,将数据写入到块缓冲进行排队,当块缓冲达到一定的量时,才会把数据写入磁盘。因此所谓的不带缓冲的I/O是指进程不提供缓冲功能(但内核还是提供缓冲的)。每调用一次write或read函数,直接系统调用。

带缓冲的I/O是指进程对输入输出流进行了改进,提供了一个流缓冲,当用fwrite函数网磁盘写数据时,先把数据写入流缓冲区中,当达到一定条件,比如流缓冲区满了,或刷新流缓冲,这时候才会把数据一次送往内核提供的块缓冲,再经块缓冲写入磁盘。(双重缓冲)

因此,带缓冲的I/O在往磁盘写入相同的数据量时,会比不带缓冲的I/O调用系统调用的次数要少。

只要涉及在多个进程间共享资源,原子操作的概念就非常重要。

dup、fcntl、sync、fsync、ioctl。


  • 文件描述符
#include <unistd.h>/* Standard file descriptors.  */
#define STDIN_FILENO    0   /* Standard input.  */
#define STDOUT_FILENO   1   /* Standard output.  */
#define STDERR_FILENO   2   /* Standard error output.  */

文件描述符的变化范围是0~OPEN_MAX。


3.1 open函数

打开或创建一个文件

#include <fcntl.h>int open(const char *pathname, int oflag, .../* mode_t mode */);/* 返回值:若成功则返回文件描述符,若出错则返回-1 */
oflag参数:O_RDONLY         只读
O_WRONLY         只写
O_RDWR           读、写
O_APPEND         追加
O_CREAT          若不存在,则创建
O_EXCL           如果同时指定了O_CREAT,而文件已经存在,则出错
O_TRUNC          如果此文件存在,而且为只读或者读写成功打卡,则将其长度截断为0
O_NOCTTY         如果pathname指的是终端设备,则不将该设备分配作为此进程的控制终端
O_NONBLOCK       非阻塞模式
O_DSYNC
O_RSYNC
O_SYNC

PATH_MAX、NAME_MAX。常量_POSIX_NO_TRUNC决定了是要截断过长的文件名或路径名,还是返回一个出错(errno设置为ENAMETOOLONG)。

3.2 creat函数

创建一个新文件

#include <fcntl.h>int creat(const char *pathname, mode_t mode);/* 返回值:若成功则返回为只写打开的文件描述符,若出错则返回-1 */

此函数等效于:

open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);

3.3 close函数

关闭一个打开的文件

#include <unistd.h>int close(int fd);/* 返回值:若成功则返回0,若出错则返回-1 */

关闭一个文件时,还会释放该进程加在该文件上的所有记录锁。

当一个进程终止时,内核自动关闭它所有打开的文件。

3.4 lseek函数

当前文件偏移量(current file offset),用以度量从文件开始出计算的字节数。按系统默认的情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0。

调用lseek显式地为一个打开的文件设置其偏移量。

#include <unistd.h>off_t lseek(int fd, off_t offset, int whence);/* 返回值:若成功则返回新的文件偏移量,若出错则返回-1 */

whence参数:SEEK_SET、SEEK_CUR、SEEK_END。

lseek仅将当前的文件偏移量记录在内核中,它并不引起任何I/O操作。

文件偏移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将加长该文件,并在文件中构建一个空洞位于文件中但没有写过的字节都被读为0

文件的空洞并不要求在磁盘上占用存储区。具体处理方式与文件系统的实现有关。当定位超出文件尾端之后写时,对于新写的数据需要分配磁盘块,但是对于原文件尾端和新开始写位置之间的部分则不需要分配磁盘块。

3.5 read函数

调用read函数从打开文件中读数据。

#include <unistd.h>ssize_t read(int fd, void *buf, size_t nbytes);/* 返回值:若成功则返回读到的字节数,若已到文件结尾则返回0,若出错则返回-1 */

有多种情况可使实际读到的字节数少于要求读的字节数:
1. 读普通文件时,在读到的要求字节数之前已达到了文件尾端。
2. 从终端设备读时,通常一次最多读一行。
3. 当从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数。
4. 当从管道或从FIFO读时,如若管道包含的字节少于所需要的字节数,那么read将只返回实际可用的字节数。
5. 当从某些面向记录的设备(如磁带)读时,一次最多返回一个记录。
6. 当某一信号造成中断,而已经读了部分数据量时。

3.6 write函数

调用write函数向打开的文件中写数据。

#include <unistd.h>ssize_t write(int fd, const void *buf, size_t nbytes);/* 返回值:若成功则返回已写的字节数,若出错则返回-1 */

3.7 文件共享

内核使用三种数据结构表示打开的文件,分别是文件描述符表、文件表 V 节点表。它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。

(1)每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,每个描述符占用一项。与每个文件描述符相关联的是:

(a) 文件描述符标志。
(b) 指向一个文件表项的指针。

(2)内核为所有打开文件维持一张文件表。每个文件表项包含:

(a) 文件状态标志(读、写、添写、同步和非阻塞等)。
(b) 当前文件偏移量。
(c) 指向该文件 V 节点表项的指针。

(3)每个打开文件(或设备)都有一个 v 节点(v-node)结构。v 节点包含了文件类型和对此文件进行各种操作的函数的指针。v 节点还包含了从磁盘读取的 i 节点(i-node)的信息,i 节点信息包含了文件的所有者、文件长度、文件所在的设备、指向文件的实际数据块在磁盘上所在位置的指针等。

打开文件描述符表可存放在用户空间,而非进程表中。

下图显示了一个进程的三张表之间的关系。该进程有两个不同的打开文件,一个文件打开为标准输入(文件描述符为 0),另一个打开为标准输出(文件描述符为 1)。

这里写图片描述

如果两个独立进程各自打开了同一个文件,则有下图所示的安排。

这里写图片描述

1)每个进程都有自己的文件表项的一个理由:这种安排使每个进程都有它自己的对该文件的当前偏移量。
2)可能有多个文件描述符项指向同一文件表项。dup函数如此。fork也会发生,父、子进程对于每一个打开文件描述符共享同一个文件表项。
3)文件描述符标志只用于一个进程的一个描述符; 文件状态标志则适用于指向该给定文件表项的任何进程中的所有描述符。

补充说明:(考虑下节要说的原子操作)

  • write后,当前文件偏移量增加所写字节数。若此时当前文件偏移量超过了当前文件长度,则在i节点表项中的当前文件长度被设置为当前文件偏移量。
  • 如果用O_APPEND标志打开了一个文件,则相应的标志也被设置到文件表项的文件状态标志中。每次对这种具有填写标志的文件执行写操作时,在文件表项中的当前文件偏移量首先被设置为i节点表项中的文件长度。
  • 若一个文件用lseek定位到文件当前的尾端,则文件表项中的当前文件偏移量被设置为i节点表项中的当前文件长度。
  • lseek函数只修改文件表项中的当前文件偏移量,没有进行任何实际IO操作。

3.8 原子操作

UNIX系统提供了一种方法:在打开文件时设置O_APPEND标志。使内核每次对这种文件写之前,都将进程的当前偏移量设置到该文件的尾端处。

原子性地定位搜索(seek)和执行I/O:

#include <unistd.h>ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);/* 返回值:读到的字节数,若已到文件结尾则返回0,若出错则返回-1 */
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);/* 返回值:若成功则返回已写的字节数,若出错则返回-1 */

pread相当于顺序调用lseek和read。

  • 调用pread时,无法中断其定位和读操作。
  • 不更新文件指针

一般而言,原子操作(atomic operation)指的是由多步组成的操作。如果该操作原子地执行,则要么执行完所有的步骤,要么一步也不执行,不可能执行所有步骤中的一个子集。

3.9 dup和dup2函数

复制一个现存的文件描述符。

#include <unistd.h>int dup(int filedes);
int dup2(int filedes, int filedes2);/* 返回值:若成功则返回新的文件描述符,若出错则返回-1 */

dup返回的新文件描述符一定是当前可用文件描述符中最小数值。
dup2则可以用filedes2参数指定新描述符的数值。如果filedes2已经打开,则先将其关闭。若filedes等于filedes2,则dup2返回filedes2,而不关闭它。

这些函数返回的新文件描述符与参数filedes共享一个文件表项。

执行:
newfd = dup(1);
则fd 1和fd 3共享同一文件表项。

这里写图片描述

调用 dup(filedes);
等效于 fcntl(filedes, F_DUPFD, 0);

调用 dup2(filedes, filedes2);
等效于 close(filedes2); fcntl(filedes, F_DUPFD, filedes);

dup2是一个原子操作。dup2和fcntl有些不同的errno。

3.10 sync、fsync和fdatasync函数

传统的UNIX实现在内核中设有缓冲区高速缓存或者页面高速缓存,大多数磁盘IO都通过缓冲进行。当将数据写入文件时,内核通常先将该数据复制到其中一个缓冲区中,如果该缓冲区未写满,则并不将其排入输出队列,而是等待其写满或者当内核需要重用该缓冲区以便存放其他磁盘块数据时,再将该缓冲排入输出队列,然后等待其到达队首,才进行实际的IO操作。这种方式被称为延迟写(delayed write)。

延迟写减少了磁盘读写次数,但是却降低了文件内容的更新速度,使得欲写到文件中的数据在一段时间内并没有写到磁盘上。当系统发生故障时,这种延迟会造成文件更新内容的丢失。为了保证磁盘上实际文件系统与缓冲区告诉缓存中的内容的一致性,UNIX系统提供了sync、fsync和fdatasync三个函数。

#include<unistd.h>int fsync(int filedes);
int fdatasync(int filedes);/* 返回值:若成功则返回0,若出错则返回-1 */
void sync(void);

sync函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。

fsync函数只对有filedes指定的单一文件起作用,并且等待写磁盘操作结束,然后返回。

fdatasync函数类似于fsync,但它只影响文件的数据部分。而除了数据部分,fsync还会同步更新文件的属性。

3.11 fcntl函数

fcntl函数可以改变已打开的文件的性质。

#include <fcntl.h>int fcntl(int fd, int cmd, .../* int arg */);/* 返回值:若成功则依赖于cmd,若出错则返回-1 */

fcntl函数有5种功能:
(1)复制一个现有的文件描述符(cmd=F_DUPFD)
(2)获得或设置文件描述符标记(cmd=F_GETFD或F_SETFD)
(3)获得或设置文件状态标志(cmd=F_GETFL或F_SETFL)
(4)获得或者设置异步IO所有权(cmd=F_GETOWN或F_SETOWN)
(5)获得或设置记录锁(cmd=F_GETLK或F_SETLK、F_SETLKW)

F_DUPFD    复制文件描述符filedes。新文件描述符作为函数值返回。它是未打开的各描述符中大于或等于第三个参数值中各值的最小值新描述符与filedes共享同一文件表项。但是,新文件描述符有它自己的一套文件描述符标志,其FD_CLOEXEC文件描述符标志被清除。
F_GETFD    对应于filedes的文件描述符标志作为函数值返回。当前只定义了一个文件描述符标志FD_CLOEXEC。
F_SETFD    对于filedes设置文件描述符标志。新标志按第三个参数设置。
F_GETFL    对应于filedes的文件状态标志作为函数值返回。在说明open函数时,已说明了文件状态标志。

这里写图片描述

F_SETFL    将文件状态标识设置为第三个参数值。
F_GETOWN   取当前接受SIGIO和SIGURG信号的进程ID或进程组ID。
F_SETOWN   设置接受SIGIO和SIGURG信号的进程ID或者进程组ID

返回值:
如果出错,所有命令都返回-1。
F_DUPFD:返回新的文件描述符。
F_GETFD、F_GETFL:返回相应标志。
F_GETOWN:返回一个正的进程ID或负的进程组ID

3.12 ioctl函数

ioctl函数是I/O操作的杂物箱。

#include <unistd.h>        /* System V */
#include <sys/ioctl.h>     /* BSD and Linux */
#include <stropts.h>       /* XSI STREAMS */
#include <termios.h>int ioctl(int filedes, int request, ...)/* 返回值:若出错则返回-1,若成功则返回其他值 */

每个设备驱动程序都可以定义它自己专用的一组ioctl命令。系统则为不同种类的设备提供通用的ioctl命令。

这里写图片描述

3.13 /dev/fd

其目录项是名为0、1、2等的文件。打开文件/dev/fd/n等效于复制描述符n(假定n是打开的)。

这篇关于APUE——Chapter 3:文件I/O的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/964657

相关文章

Chapter 13 普通组件的注册使用

欢迎大家订阅【Vue2+Vue3】入门到实践 专栏,开启你的 Vue 学习之旅! 文章目录 前言一、组件创建二、局部注册三、全局注册 前言 在 Vue.js 中,组件是构建应用程序的基本单元。本章详细讲解了注册和使用 Vue 的普通组件的两种方式:局部注册和全局注册。 本篇文章参考黑马程序员 一、组件创建 ①定义 Vue 组件是一种具有特定功能的 Vue 实

Chapter 10 Stability and Frequency Compensation

Chapter 10 Stability and Frequency Compensation Chapter 8介绍了负反馈, 这一章介绍稳定性, 如果设计不好, 负反馈系统是要发生震荡的. 首先我们学习理解稳定判断标准和条件, 然后学习频率补偿, 介绍适用于不同运放的补偿方式, 同时介绍不同补偿对两级运放slew rate的影响, 最后介绍Nyquist’s判断标准 10.1 Gener

[学习笔记]《CSAPP》深入理解计算机系统 - Chapter 3 程序的机器级表示

总结一些第三章的一些关键信息 Chapter 3 程序的机器级表示结构 updating... Chapter 3 程序的机器级表示 局部变量通常保存在寄存器中,而不是内存中,访问寄存器比内存快的多. 有些时候,局部数据必须存放在内存中, 寄存器不足够存放所有的本地数据对一个局部变量使用地址运算符 &, 因此必须能够为它产生一个地址某些局部变量是数组或结构,因此必须能够通过数组或

Chapter 10 async函数 await关键字

欢迎大家订阅【Vue2+Vue3】入门到实践 专栏,开启你的 Vue 学习之旅! 文章目录 前言一、async 函数二、await 关键字 前言 在现代 JavaScript 开发中,异步编程是一个重要的概念。随着 ES2017 的引入,async 函数和 await 关键字为处理异步操作提供了更简洁和可读的方式。本章详细讲解了这两个关键字的特性及其用法。 一、

Chapter 2 multi-armed Bandit

引用:https://blog.csdn.net/mmc2015/article/details/51247677 https://blog.csdn.net/coffee_cream/article/details/58034628 https://blog.csdn.net/heyc861221/article/details/80129310   The most importa

第三章 少量(无)标记增强现实——Chapter 3:Marker-less Augmented Reality

注释: 1、翻译书名:Mastering OpenCV with Practical Computer Vision Projects 2、翻译章节:Chapter 3:Marker-less Augmented Reality 3、电子书下载,源代码下载,请参考:http://blog.csdn.net/raby_gyl/article/details/11617875 4、本章程序

Unix环境高级编程开篇-apue.h配置

书就不多说了,被称为Unix下C编程的圣经;不过现在国内貌似部分人都喜欢向别人推荐书,我很怀疑着部分人是不是推荐的每一本都看过。这个我暂时也不敢推荐,因为我也没有看完。 这本书上几乎所有的代码都用到了作者编程的一个头文件:apue.h,但是这个不是ISO C自带的,所以需要配置一下。 我用的这本书是第三版,第三版,第三版 重要的事情说三遍 1:先去这本书的官网把源代码下载下来,传送门 2:

Focus On 3D Terrain Programming·Chapter 5(1)

写在前面的话 原著:《Focus On 3D Terrain Programming》 章节:Chapter5 Geomipmapping for the CLOD Impaired 说明:图形渣、英语渣,学习的同时记录下来的(主要是不翻译就读不下去TT),希望能和大家相互交流学习,翻译有误的地方欢迎指出^^ 正文开始↓ 哦吼!你将要学习地形编程中的核心内容,里面包含了相当复杂的算法。实

Chapter 07 watch侦听器

欢迎大家订阅【Vue2+Vue3】入门到实践 专栏,开启你的 Vue 学习之旅! 文章目录 前言一、基本用法二、深度侦听 前言 在 Vue 中,watch 侦听器是一个非常实用的工具,用于处理自定义数据的变化。本文详细讲解了 watch 侦听器的基本用法、深度侦听和常见应用场景。 一、基本用法 ①定义 watch 侦听器用于观察 Vue 实例上的数据变更。当被观察