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 <stdio.h>
FILE *fopen(const char *path, const char *mode);

/*
r:只读已存在的文件
r+:读写已存在的文件
rb:只读已存在的二进制文件
rb+:读写已存在的二进制文件

w:只写,创建/覆盖文件
w+:可读可写,创建/覆盖文件
wb:只写,创建/覆盖二进制文件
wb+:可读可写,创建/覆盖二进制文件

a:只写,创建/追加文件
a+:可读可写,创建/追加文件
ab:只写,创建/追加二进制文件
ab+:可读可写,创建/追加二进制文件
*/
  • 实例:
#include <stdio.h>
int main(){
 	FILE *fp = fopen("文件主干名.文件后缀","打开方式");
 	
	if(fp == NULL){
	    perror("fopen失败");
	    return -1;
	}
	
	fclose(fp);
	fp = NULL;				//关闭文件后记得将指针指向NULL
 	return 0;
}

fclose():

  • 作用:关闭文件指针所指向的文件
  • 参数:FILE*文件指针
  • 实例:
#include <stdio.h>
int main(){
	FILE* fp = fopen("文件路径",mode);
	
	if(fp == NULL){
		perror("fopen失败");
		return -1;
	}

	fclose(fp);
	fp = NULL;

	return 0;
}

三大标准文件指针:

标准输入流:
  • stdin:对应设备为键盘
标准输出流:
  • stdout:对应设备为显示器
标准错误流:
  • stderr:对应设备为显示器

文件写入:

fputs():

  • 作用:向指定文件输入内容
  • 适用情况:向文件写入字符串
  • 参数:输入内容的字符串指针 & 指定文件的文件指针
  • 实例:
#include <stdio.h>
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;
}

fprintf():

  • 作用:按照指定格式将内容输入到目标文件中
  • 适用情况:多用于将结构体内字段输入到文件中
  • 参数:文件指针,格式,具体内容
  • 实例:
#include <stdio.h>
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;
}

fwrite():

  • 作用:从某地址开始,将指定大小字节数据写入目标文件
  • 适用情况:文件传输
  • 参数:内容地址,每个元素大小,元素个数,文件指针
  • 返回值:比较写入元素个数和目标元素个数
    1. 写入成功完成:元素个数
    2. 写入失败:报错
  • 实例:
#include <stdio.h>
#include <string.h>
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()自带流关闭
}

文件读取:

fgets():

  • 作用:读取文件中指定字节数内容
  • 参数:结果保存的位置,读取的字节数,文件指针
  • 实例:
#include <stdio.h>
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;
}

fscanf():

  • 作用:按指定格式从文件中读取数据
  • 适用情况:从文件中读取内容写入结构体变量中
  • 参数:文件指针,输入格式,存储地址
  • 实例:
#include <stdio.h>
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;
}

fread():

  • 作用:读取文件中指定字节数的数据
  • 适用情况:文件传输
  • 参数:存储地址&每次读取字节数&读取次数&文件指针
  • 返回值:当次读取的字节数
  • 搭配函数:feof(FILE* fp)查看当前文件是否读取完毕
  • 实例:
#include <stdio.h>
#include <string.h>
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;
}

进程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);
  • 实例:
#include <stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
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. 将指定路径下的文件及其相关信息结合为一个file结构体,存储在内存中
  2. 在进程file*所指向的files_struct的fd_array[]中加入该file结构体的文件指针
与fopen()区别:
  1. fopen()是用户层,对系统调用接口open()做了进一步封装
  2. fopen()返回值为文件指针,open()返回值为fd
  3. open()打开文件,但是不维护文件读写缓冲区。fopen()打开文件,且维护对应读写缓冲区
close():
用法:
  • 参数:文件描述符
  • 作用:关闭进程对应fd_array[]中指定的fd项对应文件
  • 实例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl>
int main(){
	umask(0);				//不做权限删除
	int fd = open("myfile", O_WRONLY|O_CREAT, 0644);	//当前文件夹下创建后写入
	if(fd < 0){
		 perror("open失败");
		 return 1;
 	}
 	close(fd);
 	return 0;
}
与fclose()区别:
  1. fclose()是用户层,对系统调用接口close()进行了封装
  2. close()关闭文件但不维护文件读写缓冲区,fclose()关闭文件且维护文件读写缓冲区
write():
  • 作用:向fd_array[]中指定fd对应文件中写入内容

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

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

  • 举例:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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;
}
read():
  • 作用:读取fd_array[]中指定fd对应文件中的内容
  • 参数:fd,内容存储地址,内容大小
  • 返回值:
    1. 未读取完:本次读取内容的字节数
    2. 读取完成:0
  • 举例:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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;
}

重定向:

fd分配规则:

最小分配:

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

实例证明:

  • 直接打开一个文件,其fd值为3:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(){
	int fd = open("文件路径",O_RDONLY);
	if(fd<0){
		perror("open");
		return 1;
	}
	printf("fd: %d\n", fd);
	close(fd);
	return 0;
}
  • 关闭掉stdin/stdout/stderr,新打开的文件fd为0/1/2:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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. 进程通过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 <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
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);
}

dup2()实现重定向:

  • dup2()函数作用:
    将fd_array[newfd]和fd_array[oldfd]都指向fd_array[oldfd]所指向的文件
#include <unistd.h>
int dup2(int oldfd, int newfd);
  • 实例:通过修改fd_array[1]让printf()实现write()
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
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;
}
  • 编译运行后查看此时新建的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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(){
	printf("C语言printf函数\n");
	fprintf(stdout, "%s\n", "C语言fprintf函数");
	char* msg = "系统调用write接口\n";
	write(1, msg, strlen(msg));
	fork();
	return 0;
}
  • 分别采用打印行刷新和文件写入全刷新的方式进行打印:
    刷新
    发现行刷新时虽然有子进程,但是只打印了一次;全刷新时虽然有子进程,但是只有C语言函数写入了两次
  • 分析父子进程的写时拷贝:
  1. 写时拷贝前:
    写时拷贝前

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

  3. 过程分析:

    1. 父进程和子进程初始享有的程序/数据/files_struct/文件缓冲区一致
    2. 向屏幕打印时,printf() & fprintf()写入缓冲区后遇到\n,缓冲区内数据清空,转移到stdout内;子进程运行到return时需要清空缓冲区,关闭输入输出流,不论是否写时拷贝,此时缓冲区已经为空
    3. 向文件输出时,printf() & fprintf()写入缓冲区后等待手动fflush()或自动exit() return,此时缓冲区内保留数据,子进程运行到return时需要清空缓冲区,发生写时拷贝,子进程创建出了自己的缓冲区,且其中数据和父进程一致,父子进程return前清空各自缓冲区,关闭各自输入输出流
    4. 系统调用接口write()内数据未经过缓冲区,直接写入了文件中,说明缓冲区是由用户层的C语言创建和维护使用的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

starnight531

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值