UNIX高级编程 -3 文件IO

蒸汽
蒸汽
发布于 2025-03-24 / 13 阅读
0
0

UNIX高级编程 -3 文件IO

UNIX中大部分文件I/O只需用5个函数:open, read, write, lseek, close

不带缓冲:每个read和write都调用内核中的一个系统调用。不属于ISO C, 但是是POSIX.1和Single UNIX Specification的组成部分

多进程间共享文件

  • 原子操作很重要

  • dup, fcntl, sync, fsync, ioctl函数

3.2文件描述符

对于内核而言,所有打开文件都通过文件描述符引用。打开或创建文件时,内核返回给进程一个文件描述符。

3.3 open

#include <fcntl.h>
​
#... ISO C用...来表示之后的参数数量与类型可变
​
int open(cosnt char *path, int oflag, ...);
#oflag:
# O_RDONLY 只读打开。
# O_WRONLY 只写打开。
# O_RDWR 读、写打开。
# O_EXEC 只执行打开。
# O_SEARCH 只搜索打开(应用于目录)。
​
int openat (int fd, const char *path, int oflag, ... /* mode_t mode * / );
#作用是:
如果path是绝对路径,fd可忽略
如果path是相对路径,fd指明起始目录
如果path是绝对路径,fd是常量AT_FDCWD,则为当前目录为起始目录
​

可选常量参数很多比如:O_APPEND(追加), O_CLOEXEC, O_NOFOLLOW, O_NONBLOCK, SYNC等等, 但是注意,不同的UNIX系统可支持的可选常量参数有所差异,需要时查阅

文件名和截断 如果文件NAME_MAX = 14,超过14则会截断还是报错是可选的,可通过常量_POSIX_NO_TRUNC决定

文件状态标志 ![[Pasted image 20250313094604.png]]

3.4 create

image.png

注意此函数等效于 image.png tips:因为早期UNIX的第二个参数不能创建文件,只能打开已有文件,所以需要create,现在已经不需要了,用open即可

3.5 close

image.png

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

  • 程序终止,内核自动关闭所有它打开的文件,所以可以不显示调用close

3.6 lseek

lseek显式地为一个打开文件设置偏移量 image.png tips:该函数可以用来确定是否是可偏移量设置文件,如果是一个管道,FIFO或者网络套接字,则lseek返回-1,并将errno设置为ESPIPE

tips:image.png

3.7 read

image.png

ssize_t:带符号的整形,可以表示错误情况(-1) size_t: 不带符号的整形,不可表示错误情况(非负)

read的不同操作对象的差异

  • 读普通文件时,在读到要求字节数之前已到达了文件尾端。例如,若在到达文件尾端之前有30个字节,而要求读100个字节,则read返回30。下一次再调用read 时,它将返回 0(文件尾端)。

  • 从终端设备(stdin)读,通常一次最多读一行,因为终端设备的输入通常是行缓冲

  • 从网络读,网络的缓冲机制可能造成返回值小于所要求读的字节数

  • 当从管道或 FIFO 读时,如若管道包含的字节少于所需的数量,那么 read 将只返回实际可用的字节数。

  • 当从某些==面向记录==的设备(如磁带)读时,一次最多返回一个记录。(上述的其他情况面向字节流)

tips:读操作从文件的当前偏移量处开始,在成功返回之前,该偏移量将增加实际读到的字节数。

3.8 write

image.png 对于普通文件,写操作从文件的当前偏移量处开始。如果在打开该文件时,指定了 O_APPEND 选项,则在每次写操作之前,将文件偏移量设置在文件的当前结尾处。在一次成功写之后,该文件偏移量增加实际写的字节数。

3.9 IO性能效率

通过测试不同buf size时读写速度发现,buf size不是越大越好

read ahead 技术:预读技术,当检测到正进行顺序读取时,系统就试图读入比应用所要求的更多数据。

3.10 文件共享

打开文件的内核数据结构:

![[Pasted image 20250312211428.png]] 内核使用三种数据结构表示打开文件:如上图

  1. 进程表中的打开文件描述符表

  2. 内核为所有打开文件维持一张文件表

  3. 每个打开文件都有一个v节点结构

如果两个进程各种打开了同一文件: ![[Pasted image 20250312212017.png]] tips:之所以每个进程都有自己的文件表项,是因为每个进程都有自己对该文件的当前偏移量。

3.11 原子操作

指的是多步组成的一个操作。如果该操作原子地执行,则要么执行完所有步骤,要么一步不执行,不可能只执行一个子集

场景:早期的追加文件实现:没有append选项 ![[Pasted image 20250312212938.png]] 这样的问题是:多个进程同时读写时会乱套

什么叫原子操作? 任何要求多于一个函数调用的操作都不是原子操作。因为在两个函数调用之间,内核有可能会临时挂起进程 **tips:当一个进程在执行系统调用或用户态函数时,内核不会主动挂起该进程 除非函数内部自己阻塞或者等待资源(比如锁)时会被动挂起

2.pread与pwrite

![[Pasted image 20250312213656.png]] 调用pread相当于调用lseek后调用read,但是:

  • 调用pread时,无法中断其定位和读操作

  • 不更新当前文件偏移量

3.12 函数dup和dup2

![[Pasted image 20250312215404.png]] dup返回当前可用的最小文件描述符,dup2返回值可自选fd2,如果fd2已经打开,则先关闭。 这些函数返回的新文件描述符与参数fd共享同一个文件表项 ![[Pasted image 20250312215601.png]] tips:新描述符的执行时关闭(close on exec)标志总是由duo函数清除。

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

![[Pasted image 20250312215851.png]]

3.13 函数sync fsync fdatasync

延迟写:传统的unix系统实现在内核中设有缓冲区高速缓存或页高速缓存,大多数磁盘IO都通过缓冲区进行。通常,当内核需要重用缓冲区来存放其他磁盘块数据时,它会把所有延迟写数据块写入磁盘。 为了保证磁盘上实际文件系统与缓冲区中内容的一致性,UNIX 系统提供了sync、fsync 和 fdatasync三个函数。

![[Pasted image 20250312220328.png]]

  • sync:将修改过的块缓冲区排入写队列,然后就返回,并不等待实际写磁盘操作结束

    • 称为==update==的函数周期性调用(一般30s)sync函数,保证定期flush内核块缓冲区

    • fsync只对文件描述符fd指定文件起作用,并且等待磁盘操作结束才返回

    • fdatasync函数类似于fsync,只影响文件数据部分。但是fsync除此之外还会同步更新文件的属性

3.14 函数fcntl

![[Pasted image 20250312220905.png]]

fcntl函数有五功能:(这里的第三个参数都是整数)

  • 复制一个已有的描述符(cmd = F_DUPFD Ek 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 或E_SETLKN)。

tips: fcntl还有三种cmd是与记录锁相关的,第三个参数是指向一个结构的指针,14.3节将描述

3.15 函数ioctl

IO操作的”杂物箱“。不能用本章中其他函数表示的IO操作通常都能用ioctl表示

image.png

tips: 在此原型中,我们表示的只是ioct1函数本身所要求的头文件。通常,还要求另外的设备专用头文件。例如,除POSIX.1所说明的基本操作之外,终端1/O的ioctl命令都需要头文件<termios.h>

每个设备驱动程序可以定义它自己专用的一组ioctl 命令,系统则为不同种类的设备提供通用的 ioctl 命令。图3-15中总结了 FreeBSD 支持的通用ioct1命令的一些类别。

image.png

3.16 /dev/fd

/dev/fd目录:目录内的目录项是0,1,2...n等的文件,打开/dev/fd/n等效于==复制描述符n==(dup(n)),返回的fd与源目录项共享一个文件

image.png

tips:大多数系统忽略这里指定的mode,但是有些系统要求mod必须是所引用文件初始打开时所使用的打开模式的一个子集。例如,这里的/dev/fd/0默认为只读模式,那么mode就算是O_RDWR,也只能读,不能写

tips2: image.png

我们也可以用/dev/fd 作为路径名参数调用creat,这与调用open时用 O_CREAT作为第 2 个参数作用相同。例如,若一个程序调用creat,并且路径名参数是/dev/fd/1,那么该程序仍能工作。

tips3: image.png


评论