来点八股文(二) 文件IO

基础知识

  • 文字描述符是一个小的非负整数,内核用以标识一个特定进程正在存访的文件。当内核打开一个现存文件或创建一个新文件时,它就返回一个文件描述符。当读、写文件时,就可使用它。
    我们可以在/proc/sys/fs/file-max中查看它的大小(系统最大描述符号)。
cat /proc/sys/fs/file-max
52626599
  • 文件描述符通常从 0(表示标准输入)、1(表示标准输出)和 2(表示标准错误)开始。可以使用相同的 ><2> 符号来重定向标准输入、输出和标准错误
#include <stdio.h>
int main() {
    
    
    int c;

    while ((c = getc(stdin)) != EOF) {
    
    
        if (putc(c, stdout) == EOF) {
    
    
            perror("Error writing to stdout");
            return 1;
        }
    }

    if (ferror(stdin)) {
    
    
        perror("Error reading from stdin");
        return 1;
    }

    return 0;
}
  • 程序(program)是存放在磁盘文件中的可执行文件。使用6个exec函数中的一个由内核将程序读入存储器,并使其执行。

  • 当UNIX函数出错时,往常返回一个负值,而且整型变量errno通常设置为具有特定信息的一个值,errno是一个线程局部存储(ThreadLocalStorage,TLS)的宏或者函数。如果没有出错,则其值不会被一个例程清除。因此,仅当函数的返回值指明出错时,才检验其值。而且任一函数都不会将errno值设置为0,在<errno.h>中定义的所有常数都不为0。
    ● time系统调用会产生三个时间:时钟时间:又称为墙上时钟时间(wallclocktime)。它是进程运行的时间总量,其值与系统中同时运行的进程数有关。用户CPU时间:执行用户指令所用的时间量。系统CPU时间:为该进程执行内核所经历的时间。
    在这里插入图片描述

不带缓存的IO

文件IO可以分为带缓存的IO和不带缓存的IO。
不带缓存的IO典型的例如:open、read、write、lseek、close。

open函数

在这里插入图片描述
open函数可以指定只读、只写、读写打开。
并且可以指定是否每次写都追加到文件末尾(使之变为原子操作)、是否创建文件、如果有同名文件时的操作等。
由open函数返回的文件描述符一定是最小的未用描述符。

#include <iostream>
#include <unistd.h>
#include <fcntl.h>

int main() {
    // 关闭标准输出
    close(STDOUT_FILENO);

    // 打开一个名为output.txt的文件,由open返回的文件描述符一定是最小的未用描述符数字
    int fd = open("output.txt", O_WRONLY | O_CREAT, S_IRWXU);

    // 将标准输出重定向到output.txt文件
    dup2(fd, STDOUT_FILENO);

    // 输出一条信息到output.txt
    std::cout << "Hello, world!" << std::endl;

    // 关闭文件描述符
    close(fd);

    return 0;
}

create函数

在这里插入图片描述
create函数等价于:

// 只写打开、文件不存在则创建、文件存在则将其长度截断为0
open(pathname,O_WRONL|O_CREAT|O_TRUNC,mode)

close函数

在这里插入图片描述
关闭一个文件时也释放进程所有的记录锁;当进程终止时,所有打开的文件由内核自动关闭。

lseek函数

在这里插入图片描述
用来调整文件偏移量
whence可以设置为相对文件开始、相对当前值、相对文件长度
如果文件偏移量大于当前文件长度,对于文件的下一次写将会延长该文件,并且形成一个空洞,对于文件中没有写过的字节都被读为0。
lseek函数只修改文件表项中的当前文件位移量,没有进行任何I/O操作

read函数

在这里插入图片描述
如果read成功,将会返回得到的字节数,如果到达文件尾部,则返回0。
有多种情况可能导致读到的字节数小于要求字节数。
•读普通文件时,在读到要求字节数之前已到达了文件尾端。例如,若在到达文件尾端之
前还有30个字节,而要求读100个字节,则read返回30,下一次再调用read时,它将返回0(文件
尾端)。
•当从终端设备读时,通常一次最多读一行(第11章将介绍如何改变这一点)。
•当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数。
•某些面向记录的设备,例如磁带,一次最多返回一个记录。

write函数

在这里插入图片描述
如果返回值和给定值不同,则表示出错。
出错的可能原因有:
磁盘已写满,或者超过了对一个给定进程的文件长度限制

read和write函数的buffersize值应该设置为大于文件块长(大于此长度后对读写效率无影响)

文件共享

每个进程在进程表项中维护一张文件表,记录了打开的文件描述符信息。
内核 为所有文件各维护一张文件表,进程的文件表项指向这张表,这张表包括:文件状态标志(读、写、增写、同步、阻塞)、文件偏移量、指向v节点表的指针
v节点包含了文件类型和对此文件进行操作的函数的指针信息、i节点信息。i节点信息指的是文件的索引节点信息。i节点包含了文件所有者、文件长度、文件设备、文件数据块指针等
在这里插入图片描述
当两个进程同时打开同一个文件时的情况:
在这里插入图片描述

  • 在完成每个write后,在文件表项中的当前文件位移量即增加所写的字节数。如果这使当前文件位移量超过了当前文件长度,则在i节点表项中的当前文件长度被设置为当前文件位移量(也就是该文件加长了)。

  • 如果用O_APPEND标志打开了一个文件,则相应标志也被设置到文件表项的文件状态标志中。每次对这种具有添写标志的文件执行写操作时,在文件表项中的当前文件位移量首先被设置为i节点表项中的文件长度。这就使得每次写的数据都添加到文件的当前尾端处。

  • lseek函数只修改文件表项中的当前文件位移量,没有进行任何I/O操作。

  • 若一个文件用lseek被定位到文件当前的尾端,则文件表项中的当前文件位移量被设置为i节点表项中的当前文件长度。

fcntl函数

fcntl可以改变打开的文件的性质。
在这里插入图片描述

  • 复制一个现存的描述符(cmd=F_DUPFD), 共享同一个文件文件表项。
  • 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)。
  • 获得/设置文件状态标志(cmd=F_GETFL或F_SETFL)。
  • 获得/设置异步I/O有权(cmd=F_GETOWN或F_SETOWN)。
  • 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)。
    在这里插入图片描述
    openssl用来设置文件描述符属性,nginx用来实现非阻塞io,sqlite用来实现文件锁。

dup和dup2

在这里插入图片描述
由dup打开的文件描述符共享同一个文件文件表项。

ioctl函数

在这里插入图片描述
在这里插入图片描述
嵌入式的同学,opencv可能用的比较多。

/dev/fd目录

虚拟的文件路径,每个进程各有一个,打开文件/dev/fd/n等效于复制描述符n(假定描述符n是打开的)。

文件和目录

stat、fstat和lstat

在这里插入图片描述

stat返回与此命名文件有关的信息结构,fstat获得已在描述符fieldes上打开的文件信息,lstat类似于stat,但是当文件是一个符号链接时,返回该符号链接的有关信息。
在这里插入图片描述
在这里插入图片描述

  • st_mode 文件类型 用一组文件类型宏来判断文件类型:

在这里插入图片描述
在这里插入图片描述

  • st_uid 所有者
  • st_gid 组所有者
    在这里插入图片描述
    在执行程序时保存的有效用户ID和有效组ID的副本
  • 文件存取许可权
    有几个规则:
    • 我们用名字打开任一类型的文件时,对该名字中包含的每一个目录,包括它可能隐含的当前工作目录都应具有执行许可权。如果PATH环境变量指定了一个我们不具有执行许可权的目录,那么shell决不会在该目录下找到可执行文件。
    •为了在一个目录中创建一个新文件,必须对该目录具有写许可权和执行许可权
    •为了删除一个文件,必须对包含该文件的目录具有写许可权和执行许可权。对该文件本身则不需要有读、写许可权。
    •如果用6个exec函数中的任何一个执行某个文件,都必须对该文件具有执行许可权
    在这里插入图片描述
  • st_size 包含了以字节为单位的文件的长度
  • st_atime st_ctime st_mtime 文件时间(使用utime函数更改)
    在这里插入图片描述

access

测试文件许可权

在这里插入图片描述
在这里插入图片描述

umask/chmod/fchmod

umask为进程设置文件方式创建屏蔽字,并且返回以前的值
在这里插入图片描述
fchmod对已打开的文件设立存取许可权
在这里插入图片描述

chown/fchown/lchown

用于更改文件的用户id和组id。
lchown更改符号连接的所有者而不是该符号连接指向的文件
在这里插入图片描述

文件系统

在这里插入图片描述

  • 每个i节点都有一个连接计数
  • ln命令不能跨越文件系统
  • mv文件如果在一个文件系统中,只需要更新文件的目录项即可

symlink和readlink

在这里插入图片描述

symlink函数创建指向acualpath的新目录项sympath

在这里插入图片描述
因为open函数跟随符号连接,所以使用readlink函数打开连接本身。

读目录

对某个目录具有存取许可权的任一用户都可读该目录,但是只有内核才能写目录(防止文件系统发生混乱)。一个目录的写许可权位和执行许可权位决定了在该目录中能否创建新文件以及删除文件,它们并不表示能否写目录本身。

chdir/fchdir

用来更改当前工作目录
在这里插入图片描述

sync/fsync

在这里插入图片描述

用来将已修改/指定文件的已修改但还未写入磁盘的文件缓存数据写入磁盘中。

标准IO库

标准io库的操作是围绕流进行的。

缓存

有三种缓存级别:全缓存、行缓存、不带缓存。

ANSI C要求:

  1. 当输入输出不涉及交互作用设备时,才能是全缓存的
  2. 标准错误不会是全缓存的
    在这里插入图片描述
    使用setbuf打开或关闭缓存机制,可以用setvbuf精确设置缓存类型。
    在这里插入图片描述
    例如:
#include <stdio.h>
#include <stdlib.h>

#define BUF_SIZE 1024

int main()
{
    char buf[BUF_SIZE];

    // 使用setbuf函数来指定缓冲区,以提高I/O效率
    setbuf(stdout, buf);

    // 向标准输出流输出一百万次字符串,每次输出一个字符
    for (int i = 0; i < 1000000; i++) {
        putchar('a');
    }

    return 0;
}

流操作

在这里插入图片描述
fopen打开路径名上的一个指示的文件
freopen在特定的流上打开一个指定的文件,如果已经打开,则关闭该流
fdopen取一个现存的文件描述符,并使一个标准的io流与该描述符结合。
在这里插入图片描述
流的读写
一次读一个字符在这里插入图片描述

getc可能是个宏,但是fgetc一定是个函数,所以&getc是一个函数调用表达式的地址而不是函数地址。
因为getc可以被宏展开,合并系统调用,所以效率更高。
getchar默认从标准输入流中读取,相当于getc(stdin)

因为出错和到达文件尾都返回EOF,所以需要调用ferror或者feof来区分两种情况。调用clearerr清除出错和文件结束标志。
在这里插入图片描述

每次一行的IO
在这里插入图片描述
fgets读取一个不超过n-1个字符的行,gets会造成缓存越界的问题,不推荐使用。

在这里插入图片描述
fputs将一个以null字符结尾的字符串写到新的流,终止符null不写出。
puts会多写一个新行符。
在这里插入图片描述
二进制io


读写二进制数据,例如图像、音频或视频文件,或者需要处理自定义数据结构时效率较高。
格式化输出
在这里插入图片描述

高级io

记录锁

记录锁(record locking)的功能是:一个进程正在读或修改文件的某个部分时,可以阻止
其他进程修改同一文件区 。
锁的隐含继承和释放:
当一个进程终止时,所有的锁被释放,
当一个文件描述符关闭时,进程通过这个文件描述符访存的文件上的所有锁都被释放
fork产生的子进程不继承父进程的锁,
执行exec后,新的程序继承旧的程序的锁。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/treblez/article/details/130041637