文章目錄
- 前言
- 一、重定向
- 具體操作
- dup2
- 二、關于緩沖區的了解
- 1. 什麼是緩沖區
- 2. 為什麼要有緩沖區
- 3. 緩沖區在哪裡
- 重新整理政策機制
- 如果在重新整理之前,關閉了fd會有什麼問題?
- FLIE内部的封裝
- 特殊情況
- 模拟實作封裝C标準庫
- 标準輸出和标準錯誤
- 總結
前言
本節繼續基于上節檔案描述符繼續往下拓展,講一講關于檔案操作的重定向和緩沖區。
正文開始!
首先來基于上節課的問題
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main()
{
close(1);
int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd<0)
{
perror("open");
return 1;
}
fprintf(stdout,"打開檔案成功,fd=%d",fd);
//fflush(stdout);
close(fd);
}

我們發現既沒有往顯示器列印,也沒有往檔案裡面列印!
然後我們将fflush()這一行代碼取消注釋後
我們可以發現列印内容到了檔案中了。至于為什麼要用fflush()函數,需要了解到緩沖區的内容,接下來帶大家了解!
那我有一個問題了?為什麼不往顯示器去列印,而是列印在了檔案中呢???
一、重定向
如果我們要進行重定向,上層隻認0,1,2,3這樣的fd,我們可以在OS内部,通過一定的方式調整數組的特定下标内容夠(指向),我們就可以完成重定向操作!
對于上圖,我們進行重定向後,fprintf并不知道1号檔案描述符指向了"log.txt"檔案,而繼續向1号檔案描述符列印東西。
具體操作
上面的一堆的資料,都是核心資料結構。隻有誰有權限呢???
必定是作業系統(OS)---->必定提供系統結構!
dup2
相比于dup,dup2更複雜一些,我們今天主要使用多duo2進行重定向操作!
在這裡我們首先進行輸出重定向
stdout–>1 log.txt–>fd
-
那麼對于dup2()接口,誰是誰的一份拷貝呢?
對于上面框起來的内容翻譯就是newfd是oldfd的一份拷貝,就是把oldfd的内容放置newfd裡面。最後隻剩oldfd了!!!
-
參數怎麼傳呢??
我們要輸出重定向到檔案中,即就是stdout的輸出到檔案中,即就是1号檔案描述符的内容要指向新建立檔案的描述符。
dup2(fd,1);
int main()
{
int fd=open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd<0)
{
perror("open");
return 1;
}
dup2(fd,1);
fprintf(stdout,"打開檔案成功,fd=%d",fd);
fflush(stdout);
close(fd);
}
輸入重定向
int main()
{
int fd=open("log.txt",O_RDONLY);
if(fd<0)
{
perror("open");
return 1;
}
dup2(fd,0);
char line[64];
while(fgets(line,sizeof(line),stdin)!=NULL)
{
printf(line);
}
fflush(stdout);
close(fd);
}
二、關于緩沖區的了解
1. 什麼是緩沖區
- 緩沖區的本質:就是一段記憶體。
2. 為什麼要有緩沖區
- 解放使用緩沖區的程序時間
- 緩沖區的存在可以集中處理資料重新整理,減少IO的次數,進而達到提高整機的效率的目的!
3. 緩沖區在哪裡
我來寫一份代碼帶大家驗證一下!
int main()
{
printf("hello printf\n"); //stdout-->1
const char* msg="hello write\n";
write(1,msg,strlen(msg));
}
去掉’'以後
int main()
{
printf("hello printf"); //stdout-->1
const char* msg="hello write";
write(1,msg,strlen(msg));
}
printf沒有立即重新整理的原因,是因為有緩沖區的存在
write可是立即重新整理的!
是以我們根據以上的實驗現象我們可以發現stdout必定封裝了write!
那麼這個緩沖區不在哪裡?? ---->一定不在wirte内部!
是以我們曾經讨論的緩沖區,不是核心級别的!
是以這個緩沖區在哪裡???—>隻能是C語言提供的!!!(語言級别的緩沖區)
因為printf是往stdout中列印,stdout—>FILE—>struct—>封裝很多的屬性---->fd—>該FILE對于的語言級别的緩沖區!
重新整理政策機制
int main()
{
printf("hello printf");//stdout->1->封裝了write
fprintf(stdout,"hello fprintf");
fputs("hello fputs",stdout);
const char* msg="hello write";
write(1,msg,strlen(msg));
return 0;
}
什麼時候重新整理?
- 無緩沖(立即重新整理)
- 行緩沖(逐行重新整理)—>顯示器檔案
- 全緩沖(緩沖區滿,重新整理)—>塊裝置對應的檔案(磁盤檔案)
無緩沖的特殊情況
a.程序退出
b.使用者強制重新整理
如果在重新整理之前,關閉了fd會有什麼問題?
int main()
{
printf("hello printf");//stdout->1->封裝了write
fprintf(stdout,"hello fprintf");
fputs("hello fputs",stdout);
const char* msg="hello write";
write(1,msg,strlen(msg));
close(1);
//close(stdout->_fileno);//和上面close(1)效果相同
return 0;
}
一開始我們隻是把資料寫入到FILE結構體的緩沖區,然後你把檔案描述符給關了,當然就寫不進檔案中了!!!
現在我們就能來了解一開始我們抛出的問題了!!!
既然緩沖區在FILE内部,在C語言中,而我們每一次打開一個檔案,都要有一個FILE*會傳回!!
那就意味着,每一個檔案都有一個fd和屬于他自己的語言級别的緩沖區!!!
FLIE内部的封裝
在/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. */
特殊情況
int main()
{
const char* str1="hello printf\n";
const char* str2="hello fprintf\n";
const char* str3="hello fputs\n";
const char* str4="hello write\n";
//C庫函數
printf(str1);
fprintf(stdout,str2);
fputs(str3,stdout);
//系統接口
write(1,str4,strlen(str4));
//是調用完了上面的代碼,才執行的fork
fork();
}
對代碼去掉’\n’
int main()
{
const char* str1="hello printf";
const char* str2="hello fprintf";
const char* str3="hello fputs";
const char* str4="hello write";
//C庫函數
printf(str1);
fprintf(stdout,str2);
fputs(str3,stdout);
//系統接口
write(1,str4,strlen(str4));
//是調用完了上面的代碼,才執行的fork
fork();
}
- 重新整理的本質,就是把緩沖區的資料寫到OS内部,清空緩沖區!
- 緩沖區是自己的FILE内部維護的,屬于父程序
- 子程序也繼承了父程序的緩沖區,也就列印了父程序緩沖區的内容!
模拟實作封裝C标準庫
隻封裝了C語言庫的一部分!
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include <sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#define NUM 1024
#define NONE_FLUSH 0x0
#define LINE_FLUSH 0x1
#define FULL_FLUSH 0x2
typedef struct _MyFILE
{
int _fileno;
char buffer[NUM];
int _end;
int _flags;//fflush method
}MyFILE;
MyFILE* my_fopen(const char* filename,const char* method)
{
assert(filename&&method);
int flags=O_RDONLY;
if(strcmp(method,"r")==0)
{}
else if(strcmp(method,"r+")==0)
{}
else if(strcmp(method,"w")==0)
{
flags=O_WRONLY|O_CREAT|O_TRUNC;
}
else if(strcmp(method,"w+")==0)
{}
else if(strcmp(method,"a")==0)
{
flags=O_WRONLY|O_CREAT|O_APPEND;
}
else if(strcmp(method,"a+")==0)
{}
int fileno=open(filename,flags,0666);
if(fileno<0) return NULL;
MyFILE* fp=(MyFILE*)malloc(sizeof(MyFILE));
if(fp==NULL)
{
return NULL;
}
memset(fp,0,sizeof(MyFILE));
fp->_fileno=fileno;
fp->_flags|=LINE_FLUSH;
fp->_end=0;
return fp;
}
void my_fflush(MyFILE* fp)
{
assert(fp);
if(fp->_end>0)
{
write(fp->_fileno,fp->buffer,fp->_end);
fp->_end=0;
syncfs(fp->_fileno);
}
}
void my_fwrite(MyFILE* fp,const char* start,int len)
{
assert(fp&&start&&len>0);
strncpy(fp->buffer+fp->_end,start,len);//将資料寫入到緩沖區了
fp->_end+=len;
if(fp->_flags&NONE_FLUSH)
{
}
else if(fp->_flags&LINE_FLUSH)
{
if(fp->_end>0&&fp->buffer[fp->_end-1]=='\n')
{
//僅僅是寫入到核心中
write(fp->_fileno,fp->buffer,fp->_end);
fp->_end=0;
}
}
else if(fp->_flags&FULL_FLUSH)
{
}
}
void my_close(MyFILE* fp)
{
my_fflush(fp);
close(fp->_fileno);
free(fp);
}
int main()
{
MyFILE* fp=my_fopen("log.txt","w");
if(fp==NULL)
{
printf("my_fopen fail\n");
return 1;
}
const char* s="hello my 111\n";
my_fwrite(fp,s,strlen(s));
printf("消息立即重新整理");
sleep(3);
const char* ss="hello my 222";
my_fwrite(fp,ss,strlen(ss));
sleep(3);
printf("寫入了一個不滿足條件的字元串\n");
const char* sss="hello my 333";
my_fwrite(fp,sss,strlen(sss));
sleep(3);
printf("寫入了一個不滿足條件的字元串\n");
const char* ssss="end\n";
my_fwrite(fp,ssss,strlen(ssss));
sleep(3);
printf("寫入了一個滿足條件的字元串\n");
const char* sssss="aaaaaaaaaa";
my_fwrite(fp,sssss,strlen(sssss));
printf("寫入了一個不滿足條件的字元串\n");
sleep(1);
my_fflush(fp);
sleep(3);
my_close(fp);
return 0;
}
标準輸出和标準錯誤
#include<iostream>
#include<cstdio>
int main()
{
//stdout
printf("hello printf 1\n");
fprintf(stdout,"hello fprintf 1\n");
fputs("hello puts 1\n",stdout);
//stderr
fprintf(stderr,"hello fprintf 2\n");
fputs("hello puts 2\n",stderr);
perror("hello perror 2");
//cout
std::cout<<"hello cout 1"<<std::endl;
//cerr
std::cerr<<"hello cerr 2"<<std::endl;
}
我們發現打1的都不見了
向stdout裡面打的都重定向到log.txt檔案中,stderr繼續列印到顯示器。
./a.out >stdout.txt 2>stderr.txt
一條語句進行兩次重定向
那麼意義在哪裡呢?
可以區分那些是程式日常輸出,那些是錯誤!
我們也可以将上面的兩個檔案列印在一個檔案
./a.out >all.txt 2>&1