UNIX环境高级编程 第三章 文件I/O

回眸只為那壹抹淺笑 提交于 2020-01-14 02:48:55

3.1 文件描述符

作用:唯一表示一个文件(unix中设备也被看作文件)
当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。
文件描述符的范围:0~OPEN_MAX-1

标准shell建立的文件描述符关联:
STDIN_FILENO (文件描述符:0):标准输入
STDOUT_FILENO (文件描述符:1):标准输出
STDERR_FILENO (文件描述符:2):标准错误

3.2 函数open和openat

利用open或openat函数可以打开或创建一个文件

#include<fcntl.h>

int open(const char *path,  int oflag, ....);
int openat(int fd, const char *path, int flog, ....);

参数

path:

打开或创建的文件的名字

oflag:
oflag参数 功能
O_RDONLY 只读打开
O_WEONLY 只写打开
O_RDWR 读、写打开
O_EXEC 只执行打开
O_SEARCH 只搜索打开
O_APPEND 每次写时都追加到文件末尾
O_CLOEXEC 把FD_CLOEXEC设置为文件描述符标志
O_CREAT 若文件不存在则创建它。open函数需要指定第3个参数mode(openat指定第四个参数mode),用来指定文件的权限位
O_DIRECTORY 如果path引用的不是目录,则出错
O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则出错。
O_NOCTTY 如果path引用的是终端设备,则不将该设备分配作为此进程的控制终端
O_NOFOLLOW 如果path是一个符号链接,则出错
O_NONBLOCK 如果path引用的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选项为文件的本次打开操作和后续的I/O操作设置非阻塞方式
O_SYNC 使每次weite等待物理I/O操作完成,包括由该write操作引起的文件属性更新所需要的I/O
O_TRUNC 如果文件存在,并且为只写或读写成功打开,则将其长度截断为0
O_TTY_INIT 如果打开一个还未打开的终端设备,设置非标准termios结构,使其符合Single UNIXSpecification
O_DSYNC 使每次write操作要等待物理I/O完成,但是如果该操作并不影响刚写入的数据,则不需要等待文件更新
O_RSYNC 使每一个以文件描述符作为参数进行的read等待,直至所有对文件同一部分挂起的写操作完成
fd:

fd参数把open和openat区分开,共有三种可能性
 1.path参数指定绝对路径名。这种情况下fd参数被忽略。
 2.path参数指定的是相对路径名,fd参数指出了相对路径名在文件系统中的开始地址(通过打开相对路径名所在的目录来获取)。
 3.path参数指定了相对路径名,fd参数具有特殊值AT_FDCWD。这种情况下,路径名在当前工作目录中获取

文件名和路径名截断

如果文件名过长,常量(_POSIX_NO_TRUNC)决定要截断过长的文件名或路径名,还是返回出错

3.3 函数creat

调用creat创建一个新文件。

#include <fcntl.h>

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

此函数等效于:

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

creat不足之处:以只写的方式打开创建的文件

3.4 函数close

调用close函数关闭一个打开的文件。

#include  <unistd.h>

int close(int fd)
//返回值:若成功,返回0;若出错,返回-1

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

3.5 函数lseek

  每个打开文件都有一个与其相关联的“当前文件偏移量(通常为非负整数)”用以度量从文件开始处计算的字节数。
  通常,读、写操作都是从当前文件偏移量开始的,并使偏移量增加所读写的字节数。系统默认情况下,若非指定O_APPEND选项,该偏移量设置为0。

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

#include <unistd.h>

off_t lessk(int fd, off_t offset, int whence);
//返回值:若成功,返回新的文件偏移量,若出错,返回-1

通常,文件的当前偏移量是一个非负整数,但是,某些设备可能允许负的偏移量。对于普通文件来说,偏移量必须为非负值。因此在比较lseek的返回值时应当测试它是否等于-1,而不是判断是否小于0。

3.6 函数read

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

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t nbytes);//void *表示通用指针
//返回值:读到的字节数,若已到文件尾,返回0;若出错返回-1
  • 当读取普通文件时,如果读到要求字数之前就已经到达尾端,则read返回读到的字节数。
  • 当从终端设备读取时,通常一次最多读一行。
  • 当从网络读时,网络中的缓冲机制可能造成返回之小于所要求读的字节数。
  • 当从管道或FIFO读时,如若管道包含的字节少于所需数量,那么read之返回实际可用的字符数。
  • 当从面向记录的设备(如磁带)读取时,一次最多返回一个记录。

3.7 函数write

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

#include <unistd.h>

ssize_t write(int fd, void *buf, size_t nbytes);
//返回值:若成功,返回已写的字节数;若出错,返回-1

如果返回值通常与参数nbytes值相同,否则表示出错,writee出错的一个常见原因是磁盘已写满,或者超过了一个给定进程的文件长度限制。

3.8 I/O的效率

当缓冲区大小与磁盘block大小一致时系统cpu时间最少(效率最高)当继续增大缓冲区大小时并不会提高效率

3.9 文件共享

UNIX系统支持在不同进程间共享打开文件。
内核使用三种结构打开文件:
(1) 进程表项:每个进程中都有一个记录项,记录项中包含一张打开文件描述符表。
   文件描述符标志
   指向一个文件表项的指针
(2) 文件表项:内核为所有打开文件维持一张文件表,每个表项包含:
   文件状态标志
   当前文件偏移量
   指向该文件v节点表项的指针
(3) v节点表项:每个打开文件都有一个v节点表项
   文件类型
   进行各种操作函数的指针
   大多数文件还包含i节点(索引节点)
在这里插入图片描述
若两个独立进程各自打开了同一个文件,则有下图所示关系
在这里插入图片描述

之所以每个进程可以获得自己的文件表项,是因为这可以使每个进程都有自己对该文件的当前偏移量

3.10 原子操作

多进程同时对同一个文件进行写操作,会产生混乱,原因是两个进程使用了两个分开的函数调用。
解决办法为使这两个操作对于其他进程而言组成一个原子操作。

可以理解为多步不同的操作组成一个操作,该操作原子的执行,要么执行完所有步骤,要么一步也不执行,不可能只执行一部分。

函数pread和pwrite

pread相当于调用lseek后调用read,但有些许区别:

  • 调用pread时,无法中断其定位和读操作
  • 不更新当前文件的偏移量
    pwrite相当于调用lseek后调用write,并且也具有类似区别。

3.11 函数dup和dup2

两个函数都可以用来复制一个现有的文件描述符。

#include <unistd.h>

int dup(int fd);

int dup2(int fd, int fd2);
//两函数的返回值:若成功,返回新的文件描述符;若出错,返回-1
  • 用dup返回的文件描述符一定是当前可用文件描述符中的最小值。
  • 对于dup2,可以用fd2参数指定新描述符的值,如果fd2已经打开了,则先将其关闭。若fd == fd2,返回fd2,而不关闭它,否则fd2的FD_CLOEXEC文件描述符标志就会被清空,这样fd2在调用exec时总是打开的状态。

这些函数返回的新文件描述符与参数fd共享一个文件表项,如下图所示:
在这里插入图片描述

3.12 函数sync、fsync和fdatasync

当我们向文件写入数据时,内核通常将文件复制到缓冲区,然后排入队列,晚些时候再写入磁盘,这种方式称为延迟写
通常,当内核需要重用缓冲区来存放其他磁盘块数据时,它会把所有延迟写数据写入磁盘。为了保证磁盘上实际文件系统与缓冲区中内容的一致性,UNIX提供了sync、fsync和fdatasync三个函数。

#include <unistd.h>

int fsync(int fd);
int fdatasync(int fd);
//返回值:若成功,返回0;若出错,返回-1
void sync(void);
  • sync只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。
  • fsync函数只对由文件描述符fd指定的一个文件起作用,并且等待写磁盘操作结束后才返回。fsync可用于数据库这样的应用程序
  • fdatasync类似于fsync,但它只会影响文件的数据部分。除数据外,fsync还会同步更新文件的属性。

3.13 函数fcntl

fcntl函数可以改变已经打开的文件属性

#include <fcntl.h>

int fcntl(int fd, in tcmd, .../*int arg*/);
//返回值:若成功,则依赖于cmd;若出错,返回-1

fcntl有以下5个功能:

  • 复制一个已经存在的描述符(cmd = F_DUPFD 或 F_DUPFD_CLOEXEC)
  • 获取/设置文件描述符标志(cmd = F_GETFD 或 F_SETFD)
  • 获取/设置文件标志状态(cmd = F_GETFL 或 F_SETFL)
  • 获取/设置异步I/O所有权(cmd = F_GETOWN 或 F_SETOWN)
  • 获取设置记录锁(cmd = F_GETLK 、F_SETLK 或 F_SETLKW)

cmd中的前8种:

参数cmd 功能
F_DUPFD 复制文件描述符fd
F_DUPFD_CLOEXEC 复制文件描述符,设置与新描述符关联的FD_CLOEXEC文件描述符标志的值,返回新的文件描述符。
F_GETFD 返回对应于fd的文件标志符
F_SETFD 设置文件描述符标志,新标志按照第3个参数设置
F_GETFL 返回文件状态
F_SETFL 设置文件状态标志,新标志按照第3个参数设置
F_GETOWN 获取当前接收SIGIO和SIGURS信号的进程ID或进程祖ID
F_SETOWN 设置接收SIGIO和SIGURS信号的进程ID或进程祖ID

当支持同步写时,系统时间和时钟时间将显著增加。

fcntl的必要性:我们的程序在一个描述符上进行操作,根本不知道shell打开的相应文件的文件名。因为是shell打开的,因此不能按照我们的要求设置O_SYNC标志,使用fcntl,我们只需要知道文件描述符就可以修改文件的属性了。

3.14 函数ioctl

ioctl一直是I/O操作的杂货箱,不能用以上其他函数表示的I/O操作基本上都可以用ioctl表示。
终端I/O是使用ioctl最多的地方

#include  <unistd.h>
#include <sys/ioctl.h>

int ioctl(int fd, int request, ...)

除POSIX.1所说明的基本操作以外,终端I/O的ioctl命令都需要头文件<termios.h>

3.15 /dev/fd

用文件来表示文件描述符

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!