Linux基础IO知识点总结

C语言中打开文件的方式

文件使用方式 含义 如果指定文件不存在
“r”(只读) 为了输入数据,打开一个已经存在的文本文件 出错
“w”(只写) 为了输出数据,打开一个文本文件 建立一个新的文件
“a”(追加) 向文本文件尾添加数据 出错
“rb”(只读) 为了输入数据,打开一个二进制文件 出错
“wb”(只写) 为了输出数据,打开一个二进制文件 建立一个新文件
“ab”(追加) 向一个二进制文件尾添加数据 出错
“r+”(读写) 为了读和写,打开一个文本文件 出错
“w+”(读写) 为了读和写,新建一个新的文件 建立一个新的文件
“a+”(读写) 打开一个文件,在文件尾进行读写 建立一个新的文件
“rb+”(读写) 为了读和写打开一个二进制文件 出错
“wb+”(读写) 为了读和写,新建一个新的二进制文件 建立一个新的文件
“ab+”(读写) 打开一个二进制文件,在文件尾进行读和写 建立一个新的文件

C语言文件操作中

  • C默认会打开三个输入输出流,分别是stdin, stdout, stderr
  • 仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型也是文件指针。

系统文件IO

open接口介绍

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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: 追加写。

  • mode:
    参数mode具体指明了使用权限,他通常也会被umask修改,所以一般新建文件的权限为(mode&~umask)。mode的具体参数详见man手册。
    mode只有当在flags中使用O_CREAT时才有效,否则被忽略。

  • 返回值:
    成功:新打开的文件描述符
    失败:-1

  • open 函数具体使用哪个,和具体应用场景相关。如目标文件不存在,使用open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。

write read close lseek类比C文件相关接口

系统调用 和 库函数

  • fopen fclose fread fwrite 都是C标准库当中的函数,是库函数(libc)。而, open close read write lseek 都属于系统提供的接口,是系统调用接口。
  • 库函数都是对系统调用的封装,方便了二次开发。

0 & 1 & 2

  • Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.
  • 0,1,2对应的物理设备一般是:键盘,显示器,显示器
  • 所以输入输出还可以采用如下方式:
char buf[1024];
ssize_t s = read(0, buf, sizeof(buf));
if(s > 0){
	buf[s] = 0;
 	write(1, buf, strlen(buf));
 	write(2, buf, strlen(buf));
}

文件描述符fd

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

文件描述符的分配规则

  • 在files_struct数组当中,找到当前没有被使用的最小的一个下标,即最小未使用,作为新的文件描述符。
  • 如果没有关闭标准输入(0)标准输出(1)标准错误(2)的话,文件描述符从3开始分配。

重定向

 close(1);
 int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
 if(fd < 0){
 	perror("open");
 	return 1;
 }
 printf("fd: %d\n", fd);
 fflush(stdout);

 close(fd);

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

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

  • 归根到底还是文件描述符的分配规则所决定的!!
  • printf是C库当中的IO函数,一般往 stdout 中输出,但是stdout底层访问文件的时候,找的还是fd:1, 但此时,fd:1下标所表示内容,已经变成了myfile的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入,进而完成输出重定向。

使用 dup2 系统调用

#include <unistd.h>
int dup2(int oldfd, int newfd);

使用dup2系统调用便可实现上面代码

 int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
 if(fd < 0){
 perror("open");
 return 1;
 }
 dup2(fd,1);
 printf("fd: %d\n", fd);
 fflush(stdout);

 close(fd);

FILE

  • 因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。
  • 所以C库当中的FILE结构体内部,必定封装了fd
  • 有一个很有意思的代码一起来看看:
#include <stdio.h>
#include <string.h>
int main()
{
 	const char *msg0="hello printf\n";
 	const char *msg1="hello fwrite\n";
 	const char *msg2="hello write\n";
 	printf("%s", msg0);
 	fwrite(msg1, strlen(msg0), 1, stdout);
 	write(1, msg2, strlen(msg2));
 	fork();
 	return 0;
 }
  • 运行结果:
    hello printf
    hello fwrite
    hello write
  • 如果对进程实现输出重定向呢 ./hello > file , 结果变成了:
    hello write
    hello printf
    hello fwrite
    hello printf
    hello fwrite

这是为什么呢?

  • 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。(即见到\n刷新缓冲区)
  • printf fwrite 库函数会自带缓冲区,当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
  • 而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后
  • 但是进程退出之后,会统一刷新,写入文件当中。
  • 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
  • write是系统调用接口, 没有变化,说明没有所谓的缓冲。
  • 库函数的缓冲区是由函数所在的标准库提供。
发布了161 篇原创文章 · 获赞 52 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_42837885/article/details/102307839