成都网站建设设计

将想法与焦点和您一起共享

C语言缓冲区与重定向-创新互联

目录

创新互联主营丰林网站建设的网络公司,主营网站建设方案,APP应用开发,丰林h5微信小程序搭建,丰林网站营销推广欢迎丰林等地区企业咨询

什么是缓冲区?

刷新策略 

模拟实现重定向

标准输出和标准错误有什么区别?


上文提到关闭1号文件(标准输出文件),根据文件描述符分配规则,再打开的文件的描述符就是1,看以下代码:

运行代码后,前10s中file.txt文件被创建出来了,但是没有内容:

过了10秒钟,程序退出了,内容才被刷新到了磁盘文件上

为什么数据没有被马上打印到文件上呢?

因为存在缓冲区,这里的缓冲区是C语言封装的缓冲区。

什么是缓冲区?

缓冲区的本质其实就是一段内存,用来暂时存放数据的地方。

为什么要有缓冲区呢?

联系生活,比如快递的出现,让货物的配送交给物流,这样就可以解放你的时间 ,而且集中配送货物可以提高总体的效率。对于缓冲区而言,缓冲区对于进程而言效果也是类似的,首先缓冲区解放使用缓冲区的进程的时间,比如进程往磁盘写入的时候,可以直接往缓冲区去写入,进程不用等待写入磁盘完成,可以继续执行后续的代码,其次缓冲区集中处理缓冲区中数据的刷新,减少了IO次数,因为外设上IO的效率是很低的(相比其他硬件),故而提高整机的效率。

缓冲区在哪里?

将上面的代码中的printf函数换成系统调用write:

发现,在sleep期间,进程已经把数据打印到了file.txt上,可见write内部并没有缓冲区,printf封装了write,这个缓冲区是C语言的缓冲区。

在C语言的文件描述结构体FILE当中封装了该FILE对应的语言级别的缓冲区,每次打开文件,每个FILE都有对应的fd和属于自己的语言级别缓冲区,写入时先写到缓冲区,当缓冲区需要刷新的时候,内部再调用write刷新到对应文件中,那么何时刷新呢?这取决于C语言的刷新策略。

刷新策略 

1.无缓冲:也就是立即刷新

进程退出时:当进程正常退出前,C语言内部就会一系列工作,包括调用fclose函数刷新缓冲区

用户强制刷新:用户调用fflush函数直接刷新缓冲区

2.行缓冲:逐行刷新,当输入的字符中有\n就直接刷新之前缓冲区中的内容,显示器文件就是这个刷新策略。

3.全缓冲:也即当缓冲区写满了才刷新,块设备对应的磁盘文件是采用全缓冲刷新策略的。

现在就可以解释原本的代码为什么不立即刷新到磁盘上了,首先1号文件标准输出被关闭了,而打开了磁盘文件,printf会往1号文件打印,也就是说标准输出被重定向到了磁盘文件,而标准输出的刷新策略本来是行刷新的,现在改变了刷新策略变成了全缓冲刷新策略,因为缓冲区没有被写满所以在sleep期间也没有看见数据输出到文件中,当进程退出或调用fflush时,缓冲区就被刷新到了磁盘文件上,于是进程退出后数据就才刷新到了文件当中。

模拟实现重定向

在上上篇博客中,实现了一个简易版的shell,现在根据现有知识再模拟实现一下重定向符号输出重定向>,追加重定向>>和输入重定向<添加入minishell吧。

的博客-博客">进程程序替换_且随疾风前行->的博客-博客

实现方法:

先用一些宏标识几种重定向方法,用全局变量记录一下要重定向到的文件,和如何进行重定向,比如下面代码:

在命令分割之前检查一下命令中是否有重定向符号,并记录之。

然后再在创建子进程之后程序替换之前,将要重定向的文件打开,并用dup2函数重定向之即可。

代码如下:

#include#include#include#include#include#include#include#include#define NONE_REDIR -1
#define INPUT_REDIR 0
#define OUTPUT_REDIR 1
#define APPEND_REDIR 2
int g_redir_flag=NONE_REDIR;
char* g_redir_filename =NULL;

void CheckRdir(char*command)
{
    char*begin=command;
    while(*begin)
    {
        if(*begin=='>')
        {
            if(*(begin+1)=='>')
            {
                g_redir_flag=APPEND_REDIR;
                *begin='\0';
                begin+=2;
                while(*begin==' ')++begin;
                g_redir_filename=begin;
                break;
            }
            else
            {
                g_redir_flag=OUTPUT_REDIR;
                *begin='\0';
                begin+=1;
                while(*begin==' ')++begin;
                g_redir_filename=begin;
                break;
            }
        }
        else if(*begin=='<')
        {
            g_redir_flag=INPUT_REDIR;
            *begin='\0';
            begin+=1;
            while(*begin==' ')++begin;
            g_redir_filename=begin;
            break;
        }
        else ++begin;
    }
}
int main()
{
    //保持完整的字符串
    char inbuff[1024];
    //保持打散之后的字符串
    char *argv[32];
    char g_envar[64];//保存环境变量
    while(1)
    {
        printf("Please Enter#");
        fflush(stdout);
        memset(inbuff,'\0',sizeof(inbuff));
        if(fgets(inbuff,sizeof inbuff,stdin)==NULL)
            continue;
        inbuff[strlen(inbuff)-1]='\0';

        //检查重定向符号,并记录重定向方法
        CheckRdir(inbuff);

        argv[0]=strtok(inbuff," ");
        int index=1;
        if(strcmp(argv[0],"ls")==0)argv[index++]="--color=auto";
        if(strcmp(argv[0],"ll")==0)argv[0]="ls",argv[2]="-l",argv[1]="--color=auto",index=3;
        while(argv[index++]=strtok(NULL," "));//第二次调用解析原字符串时,传入NULL
        if(strcmp(argv[0],"export")==0&&argv[1]!=NULL)
        {
            strcpy(g_envar,argv[1]);
            if(putenv(g_envar)==0)
                printf("%s export success!\n",argv[1]);
            continue;
        }
        //内建命令
        if(strcmp(argv[0],"cd")==0 && argv[1]!=NULL)
        {
            chdir(argv[1]);
            printf("cd success!\n");
            continue;
        }
        pid_t id=fork();
        if(id==0)//child
        {
            if(g_redir_flag!=NONE_REDIR)
            {
                int fd;
                switch(g_redir_flag)
                {
                    case INPUT_REDIR:
                        fd=open(g_redir_filename,O_RDONLY,0666);
                        dup2(fd,0);
                        break;
                    case OUTPUT_REDIR:
                        fd=open(g_redir_filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
                        dup2(fd,1);
                        break;
                    case APPEND_REDIR:
                        fd=open(g_redir_filename,O_WRONLY|O_CREAT|O_APPEND,0666);
                        dup2(fd,1);
                        break;
                    default:
                        break;
                }
            }
            execvp(argv[0],argv);
            exit(0);
        }
        int status=0;
        pid_t ret=waitpid(id,&status,0);
        if(ret>0)printf("exit code:%d\n",WEXITSTATUS(status));

    }
    return 0;
}

编译运行程序,我们的minshell就可以进行重定向了:

在打印的时候标准输出和标准错误都往屏幕上打印,二者有区别吗?

标准输出和标准错误有什么区别?

看如下代码:

运行程序,标准输出和标准错误都输出到屏幕中:

但是我们使用重定向符号将程序运行的重定向输出输出到文件时,只重定向了标准输出到file.txt文件中,标准错误还是打印到屏幕中:

这是因为重定向默认是使用输出重定向,在命令行中可以显式写出要如何重定向,

比如:

./a.out 1>stdout.txt 2>stderr.txt 让文件输出到标准输出文件的重定向输出到stdout.txt,标准错误输出的重定向输出到stderr.txt 文件:

还可以用 ./a.out>all.txt 2>&1表示让输出到标准输入和标准错误都重定向输出到all.txt文件中:

之所以有标准输出和标准错误,主要是用于区分哪些是程序的日常输出,哪些是程序运行中产生的错误。

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


标题名称:C语言缓冲区与重定向-创新互联
URL分享:http://chengdu.cdxwcx.cn/article/cdoiji.html