Linux下基础IO

1 文件

这里首先得理解一下文件,文件存放在磁盘中(磁盘是永久性存储介质,是一种外设,也是一种输入输出设备),磁盘上的文件的所有操作,都是对外设的输入和输出简称IO,linux下一切皆⽂件,无论是键盘、显示器、网卡、磁盘都可以抽象的理解为文件。

1.对于0KB的空⽂件是占用磁盘空间的(就是文件属性占用了空间)

2.文件是文件属性(元数据)和文件内容的集合(文件=属性(元数据)+内容

3.所有的文件操作本质是文件内容操作和文件属性操作

理解:谁来操作文件?谁来管理文件 ?

文件操作的本质:进程对文件的操作。

管理者:磁盘的管理者是操作系统。

(另外在之前学习C语言时,我们通过C语言的函数接口,实现了对文件的操作,其实这些库函数只是方便用户使用,本质是封装的是系统调用接口来实现的)

2 文件路径

程序在当前路径下,系统怎么寻找到程序的当前路径?

ls  /proc/[进程id] -l 

可以查看当前正在运行进程的信息:

lrwxrwxrwx 1 iu iu 0 Aug 26 16:53 cwd -> /home/iu/io 
lrwxrwxrwx 1 iu iu 0 Aug 26 16:53 exe -> /home/iu/io/test

cwd:指向当前进程运行目录的⼀个符号链接。

exe:指向启动当前进程的可执行文件(完整路径)的符号链接。

 打开文件,本质是进程打开,所以,进程知道自己在哪里,所以文件不带路径,进程也知道。由此OS就能知道要创建的文件放在哪里。(之前讲的环境变量中,也存放着路径)

 3 stdin&stdout&stderr

启动进程,C默认会打开三个输⼊输出流,分别是stdin,stdout,stderr

#include <stdio.h>
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;

可以看到这是三个结构体指针。

 所以向显示器上打印,除了使用printf,还有fwrtie,fprintf等,只需要修改一下写入对象为stdout即可。

#include <stdio.h>
#include <string.h>
int main()
{const char *msg = "hello world\n";fwrite(msg, strlen(msg), 1, stdout);printf("hello world\n");fprintf(stdout, "hello world\n");return 0;
}

在之前学习C语言打开文件等操作时,还有就是权限问题:只读,只写,可读可写,追加等

r:只读

r+:可读可写

w:只写,如果文件不存在创建文件,每次写文件都会进行清空

w+ :可读可写

a:在文件末尾追加

a+:可读,在文件末尾追加

 这里会和后面使用系统调用操作文件进行比较。

4 系统文件IO

4.1接口介绍  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。

返回值:

        成功返回新打开的文件符(fd)

        失败返回-1

O_RDONLY: 只读打开

O_WRONLY: 只写打开

O_RDWR : 读,写打开

(这三个常量,有且只有一个)

O_CREAT : 若文件不存在,则创建它。(这里必须确定文件访问权限,也就是需要传入mode参数)

O_APPEND: 追加写

 4.2 open返回值

这里还需要区分一下,库函数和系统调用的概念,也就是库函数其实就是对系统调用进行了一层封装,可以通过下图理解一下:

 4.2.1 文件描述符fd

文件描述符其实就是一个整数。

在前面说进程启动会默认打开标准输入,标准输出,标准错误,其实对应的fd也就是0,1,2,这里对应的一般物理设备是:键盘,显示器,显示器。

所以就可以通过下面代码进行从键盘读取向屏幕打印:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{char buf[1024];ssize_t s = read(0, buf, sizeof(buf));if(s > 0){buf[s] = 0;write(1, buf, strlen(buf));write(2, buf, strlen(buf));}return 0;
}

这里就有新的问题要思考了?0,1,2到底有什么含义,还是随机分配的数字?

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

得出结论本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。 

可以查看内核源码观察确实如此:

 4.2.2 文件描述符的分配规则

通过下面两段代码对比:

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

结果:分别是3和0;

首先第一段代码,进程启动默认打开0,1,2三个文件,所以再打开“myfile”时fd就为3了,第二段代码将一给关闭了,再打开“myfile”fd就变成了0;

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

4.3 重定向

这里通过一段代码来说明:

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

将1(标准输出流)关闭,打开“myfile”文件,再调用printf,发现并不会写入到显示器文件上了,而是写入到myfile中去了,这个就叫做输出重定向 (前面linux指令中 >, >> , <等符号也就是表示的是输出重定向,追加重定向,输入重定向)

4.3.1 重定向的本质

前面有一个结论:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。所以这里将1关闭之后,再打开一个文件,就会占据1下标(文件描述符)的位置,而printf默认是向1中写入,所以但printf并不知道文件描述符为1的文件已将改变了,所以就写入到新打开的文件中去了。

 4.3.2 dup2系统调用

这就是系统提供的一个重定向的函数。

#include <unistd.h>
int dup2(int oldfd, int newfd);

将oldfd重定向到newfd中。

例如:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{int fd = open("./log", O_CREAT | O_RDWR);if (fd < 0) {perror("open");return 1;}close(1);dup2(fd, 1);while(true){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;
}

这里通过将打开的文件重定向到1(标准输出)中,再从标准输入中读取,再利用printf向文件中写入。printf是C库当中的IO函数,⼀般往stdout中输出,但是stdout底层访问文件的时候,找的还是fd:1, 但此时,fd:1下标所表示内容,已经变成了myfifile的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入,进而完成输出重定向。

5 linux中“一切皆文件”

这里我们可以从windows中理解,再windows中看到C盘,D盘中的文件,在linux下也是文件,但在linux下,进程,磁盘,显示器,键盘这样的设备也被抽象成文件,这样就可以通过访问文件的方式访问文件。

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

 这里通过观察file结构体来看看:

struct file {...struct inode *f_inode; /* cached value */const struct file_operations *f_op;...atomic_long_t f_count; // 表⽰打开⽂件的引⽤计数,如果有多个⽂件指针指向它,就会增加f_count的值。 unsigned int f_flags; // 表⽰打开⽂件的权限 fmode_t f_mode; // 设置对⽂件的访问模式,例如:只读,只写等。所有的标志在头⽂件<fcntl.h> 中定义 loff_t f_pos; // 表⽰当前读写⽂件的位置 ...} __attribute__((aligned(4))); 

值得注意的是这里有一个f_op的结构体成员, f_op 指针指向了⼀个 file_operations 结构体,这个结构体中的成员除了struct module* owner其余都是函数指针。

file_operation 就是把系统调用和驱动程序关联起来的关键数据结构,这个结构的每⼀个成员都对应着⼀个系统调用。读取 file_operation 中相应的函数指针,接着把控制权转交给函数,从而完成了Linux设备驱动程序的工作。

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

6 缓冲区

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

 6.1 缓冲区机制的作用

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

(这里举个例子,比如说:寄快递,如果是本人亲自送到目的地,那是不是浪费了自己很多时间,而如果我把它放到菜鸟驿站,让快递员去负责运输,那是不是本人就从这里面“解放出来了”,就可以去干更多的事)

为了减少使用系统调用的次数,提高效率,我们就可以采用缓冲机制。可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大提高计算机的运行速度。

 从磁盘里取信息,可以在磁盘文件进行操作时,可以⼀次从文件中读出大量的数据到缓冲区中,以后对这部分的访问就不需要再使用系统调用了,等缓冲区的数据取完后再去磁盘中读取。

6.2 缓冲类型

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

全缓冲区:这种缓冲方式要求填满整个缓冲区后才进行I/O系统调用操作。

(对于磁盘问件的操作通常使用全缓冲的方式访问)

行缓冲区:在行缓冲情况下,当在输入和输出中遇到换行符时,标准I/O库函数将会执行系统调用操作。

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

无缓冲区:无缓冲区是指标准I/O库不对字符进行缓存,直接调用系统调用。

(标准出错流stderr通常是不带缓冲区的,这使得出错信息能够尽快地显示出来)

 以下为特殊刷新方式:

1. 缓冲区满时;

2. 执行flush语句;

 通过下面一个例子:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() 
{close(1);int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0) {perror("open");return 0;}printf("hello world: %d\n", fd);close(fd);return 0;
}

使用重定向,让本应该打印在显示器上的内容写到“log.txt”文件中,但我们发现, 程序运行结束后,文件中并没有被写入内容。

这里是因为1号重定向到磁盘文件后,隐式转换成全缓冲,所以这里\n,也不会刷新了,这里close了fd,缓冲区就没刷新到文件里面,可以使用fflush来强制刷新。

关闭文件之前,加一句下面代码就可以了。

fflush(stdout);

这里补充一个:

如果是重定向到2(stderr)是不带缓冲区的,就可以直接刷新到文件里面。

7 FILE

之前在调用C语言封装的文件操作的库函数,可以看到一些函数返回值是FILE*

如:

 前面讲的系统调用,都是通过文件描述符fd来访问文件的,所以这个FILE结构体内,肯定封装这个fd成员。还有一点要注意,就是FILE中也存在语言级缓冲区。

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

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

相关文章

云原生核心技术 (6/12): K8s 从零到一:使用 Minikube/kind 在本地搭建你的第一个 K8s 集群

摘要 本文是一篇保姆级的实践指南&#xff0c;旨在解决学习 Kubernetes (K8s) 时“环境搭建难”的头号痛点。我们将对比分析 Minikube、kind、K3s 和 Docker Desktop Kubernetes 等主流本地 K8s 环境方案的优缺点&#xff0c;帮助你选择最适合自己的工具。随后&#xff0c;文章…

线程运行的现象和相关指令

一.多个线程运行的现象 1.规律 交替执行谁先谁后&#xff0c;不由我们控制 2.举例 Slf4j(topic "c.Test6") public class Test06 {public static void main(String[] args) {//创建并运行线程1new Thread(()->{while (true){log.debug("running");…

Windows网络配置避坑指南

Windows网络配置避坑指南 一、网络配置是什么?防火墙的“信任开关”二、何时需要手动切换网络配置文件?​必需切换的场景高危!绝对禁止选错的两个场景三、3种切换指南(Win10/11通用)方法1:图形化操作(推荐小白)​方法2:用PowerShell强制切换方法3:注册表底层修改(应…

基于ThinkPHP8.*的后台管理框架--Veitool框架学习使用

基于ThinkPHP8.*的后台管理框架--Veitool框架学习使用 一、安装部署二、目录结构 一、安装部署 环境要求 Linux、Unix、macOS、Windows Nginx、Apache、IIS PHP > 8.1.0 MySQL > 5.7 下载地址 官网下载&#xff1a;https://www.veitool.com/download 境内仓库&#xff…

Java多线程通信核心机制详解

在Java中&#xff0c;多线程通信与协作主要通过以下几种核心机制实现&#xff0c;每种方式适用于不同的并发场景&#xff1a; &#x1f504; 一、共享变量同步控制&#xff08;基础方式&#xff09; // 使用volatile保证可见性 private volatile boolean flag false;// 线程A…

Django知识-视图

视图设置 一个视图函数&#xff0c;简称视图&#xff0c;是一个简单的Python 函数&#xff0c;它接受Web请求并且返回Web响应。代码写在哪里也无所谓&#xff0c;只要它在你的应用目录下面。但是为了方便视图一般被定义在“应用/views.py”文件中。 视图的第一个参数必须为Ht…

DevSecOps实践:CI/CD流水线集成动态安全测试(DAST)工具

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 让安全扫描成为代码交付的“守门员” 引言&#xff1a;安全漏洞的代价&#xff0c;越早发现越好 在软件开发领域&#xff0c;安全漏洞的修复成本随着开发…

使用CSDN作为Markdown编辑器图床

使用CSDN作为Markdown编辑器图床 一、CSDN图床的优势 MD编辑器在撰写文档时功能强大&#xff0c;但插入图片需借助图床。CSDN作为免费图床&#xff0c;操作简单且稳定性高&#xff0c;适合日常使用。只需将图片上传至CSDN的MD编辑器&#xff0c;即可获取可直接访问的图片地址…

python打卡day51

复习日 作业&#xff1a;day43的时候我们安排大家对自己找的数据集用简单cnn训练&#xff0c;现在可以尝试下借助这几天的知识来实现精度的进一步提高 还是继续用上次的街头食物分类数据集&#xff0c;既然已经统一图片尺寸到了140x140&#xff0c;所以这次选用轻量化模型 Mobi…

Vuex 自动化生成工具

Vuex 自动化生成工具需求文档 1. 需求背景 为提升前端开发效率&#xff0c;减少重复代码编写&#xff0c;需开发一个自动化工具&#xff0c;根据输入参数自动生成完整的 Vuex 存储模块&#xff08;包括api.js&#xff0c;mutations.js&#xff0c;actions.js&#xff0c;gette…

深入浅出多路归并:原理、实现与实战案例解析

文章目录 二路归并多路归并方法一&#xff1a;指针遍历&#xff08;多指针比较法&#xff09;方法二&#xff1a;小根堆法&#xff08;最小堆归并&#xff09; 实际场景外部排序 经典题目丑数Ⅱ方法一&#xff1a;三指针法方法二&#xff1a;优先队列法&#xff08;K路归并&…

Koji构建系统宏定义注入与Tag体系解析

在Red Hat生态的持续集成链条中&#xff0c;Koji作为核心构建系统&#xff0c;其灵活的宏定义机制与精密的Tag体系是保障软件包高效流转的关键。本文将系统阐述在既有构建目标中注入宏定义的技术路径&#xff0c;并深度解析Koji中Target与Tag的概念架构及其版本演进差异。 一、…

【Kubernetes】架构与原理:核心概念、组件协同及容器化部署解析

文章目录 一、前言二、为什么需要 Kubernetes1. 传统部署方法2. 虚拟化部署3. 容器化部署Ⅰ. 基本概念Ⅱ. 容器编排的必要性Ⅲ. 容器化部署的优势4. k8s 的历史与发展三、Kubernetes 基本概念1. k8s 核心架构解析Ⅰ. 控制平面与工作节点Ⅱ. 各组件协同工作原理2. k8s 核心概念Ⅰ…

Pip Manager本地Python包管理器

在Python开发领域&#xff0c;包管理是每个开发者日常工作中不可或缺的一部分。虽然命令行工具pip功能强大&#xff0c;但对于初学者和非技术背景的用户来说&#xff0c;命令行界面往往显得不够友好。如果使用PyCharm&#xff0c;则可以非常简单的管理安装的Python包&#xff1…

vscode界面设置透明度--插件Glasslt-VSC

【快捷键:透明度提高(CtrAlt Z)&#xff0c;透明度降低(CtrAlt C)】

OPENCV形态学基础之一膨胀

一.膨胀的原理 数学表达式&#xff1a;dst(x,y) dilate(src(x,y)) max(x,y)src(xx,yy) 膨胀是图像形态学的基本功能之一&#xff0c;膨胀顾名思义就是求图像的局部最大值操作&#xff0c;它的数学表达式是dst(x,y) dilate(src(x,y)) max(x,y)src(xx,yy)。 从数学的角度来看…

彻底禁用Windows Defender通知和图标

方法 一&#xff1a;通过注册表强制隐藏 Defender 图标&#xff08;永久生效&#xff09;​​ &#xff08;适用于彻底隐藏图标&#xff0c;但需谨慎操作&#xff09; ​​打开注册表编辑器​​ 按 Win R&#xff0c;输入 regedit 回车。 ​​导航到 Defender 相关注册表项​…

Kafka 2.7.0 单节点安装与启动教程(适配 JDK 1.8)

1. 下载与解压 官方下载 Kafka 2.7.0 https://archive.apache.org/dist/kafka/2.7.0/kafka_2.13-2.7.0.tgz 上传到虚拟机&#xff08;如 /home/wang/soft/kafka&#xff09;解压&#xff1a; tar -zxvf kafka_2.13-2.7.0.tgz 2. 配置环境变量&#xff08;可选&#xff0c;便…

23、Python字符串核心机制解析:驻留原理、对象比较与成员检测实战

适合人群&#xff1a;零基础自学者 | 编程小白快速入门 阅读时长&#xff1a;约5分钟 文章目录 一、问题&#xff1a;Python的字符串驻留机制&#xff1f;1、例子1&#xff1a;字符串驻留现象2、答案&#xff1a;&#xff08;1&#xff09;字符串驻留 二、问题&#xff1a;Pyth…

pikachu靶场通关笔记22-2 SQL注入05-2-update注入(报错法)

目录 一、SQL注入 二、update注入 三、报错型注入 四、源码分析 1、代码审计 2、渗透思路 五、渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff…