目录
使用 dup2 系统调用
为命令行解释器添加重定向功能
理解缓冲区问题
缓存区的刷新策略
FILE的本质
尝试封装C语言的FILE
小共识:
cd->当前路径->当前进程的工作路径->工作路径可以被修改->每个进程都有当前路径->故cd改的是子进程的路径->修改父进程的路径需要系统调用
fd的分配规则:从小到大,按照顺序从struct file* fd_array[]寻找最小且没有被占用的fd
重定向的本质:
重定向的本质:上层用的fd不变,在内核中更改fd对应的struct file*的地址。
使用 dup2 系统调用
#include <unistd.h>
int dup2(int oldfd, int newfd);
将newfd中内容拷贝到oldfd.
将向显示器打印的内容重定向到log.txt.
为命令行解释器添加重定向功能
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define NUM 1024
#define OPT_NUM 64
#define trimSpace(start) do{\
while(isspace(*start)) ++start;\
}while(0)
char lineCommand[NUM];
char* myargv[OPT_NUM];
int lastCode=0;
int lastSig=0;
int redirType=NONE_REDIR;
char* redirFile=NULL;
void comandCheck(char* commands)
{
assert(commands);
char* start=commands;
char* end=commands+strlen(commands);
while (start<end)
{
if(*start=='>')
{
*start='\0';
start++;
if(*start=='>')
{
//"ls -a >> file.log"
redirType=APPEND_REDIR;
start++;
}
else
{
//"ls -a> file.log"
redirType=OUTPUT_REDIR;
}
trimSpace(start);
redirFile=start;
break;
}
else if(*start=='<')
{
//"cat < file.txt"
*start='\0';
start++;
trimSpace(start);
redirType=INPUT_REDIR;
redirFile=start;
break;
}
else
{
start++;
}
}
}
进程具有独立性,所以子进程会拷贝一份父进程的files_struct二者相互独立。而文件部分二者共享(上图中的虚线部分)。程序替换时不影响PCB及PCB内部的细节。
一个被打开的文件中包含引用计数,父进程或子进程关闭文件其本质是让引用计数--。
理解缓冲区问题
缓存区的刷新策略
a.立即刷新-无缓冲
b.行刷新---显示器(键盘)
c.缓冲区满---全缓存----磁盘文件
两种特殊情况
1.用户强制刷新---(fflush)
2.进程退出----通常情况下会进行缓冲区的刷新。
例如:
printf("hello printf\n");
fprintf(stdout,"hello printf\n");
const char* fputsString="hello fputs\n";
fputs(fputsString,stdout);
const char* wstring="hello write\n";
write(1,wstring,strlen(wstring));
fork();
c语言的库函数被打印了两次而系统接口write只被打印了一次。这是为什么????????
在代码结束之前创建子进程
1.如果没有进行> 可以看到4条消息,stdout默认使用的是行刷新,在进程fork之前,C函数已经被打印输出到显示器上(外设),FIEL内部,进程内部就没有对应的数据了。
2.如果进行>,写入文件不在是显示器,而是普通文件,采用的刷新策略是全缓冲,三条C函数虽然带了'\n’, 但是不足以将整个缓存区写满,!!!!!!所以数据没有被刷新。
执行fork()函数时,stdout属于父进程,创建子进程时,创建之后的操作就是退出!!先退出的进程要进行缓存区的刷新(即为修改),这时会发生写时拷贝,最终数据会显示两份(父子进程最终都会去找缓冲区,然后将数据进行刷新)。
3.而write为什么没有呢??? write没有FILE,而是用的fd,故没有C提供的缓冲区。
FILE的本质
>log.txt
清空文件
尝试封装C语言的FILE
#pragma once
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<errno.h>
#include<ctype.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/wait.h>
#define SIZE 1024
#define SYNC_NOW 1
#define SYNC_LINE 2
#define SYNC_FULL 4
typedef struct _FILE
{
int flags;//刷新方式
int fileno;
int cap;//buffer的总容量
int size;//buffer当前容量
char buffer[SIZE];
}FILE_;
FILE_* fopen_(const char* path_name,const char* mode);
void fwrite_(const void* ptr,int num,FILE_* fp);
void fclose_(FILE_* fp);
void fflush_(FILE_* fp);
FILE_* fopen_(const char* path_name,const char* mode)
{
int flags=0;
int defaultMode=0666;
if(strcmp(mode,"r")==0)
{
flags|=O_RDONLY;
}
else if(strcmp(mode,"w")==0)
{
flags|=(O_WRONLY|O_CREAT|O_TRUNC);
}
else if(strcmp(mode,"a")==0)
{
flags|=(O_WRONLY|O_CREAT|O_APPEND);
}
else
{
//..
}
int fd=0;
if(flags&O_RDONLY) fd=open(path_name,flags);
else fd=open(path_name,flags,defaultMode);
if(fd<0)
{
const char* err=strerror(errno);
write(2,err,strlen(err));
return NULL;
}
FILE_* fp=(FILE_*)malloc(sizeof(FILE_));
assert(fp);
fp->flags=SYNC_LINE;//默认是行刷新
fp->fileno=fd;
fp->cap=SIZE;
fp->size=0;
memset(fp->buffer,0,SIZE);
return fp;
}
void fwrite_(const void* ptr,int num,FILE_* fp)
{
//1.写入到缓冲区中
memcpy(fp->buffer+fp->size,ptr,num);
fp->size+=num;
//2.判断是否刷新
if(fp->flags&SYNC_NOW)
{
write(fp->fileno,fp->buffer,fp->size);
fp->size=0;
}
else if(fp->flags&SYNC_FULL)
{
if(fp->size==fp->cap)
{
write(fp->fileno,fp->buffer,fp->size);
fp->size=0;
}
}
else if(fp->flags&SYNC_LINE)
{
if(fp->buffer[fp->size-1]=='\n')
{
write(fp->fileno,fp->buffer,fp->size);
fp->size=0;
}
}
else
{
}
}
void fflush_(FILE_* fp){
if(fp->size>0) write(fp->fileno,fp->buffer,fp->size);
}
void fclose_(FILE_* fp)
{
fflush_(fp);
close(fp->fileno);
}
故数据被写到硬件上至少会进过三次拷贝。
void fflush_(FILE_* fp){
if(fp->size>0) write(fp->fileno,fp->buffer,fp->size);
fsync(fp->fileno);//将数据,强制要求os进行外设刷新
fp->size=0;
}