Linux学习笔记之文件下

1.重定向

        首先我们先了解下Linux中的重定向操作,> ,>> ,<可以实现重定向的操作。

        1.1 >输出重定向

        在Linux下可以使用>将原本输出在显示屏的内容指定输出在文件中,如果指定文件不存在就创建,存在就清除数据,和我们之前使用fopen的w状态一样

        1.2 >>追加重定向

        与>不同的是,如果文件存在,他不清除数据,而是在文件末尾追加数据。与fopen的a状态一样。如下代码。

        1.3 <输入重定向

        我们在使用指令的时候可以加上选项,例如下图 grep指令。这个选项实际上就是从键盘读取的。使用<也可以从文件中读取。如下图

        1.4 重定向底层理解

        我们知道,当运行一个程序的时候,会默认打开三个文件,分别是标准输入,标准输出,标准错误,他们分别占据着task_struct里文件描述符的0,1,2位置。但是他们也是可以被关闭的

并且新的文件fd从没有被使用的最小下标开始。

  • 标准输入(0)默认为键盘
  • 标准输出 (1)默认为显示器
  • 标准错误 (2)默认为显示器

        如下代码,关闭文件描述符为1的文件,即标准输出,打开新文件的fd就为1,结果如下。

#include<stdio.h>
#include<unistd.h>
#include <fcntl.h>
#include<string.h>

int main()
{

    close(1);
    int fd=open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0777);
    char s[]="hello write\n";
    write(fd,s,strlen(s));

    return 0;
}

       

        同理在C语言中printf默认向标准输出打印文本,也就是说,如果我们先关闭fd为1的标准输出,在打开一个文件,此时该文件的fd就为1,printf就会向文件中打印文字。如下代码

#include<stdio.h>
#include<unistd.h>
#include <fcntl.h>
#include<string.h>

int main()
{
    //先关闭标准输出
    close(1);

    //再打开文件
    FILE * fp=fopen("log.txt","w");

    printf("文件fd为:%d\n",fp->_fileno);
    printf("文件fd为:%d\n",fp->_fileno);
    printf("文件fd为:%d\n",fp->_fileno);



    return 0;
}

1.4.1 C语言缓冲区

         注意上述代码中关闭文件采用的是系统调用函数close,而没有采用C语言封装的函数fclose。如果采用C语言fclose关闭结果如下。

        文件创建出来了,但是里面的内容什么都没有!首先C语言函数封装了系统调用,尽管速度相比于其他语言很快,但还是有消耗的,如果每次输出字符,都向操作系统内核写入,效率就会比较低,所以C语言就设计了一套自己的缓冲区。

        一般这个缓冲区直接定义在FILE结构体里面,如下图。

        回到最初的问题,我们可以理解为printf内部写死了,在编译之后,只认stdout里面的缓存区。类似下述代码.

write(stdout->_IO_buf_base,.....)

        当我们调用fclose关闭时,C语言会把stdout内部的缓冲区释放,此时在调用printf就向已经释放的区域写入,可能引发错误

        但如果我们调用close,stdout这个结构体依然在,他的缓冲区依旧在,我们就可以使用printf向里面写入数据。当刷新缓存区的时候,数据就会被内核拿到,故保留了下来。

1.4.2 系统级刷新缓存区函数

        C语言有自己的缓存区,同样OS为了避免频繁的向磁盘写入文件也设有缓存区,原理如下。

        由此可见,为了效率,C语言和Linux都做了不少优化。

        fflush是C语言刷新缓存区函数,可以将FILE中缓存的数据刷入到内核缓存区里面。

注意当C语言结束或者fclose文件关闭的时候,会自动刷新所打开的文件流的缓存区

        我们可以运行下述程序验证下。

#include<stdio.h>
#include<unistd.h>
#include <fcntl.h>
#include<string.h>

int main()
{
    //先关闭标准输出
    close(1);
    //再打开文件
    FILE * fp=fopen("log.txt","w+");//以读写方式打开

    printf("文件fd为:%d\n",fp->_fileno);
    char s[1024]="\0";
    fscanf(fp,"%s",s);    
    perror(s);//我们只关闭了标准输出,标准错误依然是正常的,可以向屏幕打印


    return 0;
}

        在运行的时候,没有读取到说明此时的字符还在C语言缓存区,或者内核缓存区里面,没有刷新到磁盘里面。

一般如果是显示器文件C语言遇到\n就立刻刷新,普通文件等到缓存区满了才刷新。

        此时我们可以用函数控制他刷新缓存区。

        此时字符到了内核缓存区里面,对于内核级缓冲区它只有满了的时候才会刷新到磁盘,为了让他刷新到磁盘里面,可以用系统级刷新缓存函数。sync

#include<stdio.h>
#include<unistd.h>
#include <fcntl.h>
#include<string.h>

int main()
{
    //先关闭标准输出
    close(1);
    //再打开文件
    FILE * fp=fopen("log.txt","w+");//以读写方式打开

    printf("文件fd为:%d\n",fp->_fileno);

    fflush(stdout);
    sync();
    rewind(fp);//使文件下标回到开始

    char s[1024]="\0";
    fscanf(fp,"%s",s);    
    perror(s);//我们只关闭了标准输出,标准错误依然是正常的,可以向屏幕打印


    return 0;
}

        此时需要注意的是,虽然字符被刷新到了磁盘,但文件的指针指向了结尾,如果想要读取内容,要让他回到开头。

        执行上述代码结果如下。此时就可以读取到文件内容了。

1.4.3 重定向

        所以所谓的重定向,实际上就是将原本进程的标准输入,标准输出换成我们指定的文件罢了

        我们可以采用上述先关闭一个文件,在打开一个文件,利用OS分配文件描述符fd的特点实现重定向,也可以使用函数dup2来实现重定向。

       dup2是把文件描述符oldfd拷贝到newfd处,最后保留oldfd。为什么传入两个数字就可以实现文件跳转,原理如下。

上述是进程的逻辑结构,可以抽象为如下图示

我们传入的数字其实就是文件在进程管理数组中的小标,重定向就相当于把文件指针拷贝过去,如下图。

这也说明了一个文件可以被多个文件指针指向,而文件内部采用引用计数的方式,每次关闭文件count减一,如果count为0就释放文件。

        我们就可以用dup2改写上述代码,不用那么复杂了。

#include<stdio.h>
#include<unistd.h>
#include <fcntl.h>
#include<string.h>

int main()
{

    int fd=open("log.txt",O_TRUNC| O_WRONLY|O_CREAT, 0777);
    dup2(fd,1);

    char s1[]="aaaaaaaaaaa\n";
    char s2[]="bbbbbbbbbbb\n";

    write(fd,s1,strlen(s1));
    write(1,s2,strlen(s2));//此时fd与1都指向log文件

    return 0;
}

        

1.5 子进程创建

        当进程创建子进程的时候,会继承父进程的内核数据结构与代码加数据。并且采用写时拷贝的技术,保持进程间的独立性。

        文件描述符表也是task_struct的一部分数据,也就是说当创建子进程的时候,子进程默认打开了父进程打开的文件。

        

2. 模拟实现C语言文件函数

        C语言封装的是系统调用,也就是利用系统函数,模拟封装下fopen,fwrite,fclose,fflush等函数。

2.1头文件 mystdio.h 

        定义一个FILE结构体,加入属性fd,buf

#pragma once //防止头文件被重复包含
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include <fcntl.h>
#include <stdio.h>

typedef struct FILE
{

    int fd;//文件描述符
    char buf[1024];//缓冲区
    int size;//缓冲区字符个数
}myFILE;

myFILE *mfopen(const char *path, const char *mode);
size_t mfread(void *ptr,size_t size, size_t nmemb,myFILE * stream);
size_t mfwrite(const void* ptr,size_t size, size_t nmemb,myFILE * stream);
int mfflush(myFILE * stream);

2.2 库文件 mystdio.c

        在库文件中实现上述函数。
 

#include"mystdio.h"



myFILE *mfopen(const char *path, const char *mode) {
    myFILE *fp = (myFILE *)malloc(sizeof(myFILE));
    if (fp == NULL) {
        perror("malloc(sizeof(myFILE))");
        return NULL;
    }
    fp->buf[0] = '\0';  // 假设 buf 是字符串缓冲区
    fp->size=0;//缓存区字符个数

    int flags = 0;
    mode_t permissions = 0777;

    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 if (strcmp(mode, "r") == 0) {
        flags = O_RDONLY;
    } else {
        fprintf(stderr, "Invalid mode: %s\n", mode);
        free(fp);
        return NULL;
    }

    fp->fd = open(path, flags, permissions);
    if (fp->fd == -1) {
        perror("open");
        free(fp);
        return NULL;
    }

    return fp;
}


int mfflush(myFILE * stream)
{
    write(stream->fd,stream->buf,stream->size);
    stream->size=0;
}


size_t mfread(void *ptr,size_t size, size_t nmemb,myFILE * stream)
{
    return read(stream->fd,ptr,size*nmemb);
}
size_t mfwrite(const void* ptr,size_t size, size_t nmemb,myFILE * stream)
{
    if(stream->size+size*nmemb>1024)
        mfflush(stream);
    snprintf(stream->buf+stream->size,size*nmemb,"%s",(char*)ptr);
    stream->size+=size*nmemb;

    return size*nmemb;
}



        2.3 测试代码 tes.c

#include"mystdio.h"

int main()
{
    myFILE* fp =mfopen("log.txt","w");
    char s[]="sssssssssssssss\n";
    char s2[]="bbbbbbbbbbbbbbb\n";
    char s3[]="=gggggggggggg\n";

    mfwrite(s,1,strlen(s),fp);
    mfwrite(s2,1,strlen(s2),fp);
    mfwrite(s3,1,strlen(s3),fp);

    mfflush(fp);

    return 0;
}

        运行结果如下图,在指定文件打印字符。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值