Linux:基础IO

 

目录

1. stdin & stdout & stderr

2. 系统文件I/O 

1. 接口介绍

open

write

read

close

lseek

 2. open函数返回值

3. 文件描述符fd

0 & 1 & 2

文件描述符的分配规则

重回定向

 dup2

 简易Shell的模拟实现

4. FILE 

5. 再谈对文件的理解 


1. stdin & stdout & stderr

a: C语言默认会打开三个输入输出流,分别是stdin, stdout, stderr

  • stdin:标准输入流,通常用于从键盘接收输入,其类型是 FILE*
  • stdout:标准输出流,通常用于向屏幕输出文本,其类型也是 FILE*
  • stderr:标准错误流,用于输出错误信息,同样也是 FILE* 类型。

b: 仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针

FILE* fopen(const char *filename, const char *mode);
  • 第一个参数 filename 是要打开的文件的名称。
  • 第二个参数 mode 指定文件打开的模式,如 "r" 表示只读,"w" 表示写入并覆盖现有内容等。

fopen 成功时返回一个 FILE* 类型的非空指针,失败时返回 NULL。这里要注意的是,虽然 stdinstdoutstderr 的类型都是 FILE*,但它们不是通过 fopen 函数获得的,而是 C 语言运行时环境自动提供的。

        文件指针 FILE* 是 C 语言标准 I/O 库中用于指向文件结构的指针,该结构包含了进行文件操作所需的所有信息,如文件位置指示器、文件状态标志等。当你使用 fopen 打开一个文件时,返回的 FILE* 指针指向这样一个结构,它允许你通过标准 I/O 函数(如 freadfwritefprintf 等)对文件进行操作。

2. 系统文件I/O 

 操作文件,除了上述C接口,我们还可以采用系统接口来进行文件访问。

1. 接口介绍

 这些操作都很简单,需要的时候在命令行 man 一下!

open

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。参数:O_RDONLY: 只读打开O_WRONLY: 只写打开O_RDWR : 读,写打开这三个常量,必须指定一个且只能指定一个O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限O_APPEND: 追加写返回值:成功:新打开的文件描述符失败:-1
write

ssize_t write(int fd, const void *buf, size_t count); 参数:int fd:文件描述符,这是一个整数,代表要写入数据的文件或其他 I/O 设备。对于标准输入、输出和错误流,文件描述符分别为 0(stdin)、1(stdout)和 2(stderr)。const void *buf:指向要写入的数据缓冲区的指针。buf 参数是一个 const void* 类型的指针,意味着它可以指向任何类型的数据。数据的实际类型应与 write 被调用的上下文匹配。size_t count:要写入的字节数。count 参数指定了从 buf 指向的缓冲区中写入多少字节的数据到文件描述符 fd。返回值:write 函数的返回值表示成功写入的字节数。在成功的情况下,返回值应该等于 count。如果发生错误,函数返回 -1 并设置 errno 以指示错误类型。
read

ssize_t read(int fd, void *buf, size_t count); 参数:int fd:文件描述符,这是一个表示要读取数据的文件、套接字或其他 I/O 设备的整数。例如,标准输入(stdin)的文件描述符是 0。void *buf:指向一个缓冲区的指针,用于存储从文件描述符 fd 读取的数据。buf 参数是一个 void* 类型的指针,这意味着它可以指向任何类型的数据缓冲区。调用者应确保提供足够大的缓冲区来存储预期数量的数据。size_t count:要读取的字节数。count 参数指定了从文件描述符 fd 读取的最大字节数。返回值:read 函数的返回值表示成功读取的字节数,如果返回值为 0,则表示到达了文件末尾(EOF)。如果发生错误,函数返回 -1 并设置全局变量 errno 以指示错误类型。

close

#include <unistd.h>int close(int fd);功能说明:close 函数用于关闭一个由 fd 参数指定的文件描述符。文件描述符通常与打开的文件、套接字或其他 I/O 设备相关联。参数:int fd:要关闭的文件描述符的整数标识符。返回值:成功时,返回 0。失败时,返回 -1,并设置 errno 以指示错误。
lseek

 

#include <sys/types.h>
#include <unistd.h>off_t lseek(int fd, off_t offset, int whence);功能说明:lseek 函数用于重新定位指定文件描述符 fd 的文件读取/写入偏移量。参数:int fd:文件描述符,是一个整数,表示要操作的文件或 I/O 设备。off_t offset:要设置的偏移量。这个值的含义取决于 whence 参数。int whence:指定 offset 参数如何被解释:SEEK_SET:文件开头,offset 是文件开头的绝对偏移量。SEEK_CUR:当前位置,offset 是相对于当前文件位置的偏移量。SEEK_END:文件末尾,offset 是相对于文件末尾的偏移量,通常用于设置文件指针到文件末尾之后。返回值:成功时,lseek 返回新的文件偏移量,即从文件开头到当前位置的字节数。失败时,返回 (off_t)-1,并且 errno 被设置为表示错误原因的值。错误代码:EBADF:指定的文件描述符 fd 无效。EINVAL:whence 参数的值无效。EOVERFLOW:新的文件偏移量超出了 off_t 类型可以表示的范围描述:lseek 函数允许程序修改文件的内部偏移量,该偏移量决定了下一次读取或写入操作的起始点。如果 lseek 成功执行,文件的当前偏移量将被更新为指定的新位置。对于某些类型的文件(如某些管道或设备文件),lseek 可能不会产生预期的效果或可能失败。注意:lseek 通常用于基于文件偏移量进行操作的文件 I/O 模式。对于不支持随机访问的 I/O 设备,lseek 可能无法改变偏移量,或者可能总是返回 0。

 2. open函数返回值

 先来认识一下两个概念: 系统调用 库函数。上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。而, open close read write lseek 都属于系统提供的接口称之为系统调用接口

3. 文件描述符fd

 文件描述符就是Unix和类Unix操作系统中用于表示打开文件或其他I/O资源的整数

0 & 1 & 2

 Linux进程默认情况下会有3个缺省打开的文件描述符:

        分别是标准输入0标准输出1标准错误2.

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

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

        文件描述符就是从0开始的小整数当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件!!!

文件描述符的分配规则

 文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

重回定向

有了对 文件描述符的分配规则 的认识我们来看一段代码:

#include <stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{::close(1);int fd = open("./myfile.txt", O_WRONLY | O_CREAT, 00644);if (fd < 0){perror("open");return 1;}printf("fd: %d\n", fd);fflush(stdout);::close(fd);exit(0);
}

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

常见重定向:

1:>(大于符号) - 强制输出重定向
  • 将命令的输出重定向到一个文件中。如果该文件已存在,其原有内容将被覆盖。
  • 例如:ls > file.txt 将 ls 命令的输出保存到 file.txt 文件中。
2:>>(双大于符号) - 追加输出重定向
  • 将命令的输出追加到现有文件的末尾,而不是覆盖文件。
  • 例如:echo "Hello" >> file.txt 会在 file.txt 文件的末尾追加字符串 "Hello"。
3:<(小于符号) - 输入重定向
  • 将文件内容作为命令的输入。这常用于从文件中读取数据,然后将其传递给命令。
  • 例如:cat < file.txt 会读取 file.txt 文件的内容并显示在终端上。
4:2>(标准错误重定向)
  • 将错误输出重定向到指定的文件。这允许你将错误消息保存到文件中,而不是显示在终端上。
  • 例如:./your_program 2> error.log 将程序的错误输出保存到 error.log 文件中。
5:&>(输出和错误输出重定向)
  • 同时将标准输出和标准错误输出都重定向到同一个文件。
  • 例如:./your_program &> full_output.log 将所有输出保存到 full_output.log
6:|(管道符) - 管道重定向
  • 管道不是重定向操作符,但它与重定向一起使用,可以将一个命令的输出作为另一个命令的输入。
  • 例如:ls | grep "txt" 会列出所有以 .txt 结尾的文件。
7:*(星号)和 ?(问号) - 通配符
  • 星号可以匹配任意数量的字符,而问号可以匹配单个字符。它们通常用于文件名的模式匹配。
  • 例如:rm *.txt 删除当前目录下所有扩展名为 .txt 的文件。

 dup2

#include <unistd.h>int dup2(int oldfd, int newfd);参数:int oldfd:要复制的旧文件描述符。int newfd:复制到的新文件描述符。返回值:成功时,返回新的文件描述符(newfd)。失败时,返回 -1 并设置 errno 以指示错误。错误代码:EBADF:oldfd 或 newfd 是一个无效的文件描述符。EINVAL:newfd 是一个保留的文件描述符编号。描述:dup2 函数通常用于改变一个文件描述符的值,或者将一个文件描述符的作用扩展到另一个文件描述符。如果 newfd 是一个已经打开的文件描述符,调用 dup2 会导致 newfd 关闭并丢弃其关联的文件状态,然后 newfd 被设置为与 oldfd 相同。dup2 可用于重定向标准 I/O 流(如 stdin、stdout、stderr)。

直接看代码感受感受: 

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{int fd = ::open("./log.txt", O_CREAT | O_RDWR);if (fd < 0){perror("open");return 1;}::close(1);::dup2(fd, 1);for (;;){char buf[1024] = {0};ssize_t read_size = ::read(0, buf, sizeof(buf) - 1);if (read_size < 0){perror("read");break;}printf("%s", buf);fflush(stdout);}return 0;
}

 简易Shell的模拟实现


http://t.csdnimg.cn/lecBdicon-default.png?t=N7T8http://t.csdnimg.cn/lecBd

4. FILE 

 a:因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。

b:所以C库当中的FILE结构体内部,必定封装了fd。

来段代码来感受一下这些接口都有什么区别:

直接执行:

 重定向:./test > ./file

为什么会是这个结果?

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

        一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。

        printf fwrite 库函数会自带缓冲区,当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。

        而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后

        但是进程退出之后,会统一刷新,写入文件当中。

        但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。

        write 没有变化,说明没有所谓的缓冲。

        综上: printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。

        那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供

5. 再谈对文件的理解 

         我们要对文件进行操作,前提是我们的程序先要跑起来。文件的打开和关闭,其实是CPU在执行我们的代码。什么是文件?

文件 = 内容(该目录里的所有文件信息详细数据) + 属性
http://t.csdnimg.cn/k8Ngticon-default.png?t=N7T8http://t.csdnimg.cn/k8Ngt

 A:打开文件:本质是进程打开文件

 B:文件没有被打开的时候,在哪里?存在磁盘里

 C:进程能到打开很多文件吗?能!(fd就是组织文件数组的下标!!!)

 D:文件 -> 磁盘 -> 外设 -> 硬件 -> 向文件中写入,本质向硬件中写入

        -> 用户没有权利直接写入 -> OS是硬件的管理者 -> 通过OS写入

        ->OS必须给我们提供系统调用

        -> fopen/fwrite/fread/fprintf/scanf/printf/cin/cout...

        -> 我们用的C/C++/... 其实都是对系通过调用接口的封装!

E:open的过程。write、read函数,本质是拷贝函数!!!

F:为什么 fd: 0、1、2 默认是打开的 ?键盘、鼠标、显示器、都是硬件啊?

Linux下一切皆文件!

        在Linux和Unix系统中,"一切皆文件"的概念是一个核心哲学,意味着系统资源、硬件设备和常规文件都可以统一地用文件描述符来访问文件描述符(fd)是一个抽象的概念,用于表示打开的文件或其他输入/输出资源

E:C语言或者其他语言为什么要将系统调用进行封装?

        系统调用不具有跨平台性,由于系统不同,系统调用的接口可能就不一样,所有的语言都要对不同的平台的系统调用进行封装屏蔽掉底层的差异,实现自身的跨平台性!!!

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

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

相关文章

threejs webgl效果 功能特效

雷达效果 ​飘扬的红旗 光柱效果 OD线 下雪 下雨 光墙效果 能源球 烟火效果 threejs烟花效果 光圈效果 threejs 光圈 波动 function initScene() {scene new THREE.Scene();}function initCamera() {camera new THREE.PerspectiveCamera(45, window.innerWidth / window.inne…

深入探索PDF源码解析:从PDF到Excel的数据统计分析找到正文

在数字化时代&#xff0c;数据已成为企业决策和业务运营的关键。PDF文档作为一种广泛使用的文件格式&#xff0c;其中蕴含着大量有价值的信息。然而&#xff0c;PDF文档的结构和格式使得直接对其进行数据提取和分析变得复杂。为了解决这个问题&#xff0c;我们采取了一种创新的…

SQL注入实例(sqli-labs/less-17)

0、初始网页 1、确定闭合字符 注入点在于password框&#xff0c;闭合字符为单引号 2、爆库名 1 and updatexml(1,concat(0x7e,database(),0x7e),1)# 1 and (select 1 from (select count(*),concat((select database()),floor(rand()*2))x from information_schema.tables gr…

经纬恒润亮相第四届焉知汽车年会,功能安全赋能域控

8月初&#xff0c;第四届焉知汽车年会在上海举行。此次年会围绕当下智能电动汽车的热点和焦点&#xff0c;聚焦于智能汽车场景应用、车载通信、激光雷达、智能座舱、功能安全、电驱动系统等多个领域&#xff0c;汇聚了来自OEM、科技公司、零部件供应商、测试认证机构、政府院校…

Spark SQL Catalyst工作流程

我们写的SQL语句&#xff0c;会经过一个优化器 (Catalyst)&#xff0c;转化为 RDD&#xff0c;交给集群执行。 而Catalyst在整个Spark 生态中的地位也是至关重要的。 SQL到RDD中间经过了一个Catalyst&#xff0c;它就是Spark SQL的核心&#xff0c;是针对Spark SQL语句执行过程…

使用pytest+selenium编写网页UI自动化脚本和用例

1 UI自动化测试 UI自动化测试&#xff08;User Interface Automation Testing&#xff09;是一种通过编写脚本或使用自动化测试工具&#xff0c;对界面&#xff08;UI&#xff09;进行自动化测试的方法。原理主要是模拟用户打开客户端或网页的UI界面&#xff0c;自动化执行用户…

kali安装docker

docker 安装 ● 1、更新 kali 下载资料源&#xff1a;apt-get update ● 2、如果出现上面没有数字签名问题&#xff0c;那就是需要下载证书 使用命令&#xff1a; wget archive.kali.org/archive-key.asc #下载证书 apt-key add archive-key.asc #添加证书 ● 3、重新更新一…

redis列表若干记录

2、列表 ziplist ziplist参数 entry结构 entry-data:节点存储的元素prelen&#xff1a;记录前驱节点长度encoding&#xff1a;当前节点编码格式encoding encoding属性 使用多个子节点存储节点元素长度&#xff0c;这种多字节数据存储在计算机内存中或者进行网络传输的时的字节…

redis面试(十六)公平锁释放和排队加锁

锁释放 RedissonFairLock.unlockInnerAsync()方法 这和加锁的逻辑没有太大区别 也就是说在客户端A他释放锁的时候&#xff0c;也会走while true的脚本逻辑&#xff0c;看一下有序集合中的元素的timeout时间如果小于了当前时间&#xff0c;就认为他的那个排队就过期了&#xf…

如何减少 Docker 镜像大小:6 种优化方法

如果您想减少docker镜像的大小&#xff0c;您需要使用构建docker镜像的标准最佳实践。 本博客讨论了您可以快速实施的各种优化技术&#xff0c;以制作最小、最精简的 docker 镜像。我们还将介绍一些用于 Docker 镜像优化的最佳工具。 Docker 作为一种容器引擎&#xff0c;可以…

k8s核心架构分析

k8s核心概念概述 Kubernetes入门&#xff1a;掌握集群核心&#xff0c;释放容器潜能 技术爱好者们&#xff0c;CD集群的核心概念是构建、部署和管理容器化应用的基石。掌握这些概念&#xff0c;不仅助你深入理解技术细节&#xff0c;更能在CD集群中自如操作&#xff0c;无论是…

2 C 语言开发工具选择、 MinGW 的安装与配置、VS Code 的安装与配置、插件推荐

目录 1 开发工具选择 1.1 Visual Studio 1.2 Code::Block 1.3 Clion 1.4 VS Code 1.5 在线编辑工具 2 开发工具安装 2.1 安装 MinGW-w64 2.1.1 MinGW-w64 介绍 2.1.2 解压 MinGW 2.1.3 将 MinGW 添加至环境变量 2.1.4 验证安装 2.2 安装 VS Code 2.2.1 下载安装包…

Avnet ZUBoard 1CG开发板上手—深度学习新选择

Avnet ZUBoard 1CG 开发板上手—深度学习新选择 摘要 本文主要介绍了 Avnet ZUBoard 1CG 开发板的特性、架构、硬件单元等概念&#xff0c;并对如何使用以太网接口和串口连接开发板进行基本介绍&#xff0c;同时辅以两个应用例程演示其功能。 原文链接&#xff1a; FreakSt…

如何编写一个CMakeLists.txt文件(由简到难,较详细)

在Linux系统下&#xff0c;经常使用CMakeLists.txt文件来链接、编译C工程&#xff0c;大部分人clone的代码里都是有CMakeLists.txt文件的&#xff0c;只需要cmake .. 和make就完事了&#xff0c;但在工作中&#xff0c;你必须要有从无到有编写CMakeLists.txt文件的能力。 一、…

【QGroundControl二次开发】十. QT添加GStreamer视频播放同时保存

上一章介绍使用QT播放GStreamer视频流 【QGroundControl二次开发】八. QT实现播放gstreamer视频。 这章介绍如何在原有基础上保存为视频&#xff0c;同时保存为一个个规定大小的小视频。 一. 思想 之前的文章展示了如何在QT中播放GST视频流&#xff0c;这章在原有的基础上增加…

金九银十,软件测试面试题合集(含答案)

前言 前面看到了一些面试题&#xff0c;总感觉会用得到&#xff0c;但是看一遍又记不住&#xff0c;所以我把面试题都整合在一起&#xff0c;都是来自各路大佬的分享&#xff0c;为了方便以后自己需要的时候刷一刷&#xff0c;不用再到处找题&#xff0c;今天把自己整理的这些…

常见的几种用例测试方法

等价类划分法 适用场景&#xff1a;需要有大量的测试数据输入&#xff0c;但是我们实际测试中不可能一一列举进行测试&#xff0c;所以讲数据进行分类&#xff0c;选出具有代表性的数据代表一类数据进行测试。 分类&#xff1a; 有效等价类&#xff1a;满足需求的数据无效等…

普元EOS-新项目不停提示登录信息已过期

1 问题 新创建的EOS精简应用&#xff0c; 项目端口为 28015 启动后&#xff0c;在浏览器输入地址 http://127.0.0.1:28015 。 页面不停提示 “登录信息已过期” 2 解决办法 EOS的项目对Login-Filter的配置错误&#xff0c; EOS的项目在Http安全过滤管理的时候&#xff0c;会…

【原创】java+swing+mysql商品信息管理系统设计与实现

个人主页&#xff1a;程序员杨工 个人简介&#xff1a;从事软件开发多年&#xff0c;前后端均有涉猎&#xff0c;具有丰富的开发经验 博客内容&#xff1a;全栈开发&#xff0c;分享Java、Python、Php、小程序、前后端、数据库经验和实战 开发背景&#xff1a; 使用javaswing技…

使用mybatis注解和xml映射执行javaWeb中增删改查等操作

Mapper接口 使用注解执行SQL语句操作和相应的Java抽象类&#xff08;对于简单的增删改查使用注解&#xff09; Mapper public interface EmpMapper {// 根据id删除员工信息Delete("delete from mybatis.emp where id#{id}")public int EmpDelete(Integer id);// 查…