【Linux】基础IO文件操作

news2024/11/16 18:46:46

目录

基础IO

重谈文件

重谈C语言的文件操作

系统文件IO

理解文件

文件描述符fd

0 & 1 & 2

文件描述符的分配规则

重定向

使用 dup2 系统调用

在minishell中添加重定向功能

缓冲区

理解缓冲区

再次理解缓冲区


基础IO

重谈文件

1、空文件,也要占磁盘的空间。

2、文件 = 内容 + 属性。

3、文件操作:等于对内容和对属性进行操作。

4、标定一个问题,必须使用:文件路径 + 文件名【唯一性】

5、如果没有指明对应文件路径,默认是当前路径进行文件访问。

6、当我们把fopen,fclose,fread,fwrite等接口写完后,代码编译之后,形成的二进制可执行文件后,但是没有运行,文件对应的操作有没有被执行呢?

答:没有被执行,本质:进程对文件的操作!

7、一个文件如果没有被打开,可以直接进行文件访问吗?

答:不能!一个文件要被访问,就必须先打开!

重谈C语言的文件操作

//打开文件
FILE * fopen ( const char * filename, const char * mode );
//关闭文件
int fclose ( FILE * stream );
文件使用方式含义如果指定文件不存在
“r”(只读)为了输入数据,打开一个已经存在的文本文件出错
“w”(只写)为了输出数据,打开一个文本文件建立一个新的文件
“a”(追加)向文本文件尾添加数据建立一个新的文件
“rb”(只读)为了输入数据,打开一个二进制文件出错
“wb”(只写)为了输出数据,打开一个二进制文件建立一个新的文件
“ab”(追加)向一个二进制文件尾添加数据出错
“r+”(读写)为了读和写,打开一个文本文件出错
“w+”(读写)为了读和写,建议一个新的文件建立一个新的文件
“a+”(读写)打开一个文件,在文件尾进行读写建立一个新的文件
“rb+”(读写)为了读和写打开一个二进制文件出错
“wb+”(读写)为了读和写,新建一个新的二进制文件建立一个新的文件
“ab+”(读写)打开一个二进制文件,在文件尾进行读和写建立一个新的文件
功能函数名适用于
字符输入函数fgetc所有输入流
字符输出函数fputc所有输出流
文本行输入函数fgets所有输入流
文本行输出函数fputs所有输出流
格式化输入函数fscanf所有输入流
格式化输出函数fprintf所有输出流
二进制输入fread文件
二进制输出fwrite文件
  • 向文件中写入数据:

#include <stdio.h>  
#include <stdlib.h>  
  
#define FILE_NAME "log.txt"  
​
int main(){   
    
    // 打开文件.                     
    FILE* pf = fopen(FILE_NAME, "w");  
    if(pf == NULL){                  
        perror("fopen");  
        exit(1);        
    }           
                      
    // 打开文件成功.  
    int count = 5;     
    while(count > 0){                            
        fprintf(pf, "Hello FILE: %d\n", count);  
        --count;                               
    }           
                      
    // 关闭文件资源.  
    fclose(pf);     
    pf = NULL;  
    return 0;  
}
从文件中读取数据:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>
  
#define FILE_NAME "log.txt"  
int main() {
​
    FILE* pf = fopen(FILE_NAME, "r");
    if(pf == NULL){
​
        perror("fopen");
        exit(1);
    }
​
    // 程序走到这里说明打开文件成功.
    char buffer[64] = {0};
    while(fgets(buffer, sizeof(buffer) - 1, pf) != NULL){
        buffer[strlen(buffer) - 1] = 0;
        puts(buffer);
    }
​
    // 关闭文件资源.
    fclose(pf);                                                                                                                                                         
    pf = NULL;
​
    return 0;
}

系统文件IO

操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码:

// C语言中fopen -> 对应的系统文件中的open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
​
// 创建文件
int creat(const char *pathname, mode_t mode);
// 打开文件
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
// pathname: 要打开或创建的目标文件
// flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
// 参数:
// O_RDONLY: 只读打开
// O_WRONLY: 只写打开
// O_RDWR : 读,写打开
// 这三个常量,必须指定一个且只能指定一个
// O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
// O_APPEND: 追加写
// 返回值:
// 成功:新打开的文件描述符
// 失败:-1
​
​
// C语言中fclose -> 对应的系统文件中的close
#include <unistd.h>
// 关闭文件
int close(int fd);
​
// 写入数据系统接口.
#include <unistd.h>
ssize_t write(int fd, const void* buf, size_t count);
​
// 读取数据系统接口.
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
  • 使用write写入数据(以只写的方式打开)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
​
#define FILE_NAME "log.txt"
// 使用系统接口创建文件.
int main(){
   
   // 这里执行umask将系统的umask设置为0,如果不进行设置那么 下面的0666 & 系统umask
   umask(0);
​
   // 创建文件,并且打开文件。
   // 创建文件失败会返回1. 
   // O_WRONLY - 只写
   // O_CREAT - 创建.
   // O_TRUNC - 创建后清空文件内容.
   int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);
   if(fd < 0){
       // perror("open");
       printf("%s\n", strerror(errno));
       exit(1);
   }
                                                                                           
   // 写入数据
   int count = 5;
   char outBuffer[64] = {0};
   // 系统只接收文本数据,所有要将所有的数据统一转为文本,统一接入的文件中.
    while(count > 0){
        sprintf(outBuffer, "%s:%d\n", "Hello SYS_FILE", count--);
        write(fd, outBuffer, strlen(outBuffer));
    }
​
    // 关闭对应文件资源.
    close(fd);
​
    return 0;
}
  • 使用write写入数据(以追加的方式打开)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
​
#define FILE_NAME "log.txt"
// 使用系统接口创建文件.
int main(){
​
    // 这里执行umask将系统的umask设置为0,如果不进行设置那么 下面的0666 & 系统umask
    umask(0);
​
    // 创建文件,并且打开文件。
    // 创建文件失败会返回1. 
    // O_WRONLY - 只写
    // O_CREAT - 创建.
    // O_TRUNC - 创建后清空文件内容.
    // O_APPEND - 追加选项,要删除O_TRUNC
    // int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);
    int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
    if(fd < 0){
        // perror("open");
        printf("%s\n", strerror(errno));
        exit(1);
    }
​
    // 写入数据
    int count = 5;
    char outBuffer[64] = {0};
    // 系统只接收文本数据,所有要将所有的数据统一转为文本,统一接入的文件中.                   
    while(count > 0){
        sprintf(outBuffer, "%s:%d\n", "Hello SYS_FILE", count--);
        write(fd, outBuffer, strlen(outBuffer));
    }
​
    // 关闭对应文件资源.
    close(fd);
​
    return 0;
}
  • 使用read读取数据

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
​
#define FILE_NAME "log.txt"
int main(){
    // 设定系统的umask.
    umask(0);
    // 打开文件. 
    int fd = open(FILE_NAME, O_RDONLY);
    if(fd < 0){
        printf("%s\n", strerror(errno));
        exit(1);
    }
​
    // 读取文件中的内容.
    char buffer[1024] = {0};
    ssize_t ret = read(fd, buffer, sizeof(buffer)-1);
    if(ret > 0){
        // 在这里读取完毕后需要进行处理一下:
        // 因为在C语言中的 \0 = 0 = NULL 都是 0.
        buffer[ret] = 0;
        printf("%s\n", buffer);
    }
​
    close(fd);
                                                                     
    return 0;
}

理解文件

文件操作的本质:进程和被打开文件的关系。

进程可以打开多个文件,系统中存在大量的被打开的文件,需要被操作系统OS进行管理,操作系统会对他们进行先描述,在组织,操作系统为了管理对应打开的文件,必定要为文件创建对应的内核数据结构i标识文件{struct file ()} 包含了文件大部分的属性。

文件描述符fd

  • 通过对open函数的学习,我们知道了文件描述符就是一个小整数

0 & 1 & 2

  • Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.

  • 0,1,2对应的物理设备一般是:键盘,显示器,显示器

  • 文件描述符的本质:是数组下标

而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件 。

文件描述符的分配规则

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
​
int main(){
​
    // 设定子程序的umask
    umask(0000);
    // 打开文件,设定选择项,权限设置为666
    int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
    if(fd < 0){
        perror("open");
    }
​
    printf("open df:%d\n", fd);                                                                                                                                         
​
    // 关闭文件.
    close(fd);
​
    return 0;
}
// 这段程序打印结果是3.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
​
int main(){
​
    close(0);
    // close(1);
    // close(2);                                                             
    // 设定子程序的umask
    umask(0000);
    // 打开文件,设定选择项,权限设置为666
    int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
    if(fd < 0){
        perror("open");
    }
​
    printf("open df:%d\n", fd);
​
    // 关闭文件.
    close(fd);
​
    return 0;
}
// 这段程序打印结果是0.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
​
int main(){
​
    // close(0);
    close(1);
    // close(2);                                                             
    // 设定子程序的umask
    umask(0000);
    // 打开文件,设定选择项,权限设置为666
    int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
    if(fd < 0){
        perror("open");
    }
​
    printf("open df:%d\n", fd);
​
    // 关闭文件.
    close(fd);
​
    return 0;
}
// 这段程序打印结果不显示.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
​
int main(){
​
    // close(0);
    // close(1);
    close(2);                                                             
    // 设定子程序的umask
    umask(0000);
    // 打开文件,设定选择项,权限设置为666
    int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
    if(fd < 0){
        perror("open");
    }
​
    printf("open df:%d\n", fd);
​
    // 关闭文件.
    close(fd);
​
    return 0;
}
// 这段程序打印结果是2.

发现是结果是: fd: 0 或者 fd 2 可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符 .

文件描述符的规则是:从小到大,按照循序寻找最小的且没有被占用的fd进行分配。

重定向

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
​
int main(){
​
    // close(0);
    close(1);
    // close(2);                                                             
    // 设定子程序的umask
    umask(0000);
    // 打开文件,设定选择项,权限设置为666
    int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
    if(fd < 0){
        perror("open");
    }
​
    printf("open df:%d\n", fd);
​
    // 关闭文件.
    close(fd);
​
    return 0;
}
// 这段程序打印结果不显示.
// 打印到了文件中.
​
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
​
int main(){
​
    // close(0);
    close(1);                                                            
    // close(2);
    // 设定子程序的umask
    umask(0000);
    // 打开文件,设定选择项,权限设置为666
    int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
    if(fd < 0){
        perror("open");
    }
​
    fprintf(stdout, "open df:%d\n", fd);
    fflush(stdout);
​
    // 关闭文件.
    close(fd);
​
    return 0;
}
// 观察代码,将内容打印到文件中.

此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>, >>, <

那重定向的本质是什么呢?

 

使用 dup2 系统调用

dup2可以将内核中的两个文件描述符,进行拷贝(拷贝的是内容).

// 接口.
#include <unistd.h>
​
int dup(int oldfd);
int dup2(int oldfd, int newfd); // (常用)
​
#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <fcntl.h>              /* Obtain O_* constant definitions */
#include <unistd.h>
​
int dup3(int oldfd, int newfd, int flags);

实例代码:打开重定向

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
​
int main(){
​
    // 这里全部屏蔽****                                                 
    // close(0);
    // close(1);
    // close(2);
​
    // 设定子程序的umask
    umask(0000);
    // 打开文件,设定选择项,权限设置为666
    int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if(fd < 0){
        perror("open");
    }
​
    // 重定向:本来要打印到显示器的内容,打印到文件中.
    dup2(fd,1);
    fprintf(stdout, "open df:%d\n", fd);
    fflush(stdout);
​
    // 关闭文件.
    close(fd);
​
    return 0;
}

实例代码:追加重定向

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
​
int main(){
​
    // 这里全部屏蔽****
    // close(0);
    // close(1);
    // close(2);
  
    // 设定子程序的umask
    umask(0000);
    // 打开文件,设定选择项,权限设置为666
    // int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);
    int fd = open(FILE_NAME, O_WRONLY | O_CREAT | O_APPEND, 0666);                  
    if(fd < 0){          
        perror("open");  
    }                    
                                                    
    // 重定向:本来要打印到显示器的内容,打印到文件中.
    dup2(fd,1);                       
    fprintf(stdout, "open df:%d\n", fd);
    fflush(stdout);      
                         
    // 关闭文件.         
    close(fd);           
                         
    return 0;            
}                        

实例代码:输入重定向

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
​
int main(){
​
    umask(0000);
    int fd = open(FILE_NAME, O_RDONLY);
    if(fd < 0){
        perror("open");
    }
​
    // 重定向:将原本键盘输入,改变为文件输入.
    dup2(fd, 0);                                                                   
    char buffer[64] = {0};
    while(1){
        printf("> ");
        if(fgets(buffer, sizeof(buffer), stdin) == NULL) break;
        printf("%s", buffer);
    }
​
    close(fd);
    return 0;
}

在minishell中添加重定向功能

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/stat.h>
​
#define NUM 1024
#define OPT_NUM 64
#define NONE_REDIR   0
#define INPUT_REDIR  1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define TrimSpace(pStart)                       \
        do{                                     \
             while(isspace(*pStart)) pStart++;   \
          }while(0)
​
char lineCommand[NUM];  // 数组buffer.
char* myArgv[OPT_NUM]; // 指针数组buffer.
int lastCode = 0;
int lastSig = 0;
​
int redirType = NONE_REDIR;
char* redirFile = NULL;
​
void CommandCheak(char* lineCommand){
    // 确保指针不是空指针.
    assert(lineCommand);
    // 找到 指向头的指针,指向尾巴的指针.
    char* pStart = lineCommand;
    char* pEnd = pStart + strlen(lineCommand);
​
    // 开始检查用户输入的指令,
    while(pStart < pEnd){
        if(*pStart == '>'){
            
            *pStart = '\0';
            pStart++;
            
            // 这里直接判断是不是追加重定向.
            if(*pStart == '>'){
                redirType = APPEND_REDIR;
                pStart++;
            }
            else{
                redirType = OUTPUT_REDIR;
            }
            TrimSpace(pStart);
            redirFile = pStart;
            break;
            
        }
        else if(*pStart == '<'){
​
            *pStart = '\0';
            pStart++;
            TrimSpace(pStart);
            // 填写重定向信息:
            redirType = INPUT_REDIR; 
            redirFile = pStart;
            break;
        }
        else{
            // 条件不满足,继续往下前进.
            pStart++;
        }         
    }
}
​
int main(){
    // 循环shell.
    while(1){
        /* 初始化 */
        redirFile = NULL;
        redirType = NONE_REDIR;
​
        /* 模式输出shell提示符: */
        printf("用户名@主机名 当前路径#:");
        fflush(stdout);     // 立即刷新.
​
        /* 获取用户输入,例如:ls -a -l 这种 */
        const char* s = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
        assert(s != NULL);
        (void)s;
        /* 打印发现会有两个\n - 这里需要进行处理一下 */
        lineCommand[strlen(lineCommand) - 1] = 0;
​
#if 0
        printf("%s\n", lineCommand);
#endif 
        
        // 命令检查.
        CommandCheak(lineCommand);
​
        /* 字符获取完成,需要进行分割 */
        myArgv[0] = strtok(lineCommand, " ");
​
        int i = 1;
        // ls 特殊处理.
        if(myArgv[0] != NULL && strcmp(myArgv[0], "ls") == 0){
            myArgv[i++] =(char*)"--color=auto";
        }
        
        while(myArgv[i++] = strtok(NULL, " "));
​
        // cd 特殊处理.
        if(myArgv[0] != NULL && myArgv[1] && strcmp(myArgv[0], "cd") == 0){
            chdir(myArgv[1]);
            continue;
        }
​
        // echo 特殊处理.
        if(myArgv[0] != NULL && myArgv[1] != NULL && strcmp(myArgv[0], "echo") == 0){
            if(strcmp(myArgv[1], "$?") == 0){
                printf("%d %d\n", lastCode, lastSig);
            }
            else{
                printf("%s\n",myArgv[1]);
            }
        }
​
#if 0 
        i = 0;
        while(myArgv[i] != NULL){
            printf("%s\n", myArgv[i]);
            i++;
        }
#endif 
​
        /* 子程序执行对应的命令 */
        pid_t id = fork();
        assert(id != -1);
        
        // 子程序 - 执行.
        if(id == 0){
​
            // 因为命令是子程序执行的,真正重定向的工作一定是子程序来完成的.
            // 如何重定向,是父进程要给子程序提供信息.
            // 重定向不会影响父进程.
            switch(redirType){
                case NONE_REDIR:
                    // 这里什么都不需要做.
                    break;
                case INPUT_REDIR:
                    {
                        // 打开文件.
                        int fd = open(redirFile,O_RDONLY);
                        if(fd < 0){
                            perror("open");
                            exit(2);
                        }
                        // 重定向文件更改.
                        dup2(fd, 0);
                    }
                    break;
​
                case OUTPUT_REDIR:
                case APPEND_REDIR:
                    {
                        // 去掉系统的umask.
                        umask(0000);
                        int flags = O_WRONLY | O_CREAT;
                        if(redirType == APPEND_REDIR){
                            flags |= O_APPEND;
                        }
                        else{
                            flags |= O_TRUNC;
                        }
​
                       // 打开文件.
                       int fd = open(redirFile, flags, 0666);
                       if(fd < 0){
                            perror("open");
                            exit(2);
                       }
                       dup2(fd, 1);  
                    }
                    break;
​
                default:
                    printf("Bug?\n");
                    break;
            }
​
​
            execvp(myArgv[0], myArgv);
            exit(1);
        }
​
        /* 父进程监控 */
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        if(ret > 0){
            (void)ret;
            lastCode = ((status >> 8) & 0xFF);
            lastSig = (status & 0x7F);
        }
    }
    
    return 0;
}

缓冲区

int main(){
​
    // 打印信息:C函数的接口.
    printf("Hello Printf!\n");
    fprintf(stdout, "Hello Fprintf!\n");
    fputs("Hello Fputs!\n", stdout);
​
    // 打印信息,系统的接口.
    const char* msg = "Hello System Write!\n";     
    write(1, msg, strlen(msg));
​
    return 0;
}
// 现象:
[ShaXiang@VM-8-14-centos C_LessonExercise]$ ./myShell       // 打印4次
Hello Printf!
Hello Fprintf!
Hello Fputs!
Hello System Write!
[ShaXiang@VM-8-14-centos C_LessonExercise]$ ./myShell  > log.txt
[ShaXiang@VM-8-14-centos C_LessonExercise]$ cat log.txt 
Hello System Write!                                         // 打印4次
Hello Printf!
Hello Fprintf!
Hello Fputs!
int main(){
​
    // 打印信息:C函数的接口.
    printf("Hello Printf!\n");
    fprintf(stdout, "Hello Fprintf!\n");
    fputs("Hello Fputs!\n", stdout);
​
    // 打印信息,系统的接口.
    const char* msg = "Hello System Write!\n";     
    write(1, msg, strlen(msg));
    
    fork();
    return 0;
}
// 现象
[ShaXiang@VM-8-14-centos C_LessonExercise]$ ./myShell 
Hello Printf!
Hello Fprintf!
Hello Fputs!
Hello System Write!
[ShaXiang@VM-8-14-centos C_LessonExercise]$ ./myShell  > log.txt 
[ShaXiang@VM-8-14-centos C_LessonExercise]$ cat log.txt 
Hello System Write!
Hello Printf!
Hello Fprintf!
Hello Fputs!
Hello Printf!
Hello Fprintf!
Hello Fputs!

理解缓冲区

缓冲区就是一段内存空间。

  • 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。

  • printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据 的缓冲方式由行缓冲变成了全缓冲。

  • 而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后但是进程退出之后,会统一刷新,写入文件当中。

  • 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的 一份数据,随即产生两份数据。

  • write 没有变化,说明没有所谓的缓冲 。

综上: printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区, 都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。 那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统 调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是 C,所以由C标准库提供 。

int main(){
​
    // 打印信息:C函数的接口.
    printf("Hello Printf!\n");
    fprintf(stdout, "Hello Fprintf!\n");
    fputs("Hello Fputs!\n", stdout);
    
    // 解释:
    // 代码结束之前,进行创建子进程:
    // 1、如果我们没有进行重定向(>)我们看到了4条消息,sydout默认使用的是行刷新策略,在进程fork之前,三条C语言打印函数将数据进行打印输出,到显示器(外设上),FILE内部就不存在对应的数据了。
    // 2、如果我们进行了重定向(>)写入文件不在是显示器,而是普通的文件,采用的刷新策略是全缓冲,之前的3条C语言函数虽然带了\n,但是不足以将stdout缓冲区写满,数据并没有进行刷新。在执行fork()的时候,stdout属于父进程,创建子进程的时候,紧接着就是进程的退出,谁先退出,一定要进行缓冲区刷新(就是修改)。这是执行写时拷贝,数据最终会显示两份。
    // write为什么没有呢?
    // 上面的过程和write无关,write没有FILE,而是使用的fd,就是没有C语言提供的缓冲区。
    
    // 打印信息,系统的接口.
    const char* msg = "Hello System Write!\n";     
    write(1, msg, strlen(msg));
​
    return 0;
}

再次理解缓冲区

模拟C语言实现一个缓冲区的代码来理解:

#include <stdbool.h>                                             
#include <string.h>                                              
#include <sys/types.h>                                           
#include <sys/wait.h>                                            
#include <fcntl.h>                                               
#include <stdlib.h>                                              
#include <errno.h>                                               
#include <unistd.h>                                              
#include <stdlib.h>                                              
                                                                 
#define SIZE 1024                                                
#define SYNC_NOW  1                                              
#define SYNC_LINE 2                                              
#define SYNC_FULL 4                                              
                                                                 
// 自定义文件类型                                                
typedef struct _FILE{                                            
    int _flags;         // 刷新方式.                             
    int _fileno;                                                 
    int _capacity;      // buffer的容量.                         
    int _size;          // buffer的已使用量.                     
    char _buffer[SIZE];                                          
                                                                 
}_FILE;                                                          
                                                                 
                                                                 
// 自定义文件接口.                                               
_FILE* _Open (const char* pathName, const char* mode);           
                                                                 
void _Fwrite(const void* ptr, int num, _FILE* fp);               
                                                                 
void _Fclose(_FILE* fp);                                         
#include "myStdio.h"
​
// 自定义文件接口.
_FILE* _Open (const char* pathName, const char* mode){
​
    // 判断模式处理.
    int flags = 0;
    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{
        // TODO.
    }
​
    // 打开文件接口处理.
    int fd = 0;
    if(flags & O_RDONLY)
        fd = open(pathName, flags);
    else
        fd = open(pathName, flags, 0666);
​
    if(fd < 0){
        printf("测试!\n");
        const char* strErr = strerror(errno);
        write(1, strErr, strlen(strErr));
        return NULL;
    }
    // 文件打开成功,申请文件空间.
    _FILE* fp = (_FILE*)malloc(sizeof(_FILE));
    if(fp == NULL){
        const char* strErr = strerror(errno);
        write(1, strErr, strlen(strErr));
        return NULL;
    }
​
    // 默认初始化.
    fp->_flags = SYNC_LINE;     // 默认设置为行刷新.
    fp->_fileno = fd;
    fp->_capacity = 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_LINE){
​
        if(fp->_buffer[fp->_size - 1] == '\n'){
            write(fp->_fileno, fp->_buffer, fp->_size);
            fp->_size = 0;
        }
​
    }
    else if(fp->_flags == SYNC_FULL){
​
        if(fp->_size == fp->_capacity){
​
            write(fp->_fileno, fp->_buffer, fp->_size);
            fp->_size = 0;
        }
    }
    else{
        // TODO.
    }
}
void _Fflush(_FILE* fp){
​
    if(fp->_size > 0){
        write(fp->_fileno, fp->_buffer, fp->_size);
        fsync(fp->_fileno);
    }
}
​
void _Fclose(_FILE* fp){
​
    _Fflush(fp);
    close(fp->_fileno);
}
#include "myStdio.h"
​
int main(){
   _FILE* pf = _Open("log.txt", "w");
   if(pf == NULL){
       return 1;
   }
​
   const char* msg = "Hello ShaXiang! ";
   int cnt = 10;
   while(1)
   {
        _Fwrite(msg, strlen(msg), pf);
        sleep(1);                              
        if(cnt == 0)
            break;
​
        cnt--;
   }
​
   _Fclose(pf);
​
    return 0;
}
​

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/184411.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++STL入门:string的基本使用小笔记

目录 一.string类简介 二.string类的常用成员接口 1.string类对象的构造函数接口 2. string类对象的容量操作接口 std::string::size std::string::length std::string::empty std::string::clear std::string::resize std::string::reserve 3.string类对象的访问及遍历操作…

【精品】k8s的CKA考题17道解析

目标一:记住命令关键单词 第4道题:scale replicas 第5道题:cordon、uncordon、drain 第8道题:target-port 目标二:完成操作要求 NoSchedule 查看工作节点的健康状态 ,确定集群中有多少节点为 Ready 状态,并且去除包含 NoSchedule 污点的节点。之后将数字写到/opt/repl…

Mybatis-Plus使用指南

1、了解Mybatis-Plus 1.1、Mybatis-Plus介绍 MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 官网&#xff1a;https://mybatis.plus/ 或 https://mp.baomi…

基础IO详解

目录 一、系统文件IO 1.1 open 1.1.1 open的第一个参数 1.1.2 open的第二个参数 1.1.3 open的第三个参数 1.1.4 open的返回值 1.2 close 1.3 write 1.4 read 二、文件描述符 2.1 进程与文件描述符 2.2 文件描述符的分配规则 三、重定向 3.1 自实现重定向原理 3.…

关于电流互感器电流以及采集电路理解

今天看了下零序电流互感器的作用&#xff0c;跟电路互感器的相似&#xff0c;这个相似是对于二次侧的电路检测功能相似&#xff1b;下面来记录下零序电流互感器以及二次侧采样电路&#xff08;1&#xff09;零序电流互感器&#xff0c;主要用于漏电检测从图中我们看到从断路器到…

L1、L2正则化的原理及适用场景

1. L1正则化&#xff0c;也称Lasso回归 1.1 含义 权值向量 中各元素的绝对值之和&#xff0c;一般记作 。 1.2 公式表示 添加了L1正则化的损失函数一般可表示为&#xff1a; 1.3 作用 L1正则常被用来解决过拟合问题&#xff1b; L1正则化容易产生稀疏权值矩阵&#x…

RK3568 LVDS G121EAN01.3屏幕及触摸ILI2511 调试

1: 屏幕的规格书2&#xff1a;RK3568中DTS的配置// SPDX-License-Identifier: (GPL-2.0 OR MIT) /** Copyright (c) 2020 Rockchip Electronics Co., Ltd.https://www.cnblogs.com/chenfulin5/p/12918924.htmlhttps://blog.csdn.net/qq_28515331/article/details/90763875?spm…

[架构之路-93]:《软件架构设计:程序员向架构师转型必备》-3-软件架构设计中的视图View

前言&#xff1a;同一个软件系统&#xff0c;从不同的视角View&#xff0c;得到不同的视觉和感受。所有的视角得到的视觉感受综合而成了整个系统的架构。有些视角是用眼睛看&#xff0c;有些视角是耳朵听&#xff0c;有些视角用探测器探。不同的人&#xff0c;视角不同&#xf…

【算法】冒泡排序算法原理及实现

1.什么是冒泡排序 冒泡排序&#xff08;Bubble Sort&#xff09;&#xff0c;它是一种最为基础的交换排序。之所以叫冒泡排序&#xff0c;是因为这一种排序算法的每一个元素可以根据自身的大小&#xff0c;一点点的向着一侧来移动。每一轮都会找到一个最大的数字冒泡到数组数组…

一文探索预训练的奥秘

2022年下半年开始&#xff0c;涌现出一大批大模型的应用&#xff0c;其中比较出圈的当属AI作画与ChatGPT&#xff0c;刷爆了各类社交平台&#xff0c;其让人惊艳的效果&#xff0c;让AI以一个鲜明的姿态**&#xff0c;站到了广大民众面前&#xff0c;让不懂AI的人也能直观地体会…

一刷代码随想录——哈希表

1 理论基础常见的三种哈希结构当我们想使用哈希法来解决问题的时候&#xff0c;我们一般会选择如下三种数据结构。数组set &#xff08;集合&#xff09;map(映射)这里数组就没啥可说的了&#xff0c;我们来看一下set。在C中&#xff0c;set 和 map 分别提供以下三种数据结构&a…

Node.js+Vue.js全栈开发王者荣耀手机端官网和管理后台(三) | 前台页面part

文章目录工具样式概念和SASS样式重置网站色彩和字体定义&#xff08;colors text&#xff09;通用flex布局样式定义常用边距定义&#xff08;margin padding&#xff09;主页框架和顶部菜单首页顶部轮播图片&#xff08;vue swiper&#xff09;使用精灵图片&#xff08;sprite&…

【ThreeJs 初学习】基本API的使用方式

基本API的使用方式 根据官网的文档整理出一份API文档, 地址是&#xff1a;ThreeJs 官网文档&#xff0c;其目的还是为了方便查阅 下列代码源码地址 // 此处表示导入three import * as THREE from three;// 1. 创建一个场景 const scene new THREE.Scene();// 2. 创建一个相机…

文献阅读:Language Models are Unsupervised Multitask Learners

文献阅读&#xff1a;Language Models are Unsupervised Multitask Learners 1. 内容介绍2. 模型介绍3. 实验结果 1. 语言模型2. QA & 常识推断3. 生成任务 4. 总结 & 思考 文献链接&#xff1a;https://cdn.openai.com/better-language-models/language_models_are_u…

python-布隆过滤器

在学习redis过程中提到一个缓存穿透的问题&#xff0c; 书中参考的解决方案之一是使用布隆过滤器&#xff0c; 那么就有必要来了解一下什么是布隆过滤器。在参考了许多博客之后&#xff0c; 写个总结记录一下。 一、布隆过滤器简介 什么是布隆过滤器&#xff1f; 本质上布隆…

橘子学docker01之基本玩法

docker docker镜像集成了最核心需要得环境&#xff0c;所以占空间小&#xff0c;运行快&#xff0c;启动秒级。 docker的几个概念&#xff1a; 注册中心&#xff1a;相当于超级码头&#xff0c;上面放的就是集装箱。 镜像&#xff08;image&#xff09;&#xff1a;集装箱,好比…

Spring Boot学习之Dubbo+Zookeeper初识

文章目录一 分布式理论基础知识1.1 单一应用架构1.2 垂直应用架构1.3 分布式服务架构1.4 流动计算架构1.5 PRC[Remote Procedure Call]二 Dubbo2.1 Dubbo简介三 Dubbo环境搭建3.1 Zookeeper简介3.2 Zookeeper下载与安装3.3 解决问题3.3.1 错误一的分析和解决3.3.2 错误二的分析…

JavaEE day8 初识HTTP

HTTP协议 HTTP协议&#xff0c;又称超文本传输协议&#xff0c;是一种应用广泛的应用层协议。所谓超文本&#xff0c;其实就是除了文本还能传输其他资源。而HTTP本身是基于传输层的TCP协议实现的。目前HTTP协议3版本已经在完善中。本文采用1.1版本。 它是一种请求--响应的工作…

MyBatis 持久层框架详细解读:Mapper代理开发

文章目录1. 前言2. Mapper 代理开发3. 过程剖析4. 总结1. 前言 前面在 MyBatis 快速入门篇中&#xff0c;我们使用了 MyBatis 原生的开发方式操作数据库&#xff0c;解决了 JDBC 操作数据库时的硬编码和操作繁琐的问题。实际上&#xff0c;在 Java 项目中&#xff0c;我们更常…

MVC和MVVM的区别

一、MVC mvc&#xff1a;是一种代码架构设计模式&#xff0c;前端中的mvc最主要的作用就是将视图和数据模型进行分离 &#xff08;1&#xff09; 为什么需要 MVC 简单理解&#xff1a;也就是为什么需要将视图和数据模型进行分离 <select id"drinkSelect">&…