【Linux】基础IO —— 上

news2024/11/30 2:36:12

🎇Linux:基础IO详解


  • 博客主页:一起去看日落吗
  • 分享博主的在Linux中学习到的知识和遇到的问题
  • 博主的能力有限,出现错误希望大家不吝赐教
  • 分享给大家一句我很喜欢的话: 看似不起波澜的日复一日,一定会在某一天让你看见坚持的意义,祝我们都能在鸡零狗碎里找到闪闪的快乐🌿🌞🐾。

在这里插入图片描述

✨ ⭐️ 🌟 💫

目录

  • 💫 1. 简单复习文件操作
    • 🌟 1.1 写文件
    • 🌟 1.2 读文件
    • 🌟 1.3 追加文件
    • 🌟 1.4 一切皆文件
  • 💫 2. 系统文件I/O
    • 🌟2.1 open
    • 🌟2.2 close
    • 🌟2.3 read
    • 🌟2.4 write
    • 🌟2.5 测试用例
  • 💫 3. 文件描述符fd
    • 🌟 3.1 文件描述符的分配规则
  • 💫 4. 重定向原理
  • 基础IO上总结

💫 1. 简单复习文件操作

🌟 1.1 写文件

如果以"w"模式打开文件,默认是文本读写,且会把原始内容清掉再写。

#include<stdio.h>

int main()
{
	FILE* fp = fopen("./log.txt", "w");//以写的方式打开当前目录下的log.txt文件,没有就新建文件,如果目标文件存在,w写时会清空目标文件	
	//FILE* fp = fopen("log.txt", "w");//没有./,它默认是在当前路径下新建文件
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }

    int count = 0;
    while(count < 10)
    {
        fputs("hello byih\n", fp);//往log.txt文件中写数据
        count++;                      
    }

    fclose(fp);//关闭文件

    return 0;
}

FILE* fp = fopen(“log.txt”, “w”);

虽然没有 ./ 指定路径,但是它还是在当前路径下新建文件了,因为每个进程都有一个内置的属性 cwd(可以在 /proc 目录下查找对应进程的属性信息),cwd 可以让进程知道自己当前所处的路径,这也解释了在 VS 中不指明路径,它也能新建对应的文件在对应的路径,换言之,进程在哪个路径运行,文件的新建就哪个路径。

在这里插入图片描述


🌟 1.2 读文件

fgets从特定文件流中按行读取,内容放在缓冲区。读取成功返回字符串起始地址,读失败返回NULL.

#include<stdio.h>

int main()
{
    FILE* fp = fopen("./log.txt", "r");//以读的方式打开当前目录下的log.txt文件,没有就报错
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }

    int count = 0;
    char buffer[128];
    while(count < 10)
    {
        fgets(buffer, 128, fp);//从log.txt文件中读128个字符到buffer,\n会使fgets停止读取
        printf("%s\n", buffer);
        count++;
    }

    fclose(fp);//关闭文件

    return 0;
}


🌟 1.3 追加文件

#include<stdio.h>
#include<string.h>

int main()
{
    FILE* fp = fopen("./log.txt", "a");//以追加的打开当前目录下的log.txt文件,没有就新建,如果目标文件存在,a写时不会清空目标文件,在文件内容最后写入
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }

    const char* msg = "Hello DanceBit\n";
    //fwrite(msg, strlen(msg) + 1, 1, fp);//乱码
    fwrite(msg, strlen(msg), 1, fp);                                                                               

    fclose(fp);

    return 0;
}


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

size 表示你要写入的基本单元是多大(以字节为单位),count 表示你要写入几个这样的基本单元。

fwrite(msg, strlen(msg) + 1, 1, fp);

strlen(msg) + 1 -> 乱码,也就是把 \0 也追加会造成,因为 \0 是 C 的规定,和文件无关。这里 cat log.txt 并没有看到乱码的原因是 \0 是不可见的,所以这里 vim log.txt 才可以看到乱码。


🌟 1.4 一切皆文件

C语言默认会打开三个输入输出流:stdin、stdout、stderr,它们的类型都是FILE*,C语言把它们当做文件看待;站在系统角度,stdin对应的硬件设备是键盘、stdout对应显示器、stderr对应显示器,本质上我们最终都是访问硬件。C++中也有cin、cout、cerr,几乎所有语言都提供标准输入、标准输出、标准错误。

请添加图片描述
默认情况下,标准输入是键盘文件,标准输出是显示器文件,标准错误是显示器文件。而这三个本身是硬件,如何理解 Linux 中,一切皆文件?

所有的外设硬件,本质对应的核心操作无外乎是 read 或 write。对于键盘文件,它的读方法就是从键盘读取数据到内存,对于显示器文件,如调用 printf 函数时,操作系统是要往显示器上写入的,其实你输入的命令是你通过键盘输入的,所以系统应该是往键盘读数据。至于用户能看到输入的命令,仅仅是为了方便用户,操作系统把从键盘输入的数据,一方面给了系统读取,一方面给显示器方便用户。所以不同的硬件,对应的读写方式肯定是不一样的,但是它们都有 read 和 write 方法,换言之,这里的硬件可以统一看作一种特殊的文件。比如这里设计一种结构叫做 struct file,它包括文件的属性、文件的操作或方法等。

Linux下的六字真言:先描述,在组织

组织就是要把每一个硬件对应的结构体关联起来,并用 file header 指向。所以在操作系统的角度,它看到的就是一切皆文件,也就是说所有硬件的差异,经过描述,就变成了同一种东西,只不过当具体访问某种设备时,使用函数指针执行不同的方法,就达到了不同的行为。

#include<stdio.h>
#include<string.h>

int main()
{
	const char* msg = "Hello DanceBit\n";
	fwrite(msg, strlen(msg), 1, stdout);

  	char buffer[64];
  	fread(buffer, 1, 10, stdin);//你输入时没有写\0,fread时也不会加,所以一旦超过10,就会出现乱码
  	buffer[10] = '\0';
  	printf("%s\n", buffer);

    return 0;
}

这里可以直接使用 fwrite 这样的接口,向显示器写数据的原因是因为 C 程序一运行,stdout 就默认打开了。同理 fread 能从键盘读数据的原因是 C 程序一运行,stdin 就默认打开了。

也就是说 C 接口除了对普通文件进行读写之外(需要打开),还可以对 stdin、stdout、stderr 进行读写(不需要打开)。

scanf -> 键盘、printf -> 显示器、perror -> 显示器


💫 2. 系统文件I/O

如上我们知道,这些文件操作最终都是访问硬件(显示器、键盘、文件(磁盘))。众所周知,OS是硬件的管理者。所有语言上对“文件”的操作,都必须贯穿操作系统。然而OS不相信任何人,访问操作系统,就必须要通过系统接口!!

其实我们学过的几乎所有的语言中,fopen/fclose,fread/fwrite,fputs/fgets,fgets/fputs 等底层一定需要使用OS提供的系统调用接口,下面咱们就来学习文件的系统调用接口

在这里插入图片描述

🌟2.1 open

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>

int main()
{
    int fd = open("log.txt", O_WRONLY|O_CREAT, 0644);//打开
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    //操作
    const char* byh = "Hello System Call!\n";
    write(fd, byh, strlen(byh));
    write(fd, byh, strlen(byh));
    write(fd, byh, strlen(byh));
    write(fd, byh, strlen(byh));
                                                           
    close(fd);//关闭 

    return 0;
}

使用 open 需要包含三个头文件,它有两个版本。版本一:以 flags 方式打开 pathname;版本二:以 flags 方式打开 pathname,并设置 mode 权限。
请添加图片描述

pathname: 要打开或创建的目标文件文件名
flags:    打开方式。传递多个标志位,下面的一个或者多个常量进行“或”运算,构成flags.
             O_RDONLY: 只读打开
             O_WRONLY: 只写打开
             O_RDWR  : 读写打开
          以上这三个常量,必须指定一个且只能指定一个
             O_CREAT : 若文件不存在,则创建它。同时需要使用mode选项,来指明新文件的访问权限
             O_APPEND: 追加写
mode: 	  设置默认权限信息 

flags 可以是 O_RDONLY(read-only)、O_WRONLY(write-only)、O_RDWR(read/write),且必须包含以上访问模式之一。此外访问模式还可以带上 |标志位,下面会介绍一两个标志位,实际还要看场景使用。

以写的方式打开一个存在的文件,它同 fopen 一样,如果没有写操作,原文件的内容不会被覆盖;如果写操作,原文件的内容会被覆盖成写的内容。

以写的方式打开不存在的文件,权限是 644,运行程序发现没有新建文件 。

O_CREATE 发现文件不存在,将会新建文件,且必须指定 mode 权限(如果没有指定,那么新建的文件会变成可执行程序),如果没有 O_CREATE,说明文件是存在的,则可忽略 mode 权限(就算写了权限也不会对原来的文件更改权限)。


🌟2.2 close

请添加图片描述
使用 close 关闭文件,需要包含 unistd 头文件。fd 是 open 的返回值。


🌟2.3 read

要使用 read 读文件,需要包含 unistd 头文件。read 从 fd 文件描述符中读数据到 buf,读 count 个字节,返回值是实际读到的数据。

请添加图片描述

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>

int main()
{
    int fd = open("log.txt", O_RDONLY);//打开
    if(fd < 0)
    {
        perror("open");
        return 1;
    }

    //操作
    char buffer[1024];
    ssize_t sz = read(fd, buffer, sizeof(buffer) - 1);//期望读1023个,但实际可能只有100个,是从文件读,文件并不遵守字符串\0的规则,所以要主动\0
    if(sz > 0)
    {
    	buffer[sz] = '\0';//利用read的返回值,实际读到的个数就是该被\0的位置
    	printf("%s\n", buffer);
    }                                                       

    close(fd);//关闭 

    return 0;
}


🌟2.4 write

使用 write 写入文件,需要包含 unistd 头文件。write 向 fd 文件描述符中写入 buf,写 count 个字节,返回值是写了多少个

请添加图片描述


🌟2.5 测试用例

#include<string.h>  
#include<stdio.h>  
#include<sys/types.h>  
#include<sys/stat.h>  
#include<fcntl.h>  
#include<unistd.h>

int main()
{
    int fd = open("log.txt", O_WRONLY|O_APPEND);//打开
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    
    //操作
    const char* byh = "Hello System Call!\n";
    write(fd, byh, strlen(byh));
    write(fd, byh, strlen(byh));                    
                                                      
    close(fd);//关闭                                  
                                                      
    return 0;                                         
}                                                     

请添加图片描述


💫 3. 文件描述符fd

#include<stdio.h>  
#include<sys/types.h>  
#include<sys/stat.h>  
#include<fcntl.h>  
  
int main()  
{  
    int fd1 = open("log1.txt", O_WRONLY|O_APPEND|O_CREAT, 0644);
    int fd2 = open("log2.txt", O_WRONLY|O_APPEND|O_CREAT, 0644);
    int fd3 = open("log3.txt", O_WRONLY|O_APPEND|O_CREAT, 0644);
    int fd4 = open("log4.txt", O_WRONLY|O_APPEND|O_CREAT, 0644);
    int fd5 = open("log5.txt", O_WRONLY|O_APPEND|O_CREAT, 0644);

    printf("fd1: %d\n", fd1);                                   
    printf("fd2: %d\n", fd2);  
    printf("fd3: %d\n", fd3);  
    printf("fd4: %d\n", fd4);  
    printf("fd5: %d\n", fd5);  
    
    return 0;  
}                                    

请添加图片描述
我们说过返回小于 0 的数,则代表 open 失败,显示这里 open 都成功了。但是这里为什么不从 0 开始依次返回?—— 上面我们说过 C 程序运行起来,默认会打开三个文件(stdin、stdout、stderr),所以 0, 1, 2 分别与之对应。

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.

0,1,2对应的物理设备一般是:键盘,显示器,显示器
在这里插入图片描述

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


🌟 3.1 文件描述符的分配规则

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unisty.h>
int main()
{
	//close(0);
	close(2);
	int fd = open("myfile", O_RDONLY);
	if(fd < 0){
		perror("open");
		return 1;
	}
	
	printf("fd: %d\n", fd);
	
	close(fd);
	return 0;
}

请添加图片描述

每次给新文件分配的fd,是从fd_array[]中找一个最小的、未被使用的作为新的fd.

这其实很好理解,打开的文件要和进程产生关联,就要线性遍历数组中找一个未被使用的下标,填入文件地址。


💫 4. 重定向原理

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>

int main()
{
    //close(0);
    close(1);

    int fd1 = open("log3.txt", O_CREAT|O_WRONLY, 0644);
    int fd2 = open("log4.txt", O_CREAT|O_WRONLY, 0644);

    printf("hello byh!: %d\n", fd1);
    printf("hello byh!: %d\n", fd2);

    fflush(stdout);
                                                       
    close(fd1);
    close(fd2);

    return 0;
}

请添加图片描述
要想看到数据也很简单,在 close 之前 fflush 强制刷新即可,但这里要注意 fd1 和 fd2 对应 1 和 3,它们都是磁盘文件,printf 时,因为缓冲区没满,所以都在语言层的缓冲区,但是 fflush 之后,就会一次性的把两次数据都往 fd1 指向的文件中刷新。本来 printf 应该往显示器上输出,但 close 1,open 新文件,导致 1 的指向由显示器转换为磁盘,导致最终往文件里输出,本质重定向改变的是底层文件描述符下标指针的内容,上层是不知道的,这种技术叫做输出重定向。

虽然 stdout 和 stderr 对应的设备都是显示器,但是它们是两个独立的文件描述符,且作用却大不相同。这里虽然它们最终都往显示器上输出,但是重定向时,却只能对 stdout 重定向,因为底层改的是 1,没有影响 2

在这里插入图片描述


基础IO上总结

  • 如何理解—— 一切皆文件。
  • 进程在启动时,默认会打开 0, 1, 2,对应 C 语言上,就是 stdin, stdout, stderr。
  • 库函数的文件操作和系统调用的文件操作。
  • FILE* 和 fd。
  • fd 本质是进程和文件之间对应关系的数组的下标,有了 fd,就可以找到,打开文件的所有细节。
  • FILE*,FILE 是一个结构体,它主要有两块重要的成员 _fileno、缓冲区。
  • 数据在文件层面的流动过程。
  • 初步了解了重定向的原理和现象。

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

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

相关文章

Web渗透测试攻防之浅述信息收集

前言 众所周知渗透测试的本质是信息收集&#xff0c;在渗透测试中信息收集的质量直接关系到渗透测试成果的与否。在对系统进行渗透测试前的信息收集是通过各种方式获取所需要的信息&#xff0c;收集的信息越多对目标进行渗透的优势越有利。通过利用获取到的信息对系统进行渗透…

Java Spring Cloud XVIII 之 Kafka I

Java Spring Cloud XVIII 之 Kafka I Kafka 1.Kafka简介 Kafka是由Apache软件基金会开发的一个开源流处理平台&#xff0c;由Scala和Java编写。该项目的目标是为处理实时数据提供一个统一、高吞吐、低延迟的平台。Kafka最初是由LinkedIn开发&#xff0c;并随后于2011年初开源…

C++ 类和对象 (中)

作者&#xff1a;小萌新 专栏&#xff1a;C初阶 作者简介&#xff1a;大二学生 希望能和大家一起进步 本篇博客目标&#xff1a;梳理自己六个小时学到的知识 并且将类和对象知识分享给大家 专注的去做一件事 如果累了就去休息 C 类和对象 中本章学习目标前言一. 构造函数1.1 概…

破解系统密码与重装windows系统

数据来源 一、利用5次shift漏洞破解win7密码 1.1 漏洞 1. 在未登录时&#xff0c;连续按5次shift键&#xff0c;弹出程序C:\Windows\System32\sethc.exe 2. 部分win7及win10系统在未进入系统时&#xff0c;可以通过系统修复漏洞篡改系统文件名&#xff01; 注意&#xff1a;…

使用Maven部署到远程Linux服务器Tomcat

一、安装JDK 首先给服务器安装jdk&#xff0c;访问官网下载&#xff1a;Java Downloads | Oracle&#xff0c;下载图中的版本。首先我使用的是tomcat10&#xff0c;最低支持jdk1.8。安装了jdk19&#xff0c;是当时的最新版实测tomcat开启失败&#xff0c;新版jdk也不自带jre&a…

Windows上使用QEMU创建aarch64(ARM64)虚拟机

前言 随着国产化的推进&#xff0c;现在采用ARM、MIPS的机器越来越多&#xff0c;作为开发、运维人员要调测软件总不能每种架构的机器都去买一台吧&#xff1f;主要像博主这样的穷B&#xff0c;实在也是承受不起。。 需要的工具 1、QEMU Windows版官网下载地址&#xff1a;…

软件测试最最最重要的事

软件测试用例得出软件测试用例的内容&#xff0c;其次&#xff0c;按照软件测试写作方法&#xff0c;落实到文档中&#xff0c;两者是形式和内容的关系&#xff0c;好的测试用例不仅方便自己和别人查看&#xff0c;而且能帮助设计的时候考虑的更周。 一个好的测试用例必须包含…

Articulate360在线学习课件制作工具

Articulate是一款全新理念的在线和移动学习课件制作工具&#xff0c;可以说是目前国际上用户最广泛的e-learning课件制作工具之一。它包含了全新版的Storyline 360和Rise 360以及大量其他创作应用程序。使用Storyline 360开发可在所有设备上运行的自定义交互式课程&#xff0c;…

Java日志框架的发展历史,你不想了解一下吗

前言 相信大家在项目开发中肯定遇到过log4j&#xff0c;JUL&#xff0c;slf4j&#xff0c;logback&#xff0c;log4j2等日志框架相关名词&#xff0c;这些日志框架之间到底有什么关系&#xff0c;Java日志框架究竟经历了什么样的发展历程&#xff0c;相信有很多人都对此充满了好…

Socket 编程基础

文章目录一、socket 简介二、socket 编程接口介绍1. socket()函数2. bind()函数3. listen()函数4. accept()函数5. connect()函数6. 发送和接收函数read()函数recv()函数write()函数send()函数7. close()关闭套接字三、IP 地址格式转换函数inet_pton()函数inet_ntop()函数本篇会…

决策树与随机森林在分类预测中的应用(附源码)

写在前面 今天给大家分享一下基于决策树和随机森林在乳腺癌分类中实战。决策树和随机森林是白盒模型&#xff0c;数学建模中常用到这两种模型&#xff0c;用于预测或分类&#xff0c;随机森林还可以进行特征选择&#xff0c;故很推荐大家学习&#xff01;&#xff01;&#xff…

Qt5.14.2在Windows下使用mysql

第一步:下载依赖 1.1去Qt官方下载Qt5.14.2的源代码: Index of /archive/qt/5.14/5.14.2/singlehttps://download.qt.io/archive/qt/5.14/5.14.2/single/ 下载链接:https://download.qt.io/archive/qt/5.14/5.14.2/single/qt-everywhere-src-5.14.2.zip 1.2去Mysql官网下载Mys…

面试官:说说TCP如何实现可靠传输

今天来讲一下TCP是如何保证可靠传输的。这也是面试常问的一个题目&#xff0c;这个问题不单止能看出你是否真的了解TCP原理&#xff0c;更看出你是否有一个总结的能力。 我们从三个部分来讲TCP是如何实现可靠传输的。 滑动窗口 首先是讲TCP中的滑动窗口&#xff0c;它和TCP的…

基本的Dos命令

基本的Dos命令 打开CMD的方式 开始系统命令提示符win键R输入cmd (推荐使用)在任意的文件夹下&#xff0c;按住Shift键鼠标右击&#xff0c;打开命令行窗口在资源管理器地址栏路径前面加 “cmd ”管理员运行方式&#xff1a;命令提示符右键以管理员身份运行&#xff08;最高权…

动态规划--01背包问题详解

代码随想录day42和day43 动态规划 模块01背包问题 “即使到不了远方&#xff0c;心中也要有远方的模样。” 文章目录1. 01背包理论基础1.1什么是背包问题1.2二维dp数组01背包1.3一维dp数组(滚动数组)01背包2.leetcode 416.分割等和子集2.1 详细思路及思考难点2.2具体步骤及代码…

当食品制造业遇见数字化工具,如何借助S2B2C电商系统实现企业新增长

食品制造业是我国产业发展中的重要组成部分&#xff0c;具有点多、面广、投资小、见效快的特点&#xff0c;在经济发展中发挥着重要作用。根据工信部数据统计&#xff0c;从2018至2021年我国食品制造业经营规模稳步增长&#xff0c;2021年我国食品制造业营业收入达21268.1亿元&…

QT·移植Qt到ARM平台及搭建Qt交叉编译环境

目录 一、编译tslib库 二、移植 tslib 到文件系统 三、编译Qt源码&#xff08;用于移植到ARM&#xff09; 四、移植Qt到文件系统 五、搭建 Qt Creator 交叉编译环境 六、获得Qt可执行文件的另一种方法 要想在ARM平台上运行Qt程序&#xff0c;得满足以下两个点&#xff1a;1、…

【表达式求值】

目录&#xff1a;前言一、有效的括号&#xff08;一&#xff09;题目分析&#xff08;二&#xff09;整体代码二、表达式求值&#xff08;一&#xff09;题目分析1.栈的基本操作&#xff1a;2. 大体思路&#xff1a;3.具体计算过程&#xff1a;&#xff08;二&#xff09;整体代…

【代码随想录】二刷-哈希表

哈希表 《代码随想录》 哈希表一般用来快速查找某个元素是否在一个集合中。如果使用枚举的话时间复杂度为O(n)&#xff0c;而使用哈希表只O(1)就可以做到。——元素查询。 242.有效的字母异位词 使用unordered_map // 时间复杂度 O(n) // 空间复杂度 O(n) class Solution { pub…

嵌入式分享合集94

一、单片机硬件电路设计 减少后级电源对前级的影响&#xff0c;防止电源正负接反烧坏后级电路&#xff0c;防止电源关电时电流倒灌&#xff0c;但经过二极管有0.4V左右压降&#xff0c;需要考虑经过0.4V降压后会不会低于后级电路的正常工作电压。 一、按键电路 R1上拉电阻&…