【Linux】深入理解文件操作:从C语言接口到系统调用与缓冲区管理

文章目录

  • 前言:
  • 1. 铺垫
  • 2. 重新使用C文件接口:对比一下重定向
    • 2.1. 什么叫当前路径?
    • 2.2. 写入文件
    • 2.3. 读文件
    • 2.4. 程序默认打开的文件流
    • 2.5. 输出
    • 2.6. 输入
  • 3. 系统调用提供的文件接口
    • 3.1. open 打开文件
    • 3.2. open函数返回值
  • 4. 缓冲区问题
  • 总结:

前言:

在计算机编程中,文件操作是基础且至关重要的技能之一。无论是在系统编程、网络编程还是数据处理,文件的读写操作都是不可或缺的。本文将深入探讨文件操作的底层原理,从C语言层面的文件接口到操作系统层面的系统调用,再到缓冲区机制的实现,逐步揭示文件操作的全貌。通过对比C语言的文件接口和系统调用,以及对缓冲区问题的深入分析,本文旨在帮助读者建立一个清晰的文件操作概念框架,从而在实际开发中更加得心应手。

1. 铺垫

a. 文件 = 内容 + 属性
b. 访问文件之前,都得先打开。修改文件,都是通过指向代码的方式完成修改,文件必须加载到内存中
c. 谁打开文件?进程在打开文件
d. 一个进程可以打开多少个文件呢?可以打开多个文件

  • 一定时间内,系统中存在多个进程,也可能同时存在更多的被打开文件,OS要不要管理多个被进程打开的文件呢?肯定的
  • 如何管理呢?先组织,再描述!

e. 进程和文件的关系,struct task_struct 和 struct XXX?

a~e 被打开文件都是:内存文件

f. 系统中是不是所有的文件都被进程打开了?不是!没有被打开文件?就在磁盘中

2. 重新使用C文件接口:对比一下重定向

 FILE *fp = fopen("./log.txt", "w");	//以只写的方式打开会把该文件清空
 if (fp == NULL)
 {
 	perror("fopen");
 	return 1;
 }
 //文件操作
 const char *str = "hallo file!\n";
 fputs(str, fp);
 
 fclose(fp);
 return 0;

在这里插入图片描述
以 w 方式打开文件的时候,该文件会被自动清空。

echo "hello bit" > log.txt		//hello bit,本质上就是写入
> log.txt		//文件直接被清空,是因为在输出重定向时需要先把文件打开

以 a 方式打开文件,就类似于重定向中的追加。

 FILE *fp = fopen("./log.txt", "a");	//以追加的形式打开
 echo "hello bit" >> log.txt			//对比追加重定向

2.1. 什么叫当前路径?

在进程文件里 ls /proc/29065 -l
在这里插入图片描述
在进程启动时,会记录自己启动时所在的路径。

2.2. 写入文件

  const char *msg = "hallo file!\n";
  int cnt = 5;
  while (cnt)
  {
    int n = fwrite(msg, strlen(msg), 1, fp);
    printf("write %d block, pid is : %d\n", n, getpid());
    cnt--;
    sleep(20);
  }

2.3. 读文件

  char buffer[64];
  while(true) 
  {
    char* r = fgets(buffer, sizeof(buffer), fp); // 按行读
    if (!r) break;
    printf("%s", buffer);
  }

2.4. 程序默认打开的文件流

stdin	//标准输入	键盘设备
stdout	//标准输出	显示器设备
stderr	//标准错误	显示器设备

2.5. 输出

  printf("hello printf\n");
  fputs("hello fputs", stdout);
  const char *msg = "hello fwrite\n";
  fwrite(msg, 1, strlen(msg), stdout);
  fprintf(stdout, "hello fprint\n"); 

2.6. 输入

  char buffer[64];
  fscanf(stdin, "%s", buffer);

3. 系统调用提供的文件接口

访问文件不仅仅有C语言上的文件接口,OS必须提供对应的访问文件的系统调用?
w: 清空文件、a: 追加文件、r: 读取文件内容

3.1. open 打开文件

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

int open(const char *pathname, int flags); // falgs   是用位图进行传参,
										  //哪个比特位被设置了就传递哪一个
int open(const char *pathname, int flags, mode_t mode); // 这里的mode 为权限掩码 umask
  • pathname: 要打开或创建的目标文件
  • flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
  • 参数:
    O_RDONLY: 只读打开
    O_WRONLY: 只写打开
    O_RDWR : 读,写打开 这三个常量,必须指定一个且只能指定一个
    O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
    O_APPEND: 追加写
  • 返回值:
    成功:新打开的文件描述符
    失败:-1

设置文件创建的掩码:

   #include <sys/types.h>
   #include <sys/stat.h>

   mode_t umask(mode_t mask); // 设置我们对应的权限掩码
  • writereadcloselseek ,类比C文件相关接口。

3.2. open函数返回值

在认识返回值之前,先来认识一下两个概念: 系统调用库函数
结论1:C语言的文件接口,本质就是封装了系统调用。
FILE: C标准库中自己封装的一个结构体,必须 封装特定的 fd
C语言问什么要封装呢?为了保证自己的跨平台性。
认识 fd:数组下标?
文件描述符的本质就是数组下标。
在这里插入图片描述
题外话:如何理一切皆文件
通过struct file {…} 屏蔽掉了各种硬件的底层硬件差异 ,VFS(虚拟文件系统)

文件 fd 的分配规则 && 利用规则实现重定向
fd 的分配规则:从最小的没被使用的数组下标,会分 配给最新打开的文件!

想实现文件描述符的重定向,不用关闭再重新打开,OS必须提供“拷贝”接口。

   #include <unistd.h>

   int dup(int oldfd);
   int dup2(int oldfd, int newfd);

4. 缓冲区问题

缓冲区它就是一块内存区域(用空间换时间)
为什么有? 提高使用者的效率
聚集数据,一次拷贝(刷新),提高整体效率。

调用系统调用是有成本的,时间&&空间

我门一直在说的缓冲区和内核中的缓冲区没有关系(尽管它有),语言层面的缓冲区,C语言自带缓冲区,缓冲到一定程度后再刷新到操作系统的缓冲区中。

  1. 无刷新,无缓冲
  2. 行刷新——显示器
  3. 全缓冲,全部刷新——普通文件,缓冲区被写满,才刷新。 还有两种刷新:强制刷新、进程退出的时候要自动刷新。

具体在哪里?

FILE *fp = fopen("log.txt", "w");
FILE *fp = ??

FILE : 其实是一个结构体(fd), 缓冲区是被FILE结构来维护的! (stdin,stdout,stderr

编码模拟:手动模拟一下 C标准库中的方法。

// mystdio.h
#pragma once

#include <stdio.h>

#define SIZE 4096
#define NONE_FLUSH (1<<1)
#define LINE_FLUSH (1<<2)
#define FULL_FLUSH (1<<3)


typedef struct _myFILE
{
    // char inbuffer[];
    char outbuffer[SIZE];
    int pos;
    int cap;
    int fileno;  
    int flush_mode;
}myFILE;

myFILE* my_fopen(const char* pathname, const char* mode);
int my_fwrite(myFILE *fp, const char* s, int size);
void my_fclose(myFILE* fp);
void my_fflush(myFILE* fp);
void DebugPrint(myFILE *fp);
// mystdio.c
#include "mystdio.h"
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>

const char* toString(int flag)
{
    if(flag & NONE_FLUSH) return "None";
    else if(flag & LINE_FLUSH) return "Line";
    else if(flag & FULL_FLUSH) return "FULL";
    return "Unknow";
}

void DebugPrint(myFILE *fp) 
{
    printf("outbuffer: %s\n", fp->outbuffer);
    printf("fd: %d\n", fp->fileno);
    printf("pos: %d\n", fp->pos);
    printf("flush_mode: %s\n", toString(fp->flush_mode));
}

myFILE* my_fopen(const char* pathname, const char* mode)
{
    int flag = 0;
    if (strcmp(mode, "r") == 0)
    {
        flag |= O_RDONLY;
    }
    else if(strcmp(mode, "w") == 0) 
    {
        flag |= (O_CREAT| O_WRONLY | O_TRUNC);
    }
    else if(strcmp(mode, "a") == 0)
    {
        flag |= (O_CREAT| O_WRONLY | O_APPEND);
    }
    else 
    {
        return NULL;
    }

    int fd = 0;
    if(flag & O_WRONLY)
    {
        umask(0);
        fd = open(pathname, flag, 0666);
    }
    else 
    {
        fd = open(pathname, flag);
    }
    if (fd < 0) return NULL;

    myFILE *fp = (myFILE*)malloc(sizeof(myFILE));
    if(fp == NULL) return NULL;
    fp->fileno = fd;
    fp->cap = SIZE;
    fp->pos = 0;
    fp->flush_mode = LINE_FLUSH;

    return fp;
}

void my_fflush(myFILE* fp)
{
    if (fp->pos == 0) return;
    write(fp->fileno, fp->outbuffer, fp->pos);
    fp->pos = 0;
}

int my_fwrite(myFILE *fp, const char* s, int size) 
{
    // 1. 写入
    memcpy(fp->outbuffer + fp->pos, s, size);
    fp->pos += size;
    if ((fp->flush_mode & LINE_FLUSH) && fp->outbuffer[fp->pos-1] == '\n')
    {
        my_fflush(fp);
    }
    else if((fp->flush_mode & FULL_FLUSH) && fp->pos == fp->cap)
    {
        my_fflush(fp);
    }
    return size;
}

void my_fclose(myFILE* fp)
{
    my_fflush(fp);
    close(fp->fileno);
    free(fp);
}
// filetest.c
#include "mystdio.h"
#include <string.h>
#include <unistd.h>

const char* filename = "./log.txt";

int main()
{
    myFILE *fp = my_fopen(filename, "w");
    if (fp == NULL) return 1;


    int cnt = 5;
    char buffer[64];
    while (cnt)
    {
        snprintf(buffer, sizeof(buffer), "helloword,hellohd,%d!!! ",cnt--); 
        my_fwrite(fp, buffer, strlen(buffer));
        DebugPrint(fp);
        sleep(2);
        my_fflush(fp);
    } 

    my_fclose(fp); 
    return 0;
}

在这里插入图片描述

在这里插入图片描述

总结:

本文首先介绍了文件操作的基本概念,包括文件的定义、访问文件前的打开过程、以及进程与文件的关系。接着,通过C语言的文件接口示例,详细讨论了文件的打开、写入、读取以及默认文件流的使用。文章进一步探讨了系统调用层面的文件接口,特别是open函数的使用方法和返回值,揭示了C语言文件接口背后封装的系统调用机制。

在缓冲区问题部分,文章解释了缓冲区的作用、类型以及与内核缓冲区的关系,并提供了一个简单的缓冲区管理模拟实现。通过这个模拟实现,读者可以更直观地理解缓冲区在文件操作中的重要性和工作机制。

最后,文章通过一个实际的文件测试程序,展示了如何使用自定义的文件操作函数来模拟标准C库中的文件操作,这不仅加深了对文件操作原理的理解,也提高了编程实践能力。

通过本文的学习,读者应该能够对文件操作有一个全面而深入的理解,无论是在理论层面还是实践层面,都能够更加自信和高效地进行文件相关的编程工作。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MongoDB~索引使用与优化

Study by&#xff1a; https://docs.mongoing.com/indexeshttps://www.cnblogs.com/Neeo/articles/14325130.html#%E5%85%B6%E4%BB%96%E7%B4%A2%E5%BC%95 作用 如果你把数据库类比为一本书&#xff0c;那书的具体内容是数据&#xff0c;书的目录就是索引&#xff0c;所以索引…

【随笔】Git 实战篇 -- 开心 commit 之后,发现有一处bug还需要改,只能 reset 撤销然后再次提交 -- git reset --(四十三)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

RabbitMQ小结

MQ分类 Acitvemq kafka 优点&#xff1a;性能好&#xff0c;吞吐量高百万级&#xff0c;分布式&#xff0c;消息有序 缺点&#xff1a;单机超过64分区&#xff0c;cpu会飙高&#xff0c;消费失败不支持重试 &#xff0c; Rocket 阿里的mq产品 优点&#xff1a;单机吞吐量也…

如何赋予LLM多模态能力(MLLM)

基本概念 多模态大型语言模型&#xff08;MLLMs&#xff09;是人工智能领域的一项前沿技术&#xff0c;旨在设计能够理解和生成跨越多种形式数据输入&#xff08;如文本和图像&#xff09;内容的模型。 链接文本和视觉模态&#xff1a;MLLMs能够整合文本和视觉数据源的信息。…

众汇:外汇狙击指标如何使用?

对于投资者来说&#xff0c;我们各位交易的目的是什么?WeTrade众汇认为那就是盈利。所以来说有一个指标对各位投资者来说那是相当有帮助的。这是因为对于交易者而言&#xff0c;利用这些指标可以快速识别盈利的买卖时机。当我们选择一个指标之后&#xff0c;深入了解其适用范围…

【机器学习】机器学习与AI大数据的融合:开启智能新时代

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 机器学习与AI大数据的融合 &#x1f4d2;1. 引言&#x1f4d5;2. 机器学习与大数据&#x1f3a9;机器学习与大数据的特征&#x1f388;大数据如…

基于全志T507-H的Linux-RT实时性测试案例分享

本文将为各位工程师演示全志T507-H工业评估板&#xff08;TLT507-EVM&#xff09;基于IgH EtherCAT控制伺服电机方法&#xff0c;生动说明Linux-RT Igh EtherCAT的强大之处&#xff01; Linux-RT系统的优势 内核开源、免费、功能完善。 RT PREEMPT补丁&#xff0c;使Linux内…

树形结构获取所有直属父级节点

递归获取 let arr [{name: "/",meta: {},children: [{name: "home",},{name: "home2",},{name: "common-components",children: [{name: "form-component",}]},{name: "multilevel-menu",children: [{name: &qu…

【数据结构】复杂度的重要性—–决定程序运行的效率

【数据结构】复杂度的重要性—–决定程序运行的效率 前言 在我们写算法的时候&#xff0c;常常会需要考虑一个问题&#xff1a;这个算法好不好&#xff1f;而这个“好”实际上就取决于是算法的复杂度。 算法复杂度&#xff08;Algorithmic Complexity&#xff09;是指算法在编…

粒子系统技术在AI绘画中的创新应用

引言&#xff1a; 随着人工智能技术的飞速发展&#xff0c;AI绘画已经成为艺术创作和数字媒体领域的一大热点。粒子系统作为一种模拟复杂物理现象的技术手段&#xff0c;其在AI绘画中的应用为创作过程带来了前所未有的灵活性和创新性。本文将深入探讨粒子系统技术的原理、特点以…

Nvidia Jetson/Orin +FPGA+AI大算力边缘计算盒子:人工智能消防应用

青鸟消防股份有限公司成立于2001年6月&#xff0c;于2019年8月在深圳证券交易所挂牌上市&#xff0c;成为中国消防报警行业首家登陆A股的企业。公司始终聚焦于消防安全与物联网领域&#xff0c;主营业务为“一站式”消防安全系统产品的研发、生产和销售。公司产品已覆盖了火灾报…

【Linux 网络】高级 IO -- 详解

一、IO 的基本概念 I/O&#xff08;input/output&#xff09;也就是输入和输出&#xff0c;在冯诺依曼体系结构当中&#xff0c;将数据从输入设备拷贝到内存就叫作输入&#xff0c;将数据从内存拷贝到输出设备就叫作输出。 对文件进行的读写操作本质就是一种 IO&#xff0c;文…

近邻算法详解:原理、Java实现及应用场景

摘要 近邻算法&#xff08;Nearest Neighbor Algorithm&#xff09;是一类基于实例的学习方法&#xff0c;广泛应用于分类和回归问题中。最常见的近邻算法是K近邻算法&#xff08;K-Nearest Neighbors, KNN&#xff09;&#xff0c;其基本思想是通过计算待分类样本与训练样本的…

内网渗透-详解代理逻辑及隧道

写在前面 红蓝对抗过程中打点以后往往需要进行内网渗透和横向移动&#xff0c;因此大家都需要扎实掌握代理和隧道知识&#xff0c;一款优秀的代理工具也可以给内网渗透带来很大的收益。 1.正向代理&#xff1a; 代理客户端&#xff0c;帮助客户端完成所需请求。 举例&#x…

系统架构设计师【第6章】: 数据库设计基础知识 (核心总结)

文章目录 6.1 数据库基本概念6.1.1 数据库技术的发展6.1.2 数据模型6.1.3 数据库管理系统6.1.4 数据库三级模式 6.2 关系数据库6.2.1 关系数据库基本概念6.2.2 关系运算6.2.3 关系数据库设计基本理论 6.3 数据库设计6.3.1 数据库设计的基本步骤6.3.2 数据需求分析6…

梵几 x TapData:如何高效落地实时数据中台,助力家居企业优化数字营销

使用 TapData&#xff0c;化繁为简&#xff0c;摆脱手动搭建、维护数据管道的诸多烦扰&#xff0c;轻量代替 OGG、DSG 等同步工具&#xff0c;「CDC 流处理 数据集成」组合拳&#xff0c;加速数据流转&#xff0c;帮助企业将真正具有业务价值的数据作用到实处&#xff0c;将“…

【FISCO BCOS 3.0】一、新版本搭链介绍

目录 一、区块链种类的变化 二、搭链演示 1.单群组区块链&#xff08;Air版本&#xff09; 2.多群组区块链&#xff08;Pro版本&#xff09; 3.可扩展区块链&#xff08;Max版本&#xff09; FISCO BCOS的发展速度如日中天&#xff0c;对于稳定的2.0版本而言&#xff0c;偶…

【【手把手教你实现Risc-V装载至FPGA】】

RiscV实现教程 参考来源 tinyriscv: https://gitee.com/liangkangnan/tinyriscv 平台实现 &#xff1a; Linux ubuntu 实现介绍 环境 &#xff1a; 需要 iverilog (切换到 v11或以上的版本&#xff09; 1.下载iverilog源码 git clone https://github.com/steveicarus/iverilog.…

zookeeper启动(一)

1.zookeeper启动入口 在zkServer.sh的启动命令中,我们可以找到zookeeper启动的关键类org.apache.zookeeper.server.quorum.QuorumPeerMain QuorumPeerMain#main 我们可以直接看org.apache.zookeeper.server.quorum.QuorumPeerMain中的main方法,从下面的main方法中,我们可以…

收银系统源码-千呼新零售2.0【线上商城商品详情页细节优化】

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货等连锁店使用。 详细介绍请查看下…