Linux:缓冲区_glibc封装

1.再谈重定向

(1)用重定向的方法,把内容输入到文件中:

1.把标准输出写道log.txt中

完整写法:./a.out 1 > log.txt, 把向标准输出显示地内容写向log.txt

大多数情况下:我们把1进行了省略。

2.cerr什么意思?

错误信息,stderr

fprintf(stderr,"hello cerr\n");,向标准错误中打印信息

3.分开两个文件里面打入,重定向操作:

./a.out 1 > log.normal 2 > log.error:

打开log.normal文件,清空,写入标准输出信息, 后面同理

4.为什么要又stderr?

通过重定向地能力,把常规消息和错误消息进行分离

5.写入一个文件里面?

(1)./a.out 1 > log.normal 2 >> log.normal,//把stderr进行追加,可以做到

(2)./a.out 1 > log.txt 2>&1
把1里面的内容,写到2里面,说明1和2都指向新文件。
解释:

结合上面的图一起分析:

本来,./a.out是要通过stout在显示器上显示的,但是由于文件描述符1被log.txt占用导致,原先在stdout显示的,只能显示显示在文件中,这样完成了写入,这也是重定向的原理。1指向log.txt

而2>&1,是把2的指针位置也只想了log.txt,后面解释同理

(2)一些小东西:

1.f_pos当前文件读写位置

2.f_version版本

3.进程和文件是解耦的

4.file有内容,里面含有内核文件缓冲区:adress_bs

文件file内部有一个属性可以帮助我们找到内核文件缓冲区--- file可以直接简介找到内核缓冲区 / inode

2.理解一切皆文件

在linux内,

磁盘,显示器,键盘等等
各种设备调用的方法可能不一样,但是都要调用这种方法。多态的实现方法,通过基类的实现不同设备相同功能的实现。

1.外设,磁盘,显示器等,一定有读写方法,但是实现方法不一样,一切外设设备都需要读写。

2.OS需要保证对设备先描述再组织地管理起来。如:struct device,对设备地管理转会成对链表地增删查改

struct file里面包含属性,内核缓冲区,以及read,write函数指针, 函数指针指向对应设备地read,write方法,这样OS就不用直接访问设备,而是通过struct file来直接访问外设。使用同名函数进行访问,来屏蔽底层差异, 同名函数指向不同设备地读写方法。

---多态

1.对设备的管理变成了对链表的管理。--->操作系统操作设备device也就是操作文件struct file进一步解释一切皆是文件

每一个struct file里面有
上层访问特定文件,将文件写入文件内核缓冲区,通过struct file里面的函数指针

2.VFS:虚拟文件系统

函数指针类型命名,参数,都一样
c版本的多态

任何计算机的问题都可以增加一层软件层来进行。

3c++通过函数指针实现多态。

操作系统内核当中存在struct_file,访问文件,进程具有文件描述符表,根据指定描述找到指定文件 ,struct_file内部包含file_operation,函数指针方法。

通过VFS来实现,通过函数指针屏蔽掉底层的差异。

3.缓冲区

为什么要引入缓冲区机制?提高效率。

缓冲区,保存数据

究竟什么时内存缓冲区?

1.库函数语言缓冲区

库函数并不是直接写入文件内核缓冲区,而是C标准库生成一个用户级,语言层缓冲区,

满足1.当强制刷新,2.刷新条件满足,3.程序退出后,再通过文件描述符【fd+系统调用,比如说write】拷贝入文件内核缓冲区。

2.打印不出情况:代码

情况:

printf, write向C语言缓冲区写入,但后面close(fd), 关闭后, 进程退出,由于fd被关闭了,找不到对应fd地内核缓冲区,所以无法打印

一种极端情况:
如果,由于三个条件都没有满足,当进程结束时,由于fd被关掉了,(close(fd))所以说没有办法把语言层缓冲区拷贝到文件内核缓冲区内部,所以也就没有办法在显示器显示。

解决方案 :在fd被关掉之前,fflush(stdout)把程序进行刷新。

3.FILE:

1.FILE是C预压提供的一个结构体
2.里面有fd和缓冲区
3.任何一个文件打开都需要一个FILE*, 每打开一个文件,都会有一个FILE对象被创建 4.printf, fprintf, fputs, fwrite都是把内容先写道C标准库地缓冲区,等待条件满足再根据fd+系统调用拷贝到文件内核缓冲区。

4.什么叫做格式化输出?

把数据格式化输出成字符串,满足刷新/write,就把数据刷新到操作系统, 操作系统就交给外设。

5.刷新条件

1.立即刷新

2.满了 -- 全缓冲 --- 一般文件

3.行刷新 --- 显示器用

6.为什么会有刷新条件? --- 减少系统调用

答:系统调用是有成本的。多次调用系统调用会造成浪费
通过三个条件的刷新条件来刷新缓冲区,来减少系统调用次数,效率高。

cout 换成printf --- cout要进行系统调用,效率比较低

7.数据交给系统,交给硬件,本质就是拷贝!

8.为什么系统调用和库函数显示不一样?

系统调用1次,库函数2次

./a.out是往显示器写入

./a.out > log.txt向文件写入

(1)向显示器大印,是行刷新,所以子进程的加入无影响

(2)./a.out > log.txt, 是普通文件,则是全缓冲,子进程的

fork()的时候,库函数调用的还在缓冲区,所以子进程退出+父进程退出,就会把缓冲区的内容刷新两遍

4.模拟一下简单的glibc->文件接口

理解库是被封装的。

cpp 复制代码
//mystdio.h
#pragma once

#include <stdio.h>

#define MAX 1024
#define NONE_FLUSH (1<<0)
#define LINE_FLUSH (1<<1)
#define FULL_FLUSH (1<<2)

typedef struct IO_FILE
{
    int fileno;
    int flag;
    char outbuffer[MAX];
    int bufferlen;
    int flush_method;
}MyFile;


MyFile *MyFopen(const char *path, const char *mode);
void MyFclose(MyFile *);
int MyFwrite(MyFile *, void *str, int len);
void MyFFlush(MyFile *);
cpp 复制代码
//mystdio.c
#include "mystdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

static MyFile *BuyFile(int fd, int flag)
{
    MyFile *f = (MyFile*)malloc(sizeof(MyFile));
    if(f == NULL) return NULL;
    f->bufferlen = 0;
    f->fileno = fd;
    f->flag = flag;
    f->flush_method = LINE_FLUSH;
    memset(f->outbuffer, 0, sizeof(f->outbuffer));
    return f;
}

MyFile *MyFopen(const char *path, const char *mode)
{
    int fd = -1;
    int flag = 0;
    if(strcmp(mode, "w") == 0)
    {
        flag = O_CREAT | O_WRONLY | O_TRUNC;
        fd = open(path, flag, 0666);
    }
    else if(strcmp(mode, "a") == 0)
    {
        flag = O_CREAT | O_WRONLY | O_APPEND;
        fd = open(path, flag, 0666);
    }
    else if(strcmp(mode, "r") == 0)
    {
        flag = O_RDWR;
        fd = open(path, flag);
    }
    else
    {
        //TODO
    }
    if(fd < 0) return NULL;
    return BuyFile(fd, flag);
}
void MyFclose(MyFile *file)
{
    if(file->fileno < 0) return;
    MyFFlush(file);
    close(file->fileno);
    free(file);
}
int MyFwrite(MyFile *file, void *str, int len)
{
    // 1. 拷贝
    memcpy(file->outbuffer+file->bufferlen, str, len);
    file->bufferlen += len;
    // 2. 尝试判断是否满足刷新条件!
    if((file->flush_method & LINE_FLUSH) && file->outbuffer[file->bufferlen-1] == '\n')
    {
        MyFFlush(file);
    }
    return 0;
}
void MyFFlush(MyFile *file)
{
    if(file->bufferlen <= 0) return;
    // 把数据从用户拷贝到内核文件缓冲区中
    int n = write(file->fileno, file->outbuffer, file->bufferlen);
    (void)n;
    fsync(file->fileno);
    file->bufferlen = 0;
}
cpp 复制代码
#include "mystdio.h"
#include <string.h>
#include <unistd.h>

int main()
{
    MyFile *filep = MyFopen("./log.txt", "a");
    if(!filep)
    {
        printf("fopen error!\n");
        return 1;
    }

    int cnt = 10;
    while(cnt--)
    {
        char *msg = (char*)"hello myfile!!!";
        MyFwrite(filep, msg, strlen(msg));
        MyFFlush(filep);
        printf("buffer: %s\n", filep->outbuffer);
        sleep(1);
    }
    MyFclose(filep); // FILE *fp
    return 0;
}
相关推荐
JiMoKuangXiangQu2 小时前
Linux 内存管理:页表管理简析
linux·mmu·内存管理·页表管理
番知了2 小时前
Ubuntu 22.04 常用命令清单
linux·运维·ubuntu
旺仔Sec2 小时前
2026年河北省职业院校技能大赛“网络系统管理”(高职组)网络构建样题
运维·服务器·网络
FF-Studio2 小时前
Ubuntu 24.04 磁盘爆满“灵异“事件:Btrfs, Snapper 与删不掉的空间
linux·运维·人工智能·ubuntu
爱尔兰极光2 小时前
计算机网络--网络层
运维·服务器·计算机网络
Neolnfra2 小时前
Xshell SSH 连接故障排查
运维·服务器·网络·ssh·xshell·运程连接
极地星光2 小时前
Ubuntu 16.10 启动时 networking.service 缓慢问题
linux·ubuntu
Roadinforest2 小时前
如何使用 keyd 定制 Caps Lock:拯救你坏掉的 Left Control 键(Linux-Ubuntu)
linux·ubuntu
MonkeyKing_sunyuhua2 小时前
ubuntu22.04安装nginx
运维·windows·nginx