Linux基础io知识

理解 "文件"

狭义理解

文件在磁盘里
磁盘是永久性存储介质,因此文件在磁盘上的存储是永久性的
磁盘是外设(即是输出设备也是输入设备)
磁盘上的文件 本质是对文件的所有操作,都是对外设的输入和输出 简称 IO

广义理解

Linux 下一切皆文件(键盘、显示器、网卡、磁盘…… 这些都是抽象化的过程)

文件操作的归类认知

对于 0KB 的空文件是占用磁盘空间的
文件是文件属性(元数据)和文件内容的集合(文件 = 属性(元数据)+ 内容)
所有的文件操作本质是文件内容操作和文件属性操作

系统角度

对文件的操作本质是进程对文件的操作
磁盘的管理者是操作系统
文件的读写本质不是通过 C 语言 / C++ 的库函数来操作的(这些库函数只是为用户提供方便),而是通过文件相关的系统调用接口来实现的

C语言的fopen,fwrite,fread是库函数并不是系统调用,这三个接口都是封装了操作系统底层的系统调用

回顾C文件接口

打开文件

#include <stdio.h>
int main()
{FILE *fp = fopen("myfile", "w");if(!fp){printf("fopen error!\n");}while(1);fclose(fp);return 0;
}

写文件

#include <stdio.h>
#include <string.h>
int main()
{FILE *fp = fopen("myfile", "w");if(!fp){printf("fopen error!\n");}const char *msg = "hello bit!\n";int count = 5;while(count--){fwrite(msg, strlen(msg), 1, fp);}fclose(fp);return 0;
}

读文件

#include <stdio.h>
#include <string.h>
int main()
{FILE *fp = fopen("myfile", "r");if(!fp){printf("fopen error!\n");return 1;}char buf[1024];const char *msg = "hello bit!\n";while(1){//注意返回值和参数,此处有坑,仔细查看man⼿册关于该函数的说明 ssize_t s = fread(buf, 1, strlen(msg), fp);if(s > 0){buf[s] = 0;printf("%s", buf);}if(feof(fp)){break;}}fclose(fp);return 0;
}

管理文件

一个进程可以打开多个文件,所以系统中存在大量被打开或正要关闭的文件,所以操作系统需要管理文件,先描述,再组织!!!

操作系统通过一个结构体struct file来管理文件,在文件被打开的时候就会创建一个struct file,其中存放着文件的各种属性,结构体之间互相链接形成链表,所以操作系统对文件的管理就转换成了对链表的管理

task_struct中不止存在页表,还存在着一个文件描述符表struct files_struct,其中存放着一个  struct file数组,数组的每一个的下标都链接着一个文件的地址,系统就通过数组来管理每一个进程打开的文件。

相对应一个文件也可以被多个进程打开,那么关闭文件是否会对进程造成影响,毕竟进程是具有独立性的,而struct file使用和智能指针类似的做法,就是引用计数,只有计数为0的时候才会真正关闭文件

文件的内容会加载到文件缓冲区,文件的属性会加载到struct file

系统文件 I/O

初识标志位

打开文件的方式不仅仅是 fopen,ifstream 等流式,语言层的方案,其实系统才是打开文件最底层的方案。不过,在学习系统文件 IO 之前,先要了解下如何给函数传递标志位,该方法在系统文件 IO 接口中会使用到:

#include <stdio.h>
#define ONE 0001 //0000 0001
#define TWO 0002 //0000 0010
#define THREE 0004 //0000 0100
void func(int flags) {if (flags & ONE) printf("flags has ONE! ");if (flags & TWO) printf("flags has TWO! ");if (flags & THREE) printf("flags has THREE! ");printf("\n");
}
int main() {func(ONE);func(THREE);func(ONE | TWO);func(ONE | THREE | TWO);return 0;
}

通过这个代码能够了解标志位的作用

状态表示:每个标志位都能独立地表示一种状态。在这个例子中,ONE、TWO 和 THREE 分别代表不同的状态。

状态组合:通过按位或运算符 | 可以把多个标志位组合起来,以此表示多种状态的组合。

状态检查:借助按位与运算符 & 能够检查某个标志位是否被设置,从而依据不同的状态执行不同的操作。

系统调用读文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{int fd = open("myfile", O_RDONLY);if(fd < 0){perror("open");return 1;}const char *msg = "hello bit!\n";char buf[1024];while(1){ssize_t s = read(fd, buf, strlen(msg));//类⽐write if(s > 0){printf("%s", buf);}else{break;}}close(fd);return 0;
}

系统调用写文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{umask(0);int fd = open("myfile", O_WRONLY|O_CREAT, 0644);if(fd < 0){perror("open");return 1;}int count = 5;const char *msg = "hello bit!\n";int len = strlen(msg);while(count--){write(fd, msg, len);//fd: 后⾯讲, msg:缓冲区⾸地址, len: 本次读取,期望写⼊多少个字节的数据。 返回值:实际写了多少字节数据 }close(fd);return 0;
}

接口认识

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

第一个参数是文件,第二个参数是标志位 ,第三个参数表示新创建文件的权限,如果新创建的文件不带mode会造成新创建的文件权限位乱码

通过使用标志位用偶有效减少传参的个数

open标志位介绍

O_RDONLY:以只读模式打开文件。文件只能被读取,不能进行写入操作
O_WRONLY:以只写模式打开文件。文件只能被写入,不能进行读取操作
O_RDWR:以读写模式打开文件。文件既可以被读取,也可以被写入
O_CREAT:如果文件不存在,则创建该文件。使用此标志位时,需要提供第三个参数 mode 来指定新文件的权限。
O_EXCL:和 O_CREAT 一起使用时,如果文件已经存在,则 open 调用会失败并返回 -1,同时将 errno 设置为 EEXIST。这可用于确保文件是新创建的。
O_TRUNC:如果文件存在且以可写模式打开,则将文件长度截断为零,即清空文件内容
O_APPEND:以追加模式打开文件。每次写操作都会将数据追加到文件末尾,而不是覆盖原有内容
O_NONBLOCK:以非阻塞模式打开文件。在进行读写操作时,如果没有数据可读或无法立即写入数据,函数不会阻塞,而是立即返回 -1,同时将 errno 设置为 EAGAIN 或 EWOULDBLOCK。这在处理多个文件描述符或需要异步操作时非常有用

而标志位的组合使用|

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

第一个参数是文件操作符,第二个参数是写入文件的内容,第三个参数是需要写入的字节数

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

第一个参数是文件操作符,第二个参数是存放读到的数据,第三个参数是读入的字节数

文件操作符

write,read的第一个参数都是fd,fd就是文件操作符

操作系统只认fd,那么fd是什么呢?在文件描述符表中存在struct file数组,fd就是数组的下标。

当我们打开多个文件的时候,查看他们的fd,会发现fd是从3开始的。这是由于数组中默认的0,1,2号下标分别是标准输入,标准输出,标准错误。

fd的数据是从小到大的,只要前面有多余的位置,系统就会将文件分配到靠前的下标

open的时候就会找到进程的文件描述符表中的数组,找到一个未被使用过的位置,将struct file的地址填入其中,fd就是数组的下标

read就是用fd找到数组的下标,访问对应文件的地址,从文件缓冲区中将数据拷贝到buffer,文件缓冲区的数据是由磁盘中文件的内容预加载到缓冲区中。

重定向

Linux中存在函数dup2,用于复制文件描述符

int dup2(int oldfd, int newfd);

oldfd:需要被复制的源文件描述符。
newfd:复制到的目标文件描述符。如果newfd已经打开,会先将其关闭。

使用dup2会将newfd位置的文件覆盖到oldfd上

可以通过dup2来替换标准输入,标准输出来实现重定向操作。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
int main()
{int fd = open("test.txt", O_RDWR);dup2(fd,1);close(fd);printf("fd: %d\n", fd);return 0;
}

当我们将文件夹中的test.txt替换掉标准输出,此时使用printf, 则不会将内容输出到显示器上,而是将内容输出到test.txt中

操作系统只认识fd,printf就是为了往stdout中输出内容,但是stdout被关闭,此时fd为1的文件是log.txt,printf输出的内容就会输出到log.txt中

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
int main()
{int fd = open("test.txt", O_RDWR);dup2(fd,0);close(fd);char s[100];scanf("%s",s);printf("%s\n",s);return 0;
}

将test.txt文件替换标准输入文件,此时scanf一般是从键盘上读取数据,此时就只能从test.txt中读取数据,将test.txt的数据填写到s中。

操作系统只认fd,scanf就是从键盘中读取数据,但是标准输入被关闭,此时fd为0的文件是test.txt,此时scanf就会从 test.txt中读取数据

通过上面两端代码可以大致了解重定向的原理,就是将文件进行替换,从而将一下本该输出到显示屏中的数据输出到其他文件中,将从键盘中读取的数据转换成从其他文件中读取数据 

正确的重定向操作方法 

例如存在一个文件:log.txt,需要将原本输出到显示屏的数据输出到log.txt中

ls -l 1 > log.txt

将原本输出到1号文件的内容输出到log.txt中 ,而1号文件就是stdout

#include<cstdio>
#include<iostream>
using namespace std;
int main()
{cout<<"hellocout"<<endl;cerr<<"hellocerr"<<endl;return 0;
}

当我们使用这段代码重定向到一个文件时,cerr的内容并不会重定向到文件,而是输出在显示屏上,这是由于cerr对应的文件时stderr,它的fd为2,而默认的重定向只有1

所以可以通过将1重定向到log.txt,2追加重定向到log.txt

./a 1 > log.txt 2 >> log.txt

此时cout和cerr的内容就都输出到log.txt中了 

也可以将标准错入重定向到标准输出的目标位置,&1 表示「当前标准输出的目标位置」

./a 1 > log.txt 2>&1

效果与第一种一样 

理解“一切皆文件”

首先,在 Windows 中是文件的东西,它们在 Linux 中也是文件;其次一些在 Windows 中不是文件的东西,比如进程、磁盘、显示器、键盘这样硬件设备也被抽象成了文件,你可以使用访问文件的方法访问它们获得信息;甚至管道,也是文件;将来我们要学习网络编程中的 socket(套接字)这样的东西,使用的接口跟文件接口也是一致的。

这样做最明显的好处是,开发者仅需要使用一套 API 和开发工具,即可调取 Linux 系统中绝大部分的资源。举个简单的例子,Linux 中几乎所有读(读文件,读系统状态,读 PIPE)的操作都可以用 read 函数来进行;几乎所有更改(更改文件,更改系统参数,写 PIPE)的操作都可以用 write 函数来进行。

在操作系统中,每个外设都有一套属于自己的读写操作。操作系统中的struct file的内容中存在函数指针,他们会指向对应外设的读写操作,而每个函数指针的命名,参数,都一样

一切皆文件,是进程认为一切皆文件,进程中存储着文件描述符表,文件描述符表指向struct file,从硬件角度上来看,struct file的函数指针指向硬件的读写操作,所以管理好文件也就能管理好硬件,进程无需接触到硬件,只需接触到struct file即可

上图中的外设,每个设备都可以有自己的 read、write,但一定是对应着不同的操作方法!!但通过struct file 下 file_operation 中的各种函数回调,让我们开发者只用 file 便可调取 Linux 系统中绝大部分的资源!!这便是 “linux 下一切皆文件” 的核心理解。 

缓冲区 

什么是缓冲区

缓冲区就如同日常中的快递站,可以帮助我们在空闲时间再去拿快递,而非在快递送到家门口的时候无论在做任何事情都要回去拿快递,这样也严重影响了快递员的效率

缓冲区是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。

可以查看Linux中的FILE结构体

它的指针就指向了缓冲区的位置

为什么要引入缓冲区机制

系统调用也是有成本的,系统调用是需要操作系统进行操作的,而操作系统平时需要进行大量的资源管理,如果没有缓冲区,当我们进行文件写入时,操作系统无论做什么事情都需要停下来进行写入操作

读写文件时,如果不会开辟对文件操作的缓冲区,直接通过系统调用对磁盘进行操作 (读、写等),那么每次对文件进行一次读写操作时,都需要使用读写系统调用处理此操作,即需要执行一次系统调用,执行一次系统调用将涉及到 CPU 状态的切换,即从用户空间切换到内核空间,实现进程上下文的切换,这将损耗一定的 CPU 时间,频繁的磁盘访问对程序的执行效率造成很大的影响。

为了减少使用系统调用的次数,提高效率,我们就可以采用缓冲机制。比如我们从磁盘里取信息,可以在磁盘文件进行操作时,可以一次从文件中读出大量的数据到缓冲区中,以后对这部分的访问就不需要再使用系统调用了,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作 快于对磁盘的操作,故应用缓冲区可 大提高计算机的运行速度。

又如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的 CPU 可以处理别的事情。可以看出,缓冲区就是一块内存区,它用在输入输出设备和 CPU 之间,用来缓存数据。它使得低速的输入输出设备和高速的 CPU 能够协调工作,避免低速的输入输出设备占用 CPU,解放出 CPU,使其能够高效率工作。

缓冲类型

标准 I/O 提供了 3 种类型的缓冲区。

・全缓冲区:这种缓冲方式要求填满整个缓冲区后才进行 I/O 系统调用操作。对于磁盘文件的操作通常使用全缓冲的方式访问。

・行缓冲区:在行缓冲情况下,当在输入和输出中遇到换行符时,标准 I/O 库函数将会执行系统调用操作。当所操作的流涉及一个终端时(例如标准输入和标准输出),使用行缓冲方式。因为标准 I/O 库每行的缓冲区长度是固定的,所以只要填满了缓冲区,即使还没有遇到换行符,也会执行 I/O 系统调用操作,默认行缓冲区的大小为 1024。

・无缓冲区:无缓冲区是指标准 I/O 库不对字符进行缓存,直接调用系统调用。标准出错流 stderr 通常是不带缓冲区的,这使得出错信息能够尽快地显示出来。

缓冲区的工作

当使用printf/fprintf/fwrite等等的库函数进行文件操作的时候,数据并非直接写入到文件内核缓冲区中,c标准库中存在一个库缓冲区,使用库函数进行操作的时候,数据会先占时写入到库缓冲区中,直到某种条件的触发,会将fopen返回值中的fd找出来,通过fd进行系统调用,将库缓冲区的内容写到文件内核缓冲区中。

刷新缓冲区的条件

1.强制刷新

2.进程退出

3.刷新条件满足->(全缓冲,行缓冲,无缓冲)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/905034.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

视频编解码学习三之显示器续

一、现在主流的显示器是LCD显示器吗&#xff1f; 是的&#xff0c;现在主流的显示器仍然是 LCD&#xff08;液晶显示器&#xff0c;Liquid Crystal Display&#xff09;&#xff0c;但它已经细分为多种技术类型&#xff0c;并和其他显示技术&#xff08;如OLED&#xff09;形成…

[测试]并发模拟工具Apache Bench 进行AB压力测试

下载(windows) https://www.apachelounge.com/download/ 下载后解压&#xff0c;解压后进入bin目录&#xff0c;打开CMD&#xff0c;即可使用 命令 ab.exe -n 请求总数 -c 并发数 http://网站/ 比如ab.exe -n 1000 -c 100 http://127.0.0.1:5555/ 看不懂的话直接把结果让AI分析…

LeetCode 热题 100 138. 随机链表的复制

LeetCode 热题 100 | 138. 随机链表的复制 大家好&#xff0c;今天我们来解决一道经典的链表问题——随机链表的复制。这道题在 LeetCode 上被标记为中等难度&#xff0c;要求深拷贝一个带有随机指针的链表。 问题描述 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额…

开源分享:TTS-Web-Vue系列:Vue3实现固定顶部与吸顶模式组件

&#x1f3af; 本文是TTS-Web-Vue系列的第十三篇文章&#xff0c;重点介绍项目中固定顶部导航和内容区域吸顶模式的实现方案。通过这些优化&#xff0c;我们大幅提升了用户在滚动页面时的交互体验&#xff0c;使关键操作区域始终可见&#xff0c;同时实现了更现代化的界面视觉效…

Docker、Docker-compose、K8s、Docker swarm之间的区别

1.Docker docker是一个运行于主流linux/windows系统上的应用容器引擎&#xff0c;通过docker中的镜像(image)可以在docker中构建一个独立的容器(container)来运行镜像对应的服务&#xff1b; 例如可以通过mysql镜像构建一个运行mysql的容器&#xff0c;既可以直接进入该容器命…

用浏览器打开pdf,如何使用划词翻译?

1. 浏览器 | 扩展 | 获取 Microsoft Edge 扩展 2. 搜索 “沙拉查词” 点击“获取” 3. 扩展这里选择 管理扩展 勾选 “允许访问文件url” 注&#xff1a;这里一定要勾选&#xff0c;否则沙拉查词无法访问.pdf 文件&#xff01;&#xff01;&#xff01;会出现下图错误 4. 右击…

深入解析STM32中断机制:从原理到外部中断实战

知识点1【中断的介绍】 单片机的中断——硬件中断 Linux操作系统的中断——软件中断 中断是指计算机运行过程中&#xff0c;出现某种意外情况需要主机干预&#xff0c;机器能自动停止正在运行的程序并转入处理新情况的程序&#xff0c;处理完毕后有返回原本暂停的程序继续运…

【入门】打印字母塔

描述 输入行数N,打印图形. 输入描述 输入只有一行&#xff0c;包括1个整数。(N<15) 输出描述 输出有N行. #include <bits/stdc.h> using namespace std; int main() { char t;int n,f;cin>>n;for(int i1;i<n;i){tchar(65i);for(int j1;j<n-i;j){cout…

CentOS 7.9 安装详解:手动分区完全指南

CentOS 7.9 安装详解&#xff1a;手动分区完全指南 为什么需要手动分区&#xff1f;CentOS 7.9 基本分区说明1. /boot/efi 分区2. /boot 分区3. swap 交换分区4. / (根) 分区 可选分区&#xff08;进阶设置&#xff09;5. /home 分区6. /var 分区7. /tmp 分区 分区方案建议标准…

油冷式电动滚筒设计:关键技术解析与应用前景

引言 电动滚筒作为一种集动力传输、减速和驱动功能于一体的机电一体化设备&#xff0c;在输送机械、矿山设备、食品加工等领域广泛应用。随着工业设备向高效化、紧凑化和智能化发展&#xff0c;传统风冷式电动滚筒的散热效率与负载能力已逐渐难以满足需求。油冷式电动滚筒凭借…

Android开发-Activity附加信息

在Android应用开发中&#xff0c;除了基本的界面跳转和数据传递之外&#xff0c;我们还经常需要为Activity添加一些附加信息&#xff08;Metadata&#xff09;&#xff0c;以支持更复杂的配置需求或与系统进行交互。这些附加信息可以通过<meta-data>标签在AndroidManifes…

2025第九届御网杯网络安全大赛线上赛 区域赛WP (MISC和Crypto)(详解-思路-脚本)

芜湖~ 御网杯线上分是越来越精细 区域赛都有了 然后不过多评价 整体不算难 以下是我自己的一些思路和解析 有什么问题或者建议随时都可以联系我 目录 芜湖~ MISC #被折叠的显影图纸 #光隙中的寄生密钥 #ez_xor #套娃 #easy_misc #ez_pictre Crypto #easy签到题 …

‌中继器:网络中的“血包”与“加时器”‌

在探讨网络技术时&#xff0c;我们往往会遇到各种专业术语和设备&#xff0c;中继器便是其中之一。然而&#xff0c;对于非技术人员或初学者来说&#xff0c;这些概念可能显得抽象且难以理解。今天&#xff0c;我将通过一个生动的比喻——将中继器比作网络中的“血包”与“加时…

MySQL----高级查询

目录标题 ⭐**多表查询的格式**⭐**查询前说明**一.**使用内连接**inner join**进行多表查询****1.介绍****2.事例** 二.**使用外连接**outer join**进行多表查询**1.**介绍** ⭐多表查询的格式 其一 select *&#xff5c;字段列表 from 表1[查询类型] join 表名2 on 连接条件…

SpringBoot主入口类分析

1 &#xff09;SpringBoot主入口类 SpringBoot 主入口类如下所示&#xff0c;这个类的main方法就是整个springboot项目的入口。 package com.example.demo3;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootA…

【RabbitMQ】 RabbitMQ高级特性(一)

文章目录 一、消息确认1.1、消息确认机制1.2、手动确认方法1.2.1、AcknowledgeMode.NONE1.2.2、AcknowledgeMode.AUTO1.3.3、AcknowledgeMode.MANUAL 二、持久性2.1、 交换机持久化2.2、队列持久化2.3、消息持久化 三、发送方确认3.1、confirm确认模式3.2、return退回模式3.3、…

探索Hello Robot开源移动操作机器人Stretch 3的技术亮点与市场定位

Hello Robot 推出的 Stretch 3 机器人凭借其前沿技术和多功能性在众多产品中占据优势。Stretch 3 机器人采用开源设计&#xff0c;为开发者提供了灵活的定制空间&#xff0c;能够满足各种不同的需求。其配备的灵活手腕组件和 Intel Realsense D405 摄像头&#xff0c;显著增强了…

expo多网络请求设定。

在使用 npx expo start 启动 Expo 开发服务器时&#xff0c;你可以通过设置网络模式来控制你的应用如何连接到开发服务器。Expo 提供了几种网络模式供你选择&#xff1a; LAN (Default): 这是默认模式。在这种模式下&#xff0c;你的应用会通过本地局域网 (LAN) 连接到你的开发…

Nginx 安全防护与HTTPS部署

目录 一、核心安全配置 1、隐藏版本号 2、限制危险请求方法 3、请求限制&#xff08;CC攻击防御&#xff09; &#xff08;1&#xff09;使用Nginx的limit_req模块限制请求速率 &#xff08;2&#xff09;压力测试验证 4、防盗链 &#xff08;1&#xff09;修改 Window…

windows 环境下 python环境安装与配置

运行环境安装 第一步安装包下载 python开发工具安装包下载官网&#xff1a; https://www.python.org/ 根据自己的实际需求选择。 这里记录了各个版本的区别和差异。根据区别和差异选择适合自己的版本。 Windows Installer和Windows embeddable package是两种不同的软件包类…