Linux基础IO

目录

C语言文件接口

打开和关闭文件

读取文件

 写入文件

文件定位

其他文件操作

 系统级文件IO

open

close

read

write

lseek

文件描述符

FILE与fd的关系

文件描述符分配规则

默认打开的三个流

重定向

什么是重定向

系统调用

缓冲区 

文件系统

文件创建

文件访问

删除文件

硬链接

软链接


C语言文件接口

我们先复习一下C语言为我们提供的文件接口。

打开和关闭文件

fopen:打开一个文件,返回打开文件的FILE指针。

FILE *fopen(const char *filename, const char *mode);

filename: 要打开的文件名。

mode: 文件打开模式,如 "r" (只读),"w" (只写并截断现有文件),"a" (追加),"r+" (读写),"w+" (读写并截断),"a+" (读写并追加)等。

fclose:关闭由fopen打开的文件。

int fclose(FILE *stream);

读取文件

fread:从文件读取数据。

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

fgets:从文件读取一行文本。

char *fgets(char *s, int n, FILE *stream);

 fscanf:从文件按格式读取数据。

int fscanf(FILE *stream, const char *format, ...);

 写入文件

fwrite: 向文件写入数据。

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

fprintf: 按格式写入数据到文件。

int fprintf(FILE *stream, const char *format, ...);

fputs: 写入一个字符串到文件。

int fputs(const char *str, FILE *stream);

文件定位

fseek: 移动文件内部的位置指针。

int fseek(FILE *stream, long offset, int whence);

ftell: 获取当前文件位置指针的偏移量。

long ftell(FILE *stream);

rewind: 将文件位置指针移动到文件开头。

void rewind(FILE *stream);

其他文件操作

feof: 检查是否到达文件末尾。

int feof(FILE *stream);

ferror: 检查是否发生错误。

int ferror(FILE *stream);

clearerr: 清除文件流的错误指示。

void clearerr(FILE *stream);

 系统级文件IO

操作文件,除了上述C接口,我们还可以采用系统接口来进行文件访问,C接口或者其他语言的接口是通过在系统接口上实现文件操作。

a0c3f5ba486b459aa987a2edc32ed3ec.png

接口介绍:

open

用于打开一个文件或者设备节点,返回一个文件描述符(一个小于0的整数)用于后续的读写操作。

int open(const char *pathname, int flags, mode_t mode);

pathname:文件或设备的路径名。

flags: 打开模式,如O_RDONLY(只读),O_WRONLY (只写),O_RDWR(读写),O_CREAT(不存在则创建),O_APPEND(追加)等。

mode_t:当与O_CREAT一起使用时,指定新文件的权限位,如S_IRUSR | S_IWUSR表示文件所有者可读写。

返回值: 成功返回打开文件的文件描述符,失败返回-1。

close

关闭之前通过open打开的文件或设备。

int close(int fd);

fd: 通过open函数返回的文件描述符。

返回值: 成功返回0,失败返回-1。

read

从已打开的文件或设备中读取数据到缓冲区。

ssize_t read(int fd, void *buf, size_t count);

fd: 文件描述符。

buf: 指向缓冲区的指针,用于存放读取的数据。

count: 要读取的最大字节数。

返回值: 实际读取的字节数(成功),0(文件结束),或-1(失败)。

write

向已打开的文件或设备写入数据。

ssize_t write(int fd, const void *buf, size_t count);

fd: 文件描述符,由open函数返回。

buf: 指向要写入数据的缓冲区的指针。

count: 要写入的字节数。

返回值: 成功写入的字节数或-1(失败)。

lseek

改变文件读写位置指针的位置。

off_t lseek(int fd, off_t offset, int whence);

fd: 文件描述符。

offset: 相对于whence的偏移量。

whence: 定位方式,可以是SEEK_SET(文件开头)、SEEK_CUR(当前位置)、SEEK_END(文件结尾)。

返回值: 成功时返回新的文件位置指针的偏移量,失败时返回-1。

演示

18f9dc9079284693ac3433d08baf5726.png

 运行结果如下:

f29d92be7fae41a49e2e164f1e6e2df5.png

60651142109445219cfdfcb662f76fc9.png

可以看到程序成功在当前路径下创建了”testfile.txt“文件 ,读写文件操作也正常进行,通过cat输出文件内容,可以看到写入成功。

文件描述符

在上面的学习中我们知道了文件描述符就是一个整数。而实际上这个整数相当于一个数组的下标。

在操作系统内部,它作为一个索引来引用一个结构数组,这个数组(也称为文件表或文件描述符表)保存了所有打开文件的相关信息,如文件的状态(如当前读写位置)、文件权限、文件类型以及其他与文件I/O操作相关的信息。

FILE与fd的关系

FILE结构体是对文件描述符的进一步封装,提供了更高级、更易用的接口,同时也通过缓冲等机制优化了I/O性能,而文件描述符则是操作系统用于管理文件资源的底层机制。

当你使用fopen()打开一个文件时,C标准库会调用open()系统调用来获取一个文件描述符,并根据这个文件描述符创建一个FILE结构体实例,其中包含该文件描述符。

FILE结构体封装了文件描述符,并在此基础上提供了缓冲机制和其他便利功能,使得文件操作更为高效和用户友好。

当执行I/O操作时,C标准库函数(如fread()、fwrite())会使用FILE结构体中的文件描述符调用相应的系统调用(如read()、write())。

当不再需要时,通过fclose()调用,C标准库会关闭与FILE结构体关联的文件描述符,并释放相关的资源。

1353e29a63554278a73a1860871f8010.png

文件描述符分配规则

最小未使用值:当一个进程打开一个新文件或创建一个文件描述符时,内核会查找当前进程的文件描述符表中尚未分配的最小整数值。这意味着每次分配时,系统都会尝试复用最近关闭的文件描述符的编号,以保持对低编号的高效利用。

固定范围内的有限资源:虽然理论上文件描述符是一个非负整数,但实际上大多数系统对单个进程可以同时打开的文件描述符数量设置了上限。这个限制可以通过ulimit命令或setrlimit()系统调用如来查看和修改。

进程间不共享:文件描述符是进程相关的。当通过fork()创建子进程时,子进程会继承父进程的文件描述符表的副本,但父进程和子进程各自拥有独立的文件描述符引用计数,关闭其中一个进程的文件描述符不会影响另一个进程的同编号文件描述符。

优化与回收:高效的程序应当及时关闭不再使用的文件描述符,以便系统可以复用这些资源。长时间不关闭文件描述符可能导致资源泄露,影响程序或系统的性能。

默认打开的三个流

在C语言编程和Linux系统中,默认会打开三个标准I/O流分别是:

标准输入流(stdin):

文件描述符为0。

默认情况下,通常关联到键盘输入,程序可以通过它接收用户输入的数据。

标准输出流(stdout):

文件描述符为1。

默认情况下,用于输出程序处理结果或信息到终端屏幕。它是大多数打印语句默认的目标。

标准错误流(stderr):

文件描述符为2。

专门用于输出错误信息和诊断消息。

0,1,2对应的物理设备一般是:键盘,显示器,显示器。

所以输入输出还可以采用如下方式:

cbc42dedd16243aeb880f6dfff20aea4.png98f774a1512342b1b43c7b3d27f2dec2.png

重定向

什么是重定向

通过上面的学习我们可以写出下面这段代码:

597e5ce9756b49ee8bc70e37017a2104.png

9d08ef581b5042d9a47c154f7e17aa5f.png

我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。 

重定向的本质:

feb0a3a2a36047d3bdc5b16746f24433.png

系统调用

在编程时我们通常使用dup2进行重定向操作

#include<unistd.h>

int dup2(int oldfd, int newfd);

oldfd:要复制的现有文件描述符。

newfd:目标文件描述符编号,即你希望复制后的文件描述符是什么编号。如果这个编号已经被占用,它会被dup2关闭。

成功时,返回新的文件描述符(通常是newfd)。失败时,返回-1并设置相应的错误代码。

示例代码

15ee8960823d43f8afc42f0eebea85a0.png

 运行结果:

303f75a1f7dc4373886b618a2582e06b.png

缓冲区 

我们运行下面这段代码

d8cda0d7014345d3a35206426bb785a3.png

运行结果:

d004a4e4c3564ef0b05b77c6290b762a.png 但如果对进程实现输出重定向呢, 我们发现结果变成了:

ed37af24540d41929b3dbe4e1103a241.png

我们发现 printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?

一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。 printf fwrite 库函数会自带缓冲区),当发生重定向到普通文件时,数据 的缓冲方式由行缓冲变成了全缓冲。 而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后 但是进程退出之后,会统一刷新,写入文件当中。 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的 一份数据,随即产生两份数据。 write 没有变化,说明没有所谓的缓冲。

printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区, 都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。 那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统 调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是 C,所以由C标准库提供。 

文件系统

 计算机管理磁盘,会对磁盘进行分区。对每一个分区来说,分区的头部会包括一个启动块(Boot Block),对于该分区的其余区域,EXT2文件系统会根据分区的大小将其划分为一个个的块。一个快的大小是由格式化的时候确定的,并且不可以更改。下图为磁盘文件系统图(Linux ext2文件系统)

de324a48b4e14a8ca026072135e78b99.png

  • Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相 同的结构组成。政府管理各区的例子
  • 超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量, 未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的 时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个 文件系统结构就被破坏了 GDT,
  • Group Descriptor Table:块组描述符,描述块组属性信息,有兴趣的同学可以在了解一下
  • 块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没 有被占用
  • inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。
  • i节点表:存放文件属性 如 文件大小,所有者,最近修改时间等
  • 数据区:存放文件内容

Linux ext2文件系统将属性和数据分开存放。通过一系列复杂的机制来管理和组织文件,确保文件的存储、访问和更新高效且安全。

为了说明问题,我们将上图简化,通过文件的创建,访问,删除向大家演示文件系统时如何工作的。

76a1e12701a24c63886274b1ade6a8fa.png

文件创建

当用户或程序请求创建一个新文件时:

系统首先在inode位图中寻找一个空闲的inode位,标记为已使用,并分配给新文件。

在分配的inode中填写文件的元数据,如文件大小、所有者、权限、创建和修改时间等。

然后,在块位图中寻找足够的连续或非连续的数据块(取决于文件系统的配置和文件大小),标记这些数据块为已使用,并在inode中记录这些数据块的地址。

最终,文件数据被写入到这些分配的数据块中。

文件访问

用户或程序请求访问文件时:

系统通过文件名在目录项中找到对应的inode号。

使用inode号从inode表中加载inode信息,获取文件的元数据和数据块的地址。

依据inode中的数据块地址信息,在数据区中读取或写入数据。

删除文件

删除操作首先会更新目录项,去除指向该文件inode的链接。

然后,将该inode标记为未使用,并将其加入到自由inode列表中。

对于文件所占用的数据块,它们在块位图中的状态会被更新为未使用,以便将来重用。

ext2不立即擦除数据块内容,以提高性能,因此删除文件后,数据可能仍存在于磁盘上,直到被新数据覆盖。

硬链接

我们在文件系统谈到,真正找到磁盘上文件的并不是文件名,而是inode。 其实在linux中可以让多个文件名对应于同一个 inode吗,即硬链接。

硬链接直接在文件系统的inode层创建,它实际上是指向相同inode的一个新条目。因此,硬链接文件与原文件共享相同的inode和数据块,是原文件的另一个别名。

创建硬链接时,原文件的inode链接计数会增加,这意味着只要至少存在一个硬链接,文件数据就不会被真正删除,即使你删除了原始文件名。

在Linux系统下我们建的目录的硬链接数初始值为2,这是因为每个目录至少包含两个特殊的目录项,它们分别是:

  • .(当前目录):这是一个硬链接,指向目录自身。在任何目录中,. 总是指向当前目录的inode,因此为该目录贡献了一个硬链接计数。

  • ..(父目录):这也是一个硬链接,指向当前目录的父目录。在每个非根目录中,.. 都存在,指向其上一级目录,这也为当前目录的inode增加了一个硬链接计数。

由于这两个硬链接的存在,即使是刚创建的、看似“空”的目录,其硬链接数也会显示为2。这是Linux及类UNIX文件系统中的标准设计,用于维护目录结构的完整性并提供一种基本的导航方式。

软链接

软链接(又称符号链接或快捷方式)实质上是一个包含目标文件路径的特殊文件。它不共享inode,而是存储了目标文件的路径信息。
软链接有自己的inode和文件类型,通常在ls命令输出中以箭头符号指向实际的目标文件。
当访问软链接时,系统会解析这个链接指向的实际路径,然后访问目标文件。如果目标文件被删除或移动,软链接会失效。

8908002a666f453aafdb7476f68b1742.png

  • 100
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: Linux IO 模型是指 Linux 操作系统中的 IO 处理机制。它的目的是解决多个程序同时使用 IO 设备时的资源竞争问题,以及提供一种高效的 IO 处理方式。 Linux IO 模型主要分为三种:阻塞 IO、非阻塞 IOIO 多路复用。 阻塞 IO 指的是当程序进行 IO 操作时,会被挂起直到 IO 操作完成,这种方式简单易用,但是对于高并发环境不太适用。 非阻塞 IO 指的是程序进行 IO 操作时,如果无法立即完成,会立即返回一个错误码,程序可以通过循环不断地进行 IO 操作来实现轮询的效果。非阻塞 IO 可以提高程序的响应速度,但是会增加程序的复杂度。 IO 多路复用指的是程序可以同时监听多个 IO 设备,一旦有 IO 事件发生,就会立即执行相应的操作。IO 多路复用可以提高程序的效率,但是需要程序员手动编写代码来实现。 Linux IO 模型还有其他的实现方式,比如信号驱动 IO 和异步 IO 等。但是这些方式的使用比较复杂,一般不常用。 ### 回答2: Linux中的IO模型是指操作系统在处理输入输出的过程中所遵循的一种方式。它主要包括阻塞IO、非阻塞IO、多路复用IO和异步IO四种模型。 阻塞IO是最简单的IO模型,当一个IO操作发生时,应用程序会被阻塞,直到IO操作完成才能继续执行。这种模型的特点是简单直接,但是当有多个IO操作时会造成线程的阻塞,影响系统的性能。 非阻塞IO是在阻塞IO基础上发展而来的,应用程序在发起一个IO操作后可以继续执行其他任务,不必等待IO操作的完成。但是需要通过轮询来不断地检查IO操作是否完成,效率相对较低。 多路复用IO使用select、poll、epoll等系统调用来监听多个IO事件,当某个IO事件就绪时,应用程序才会进行读写操作,避免了前两种模型的效率问题。多路复用IO模型适用于连接数较多时的场景,如服务器的网络通信。 异步IO是最高效的IO模型,应用程序发起一个IO操作后,立即可以执行其他任务,不需要等待IO操作的完成。当IO操作完成后,操作系统会通知应用程序进行后续处理。异步IO模型常用于高吞吐量、低延迟的应用,如高性能服务器和数据库等。 总之,Linux IO模型提供了多种不同的方式来处理输入输出,每种模型都有其适用的场景和特点。选择合适的IO模型可以提高系统的性能和效率。 ### 回答3: Linux IO模型是指操作系统中用于处理输入输出操作的一种方法或机制。在Linux中,常见的IO模型有阻塞IO、非阻塞IOIO多路复用和异步IO。 阻塞IO是最基本的IO模型,当应用程序发起一个IO请求时,它将一直阻塞等待直到IO操作完成,期间无法做其他任务。虽然简单易用,但是对资源的利用不高。 非阻塞IO在发起一个IO请求后,不会阻塞等待IO操作完成,而是立即返回并继续做其他任务。应用程序需要不断地轮询IO操作状态,直到操作完成。由于需要不断轮询,对CPU的占用较高,但可以提高资源的利用率。 IO多路复用是通过一个线程同时监听多个IO事件,从而实现并发处理多个IO操作。在IO多路复用模型中,应用程序不需要进行轮询,而是通过调用select、poll或epoll等系统调用监听多个文件描述符的IO事件。这样可以在单个线程中处理多个IO操作,提高并发性能。 异步IO模型在发起一个IO请求后,应用程序不需要等待IO操作完成,而是继续做其他任务。当IO操作完成后,操作系统会通知应用程序。异步IO模型需要操作系统的支持,效率较高,但实现较为复杂。 通过选择合适的IO模型,可以根据不同的应用场景来提高IO操作的效率和性能。例如,对于需要同时处理大量连接的服务器应用,IO多路复用是一种常见的选择;而对于需要处理大量IO操作的高性能服务器,则可以考虑使用异步IO模型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值