1 引言

本章首先讨论Unix系统中大多数文件I/O最常用的5个系统函数:open、read、write、lseek以及close。

本章所说明的函数又被成为不带缓冲的I/O,不带缓冲是指每个read和write都调用内核中的一个系统调用。

接着讨论原子操作的概念,只要涉及到多个进程之间共享资源,原子操作相当重要。

最后,本章将进一步讨论在多个进程之间共享文件以及所涉及的内核数据结构,之后将说明dup、fcntl、sync、fsync和ioctl函数。

2 文件描述符

对于内核而言,所有的打开的文件都由文件描述符引用。文件描述符是一个非负整数。Unix系统会按照惯例把下列描述符和系统输入相关联:

0 标准输入 STDIN_FILENO
1 标准输出 STDOUT_FILENO
2 标准错误输出 STDERR_FILENO

3 open、create、close函数

#include <fcntl.h>

int open(const char *pathname, int oflag, ... /*mode_t mode*/);
/*返回:成功返回文件描述符,出错返回-1*/

调用open函数可以打开或者创建一个文件。对于open函数而言,仅当创建新文件时才使用最后一个参数,oflag来说明文件选项:

(1)打开方式(必选)

O_RDONLY 以只读方式打开
O_WRONLY 以只写方式打开
O_RDWR 以读写方式打开

(2)可选项

O_APPEND 每次写时都追加到文件的尾端
O_CREAT 若文件不存在则创建,若文件存在则返回错误。使用此选项时,需要第三个参数mode来指定新文件的访问权限位
O_EXCEL 测试文件是否存在,一般与O_CREAT搭配使用
O_TRUNC 若文件存在,且为只写或读写打开,则将其长度截短为0
O_NONBLOCK 如果pathname指的是一个FIFO、一个块特殊文件或者是一个字符特殊文件,则此选项为文件的本次打开和后续的I/O操作设置为非阻塞模式
……  

由open返回的文件描述符一定是最小的未用描述符数值。

也可调用一个creat创建一个新文件。

#include <fcntl.h>

int creat(const char *pathname, mode_t mode);
/*返回:成功返回只写打开的文件描述符,出错返回-1*/

此函数等同于open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);

调用close函数关闭一个打开的文件。关闭一个文件还会释放该进程加在该文件上的所有记录锁。

#include <unistd.h>

int close(int fd)
/*返回:成功返回0,失败返回-1*/

4 lseek函数

每个打开的文件都有一个与其相关联的文件偏移量offset。它通常是一个非负整数,用以度量从文件开始处计算的字节数。通常读写操作从当前文件偏移量开始,并使偏移量增加读写的字节数。当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被默认设置为0。

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

#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);
/*返回:成功返回新的偏移量,出错返回-1*/

对参数offset的解释与参数whence的值有关,若whence的值是:

SEEK_SET 将该文件偏移量设置为距文件开始处offset个字节
SEEK_CUR 当前偏移量加offset,offset可正可负
SEEK_END 文件长度加offset,offset可正可负

例如,获取当前偏移量:cur_pos = lseek(fd, 0, SEEK_CUR);

若文件描述符引用的是一个管道、FIFO或网络套接字,则lseek返回-1,并将errno设置为ESPIPE。

文件偏移量可以是负值,但对于普通文件其偏移量必须是非负子,因此在比较lseek的返回值时应谨慎,不要测试它是否小于0,而是测试它是否等于-1.

文件偏移量可以大于当前文件长度,只是这样做会在文件中形成一个空洞,位于文件中但没有写过的字节都被读为0,文件的空洞并不占用磁盘存储空间。

lseek只修改文件表项的当前文件偏移量,不进行任何I/O操作。

5 read、write函数

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t bytes);
ssize_t write(int fd, const void *buf, size_t bytes);
/*以上函数返回:成功返回读(写)到的字节数,若已到文件尾返回0,出错返回-1*/

有多种情况使得实际读到的字节数小于要求的字节数:

  • 读普通文件时,在读到要求的字节数之前就到达了文件尾端
  • 从终端设备读取,通常只能一次读一行
  • 从网络设备、FIFO或管道读取,设备的缓冲小于要求的字节数
  • 当某一设备中断,而已经读了部分数据量时
  • ……

write函数的返回值通常与参数nbytes的值相同,否则表示出错。对于普通文件,写操作从文件的当前偏移量开始。

6 文件共享

Unix系统支持在不同的进程之间共享打开的文件。内核使用三种数据结构表示打开的文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响,如下图所示:

(1)每个进程在进程表中都有一个记录项,记录项中包含有一张打开的文件描述符表。与每个文件描述符相关联的是:文件描述符标志(close_on_exec)和指向一个文件表项的指针。

(2)内核为所有打开文件维护一张文件表。每个文件表包含:文件状态标志(读、写、添写、同步、非阻塞等)、当前文件偏移量和指向该文件的v节点表项指针。

(3)每个打开的文件(或设备)都有一v节点结构。v节点包含了文件类型和对文件进行各种操作的函数指针。对于大多数文件,v节点还包含了该文件的i节点。

给出了数据结构后,对前面描述的操作进行进一步的说明:

  • 在完成每个write之后,在文件表项中的当前文件偏移量即增加的字节数。如果使当前文件偏移量超过了当前文件长度,那么i节点表项的当前文件长度被设置为当前文件偏移量(文件被加长了)
  • 如果用O_APPEND选项打开了一个文件,则相应的标志也被设置到文件表项的文件状态标志中。此时每次进行写操作时,文件表项中的当前文件偏移量首先被设置为i节点里的当前文件长度。这就使得每次写的数据都能添加到文件的尾部。
  • 如果一个文件用lseek定位到文件当前的尾端,则文件表项中的当前文件偏移量被设置为i节点里的当前文件长度(但是与O_APPEND选项打开文件时不同的)。
  • lseek函数只修改文件表项的当前文件偏移量,不进行任何I/O操作

两个独立的进程打开同一个文件:

注意,文件描述符标志和文件状态标志在作用域方面的区别,前者只用于一个进程的一个描述符,而后者则适用于指向该给定文件表项的任何进程中的所有描述符。

7 原子操作

之前描述的方法对多个进程读取同一个文件都能正确工作,因为每个进程都有自己的文件表项和当前文件偏移量。但是多个进程写一个文件时,可能产生意想不到的后果。为了避免这种情况,需要理解原子操作的概念。

原子操作是指不会被线程调度打断的操作,因此任何一个需要多个函数调用的操作都不可能是原子操作。

#include <unistd.h>

ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);

调用pread和pwrite相当于顺序调用lseek、read或write,但是他们是原子操作:

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

8 dup、dup2函数

#include <unistd.h>

int dup(int fd);
int dup2(int fd, int fd2);
/*返回:成功返回新的描述符,失败返回-1*/

dup和dup2函数用来复制一个现有的文件描述符。

dup返回值是当前可用的文件描述符的最小值,而dup2可以用fd2指定新的文件描述符。若fd2已经打开,则将其关闭,若fd2==fd,则dup2返回fd2。

正如之前描述的那样,dup和dup2函数返回的新的文件描述符和参数fd共享同一个文件表项。

复制一个文件描述符的另一个方法是使用fcntl函数:

dup(fd); 等效于 fcntl(fd, F_DUPFD, 0)

dup2(fd, fd2); 等效于 close(fd2); fcntl(fd, F_DUPFD, fd2);

它们之间的区别是:

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

9 sync、fsync、fdatasync函数

#include <unistd.h>

int sync(void);
int fsync(int fd);
int fdatasync(int fd);
/*以上函数返回:成功返回0,出错返回-1*/

Unix内核中一般设有缓存区,大多数磁盘I/O都通过缓冲进行,这样能减少磁盘读写次数。

sync只是将所有修改过的缓冲写入队列,然后就返回,不等待实际磁盘操作结束。通常称为update的系统守护进程会定期的调用sync函数。

fsync只对文件描述符fd指向的单一文件起作用,并等待磁盘操作结束后返回。

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

10 fcntl函数

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

#include <fcntl.h>

int fcntl(int fd, int cmd, .../*int arg*/);
/*返回:成功返回依赖于cmd,失败返回-1*/

fcntl函数有5种功能,取决于cmd参数:

F_DUPFD 复制一个现有的描述符
F_GETFD/F_SETFD 获得/设置文件描述符标记
F_GETFL/F_SETFL 获得/设置文件状态标志
F_GETOWN/F_SETOWN 获得/设置异步I/O所有权
F_GETLK、F_SETLK或F_SETLKW 获得/设置文件记录锁

APUE学习笔记2——文件I/O的更多相关文章

  1. APUE学习笔记3_文件IO

    APUE学习笔记3_文件IO Unix中的文件IO函数主要包括以下几个:open().read().write().lseek().close()等.这类I/O函数也被称为不带缓冲的I/O,标准I/O ...

  2. APUE学习笔记3——文件和目录

    1 简介 之前学习了执行I/O操作的基本函数,主要是围绕普通文件I/O的打开.读或写.下面继续学习Unix文件系统的其他特征和文件的基本性质.我们将从stat函数开始,了解stat结构所代表的文件属性 ...

  3. APUE 学习笔记(三) 文件和目录

    1. 文件类型 文件类型信息包含在 struct stat 里的 st_mode 成员 (1)普通文件,unix内核并不区分文本文件和二进制文件 (2)目录文件,这种文件包含了其他文件的名字以及指向这 ...

  4. APUE 学习笔记(二) 文件I/O

    1. 文件I/O 对于内核而言,所有打开的文件都通过文件描述符引用,内核不区分文本文件和二进制文件 open函数:O_RDONLY  O_WRONLY  O_RDWR create函数: close函 ...

  5. Linux系统学习笔记:文件I/O

    Linux支持C语言中的标准I/O函数,同时它还提供了一套SUS标准的I/O库函数.和标准I/O不同,UNIX的I/O函数是不带缓冲的,即每个读写都调用内核中的一个系统调用.本篇总结UNIX的I/O并 ...

  6. SpringMVC:学习笔记(8)——文件上传

    SpringMVC--文件上传 说明: 文件上传的途径 文件上传主要有两种方式: 1.使用Apache Commons FileUpload元件. 2.利用Servlet3.0及其更高版本的内置支持. ...

  7. MySQL学习笔记-数据库文件

    数据库文件 MySQL主要文件类型有如下几种 参数文件:my.cnf--MySQL实例启动的时候在哪里可以找到数据库文件,并且指定某些初始化参数,这些参数定义了某种内存结构的大小等设置,还介绍了参数类 ...

  8. Django:学习笔记(8)——文件上传

    Django:学习笔记(8)——文件上传 文件上传前端处理 本模块使用到的前端Ajax库为Axio,其地址为GitHub官网. 关于文件上传 上传文件就是把客户端的文件发送给服务器端. 在常见情况(不 ...

  9. APUE学习笔记——10.9 信号发送函数kill、 raise、alarm、pause

    转载注明出处:Windeal学习笔记 kil和raise kill()用来向进程或进程组发送信号 raise()用来向自身进程发送信号. #include <signal.h> int k ...

随机推荐

  1. 创建一个dynamics CRM workflow (四) - Development of Custom Workflows

    首先我们需要确定windows workflow foundation 已经安装. 创建之后先移除MyCustomWorkflows 里面的 Activity.xaml 从packages\Micro ...

  2. 前端开发app

    1.如果是 Angular 那就选 Ionic (一对好 CP) 2.如果是 Vue 那就选 Vux (基于 WeUI)3.如果是 jQuery 那就选 Framework7 (iOS 和 Andro ...

  3. background及background-size

    background有以下几种属性: background-color background-position background-size background-repeat background ...

  4. TensorFlow+实战Google深度学习框架学习笔记(12)------Mnist识别和卷积神经网络LeNet

    一.卷积神经网络的简述 卷积神经网络将一个图像变窄变长.原本[长和宽较大,高较小]变成[长和宽较小,高增加] 卷积过程需要用到卷积核[二维的滑动窗口][过滤器],每个卷积核由n*m(长*宽)个小格组成 ...

  5. JS防抖与节流

    在进行窗口的resize.scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕.此时我们可以采用debounce(防抖)和throttle( ...

  6. 【LibreOJ 6277】数列分块入门 1 (分块)

    emmm-学下分块~ 区间:数列中连续一段的元素 区间操作:将某个区间[a,b]的所有元素进行某种改动的操作 块:我们将数列划分成若干个不相交的区间,每个区间称为一个块 整块:在一个区间操作时,完整包 ...

  7. nlogn求逆序对&&陌上花开

    前置: nlogn逆序对: 前一个小时我还真的不会这个Orz 这里运用归并排序的思想. 对于一个序列,我们把它先分开,再合并成一个有序序列. 引自https://blog.csdn.net/qq_30 ...

  8. win主机ping不通linux的IP

    1.虚拟机的中的linux系统设置成桥接模式 2.点击虚拟机的编辑选择虚拟网络编辑器 3.点击更改设置 4点击还原默认设置即可

  9. Python - Datacamp - Introduction to Matplotlib

    Python - Datacamp - Introduction to Matplotlib Datacamp: https://www.datacamp.com/ # 1.py 基本matplotl ...

  10. 前端框架Bootstrap简单介绍

    下载: 解压之后: 把这些文件拷贝到项目中 页面使用时  只需要引入: 然后我们就可以参考官网来设计需要的前端页面了 设计一个按钮:button 只需要标明css样式中使用的类  下面就是现实效果: