• Linux-6-基础IO


    前言

    Vue框架:Vue驾校-从项目学Vue-1
    算法系列博客友链:神机百炼

    C语言文件操作函数:

    一表总结:

    • C语言属于用户层,只能通过文件指针FILE*来访问和操作文件
    函数功能适用流
    fgetc字符输入函数所有输入流
    fputc字符输出函数所有输出流
    fgets文本行输入函数所有输入流
    fputs文本行输出函数所有输出流
    scanf
    fscanf格式化输入函数所有输入流
    printf
    fprintf格式化输出函数所有输出流
    fread二进制输入文件
    fwrite二进制输出文件
    sscanf将其他类型数据,转化为格式化数据
    sprintf将格式化数据,转化为其他类型数据

    文件打开关闭:

    fopen():

    • 作用:打开指定路径下的文件,返回对该文件的指针
    • 参数:
      1. 目标文件路径
      2. 宏定义好的打开方式mode
    • 返回值:
      1. 成功:新打开文件的指针
      2. 失败:NULL
    • 打开方式:通过携带不同参数mode,实现创建编辑/追加编辑/只读
    #include 
    FILE *fopen(const char *path, const char *mode);
    
    /*
    r:只读已存在的文件
    r+:读写已存在的文件
    rb:只读已存在的二进制文件
    rb+:读写已存在的二进制文件
    
    w:只写,创建/覆盖文件
    w+:可读可写,创建/覆盖文件
    wb:只写,创建/覆盖二进制文件
    wb+:可读可写,创建/覆盖二进制文件
    
    a:只写,创建/追加文件
    a+:可读可写,创建/追加文件
    ab:只写,创建/追加二进制文件
    ab+:可读可写,创建/追加二进制文件
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 实例:
    #include 
    int main(){
     	FILE *fp = fopen("文件主干名.文件后缀","打开方式");
     	
    	if(fp == NULL){
    	    perror("fopen失败");
    	    return -1;
    	}
    	
    	fclose(fp);
    	fp = NULL;				//关闭文件后记得将指针指向NULL
     	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    fclose():

    • 作用:关闭文件指针所指向的文件
    • 参数:FILE*文件指针
    • 实例:
    #include 
    int main(){
    	FILE* fp = fopen("文件路径",mode);
    	
    	if(fp == NULL){
    		perror("fopen失败");
    		return -1;
    	}
    
    	fclose(fp);
    	fp = NULL;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    三大标准文件指针

    标准输入流:

    • stdin:对应设备为键盘

    标准输出流:

    • stdout:对应设备为显示器

    标准错误流:

    • stderr:对应设备为显示器

    文件写入:

    fputs():

    • 作用:向指定文件输入内容
    • 适用情况:向文件写入字符串
    • 参数:输入内容的字符串指针 & 指定文件的文件指针
    • 实例:
    #include 
    int main(){
    	FILE* fp = fopen("文件路径",mode);
    	if(fp == NULL){
    		perror("fopen失败");
    		return -1;
    	}
    	fputs("hello fputs\n", stdout);
    	fputs("hello fputs\n", fp);
    	fclose(fp);
    	fp = NULL;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    fprintf():

    • 作用:按照指定格式将内容输入到目标文件中
    • 适用情况:多用于将结构体内字段输入到文件中
    • 参数:文件指针,格式,具体内容
    • 实例:
    #include 
    struct test{
    	char a;
    	int b;
    	double c;
    };
    int main(){
    	struct test t = {'1', 2, 3};
    	FILE* fp = fopen("文件路径",mode);
    	if(!fp){
    		perror("fopen失败");
    		return -1;
    	}
    	fprintf(fp,"%c %d %lf",t.a, t.b, t.c);
    	fclose(fp);
    	fp = NULL;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    fwrite():

    • 作用:从某地址开始,将指定大小字节数据写入目标文件
    • 适用情况:文件传输
    • 参数:内容地址,每个元素大小,元素个数,文件指针
    • 返回值:比较写入元素个数和目标元素个数
      1. 写入成功完成:元素个数
      2. 写入失败:报错
    • 实例:
    #include 
    #include 
    int main(){
        FILE *fp = fopen("路径","打开方式");
        if(!fp){							
    		perror("文件打开失败\n");
    		return -1;
    	}
    	
    	const char *msg = "fwrite()用法示范";
        for(int i=0; i<5; i++){
    		fwrite(msg, 1, strlen(msg), fp);
        }
        
        fclose(fp);
        fp = NULL;
        return 0;	//exit()自带流关闭
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    文件读取:

    fgets():

    • 作用:读取文件中指定字节数内容
    • 参数:结果保存的位置,读取的字节数,文件指针
    • 实例:
    #include 
    int main(){
    	FILE* fp = fopen("文件路径",mode);
    	if(fp == NULL){
    		perror("fopen失败");
    		return -1;
    	}
    	char arr[20];
    	fgets(arr, 20, fp);	//从文件中读取20个字符
    	printf("%s\n", arr);
    
    	fgets(arr, 20, stdin);	//从键盘读取20个字符
    	printf("%s\n", arr);
    
    	fclose(fp);
    	pf = NULL;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    fscanf():

    • 作用:按指定格式从文件中读取数据
    • 适用情况:从文件中读取内容写入结构体变量中
    • 参数:文件指针,输入格式,存储地址
    • 实例:
    #include 
    struct test{
    	char a;
    	int b;
    	double c;
    };
    int main(){
    	struct test t;
    	FILE* fp = fopen("文件路径",mode);
    	if(fp == NULL){
    		perror("fopen失败");
    		return -1;
    	}
    	fscanf(fp, "%c %d %lf", &t.a, &t.b, &t.c);
    
    	fclose(fp);
    	fp = NULL;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    fread():

    • 作用:读取文件中指定字节数的数据
    • 适用情况:文件传输
    • 参数:存储地址&每次读取字节数&读取次数&文件指针
    • 返回值:当次读取的字节数
    • 搭配函数:feof(FILE* fp)查看当前文件是否读取完毕
    • 实例:
    #include 
    #include 
    int main(){
    	FILE* fp = fopen("文件路径",mode);
    	if(!fp){
    		perror("fopen失败");
    		return -1;
    	}
    	
    	char buffer[1024];
    	while(1){
    		ssize_t s = fread(buffer, 1, 24, fp);
    		if(s > 0){
    			buffer[s] = 0;
    			printf("%s\n", buffer);
    		}
    		if(feof(fp)) break;
    	}
    	
    	fclose(fp);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    进程files_struct:

    作用:

    • 问题:由于进程独立性,每个进程打开的文件不同,所以每个进程需要专门创建一个结构来维护其所调用的文件
    • 存储形式:files_struct单独开辟内存空间,由PCB/task_struct中的file*指针来连接
    • fd:文件描述符,本质是file*[]数组的下标
    • fd_array:文件描述符表,本质是文件指针数组
    • 图示:
      files_struct
    • 每个进程的task_struct中*file所指向的files_struct中fd_array数组中0 1 2三个元素都是默认的:
      1. fd_array[0]:标准输入,对应硬件是键盘
      2. fd_array[1]:标准输出,对应硬件是显示器
      3. fd_array[2]:标准错误,对应硬件是显示器

    == printf()不可以视作是fprintf()固定stdout参数,实时上printf()函数输出对象是fd_array[1] ==

    字符文件:

    • 含义:linux下七大文件类型中的字符设备文件,其余六大为普通文件/目录文件/块文件/套接字文件/管道文件/链接文件
    • 原因:这些文件中保存的都是char型数据
      1. scanf()将char通过匹配转化为int double…
      2. printf()将int double…转化为char
    • 举例:
      1. 键盘驱动文件,标准输入流stdin
      2. 显示器驱动文件,标准输出流stdout,标准错误流stderr

    文件操作系统调用接口:

    含义:

    • 系统调用接口比C语言库函数更加底层:
      计算机层次

    接口应用:

    • 系统调用接口比C语言文件操作函数更底层,直接体现在系统调用接口的操作对象是进程中文件描述符表的fd,而C语言文件操作函数的操作对象是文件指针

    open():

    用法:
    • 作用:打开指定路径下文件,将文件指针装填进入进程fd_array[]中
    • 参数:文件路径,打开方式,创建权限
    • 返回值:
      1. 打开成功:当前文件指针在文件描述符表中的下标
      2. 打开失败:-1
    • 函数说明:
    //创建文件时需要赋予权限
    int open(const char *pathname, int flags, mode_t mode);
    //pathname:路径名
    /*flag:打开模式
    	O_RDONLY: 只读打开
     	O_WRONLY: 只写打开
     	O_RDWR : 读,写打开
     	这三个常量,必须指定一个且只能指定一个
     	O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
     	O_APPEND: 追加写
     	这三个变量,可以不指定,但是必须搭配前三个使用
    */
    //mode:要赋予该文件的权限,还要在代码中搭配unmask(减去的权限)使用
    
    //打开已有文件时不需要赋予权限
    int open(const char *pathname, int flags);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 实例:
    #include 
    #include
    #include
    #include
    int main(){
    	umask(0);				//不做权限删除
    	int fd = open("myfile", O_WRONLY|O_CREAT, 0644);	//当前文件夹下创建后写入
    	if(fd < 0){
    		 perror("open失败");
    		 return 1;
     	}
     	close(fd);
     	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    内存角度理解:
    1. 将指定路径下的文件及其相关信息结合为一个file结构体,存储在内存中
    2. 在进程file*所指向的files_struct的fd_array[]中加入该file结构体的文件指针
    与fopen()区别:
    1. fopen()是用户层,对系统调用接口open()做了进一步封装
    2. fopen()返回值为文件指针,open()返回值为fd
    3. open()打开文件,但是不维护文件读写缓冲区。fopen()打开文件,且维护对应读写缓冲区

    close():

    用法:
    • 参数:文件描述符
    • 作用:关闭进程对应fd_array[]中指定的fd项对应文件
    • 实例:
    #include 
    #include 
    #include 
    #include 
    int main(){
    	umask(0);				//不做权限删除
    	int fd = open("myfile", O_WRONLY|O_CREAT, 0644);	//当前文件夹下创建后写入
    	if(fd < 0){
    		 perror("open失败");
    		 return 1;
     	}
     	close(fd);
     	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    与fclose()区别:
    1. fclose()是用户层,对系统调用接口close()进行了封装
    2. close()关闭文件但不维护文件读写缓冲区,fclose()关闭文件且维护文件读写缓冲区

    write():

    • 作用:向fd_array[]中指定fd对应文件中写入内容

    • 参数:fd,内容字节数,内容指针

    • 返回值:本次写入数据的字节数

    • 举例:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(){
    	umask(0);
    	int fd = open("myfile", O_WRONLY|O_CREAT, 0644);
    	if(fd < 0){
    		 perror("open");
    		 return 1;
     	}
     	
     	const char *msg = "hello world!\n";
     	for(int i=0; i<5; i++){
     		write(fd, msg, strlen(msg));
            //fd:被读文件描述符。msg:要写入内容首地址。strlen():每次读取的字节数
     	}
     	
     	close(fd);
     	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    read():

    • 作用:读取fd_array[]中指定fd对应文件中的内容
    • 参数:fd,内容存储地址,内容大小
    • 返回值:
      1. 未读取完:本次读取内容的字节数
      2. 读取完成:0
    • 举例:
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(){
    	int fd = open("myfile", O_RDONLY);					
    	if(fd < 0){
    		perror("open");
    		return 1;
    	}
    	
     	const char *msg = "hello world!\n";
     	char buf[1024];
     	while(1){
     		ssize_t s = read(fd, buf, strlen(msg));
     		if(s > 0){
     			printf("%s", buf);
     		}else{
     			break;
     		}
     	}
     	
     	close(fd);
     	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    重定向:

    fd分配规则:

    最小分配:

    	1. 背景:每个进程的files_struct中的fd_array[]前三者已经被填充完:标准输入流 / 标准输出流 / 标准错误流
    	2. 分配规则:进程每通过open()打开一个文件时,选取fd_array[]中**没有对应file且下标最小的值作为访问该文件的下标**
    
    • 1
    • 2

    实例证明:

    • 直接打开一个文件,其fd值为3:
    #include 
    #include 
    #include 
    #include 
    int main(){
    	int fd = open("文件路径",O_RDONLY);
    	if(fd<0){
    		perror("open");
    		return 1;
    	}
    	printf("fd: %d\n", fd);
    	close(fd);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 关闭掉stdin/stdout/stderr,新打开的文件fd为0/1/2:
    #include 
    #include 
    #include 
    #include 
    int main(){
     	close(0);
     	int fd = open("myfile", O_RDONLY);
     	if(fd < 0){
     		perror("open");
     		return 1;
     	}
     	printf("fd: %d\n", fd);
     	close(fd);
     	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    重定向的含义:

    • 前提:

      1. 进程通过task_struct中file*指针所指向的files_struct中的文件指针数组fd_array[fd]寻找对应文件
      2. 每个进程所创建的file_struct中fd_array数组前三元素默认是 输入流stdin 输出流stdout 错误流stderr
      3. printf()函数输出对象始终是fd_array[1]
    • 重定向:

      1. 含义:将fd_array[]中下标fd和具体文件的对应关系改变
      2. 举例:fd_array[0]原本指向键盘,现在指向具体路径下的某个文件
      3. 实现:
        1. dup2()函数实现重定向
        2. 利用文件描述符fd最小分配机制,close()前面的fd_array[]后实现重定向

    close()实现重定向:

    • 利用fd的最小分配机制:
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(){
     close(1);			//为fd_array[1]重定向
     int fd = open("当前路径下的文件可以直接用文件名", O_WRONLY|O_CREAT, 00644);
     if(fd < 0){
     	perror("open");
     	return 1;
     }
     printf("fd: %d\n", fd);	//输出为1
     fflush(stdout);			//刷新缓冲区
     close(fd);
     exit(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    dup2()实现重定向:

    • dup2()函数作用:
      将fd_array[newfd]和fd_array[oldfd]都指向fd_array[oldfd]所指向的文件
    #include 
    int dup2(int oldfd, int newfd);
    
    • 1
    • 2
    • 实例:通过修改fd_array[1]让printf()实现write()
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(){
      int fd = open("log.txt", O_RDWR|O_CREAT, 00644);
      if(fd < 0) {
        perror("open失败");
        return -1;
      }
      close(1);
      dup2(fd,1);
      while(1){
        char msg[1024];
        ssize_t len =  read(0, msg, sizeof(msg)-1);
        if(len < 0){
          perror("read结束");
          break;
        }
        printf("%s", msg);
        fflush(stdout);
      }
      close(fd);
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 编译运行后查看此时新建的log.txt内容:
      dup2实现printf()的write()

    缓冲区:

    分类:

    • 文件由进程打开,不同进程打开同一文件而缓冲区不同,同一进程打开不同文件而缓冲区亦不同,初步说明缓冲区其实是属于进程地址空间布局内
    • 用户区的缓冲区:
      程序员可操控,如fflush(stdout)
    • 系统内核区的缓冲区:
      程序员不可操控,由OS适时刷新
    • 两者关系:
      缓冲区关系

    建立:

    • 执行open(“路径”,打开方式,权限) / open(“路径”,打开方式)时:
    1. 对应文件内容和文件相关信息结合产生file结构体
    2. 进程对应fd_array内新增file*指针
    3. 进程地址空间内新增文件读写缓冲区

    缓冲方式:

    • 无缓冲:所有输入直接输出到指定文件

    • 行缓冲:

      1. 适用情况:打印到屏幕
      2. 刷新时刻:遇到\n 或 进程return
    • 全缓冲:

      1. 适用情况:写入到文件
      2. 刷新时刻:写入完毕 / fflush(stdout) / 进程return

    缓冲区维护:

    • 写入方式:
    1. printf()是C语言库函数
    2. fprintf()是C语言库函数
    3. write()是系统调用接口
    • 写一段证明代码,探究文件读写缓冲区是由OS还是用户层的C语言维护:
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    int main(){
    	printf("C语言printf函数\n");
    	fprintf(stdout, "%s\n", "C语言fprintf函数");
    	char* msg = "系统调用write接口\n";
    	write(1, msg, strlen(msg));
    	fork();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 分别采用打印行刷新和文件写入全刷新的方式进行打印:
      刷新
      发现行刷新时虽然有子进程,但是只打印了一次;全刷新时虽然有子进程,但是只有C语言函数写入了两次
    • 分析父子进程的写时拷贝:
    1. 写时拷贝前:
      写时拷贝前

    2. 写时拷贝后:
      写时拷贝后

    3. 过程分析:

      1. 父进程和子进程初始享有的程序/数据/files_struct/文件缓冲区一致
      2. 向屏幕打印时,printf() & fprintf()写入缓冲区后遇到\n,缓冲区内数据清空,转移到stdout内;子进程运行到return时需要清空缓冲区,关闭输入输出流,不论是否写时拷贝,此时缓冲区已经为空
      3. 向文件输出时,printf() & fprintf()写入缓冲区后等待手动fflush()或自动exit() return,此时缓冲区内保留数据,子进程运行到return时需要清空缓冲区,发生写时拷贝,子进程创建出了自己的缓冲区,且其中数据和父进程一致,父子进程return前清空各自缓冲区,关闭各自输入输出流
      4. 系统调用接口write()内数据未经过缓冲区,直接写入了文件中,说明缓冲区是由用户层的C语言创建和维护使用的
  • 相关阅读:
    docker和K8S环境xxl-job定时任务不执行问题总结
    Leetcode 移动零 (Javascript)
    8、学习 Java 中的方法(方法的定义、可变参数、参数的传递问题、方法重载、方法签名)通过官方教程
    关于蓝牙人员定位的几个重要问题
    C. Doremy‘s City Construction(思维)
    《MySQL高级篇》五、InnoDB数据存储结构
    网络安全(网络安全)小白自学
    CPU中的MESI协议(Intel)
    gitee上传代码到仓库步骤(最简洁图文+命令说明)
    Jmeter系列-监听器Listeners的介绍(9)
  • 原文地址:https://blog.csdn.net/buptsd/article/details/126482869