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;
}

