21 缓冲区

什么是缓冲区?

缓冲区就是一段内存空间

为什么要有缓冲区?

用户通过操作系统直接往硬盘里写入东西叫做写透模式WT,这种方法成本高,速度慢。通过缓冲区策略写入叫写会模式,可以提高整机效率,提高响应速度。这种可以类别寄东西,自己去给别人送很多东西效率低,成本高。将东西给快递公司就可以返回,用户只需要和缓冲区交互,放入缓冲区就行。通过缓冲区发送到目的地。那快递公司是收到一件东西就发送还是仓库堆满再发送?这就是缓冲区的刷新策略

缓冲区的刷新策略

1.立即刷新
2.行刷新(行缓冲) 遇到\n刷新
3.满刷新(全缓冲)

特殊情况
1.用户强制刷新(fflush)
2.进程退出

缓冲策略是一般+特殊,一般情况下满刷新,遇到\n会行刷新,遇到特殊情况也会刷新

一般而言,行缓冲的设备文件比如显示器,全缓冲的如磁盘文件。所有的设备都倾向于全缓冲,缓冲区满了才刷新,因为这样更少的IO操作,更少的访问外设,可以提高效率。和外部设备IO时,数据量的大小不是主要矛盾,IO的过程是最耗费时间的。显示器一方面要照顾效率,还要照顾用户体验,极端情况,也可以自己定义规则

缓冲区谁提供

缓冲区是C语言提供还是操作系统提供
用下面的代码验证

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

int main()
{
    //C语言提供的
    printf("hello printf\n"); 
    fprintf(stdout, "hello fprintf\n");
    const char* s = "hello fputs\n";
    fputs(s ,stdout);

    //OS提供的
    
    const char* ss = "hello write\n";
    write(1, ss ,strlen(ss));

    fork();
    return 0;
}

运行会输出这四个字符串,但当重定向到文件时,显示结果不一样了
在这里插入图片描述

在这里插入图片描述

只有write打印了一次,其他都打印了两次

上面的结果说明缓冲区只能是C标准库提供,如果是OS提供,那么上面的表现形式应该是一样的,但库函数和C函数出现了不同的结果

结果解释

如果向显示器打印,刷新策略是行刷新,那么最后执行fork的时候函数执行完了,数据已经刷新了,fork无意义

如果重定向,向磁盘打印,刷新策略变为全缓冲,\n没意义。fork的时候一定是函数执行完了,数据没有刷新。c标准库中的数据是父进程的数据,刷新数据也是一种写入的过程,所以子进程会写时拷贝,缓冲区中会有两份数据,退出刷新时会出现两次

  • 一般c库函数写入文件是全缓冲,显示器是行缓冲
  • printf fwrite库函数自带缓冲区,重定向到文件时,就变为全缓冲
  • 缓冲区数据不会立即刷新,甚至fork之后
  • 进程退出后,会统一刷新,写入文件
  • fork时,父子数据会写实拷贝,子进程也有,会产生两份数据
  • write没变化,说明没有缓冲区

综上,库函数自带缓冲区,系统函数没带。这些是用户级缓冲区,为了提升整机性能,os也提供缓冲区。

缓冲区位置

库函数的输出会先将数据放入c标准库的缓冲区,系统调用则直接将数据写给os。c库的是用户级缓冲区,在打开文件时会返回FILE*,里面除过封装了fd,还包括了fd对应的语言层的缓冲区结构
打开 /usr/include/stdio.h 可以看到FILE的重命名

在这里插入图片描述
/usr/include/libio.h 中存了这个结构
在这里插入图片描述

/usr/include/libio.h
struct _IO_FILE {
 int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
 //缓冲区相关
 /* The following pointers correspond to the C++ streambuf protocol. */
 /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
 char* _IO_read_ptr; /* Current read pointer */
 char* _IO_read_end; /* End of get area. */
 char* _IO_read_base; /* Start of putback+get area. */
 char* _IO_write_base; /* Start of put area. */
 char* _IO_write_ptr; /* Current put pointer. */
 char* _IO_write_end; /* End of put area. */
 char* _IO_buf_base; /* Start of reserve area. */
 char* _IO_buf_end; /* End of reserve area. */
 /* The following fields are used to support backing up and undo. */
 char *_IO_save_base; /* Pointer to start of non-current get area. */
 char *_IO_backup_base; /* Pointer to first valid character of backup area */
 char *_IO_save_end; /* Pointer to end of non-current get area. */
 struct _IO_marker *_markers;
 struct _IO_FILE *_chain;
 int _fileno; //封装的文件描述符
#if 0
 int _blksize;
#else
 int _flags2;
#endif
 _IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
 /* 1+column number of pbase(); 0 is unknown. */
 unsigned short _cur_column;
 signed char _vtable_offset;
 char _shortbuf[1];
 /* char* _save_gptr; char* _save_egptr; */
 _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

FILE,文件流,cout cin这些类,必须包含fd,还有缓冲区buffer,cout重载<<符号后将数据放到缓冲区内按规则刷新

write系统调用会直接将数据写给OS,也不是直接写硬件。在os中有file结构,内部有自己的内核缓冲区和刷新方法。但内核数据后不属于进程了,不会写时拷贝

简易缓冲区

创建一个FILE的结构,里面保存fd和一段空间。打开文件时根据权限设置不同的write参数,初始化FILE结构,写数据时先放到缓冲区内,判断如果是标准设备,用行刷新,当文件关闭或主动刷新,将数据写到os内或直接刷到磁盘

将文件立即刷到磁盘的函数
在这里插入图片描述

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

struct __myfile
{
    int fd;
    char buff[1024];
    int end;   //当前缓冲区的结尾
};
typedef struct __myfile MyFIle;

MyFIle* _fopen(const char* pathname, const char* mode)
{
    assert(pathname);
    assert(mode);

    MyFIle* fp = NULL;
    if (strcmp(mode, "r") == 0)
    {

    }
    else if (strcmp(mode, "w") == 0)
    {

        int fd  = open(pathname, O_WRONLY|O_CREAT|O_TRUNC, 0666);
        if (fd >= 0)
        {
             fp = (MyFIle*)malloc(sizeof(MyFIle));
             memset(fp, 0, sizeof(MyFIle));
             fp->fd = fd;
             fp->end = 0;
        }

    }
    else 
    {
        //什么都不做
    }

    return fp;
}

void _fputs(const char* str, MyFIle* fp)
{
    assert(str);
    assert(fp);

    //将内容写到缓冲区
    strcpy(fp->buff + fp->end, str);
    fp->end = fp->end + strlen(str);

    //暂时没有刷新,打印测试
    printf("%s\n", fp->buff);
    
    //暂且没有刷新,刷新策略是谁执行?通过c标准库逻辑,来刷新
    //效率提高体现?c提供了缓冲区,减少了IO的次数(不是数据量)
    //显示器行刷新
    if (fp->fd == 0)
    {

    }
    else if (fp->fd == 1)
    {
        //标准输出
        if (fp->buff[fp->end - 1] == '\n')
        {
           fprintf(stderr, "fflush: %s ", fp->buff); //stderr
            write(fp->fd, fp->buff, fp->end);
            fp->end = 0;
        }
    }
    else if (fp->fd == 2)
    {
        //标准错误
    }

}

void _fflush(MyFIle* fp)
{
    assert(fp);

    //有数据再刷新
    if (fp->end != 0)
    {
        //暂且认为刷新了,其实是吧数据放到了内核缓冲区
        write(fp->fd, fp->buff, fp->end);
        //真正将数据刷到磁盘
        syncfs(fp->fd);
        fp->end = 0;
    }
}

void _fclose(MyFIle* fp)
{
    assert(fp);

    _fflush(fp);
    close(fp->fd);
    free(fp);
}

int main()
{
   // close(1);
    MyFIle* fp = _fopen("log.txt", "w");
    if (fp == NULL)
    {
        perror("_fopen");
        return 1;
    }

    _fputs("one:hello fputs\n", fp);
   // sleep(1);
   // _fputs("two:hello fputs\n", fp);
   // sleep(1);
   // _fputs("three:hello fputs ", fp);
   // sleep(1);
   // _fputs("four:hello fputs\n", fp);
   // sleep(1);
   
    fork();
    _fclose(fp);
     return 0;
}

shell增加重定向

字符串从后往前判断有没有重定向符号,找到之后分割文件和命令,用重定向函数交换fd

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

#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define NO_REDIR 0

int g_redir = NO_REDIR;

char* checkdir(char* cmd)
{
    assert(cmd);

    char* end = cmd + strlen(cmd) - 1;
    while (end >= cmd)
    {
        //检测重定向符号
        if (*end == '<')
        {
            g_redir = INPUT_REDIR;
            *end = '\0';
            end++;
            break;
        }
        else if (*end == '>')
        {
            //追加重定向
            if (*(end - 1) == '>')
            {
                g_redir = APPEND_REDIR;
                *(end - 1) = '\0';
                end++;
                break;
            }
            g_redir = OUTPUT_REDIR;
            *end = '\0';
            end++;
            break;
        }
        else{
            end--;
        }
    }

    if (end >= cmd)
    {
        return end;
    }
    else 
    {
        return NULL;
    } 
}

//shell运行原理,通过让子进程执行命令,父进程等待 解析命令
int main()
{
    char* ary[64];
    char _env[64]; //记录环境变量
    //0. 命令解释器,是一个常驻进程,不退出
    while (1)
    {
        //1. 打印提示信息 [tmp@VM-16-7-cento myshell]#
        printf("[tmp@VM-16-7-centos myshell]# ");
        fflush(stdout);
        //2. 获取用户输入的指令和选项
        char buff[1024];
        memset(buff, '\0', sizeof buff);
        if (fgets(buff, sizeof buff, stdin) == NULL)
        {
            continue;
        }

        //printf("%s\n", buff);
        //缓冲区的回车去掉
        buff[strlen(buff) - 1] = '\0';
        //分析是否有重定向, "ls -a -l>log.txt"
        char* sep = checkdir(buff);
        //3. 分割命令行,解析
        ary[0] = strtok(buff, " ");   //第一次调用,传入原始字符串
        int index = 1;
        //ls增加颜色
        if (strcmp(ary[0], "ls") == 0)
        {
            ary[index++] = "--color=auto";
        }
        while(ary[index] = strtok(NULL, " "))
        {
            index++;
        }
        //3.5 内置命令,需要父进程执行的,如cd
        if (strcmp(ary[0], "cd")==0)
        {
            if (ary[1] != NULL)
            {
                chdir(ary[1]);
                continue;
            }
        }

        if (strcmp(ary[0], "export") == 0 && ary[1] != NULL)
        {
            strcpy(_env, ary[1]);
            putenv(_env);
            continue;
        }
        pid_t id = fork();
        if (id == 0)
        {
        if (sep != NULL)
        {
            //重定向
            //switch里不能定义变量
            int fd = 0;
            switch(g_redir)
            {
               case INPUT_REDIR:
                    fd = open(sep, O_RDONLY);
                    dup2(fd, 0);
                   break;
               case OUTPUT_REDIR:
                   fd = open(sep, O_WRONLY | O_CREAT | O_TRUNC, 0666);
                   dup2(fd, 1);
                   break;
               case APPEND_REDIR:
                   fd = open(sep, O_WRONLY | O_CREAT | O_APPEND);
                   dup2(fd, 1);
                   break;
                default:
                   printf("error\n");
                   break;
            }
        }
            //4. 子进程执行任务
            execvp(ary[0], ary);
            exit(1);
        }
        //父进程等待
        int status = 0;
       pid_t ret =  waitpid(id,&status,0 );
       if (ret > 0)
       {
           printf("等待成功,退出码: %d\n", WEXITSTATUS(status));
       }
       else
       {
           printf("等到失败\n");
       }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值