文件(Linux)


前言

我们现在要学习的文件可以分为两类,一类是在内存中的文件,另一类是在磁盘中的文件。

一、内存中的文件

我们如果想要对文件内容进行操作,或者创建关闭文件,首先得把这个文件加载到内存中,变成进城之后,再进行操作。这一个进程中可能会存在多个被打开的文件,操作系统肯定要管理这些被打开的文件,先描述,在组织

1.回顾c语言

在c语言我们学习过文件fopen,fclose相关的接口函数。

我们来看几个经典的方法
🌟🌟读方法
我们首先在log.txt中放入一些内容
在这里插入图片描述
我们看一下r方法
在这里插入图片描述
我们用下面代码做一下测试

在这里插入图片描述
进行运行之后
在这里插入图片描述

🌟🌟写方法
在这里插入图片描述
我们首先看一下w方法的说明
在这里插入图片描述
如果该文件不存在会自动创建,并且fp指针指向起始位置,也就是说每次我们运行都会清空原内容,重新开始写入。
在这里插入图片描述
我们发现确实完成了相应的操作。

🌟🌟追加方法
看一下a方法的文档,从fp的末尾开始
在这里插入图片描述
用下面代码做一下测试

在这里插入图片描述

看一下预期效果。
在这里插入图片描述

c语言默认打开三个流:标准输入(键盘)stdin,标准输出(显示器)stdout,标准错误(显示器)stderr
在这里插入图片描述
这三个都是FILE类型的指针

进程启动时都会记录自己所在的路径

2.系统调用

我们如果想对文件进行操作,必须加载到内存,访问系统里面的数据,必须经过系统调用。

打开文件的系统调用函数是open函数,还有其他write,read,close系统调用,这里小编就不一个个叙述了,主要讲解open函数

在这里插入图片描述
🌟使用这个系统调用必须包含相应的头文件
🌟我们这里有两个open函数,到底该用哪个呢??
如果我们明确知道文件存在了,我们就用第一个。
如果文件不存在我们就用第二个。
🌟pathname就是我们要操作的文件名
🌟flags相当于标记位,可以传入多个参数,这些参数之间进行或操作,构成flags。我们详细看一下几个常用的:
追加操作
在这里插入图片描述
如果文件不存在将会被创建,需要使用mode,表示新文件权限
在这里插入图片描述
清空文件
在这里插入图片描述
读写操作
在这里插入图片描述

🌟返回值
在这里插入图片描述
成功返回文件描述符(后面介绍),失败返回-1。

💝 💝 写操作
文件不存在就创建,每次清空文件,设置文件权限664。
在这里插入图片描述
我们运行看一下
在这里插入图片描述
符合我们的预期,我们可以发现这不就是我们c语言中的w(写)操作吗!!!确实是这样的,c语言的函数封装了系统调用。

我们也可以在代码中修改umask
在这里插入图片描述

在这里插入图片描述
💝 💝 追加操作
我们只需要稍微修改一下就可以
在这里插入图片描述

追加操作完成

在这里插入图片描述

💝 💝 读操作
我们先往test.txt放入一些内容
在这里插入图片描述
用read方法进行读取

在这里插入图片描述
运行结果
在这里插入图片描述

我们可以发现上面的三种方法和c语言的极其类似,c语言的函数本质就是封装了系统调用

为什么要折磨做呢???
主要是为了保证跨平台型(不同平台都可以正常使用),不用关心底层差异

3.文件描述符

我们首先创建几个文件,看看文件描述符
在这里插入图片描述
在这里插入图片描述
我们发现fd并没有从0开始,而是直接到达了3。
Linux下默认打开三个文件描述符:
🌟0表示标准输入,也就是键盘
🌟1表示标准输出,也就是显示器
🌟2表示标准错误,也就是显示器

我们画张图来理解一下文件描述符

在这里插入图片描述

一个进程可能要打开多个文件,同时对这些文件进行管理。先描述(file),在组织(数据结构)。
这些file的指针被存放在一个files_struct中的一个数组中,数组下标从0开始。PCB里面存放着files_struct的指针,所以我们就可以通过PCB快速找到这个文件进行操作。
文件描述符就是数组下标。
我们把这个文件描述符返回给上层直接使用,就可以对相应的文件进行操作。

深度理解Linux下一切皆文件

每个文件都有自己的属性和方法,例如显示器文件,他只有读方法,没有写方法,我们就把这个写方法设置为空。
在这里插入图片描述
本质就是通过屏蔽底层差异实现的,上层使用统一的接口,类似与多态调用

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

4.重定向

我们先看一下下面代码
在这里插入图片描述

我们正常运行一下看看会发生什么
在这里插入图片描述

我们发现printf本来应该向显示器打印内容,现在打印到了file.txt文件中!!!
在这里插入图片描述
这是因为我们关闭了文件描述符1,也就是显示器文件,现在新的file.txt文件会用最小的并没有使用的下标,就使用了文件描述符1。
printf是c语言的库函数,本来就应该向stdout打印内容,但是stdout底层是1号文件描述符,所以向1号文件描述符写入,现在1号文件描述符指向了file.txt文件,所以最终就向file.txt里边写入。
重定向本质就是文件描述符(指针)的指向的改变.

想对系统内核数据进行修改,必定封装了系统调用
在这里插入图片描述
把oldfd中的内容拷贝到newfd中,最终只剩下oldfd.
我们也可以通过这种方式实现重定向的功能。

5.缓冲区

缓冲区是什么呢??缓冲区就是一块内存空间
缓冲区有什么用呢??提高使用者的运行效率,同时数据整合,提高整体效率
缓冲区存在哪里呢??c语言层面存在于FILE结构体中
在这里插入图片描述

FILE结构中中肯定也封装文件描述符fd.
在这里插入图片描述
我们来详细介绍一下缓冲区

在这里插入图片描述
🌟c语言层面有缓冲区。操作系统层面也有缓冲区,只不过这个是由操作系统自己管理的,我们这里不过多介绍。
🌟我们知道,系统调用是有开销和消耗的,所以我们要尽量减少系统调用。
🌟c语言层面设置了一个缓冲区,当有内容需要通过c语言提供的函数写入操作系统中时,并不会直接写入,首先把这些内容通过c语言的函数写进缓冲区里面,等满足一定的规模再一起通过系统调用写到操作系统中。这样就减少了系统调用的次数。
🌟缓冲包括三种缓冲
   无缓冲
   行缓冲,遇到\n就写入,一般用于显示器
   全缓冲,当缓冲区的内容满了之后,再进行写入,一般用于写到文件
还有两种特别的:
   强制刷新,例如fflush
   进程退出时自动刷新。

我们通过下面代码深入了解一下缓冲区
在这里插入图片描述

我们运行这段代码
在这里插入图片描述
我们发现正常打印和写到文件中打印内容都一样。
我们稍微改变一下看一下
在这里插入图片描述
我们正常运行和把结果写到文件中分别看看

在这里插入图片描述
第二个为什么会打印这样的内容呢??
🌟我们向显示器写入,是行缓冲。现在向文件写入,变成了全缓冲。
🌟write是系统调用,不受c语言层面的缓冲区约束,虽然代码上显示最后被打印,由于printf和fwrite是全缓冲,需要等到缓冲区满了之后才可以被打印出来。所以write系统调用首先被打印。
🌟fork之后,父进程创建子进程,父子代码共享,那么缓冲区肯定也共享。
🌟缓冲区是肯定写不满的,内容太少了。进程退出,父进程先退出,缓冲区内容被刷新出来。父进程的缓冲区发生改变,子进程会发生写时拷贝,也会有一份相同的内容,子进程也退出,缓冲区的内容也被刷新出来。所以打印了两次。
🌟

缓冲区是一种典型的空间换时间做法。

我们之前学习的printf和scanf是格式化的输入输出,本质也是向缓冲区进行读写操作。

6.简单模拟实现一下系统调用

test.c中内容

#include "test.h"

//打开
myFILE*myopen(const char*path,const char*mode)
{
    int flag=0;
    if(strcmp(mode,"w")==0)
    {
        flag|=O_CREAT|O_WRONLY|O_TRUNC;
    }
    else if(strcmp(mode,"a")==0)
    {
        flag|=O_CREAT|O_APPEND|O_TRUNC;
    }
    else if(strcmp(mode,"r")==0)
    {
        flag|=O_RDONLY;
    }
    else 
    {
        return NULL;
    }
    int fd=0;
    if(flag&O_RDONLY)
    {
        fd=open(path,flag);
    }
    else 
    {
        umask(0);
        fd=open(path,flag,0664);
    }

    myFILE*fp=(myFILE*)malloc(sizeof(myFILE));
    fp->inode=fd;
    fp->pos=0;
    fp->cap=MAX;
    fp->flushmode=FLUSHLINE;
    return fp;

}
void myfflush(myFILE*fp)
{
    write(fp->inode,fp->buffer,fp->pos);
    fp->pos=0;
}
//写文件
int myfwrite(const void *ptr,size_t n,myFILE*fp)
{
    //首先放到缓冲区
    memcpy(fp->buffer+fp->pos,ptr,n);
    fp->pos+=n;

    //判断是否需要刷新
    if(fp->flushmode==1&&fp->buffer[fp->pos-1]=='\n')
    {
        myfflush(fp);
    }
    if(fp->flushmode==2&&fp->pos==fp->cap)
    {
        myfflush(fp);
    }
    return n;
}
//关闭
void  myclose(myFILE*fp)
{
    //进程退出先刷新
    myfflush(fp);
    close(fp->inode);
    free(fp);
}

test.h中代码

#pragma once 

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

#define FLUSHLINE 1
#define FLUSHFULL 2
#define FLUSHNO 3
#define MAX 4096

typedef struct myFILE
{
    int inode;
    char buffer[MAX];
    int pos;
    int cap;
    int flushmode;

}myFILE;

//打开
myFILE*myopen(const char*path,const char*mode);
//写文件
int myfwrite(const void *ptr,size_t n,myFILE*fp);
//刷新
void myfflush(myFILE*fp);
//关闭
void myclose(myFILE*fp);

main.c中测试代码

#include "test.h"

int main()
{
    myFILE*fp=myopen("file.txt","w");
    if(fp==NULL) 
    {
        perror("myopen fail:");
        return 1;
    }
    int k=5;
    char buffer[64];
    while(k)
    {
        snprintf(buffer,sizeof(buffer),"helloworld,hellobit,%d ",k);
        myfwrite(buffer,strlen(buffer),fp);
        k--;
        sleep(2);
    }
    printf("hello world\n");
    myclose(fp);
    return 0;
}

二、磁盘中的文件

大多数的文件都没有被打开,而是存放在磁盘中的。
操作系统要不要管理磁盘上的文件呢??也就是如何让操作系统快速定位一个文件

1.磁盘机械构成

在这里插入图片描述

在这里插入图片描述
我们看一下主要的
🌟磁头和中间的马达并不靠着,磁头用于读取和写入操作
🌟每一个磁头都配以一个盘片
🌟读写过程中磁头来回摆动,盘片高速运转
🌟磁盘不能够随便移动,否则容易数据丢失
🌟电脑上存存放的并不简简单单是01序列,只要是可以表示两种状态的都可以,比如南北极,信号强弱等。

扇区是磁盘进行IO操作的基本单位,不一定是系统和磁盘IO的基本单位,一般都是512字节
磁道扇区都有自己唯一的编号。

在这里插入图片描述
如果我想要访问一个扇区:
🌟首先定位是哪一个磁道–cylinder
🌟之后确定使用哪一个磁头–head
🌟最后确定哪一个扇区–sector

我们把上面的做法称为CHS定位法。

2.文件系统

Linux下文件内容和属性是分开存储的。
我们上面已经介绍过了,磁盘中有很多扇区,扇区是磁盘进行基础IO的基本单位,大小为512字节,每个扇区有自己的编号。
那么磁盘就是由很多个扇区组成的,我们对磁盘的管理就可以变成对扇区数组的增删查改。

OS认为512字节的单位太小了,一般设置4KB作为磁盘和操作系统交互的基本单位,这写块号我们称为LBA地址。
OS把这一个个的4KB的块进行编号,我们对磁盘的管理就变成了对这些块的增删查改

假设我们现在有一个800GB的磁盘,我们可以把它们划分为几个不同的区。本质上我们电脑上只有一块磁盘,但是我们的文件有C盘,D盘,E盘等等,都是分盘(分区)。
但是一个分区还是太大了,我们又将这个分区进行分组,分为很多组,磁盘都是一样的,我们只要把管理这一组的逻辑实现好,复制粘贴到其他分组就可以。我们就可以把整个磁盘管理好。
在这里插入图片描述

我们看一下一个组内具体包括什么内容
Linux ext2文件系统,下图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。
在这里插入图片描述

🌟 Super Block(超级块):存放文件系统的结构信息, 记录的信息主要有:bolck 和 inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了。是对整个分区进行管理,并不是每个组都有这个,主要是为了效率。
如果这个出现了错误,会拷贝备份相关的数据进行恢复。
操作系统要管理文件系统,就是对Super block进行管理,先描述,在组织。
🌟 GDT,Group Descriptor Table:块组描述符,描述块组属性信息
🌟 块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用。比特位的位置表示了这个块在哪个位置,比特位的值表示是否已经使用。
🌟 inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。
文件=文件内容+文件属性。内容的大小是不固定的,但是属性大小是固定的,一般是128字节。
这个属性中包括权限,时间等等,更重要的是包含了一个文件inode,inode是唯一标示这个文件的,文件名并不在文件属性中
这个inode是在整个分区内有效,一整个分区内不同。
🌟 i节点表:存放文件属性如文件大小,所有者,最近修改时间等
🌟 数据区:存放文件内容
再文件属性信息中还存在一个int datablock[15],用于存放文件内容。
但是这只有15个块,加起来也就60KB,如果一个大文件如何存储呢,这里采用了三集索引的方式:
[0,12]:正常存放4KB大小的文件
[13,14]:里面存放的是其他块的编号,我们知道编号就可以正常使用这些快了,这些块里边存放文件内容,这样就又可以又30个块可以使用了。
[15]:如果上面的还不够,这里面存放的是其他块的编号,我们这些块里面存放的又是其他块的编号,然后再这些编号里面正常放文件内容。

ls -i 可以查看文件的inode编号

在这里插入图片描述

每一个分组里面都有自己的起始inode编号和起始块号,我们通过这个就可以快速定位一个文件的位置了。通过inode编号就可以标示一个文件

目录也属于文件,文件=文件内容+文件属性,属性中肯定也包括inode,那么内容中存放什么呢??
目录内容中存放的是该目录下的文件名和该文件inode的映射关系
根据文件名就可以找到该文件的inode
我们对一个文件进行增删查改,都和该文件所处的目录有关系。
我们要查找一个文件,都要你想递归般得到根目录,从根目录进行解析(根目录inode是确定的)。
如果我们对这个路径下的文件进行了操作,该路径就会被操作系统进行路径缓存。

🌟如果我们想要新建一个文件,系统会怎末做呢??
首先确定自己的inode,在inode bitmap中找到没有被使用的inode,知道自己的inode编号;随后在block bitmap中找到所使用的块号,将文件内容填写进去;将这个文件的属性进行填写。
将自己的文件名和inode映射关系存放在目录中。
🌟如果想要删除一个文件呢?
根据inode和分区中inode的起始位置,确定在哪里分区。计算偏移量,找到自己所在的inode bitmap中,将1修改为0.同时在文件属性中,找到所使用的block bitmap,将1修改为0。最后在目录下删除该文件名与inode映射关系。删除文件仅改变位图就可以。
🌟如果想要查找一个文件呢??

一个被写进文件系统的分区,要被Linux使用,就必须先把这个具有文件系统的分区进行挂载,
挂载(Mounting)是指将文件系统或存储设备的一个分区与某个目录关联起来的操作。

三.软硬链接

1.硬链接

在Linux中,我们访问一个文件并不是通过文件名,而是改文件名对应的inode。所有在Linux中的文件中就可以允许存在多个文件名共同享有一个inode,这也就是硬链接。
本质就是起别名。

我们给文件建立硬链接采用的方法是ln 命令
在这里插入图片描述

通过上面我们可以发现,这两个文件共用一个inode.
这个绿色的区域就是文件链接数,标示有多少个文件与该inode建立映射关系。
我们创建了一个硬链接,文件hard.link与inode410566建立映射关系,变为了2.只有当连接数变为0之后,这个inode所指向的文件才会被彻底删除。

与inode410566建立映射的文件,无论哪个发生变化,其他的都受影响。

应用场景
1.路径

在这里插入图片描述

我们建立一个空的目录,为什么链接数为2呢??
这是因为存在.(当前路径)和…(上级路径)的存在。empty目录里边存在一个…(上级路径)指向这个目录,同时还有一个empty目录自身还存在一个.(当前目录),指向自己。
如果我们按照这个道理在empty目录下新建两个目录,连接数就应该是4
在这里插入图片描述
如果为我们想要知道一个目录里面的子目录个数,只需要用该目录的连接数减2就可以

2.数据备份
我们也可以用这个做数据备份
通过硬链接可以快速复制文件而不占用额外的空间,只需为新文件创建一个指向原文件的硬链接即可。

不能给目录建立硬链接,很容易发生环路问题。

2.软链接

软链接里面存放的内容是所指向文件的路径。与硬链接不同,软链接不直接指向文件的inode节点,而是通过路径名来访问文件
一般采用ln -s命令创建

在这里插入图片描述
我们可以发现,soft_link软链接的inode和soft.txt是不同的,说明这是一个新的文件。
通过名字引用另一个文件。

应用场景
文件路径很深
如果我们想要访问这样一个路径,在e目录下有一个.c文件,我们想要运行它,就需要一层层递归。
我们有了软链接之后,就可以直接在当前目录与该.c文件建立软链接,就可以直接进行访问了。
软链接类似于Win下的快捷方式

在这里插入图片描述

如果软链接所指向的目标文件或目录被移动、重命名或删除,软链接就会成为一个“死链接”。此时,尝试访问软链接将会导致错误,因为系统无法找到它所引用的目标文件或目录。然而,这并不意味着软链接本身被删除,只是它不再指向有效的目标。

在这里插入图片描述

总结

以上就是今天要讲的内容,本文仅仅详细打开的文件和磁盘中的文件系统以及软硬连接。希望对大家的学习有所帮助,仅供参考 如有错误请大佬指点我会尽快去改正 欢迎大家来评论~~ 😘 😘 😘

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lim 鹏哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值