Linux——文件的概念、操作和理解

news2024/12/22 20:08:28

引言

文件 = 文件内容 + 文件属性
要操作文件,就要先打开文件。根据冯诺依曼体系,只能操作内存中的数据。因此要先把文件内容加载到存储器,即内存中。

文件接口

语言层面的文件接口

FILE* fopen(const char *path, const char *mode);
int   fclose(FILE *fp);

size_t fwrite(const void *ptr, size_t size, size_t number, FILE *stream);
size_t fread(void *ptr, size_t size, size_t number, FILE *stream);
模式含义文件不存在时
r只读报错
w只写创建文件
a追加只写创建文件
rb二进制只读报错
wb二进制只写创建文件
ab二进制追加只写创建文件
r+读写报错
w+读写创建文件
a+追加读写创建文件
rb+二进制读写报错
wb+二进制读写创建文件
ab+二进制追加读写创建文件

一个文件不能同时读和写。

struct _iobuf {
    char *_ptr;     //文件输入的下一个位置
    int _cnt;       //当前缓冲区的相对位置
    char *_base;    //指基础位置(即是文件的起始位置) 
    int _flag;      //文件标志
    int _fileno;    //文件描述符id
    int _charbuf;   //检查缓冲区状况,如果无缓冲区则不读取
    int _bufsiz;    //文件缓冲区大小
    char *_tmpfname;//临时文件名
       };
typedef struct _iobuf FILE;

C语言的文件接口也是借助了操作系统的系统调用接口,封装了一层。

操作系统层面的文件接口

#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: 决定文件打开模式
  • mode: 创建文件时文件的权限
  • 返回值:文件描述符

man手册里提供了open的两个函数原型,看起来像是重载,但是C语言使不支持函数重载的。实际上是用到了可变参数。

__fortify_function int open (const char *__path, int __oflag, ...)

flags标志位

标志位含义
O_RDONLY只读
O_WRONLY只写
O_RDWR读写
O_APPEND追加
O_CREAT创建文件
O_TRUNC清空文件

实际上flag是一个位图,用32个bit位最多表示32种状态。

通过与操作就能判断是什么标志位,然后执行不同的逻辑。

文件权限

用第一个open接口创建的文件的权限是乱的。需要用第二个接口。

int fd = open("Log.txt", O_WRONLY | O_CREAT, 0666);
mode_t umask(mode_t mask);

但是权限的设置还和权限掩码umask有关。

权限掩码

权限掩码的目的是可以让开发者自定义文件的默认权限。

  • 默认目录文件权限,起始权限777,最终默认权限是775,111 111 101
  • 默认普通文件权限,起始权限666,最终默认权限是664,110 110 100

涉及权限掩码,umask,默认为0002
只关注后三位002,凡是在权限掩码中出现的权限,都不应该在最终权限中出现。
计算规则:最终权限 = 起始权限 & 权限掩码

目录文件普通文件
umask000 000 010000 000 010
~mask111 111 101110 110 100
默认权限111 111 111110 110 110
最终权限111 111 101110 110 100

文件描述符

open的返回值是文件描述符,用来描述进程打开的每一个文件,是一个非负整数。
Linux下一切皆文件,文件描述符可以用于IO,也可以用来操作设备,充当套接字等。当进程打开一个文件,内核会为这个文件分配一个最小的可用文件描述符。

一个进程会默认打开三个文件流,分别为标准输入,标准输出,标准错误流。
这就是为什么成功打开文件时所得到的文件描述符是从3开始进程分配的。

文件描述符表

一个进程可以打开多个文件,操作系统如何管理被打开的文件?
先描述再组织,维护内核数据结构,并用链表等管理起来。

task_struct 里有一个 struct files_struct *files,描述一个进程打开的文件集合
files_struct 里有一个 struct file * fd_array[NR_OPEN_DEFAULT],文件描述符表
元素是struct file,即每一个被打开的文件。

struct files_struct {
    atomic_t count;  // 记录的是共享文件描述符表的进程数量
// 省略大部分
    int next_fd;
    struct file * fd_array[NR_OPEN_DEFAULT];
};

可以通过ulimit指令调整文件描述符表的大小

打开一个文件,就是把文件的内容和属性从磁盘加载到内存,创建好file结构体,初始化后,从0号下标开始找,找到最小的可用文件描述符,把地址放入到fd_array中。

open函数打开文件成功时数组当中的指针个数增加,这个指针在数组当中的下标被返回。文件打开失败返回-1,因此,成功打开多个文件时返回的文件描述符就是递进的。

重定向

image.png
关闭标准流,再打开一个文件,可以实现重定向。或者使用系统接口dup和dup2

dup2(A,B)是将B重定向到A,如dup(fd,1),将原来输出到1中的内容输出到fd中。

umask(0);
int fd = open("Log.txt", O_CREAT | O_RDWR | O_TRUNC, 0666);
dup2(fd, 1); // 用fd替换1
printf("Hello\n");

标准输出和标准错误

标准输出的fd是1,错误是2,本质上都是指向显示器,目的是区分程序的错误提示和正常输出。错误可以打印到日志里,输出可以打印到显示器。且输出可以被重定向,错误不可以。
image.png

./a.out 1> output.txt 2> error.txt

缓冲区

缓冲区无处不在,文件IO也有缓冲区。

什么是缓冲区?

缓冲区就是一段内存,把要向指定流输出的内容先存在这段内存里,时机合适就一次性刷新。

为什么需要缓冲区?

因为内存与外设交互速度是非常慢的,进程先把内容放到缓冲区,就不用一直等待和外设交互,多次存储一次输出也可以减少IO的次数。这样做可以提高整体的效率。

缓冲区在哪里?

image.png
结果:立即打印write,三秒后才打印printf和fprintf。
而在printf和fprintf中加了换行后,会按顺序立即打印出来。

而printf和fprintf肯定都是封装了write,因此可以判断,库中还有缓冲区。这里是语言级别的缓冲区,实际上肯定还有操作系统级别的缓冲区,当刷新用户缓冲区的数据时,并不是直接将数据刷新到外设,而是先刷新到操作系统的缓冲区。

缓冲区的刷新策略

常规情况

  • 无缓冲:每次输入输出都直接系统调用,不经过用户空间的缓冲区。这种方式可以保证数据的实时性,但是系统调用的开销较大,且外设交互效率低。例如write就是无缓冲。
  • 行缓冲:每次输入输出都先写入用户空间的缓冲区,当遇到换行符或者缓冲区满时,才调用系统调用将数据传输到内核空间。可以提高效率,但是牺牲了一定的实时性,且需要占用资源,因为数据可能会停留在用户空间的缓冲区一段时间。例如printf和scanf就是行缓冲。
  • 全缓冲:每次输入输出操作都先写入用户空间的缓冲区,当缓冲区满时,才调用系统调用将数据传输到内核空间。实时性差,因为数据可能会停留在用户空间的缓冲区更长时间。例如磁盘文件或网络套接字的输入输出操作就是全缓冲的。

特殊情况:

  • 进程退出:当进程正常退出时,会自动关闭所有打开的文件描述符,缓冲区会被刷新。但是如果进程是异常终止,例如收到信号,那么缓冲区可能不会被刷新,导致数据丢失或不一致。
  • 主动刷新:调用如fflush或者stream的flush方法可以主动刷新缓冲区。
  • 缓冲区超时:有些系统或设备可能会设置缓冲区超时的机制,即如果缓冲区在一定时间内没有被刷新,就会强制刷新缓冲区。这样可以避免数据在缓冲区中停留过长时间,影响实时性。例如,在Linux中,对于网络套接字的输出操作,可以使用TCP_NODELAY选项来禁用Nagle算法,这样就可以避免数据在套接字缓冲区中等待合并,而是尽快发送出去。

奇怪的问题

image.png

当把fork屏蔽掉之后,没有重复。
image.png

原因

显示器是行刷新,磁盘文件是全缓冲。重定向时,三个语言级别函数会内容暂存在语言级别缓冲区,因为不是显示器文件而不能行缓冲,而write是无缓冲,所以顺序最先。
fork创建子进程,父子共享代码,数据以写时拷贝的形式继承,其中包含了语言级别的缓冲区。
进程退出之后,父子进程都要刷新缓冲区,写入到文件中,因此会有重复。

软硬链接

概念

软硬链接是Linux系统中两种不同的链接方式,用于实现文件的共享使用。
硬链接是通过索引节点(index_node)来连接的,一个文件可以有多个硬链接,它们共享同一个inode和数据块。是一个独立文件,有自己的iNode和iNode编号与文件内容,相当于快捷方式。

软链接是通过文件路径来进行连接的,它实际上是一个特殊的文件,其中包含了另一个文件的位置信息。不是独立文件,和目标文件使用同一个iNode。

硬链接和软链接有以下几点区别:

  • 硬链接不能跨文件系统,软链接可以。
  • 硬链接不能对目录进行链接,软链接可以。
  • 硬链接必须要有源文件存在,软链接可以对不存在的文件名进行链接。
  • 删除源文件后,硬链接不受影响,软链接失效。
  • 删除硬链接后,不影响源文件和其他硬链接,删除软链接后,不影响源文件。

操作

ln命令建立链接,UNlink命令接触链接。-s表示软链接,-h表示硬链接。
image.png

在删除了dir中的hello后,test下的硬链接同样能正常运行。删除了两者其中的一个,本质就是减少了一份iNode编号和文件的映射,也就是文件的硬连接数减少。

可以推测mv指令的实现原理。mv之后的iNode数不变。硬链接实际上是一份复制。吗?

实际上是在当前路径下新增一份被硬链接文件的文件名和iNode编号的映射关系。
并且可以看到,随着硬链接增多,被链接文件的硬链接数变成了3,类似引用计数的概念。

那为什么创建普通文件,他的硬连接数默认是1?
因为本身的文件名和iNode有一个映射关系,必定大于等于1。

那为什么目录默认是2?
目录本身是文件,所以会有在当前路径下的文件名和iNode编号的映射,这是1;在进入这个目录后,会有一个与当前这个目录形成映射,才有./exe的这种写法,这是2。
那这个iNode就可以类比成指针。而软链接是一个新的文件,其内容保存的是指向文件的所在路径。
image.png

此时dir的硬连接数是2,因为本身dir在当前路径就有一份文件名和iNode编号的映射,进入到dir后,还有一份dir中的 .

在dir1里创建一个目录后,硬链接数多了1,因为目录里一定有一个 … 和dir1链接起来。

所以不去自己硬链接的情况下,可以估算一个目录的下级目录中有多少个子目录,硬连接数-2。
image.png

模拟实现C文件接口

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

#define _bufferNum 512 // 缓冲区大小

#define NONE_FLUSH 0
#define LINE_FLUSH 1
#define FULL_FLUSH 2

typedef struct _myFile
{
  int _fileno;                  // 文件描述符
  char _buffer[_bufferNum];     // 语言级别缓冲区
  int _end;                     // 缓冲区内容的下标
  int _flushWay;                // 刷新策略
}myFile;

myFile* myFopen(const char* fileName, const char* mode)
{
  assert(fileName && mode);
  int _mode = O_RDONLY;
  if(strcmp(mode,"r") == 0)
  {}
  else if(strcmp(mode,"w") == 0)
  {
    _mode = O_CREAT | O_WRONLY | O_TRUNC;
  }
  else if(strcmp(mode,"a") == 0)
  {
    _mode = O_CREAT | O_WRONLY | O_APPEND;
  }
  int fileno = open(fileName, _mode, 0666);
  if(fileno < 0) return NULL;

  myFile* mfp = (myFile*)malloc(sizeof(myFile));
  if(mfp == NULL) return NULL;
  
  memset(mfp, 0, sizeof(myFile));
  mfp->_fileno = fileno;
  mfp->_flushWay |= LINE_FLUSH;
  mfp->_end = 0;
  return mfp;
}
  
void myFwrite(const char* buffer, size_t count, myFile* mfp)
{
  assert(buffer && mfp && (count > 0));
  // 先把内容写到缓冲区
  strncpy(mfp->_buffer + mfp->_end, buffer, count);
  mfp->_end += count;

  // 刷新策略 只写了行缓冲
  if(mfp->_flushWay &= LINE_FLUSH)
  {
    if(mfp->_end > 0 && mfp->_buffer[mfp->_end - 1] == '\n')
    {
      write(mfp->_fileno, mfp->_buffer, mfp->_end);
      mfp->_end = 0;
      syncfs(mfp->_fileno);
    }
  }
}

void myFflush(myFile* mfp)
{
  assert(mfp);
  if(mfp->_end > 0)
  {
    write(mfp->_fileno, mfp->_buffer, mfp->_end);
    mfp->_end = 0;
    syncfs(mfp->_fileno);
  }
}

void myFclose(myFile* mfp)
{
  myFflush(mfp);
  free(mfp);
}

通过系统调用把缓冲区内容写到内核里,不代表写到了硬件上。需要用到syncfs,syncfs是系统调用,作用是将文件系统中的所有缓存的修改(包括文件元数据和数据)写入到底层的文件系统,把数据刷到磁盘上。

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

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

相关文章

网页3行字,成立4周就快成独角兽!大模型创业狂飙中

先来看看这家公司的官网截图&#xff1a; 对的&#xff0c;你没有看错&#xff0c;加上公司名字也就只有4行字。 人工智能正在迅速发展&#xff1a;一家成立仅四周的初创公司获得了1.13亿美元的种子轮融资&#xff0c;将与OpenAI竞争&#xff0c;在构建、训练和应用大型语言模…

Ubuntu 如何启动、停止或重启服务

在本文中&#xff0c;我们向您介绍在 Ubuntu 中启动、停止和重启服务的方法。 列出 Ubuntu 中的所有服务 在开始之前&#xff0c;先获取计算机上所有服务的列表&#xff0c;因为我们需要知道服务名称来管理服务。 service --status-all 它将显示 Ubuntu 上的完整服务列表。…

八卦图总结

第一阶段知识汇总 版本号&#xff1a;V1.1 作 者 姓 名&#xff1a; 学 科 专 业&#xff1a; 软件测试 文 档 类 型&#xff1a;第一阶段 汇 总 测 试 基 础 …

strapi系列--如何建表并在原有官方自动生成的api基础上扩展接口,定制化自己的业务逻辑

为什么要进行后端定制呢&#xff1f; 在实际开发过程中&#xff0c;项目中有些需求需要我们定制化自己的业务逻辑&#xff0c;那么我们该如何处理这个需求呢&#xff1f;本文以图文并茂的形式&#xff0c;基于原有官方自动生成的api&#xff0c;定制一个我们自己的业务逻辑接口…

“破解求职之谜:应届生如何应对职场场景挑战?

今天&#xff0c;我想与你分享一个关于应届生面试的故事。这是一个真实且令人深思的故事&#xff0c;也是我个人在求职过程中的一次经历&#xff0c;这也是对尚未面试者的警醒对已面试者的勉励。 在暑期还未开始之前&#xff0c;我充满着憧憬和期待地投递了许多简历&#xff0c…

chatgpt赋能python:Python第几行是什么?了解Python中的行号

Python第几行是什么&#xff1f;了解Python中的行号 如果你是一名Python工程师&#xff0c;你肯定曾经遇到过错误提示中提到的Python第几行。那么Python中的行号是什么&#xff1f;本文将为您解答关于Python中行号的一些基本问题。 什么是Python中的行号&#xff1f; 在Pyth…

测试用例设计方法

等价类划分法 设计测试用例步骤 需求分析划分等价类&#xff1a;有效和无效设计用例 无效有5种情况 规则&#xff08;需求本身&#xff09;长度类型是否为空&#xff08;必填项&#xff09;是否重复 案例1&#xff1a;QQ号6-10位自然数 划分等价类 有效等价类有效数据无…

深入理解深度学习——BERT派生模型:BART(Bidirectional and Auto-Regressive Transformers)

分类目录&#xff1a;《深入理解深度学习》总目录 UniLM和XLNet都尝试在一定程度上融合BERT的双向编码思想&#xff0c;以及GPT的单向编码思想&#xff0c;同时兼具自编码的语义理解能力和自回归的文本生成能力。由脸书公司提出的BART&#xff08;Bidirectional and Auto-Regre…

[Hadoop] 期末答辩问题准备

0.相关概念 1.什么是NameNode&#xff1f; NameNode是整个文件系统的管理节点&#xff0c;它维护着整个文件系统的文件目录树&#xff0c;文件/目录的元信息和每个文件对应的数据块列表。并接收用户的操作请求。 2.SecondaryNameNode的主要作用&#xff1f; SecondaryNameN…

JUC阻塞队列BlockingQueue---ArrayBlockingQueue

JUC阻塞队列BlockingQueue---ArrayBlockingQueue ArrayBlockingQueue示例代码原理构造方法内部常量入队put方法出队take方法 什么是阻塞队列&#xff1f; ArrayBlockingQueue ArrayBlockingQueue是典型的有界阻塞队列&#xff0c;其内部是用数组存储元素的&#xff0c;初始化…

chatgpt赋能python:Python程序安装指南

Python程序安装指南 介绍 Python 是一种高级编程语言&#xff0c;广泛应用于数据分析、人工智能、Web开发等领域。安装 Python 程序是使用 Python 和运行 Python 脚本的必要步骤。在本文中&#xff0c;我们将提供详细的 Python 程序安装指南。 安装Python程序步骤 步骤1: 下…

1.1-python课程简介

一、python入门 1、python来源2、什么是python3、python编程软件下载4、python软件安装5、python软件运行和调试 1、python来源 Guido van Rossum 于1989年在荷兰国家数学和计算机科学研究所设计出来的。 2、什么是python Python 是一个高层次的结合了解释性、编译性、互动性…

【推荐】win 安装 rust 1.70 (GNU)

目录 一、下载二、安装三、配置环境变量四、检查是否安装成功五、参考文章 一、下载 官网地址&#xff1a;https://www.rust-lang.org/zh-CN/ https://forge.rust-lang.org/infra/other-installation-methods.html 历史版本下载地址&#xff1a; 二、安装 注意&#xff1a;安…

TypeScript ~ TS Webpack构建工具 ⑦

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; TypeScript ~ TS &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &…

chatgpt赋能python:Python相加:实现快速、高效的计算

Python相加&#xff1a; 实现快速、高效的计算 Python 运用广泛&#xff0c;是一种功能强大的编程语言。它不仅易于学习&#xff0c;而且具备许多强大的功能&#xff0c;其中包括 Python 相加。今天&#xff0c;我们将介绍如何写 Python 相加的代码&#xff0c;以及如何实现快…

chatgpt赋能python:如何将Python程序打包成App-一个详细的指南

如何将Python程序打包成App - 一个详细的指南 如果你是一位有着丰富Python编程经验的开发者&#xff0c;可能你听说过Python App打包。Python App打包就是将Python程序打包成为操作系统所支持的应用程序的过程。这个过程可以让你的Python程序在Windows、Mac和Linux操作系统上更…

Linux网络、进程

一、网络环境配置 第一种方法&#xff1a;自动获取 登陆后&#xff0c;通过界面来设置自动获取IP&#xff0c;特点&#xff1a;linux启动后会自动获取IP&#xff0c;缺点是每次自动获取的IP地址可能不一样。这种就不适合服务器使用 第二种方法&#xff1a;指定IP 直接修改配置…

【Leetcode60天带刷】day24 回溯算法—— 77. 组合

​ 理论基础&#xff1a; 什么是回溯法&#xff1f; 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。 回溯法解决的问题 回溯法&#xff0c;一般可以解决如下几种问题&#xff1a; 组合问题&#xff1a;N个数里面按一定规则找出k个数的集合切割问题&#xff…

chatgpt赋能python:Python程序如何打包成安装包

Python程序如何打包成安装包 Python是一种受欢迎的编程语言&#xff0c;许多开发者使用它构建各种类型的应用程序。然而&#xff0c;当需要分发Python应用程序时&#xff0c;将所有代码和依赖项打包成新的安装包通常是最佳选择。本文将介绍如何使用Python打包工具将Python程序…

Java并发工具Fork/Join原理

我们一直讲&#xff0c;并发编程可以分为三个层面的问题&#xff0c;分别是分工、协作和互斥&#xff0c;当你关注于任务的时候&#xff0c;你会发现你的视角已经从并发编程的细节中跳出来了&#xff0c;你应用的更多的是现实世界的思维模式&#xff0c;类比的往往是现实世界里…