linux之FILE和文件系统(磁盘的介绍)

1.FILE

(1)是什么

FILE就是c标准库用于储存文件属性和用户级缓冲区的地方,这个结构体空间在fopen这类c标准库中用于打开文件的函数内部创建(动态开辟),也就是用c标准库打开的文件都有其自己的用户级缓冲区,往这些文件中写入数据时会先存进FILE内部指向的缓冲区中。

(2)意义

系统调用是有成本的:OS是很忙的,频繁调用系统会造成程序效率低下。因此先将用户级缓冲区存满再一次性的调用OS接口就可以提高c标准库函数的效率。

(3)用户级缓冲区刷新条件

(1)立即刷新->无缓冲->写透模式(WT)

(2)缓冲区满了->全缓冲(效率最高,常用于普通文件的写入)

(3)行刷新->行缓冲(如c库就是遇到了'\n'就刷新,常用于显示器的写入以方便用户读)

OS缓冲区的刷新条件由OS自主决定,用户无法得知(但有对应的接口),因此可以认为将数据交给了OS就相当于给到了对应的硬件或文件了。

计算机内部的数据流动方式全部都是拷贝。

2.重定向与缓冲区

重定向还会更改缓冲区的刷新方式。例:原本向显示器写入的刷新条件为行刷新,重定向为向普通文件写入时刷新条件就改为全缓冲了,此时当缓冲区还没有满就开始子进程时,子进程会将父进程的用户级缓冲区中的数据也会拷贝一份,导致最后重定向文件中会有部分重复的数据。

例:

复制代码
#include <stdio.h>
#include <string.h>

int main()
{
    const char *msg0="hello printf\n";
    const char *msg1="hello fwrite\n";
    const char *msg2="hello write\n";
    printf("%s", msg0);
    fwrite(msg1, strlen(msg0), 1, stdout);
    write(1, msg2, strlen(msg2));
    //子进程中的缓冲区还会有着printf和fwrite中的数据
    //当进程结束后会把相同的数据也写入重定向的文件中
    fork();
    return 0;
}

c标准库管理FILE的方式也是先描述,再组织。

3.glibc的模拟实现

(1)glibc.h

cpp 复制代码
#pragma once
#define SIZE 1024
//刷新条件,WT,行刷新,全刷新
#define FLUSH_NONE 0
#define FLUSH_LINE 1
#define FLUSH_FULL 2

typedef struct IO_FILE
{
    //flag是刷新方式
    int flag; 
    //是fd
    int fileno; 
    //是用户级缓冲区
    char outbuffer[SIZE];
    //是outbuffer的最大的大小
    int cap;
    //是当前outbuffer的存储量
    int size;
}mFILE;

mFILE *mfopen(const char *filename, const char *mode);

int mfwrite(const void *ptr, int num, mFILE *stream);

void mfflush(mFILE *stream);

void mfclose(mFILE *stream);

(2)glibc.c

(补充)fsync

复制代码
int fsync(int fd);
//将fd对应的文件内核缓冲区数据直接刷新到硬件中
cpp 复制代码
#include "my_stdio.h"
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
//open+初始化
mFILE *mfopen(const char *filename, const char *mode)
{
    int fd = -1;
    //根据mode运行对应的open模式
    if(strcmp(mode, "r") == 0)
    {
        fd = open(filename, O_RDONLY);
    }
    else if(strcmp(mode, "w")== 0)
    {
        fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);
    }
    else if(strcmp(mode, "a") == 0)
    {
        fd = open(filename, O_CREAT|O_WRONLY|O_APPEND, 0666);
    }
    if(fd < 0) return NULL;
    //初始化
    mFILE *mf = (mFILE*)malloc(sizeof(mFILE));
    mf->fileno = fd;
    //此处是默认向显示器写入,不同情况应该要用条件判断的
    mf->flag = FLUSH_LINE;
    mf->size = 0;
    mf->cap = SIZE;
    return mf;
}
void mfflush(mFILE *stream)
{
    if(stream->size > 0)
    {
        //写入的是写入到文件缓冲区中
        write(stream->fileno, stream->outbuffer, stream->size);
        //强制刷新
        fsync(stream->fileno);
        //size清零即可以初始化了
        stream->size = 0;
    }
}
    int mfwrite(const void *ptr, int num, mFILE *stream)
    {
        //从最后空的地方开始拷贝
        memcpy(stream->outbuffer+stream->size, ptr, num);
        stream->size += num;
        //检测此时的刷新条件并分析是否满足该条件
        if(stream->flag == FLUSH_LINE && stream->size > 0 && stream->outbuffer[stream->size-1]== '\n')
    {
        mfflush(stream);
    }
        return num;
    }
    //关闭文件前先刷新一下
    void mfclose(mFILE *stream)
    {
        if(stream->size > 0)
        {
            mfflush(stream);
        }
        close(stream->fileno);
    }

题外话:

(1)linux中的函数声明与定义处是可以允许参数只写类型不写新参名的。

(2)数据库就是磁盘上的一个文件,因此调用数据库的本质就是开一个新进程,也就是说对数据库的增删查改是在内存中进行的,然后通过文件缓冲区写进磁盘中。这种将数据写进持久化文件中的操作叫落盘。

(3)C++不允许const char*的变量传进void*的形参中,因为这里传的时候会进行隐式丢弃const限定符,但C++不允许这么做。

4.文件系统

所有的文件是由目录结构组织的,目录结构是树状的,由路径来寻找。

从硬件角度理解磁盘

磁盘简化上来讲就是由很多个小磁铁构成,因此往磁盘上写入就是更改磁盘上的那些小磁铁的南北极(北是0,南是1)

磁盘由几个盘片组成,一个盘片中由很多个同心圆形状的磁道组成,每一个磁道由多个有间隙的扇区组成,每一个扇区能存512字节。

磁盘的存储单位是扇区,也就是对磁盘进行读写时,数据的拷贝无论修改多少都要以扇区的大小进行拷贝。

盘片的两面都是独立的,也就是两面都可以用,每一面都有其独立的磁头。磁头在传动臂的带动下共进退。(即所有的磁头都绑在同一个传动臂上)

在同一时间下的所有磁头指向的所有磁道的整体在几何上叫柱面。

传动比的旋转是为了调整指向的磁道(柱面),盘片自身的旋转只为调整指向的扇区。

1.CHS

当我们知道了柱面,磁头(第几个磁头),扇区的位置后就可以定位磁盘中的任意一格扇区,这种定位扇区的方式就叫CHS。

对一个文件数据的储存就是存进一个或多个扇区里。

2.LBA

简化上来说我们可以将一个磁道视为一个线性结构,基本单元是扇区,从这个角度看磁道就变成一个一维数组了。

将一个柱面剪开,能发现其本质就是一个二维数组

而整个磁盘是由多个柱面组成,说白了就是一个三维数组,这个三维数组的读取数值就叫LBA。

LBA与CHS之间的相互转换

从表面看磁盘只认识LBA,但其在内容上还是会将LBA转换为CHS来寻址(由磁盘自己操作转换)。

LBA其实是一个值,但其能拆分出CHS的三个地址(柱面,磁头,扇区的展现就是一个地址)。

LBA转CHS(/默认取整)---(我们只要明白%的结构就是/的余数即可)

C = LBA / 一个柱面的扇区总数(停在第几个柱面的意思,余数就是在一个柱面中的第几个扇区中)

H = LBA % 一个柱面的扇区总数 / 一个磁道扇区总数(停在一个柱面的第几个磁道中,余数为一个磁道中的第几个扇区中)

S = LBA % 每一个磁道中的扇区数 + 1(停留在一个磁道中的第几个扇区中)

CHS转LBA

LBA = C * 一个柱面的扇区总数 + H * 一个磁道扇区总数 + S - 1

相关推荐
followless3 小时前
linux server中搭建questasim 10.6c & ise14.7
linux·fpga开发
The Chosen One9853 小时前
【Linux】深入理解Linux进程(二):进程的状态
linux·运维·服务器·开发语言·git
草莓熊Lotso3 小时前
Linux Socket 编程筑基:从底层本质到核心 API,一文吃透 Socket 预备知识
linux·运维·服务器·数据库·c++
hhb_6183 小时前
Terra常见技术问题梳理与实战应用案例解析
运维·服务器·网络
say_fall3 小时前
装软件慢到崩溃?用户创建总出错?Linux 工具避坑指南
linux·运维·服务器·c++·学习
GZ_TOGOGO4 小时前
2026 年 RHCE 考试到底有哪些变化?给你盘盘干货
运维·rhce·rhce考试·rhce认证·it培训·rhce 10.0
小则又沐风a4 小时前
基础的开发工具(2)---Linux
java·linux·前端
yqcoder4 小时前
JavaScript 事件流:从“捕获”到“冒泡”的完整旅程
服务器·前端·javascript
一个学Java小白4 小时前
LV.12 Linux应用开发综合实战-在线词典
linux·运维·服务器