【linux】——基本的文件操作

目录

文件操作

🍎头文件

🍎打开文件——open      

 🍎关闭文件——close

 🍎write

 🍎read 

 🍎文件描述符

 🍎文件标识符的分配规则

 🍎重定向的原理

  🍎 dup2

 🍎缓冲区


文件操作

我们之前学习的语言库函数的文件操是用户操作接口,是对系统调用接口的封装,当我们调用用户操作接口,本质是向下区调用系统的接口,接下来我们学习的文件操作是系统调用接口。

 为什么要对系统接口进行封装产生我们的语言库呢?

1.首先语言库的使用会比系统调用接口使用较为简单。

2.语言库可以跨平台,语言库可以在linux下运行,也可以在windows下运行,而linux和windows的系统调用是不同的,所以系统调用接口是不能够跨平台的。

🍎头文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>       //定义宏

🍎打开文件——open      

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;

     创建并打开一个文件myfile,并对该文件只进行读,参数的组合需要用 “ | ”间隔开来。


int fd=open("myfile",O_CREAT|O_RDONLY);
  

参数其实本质是宏定义 :

每一个参数对应一个int数字,每个int数字我们把它看成有32个bit位,每个参数都对应占着bit位是1,其他的bit位都是0.

例如:
O_RDONLY: 0000 0000 0000 0000 0000 0000 0000 0001

O_WRONLY:  0000 0000 0000 0000 0000 0000 0000 0010

所以,参数的组合要用" | "来进行或,然后对最终的数值进行判断,只要我们判断相对应的bit位是否为1,就可以判断是否进行要进行相对应的读写。

 🍎关闭文件——close

int close(int fd);

fd:文件描述符

返回值:关闭成功返回0,失败返回-1.

 🍎write

函数定义:ssize_t write (int fd, const void * buf, size_t count); 

函数说明:write()会把参数buf所指的内存写入count个字节到参数fd所指的文件内。 

返回值:如果顺利write()会返回实际写入的字节数(len)。当有错误发生时则返回-1,错误代码存入errno中。

 🍎read 

函数定义:ssize_t read(int fd, void * buf, size_t count);

函数说明:read()会把参数fd所指的文件传送count 个字节到buf 指针所指的内存中。

返回值:返回值为实际读取到的字节数, 如果返回0, 表示已到达文件尾或是无可读取的数据。若参数count 为0, 则read()不会有作用并返回0。 

 🍎文件描述符

在上面的文件接口中,打开文件会返回一个文件描述符,关闭文件描述符也需要通过文件描述符来关闭,那么这个文件描述符到底是什么东西呢?

当我们打开一个文件的时候,操作系统中会在内存中创建一个file结构体变量来描述这个目标文件,这个file结构体变量中包含一个会包含着该文件的各种各样的信息,包括可以找到该文件的地址,正常情况下,文件都是由进程打开的,为了让进程和文件关联起来,每个进程都有一个*file的指针变量,该变量指向的是一张表file_struct,该表中一个指针数组,该数组存储的是该进程中已打开文件的file指针,而文件描述符就是该数组的下标。 进程只要拿到文件描述符就可以通过该数组哪个位置找到相对应的文件。

 文件描述符就是 fd_array[]数组的下标

当我们创建进程的时候,进程会默认打开三个文件:

标准输入,标准输出,标准错误

对应的是:

键盘,显示器,显示器。

对应的文件标识符是

0   1    2

标准输出,标准错误都是显示器,显示器,其实它们在内存中是不同的文件,相当于磁盘上文件被打开了两次。

 🍎文件标识符的分配规则

输出:3

 输出0

由结果我们可以得

文件符分配规则:在file_struct数组当中,找到当前没有被使用的最小数组的下标。

 🍎重定向的原理

进程运行结果是显示器什么都没有显示,而myfile文件上则有hello world的字符串。

为什么会出现这种情况?

此时我们还需要在认识一下c语言的概念?。

在c语言中,文件的类型都是FILE结构体,而在FILE结构体内部里包含着封装着一个文件标识符和缓冲区等文件信息。而c语言中默认打开的是stdout,stdin,stderr也是FILE类型,它们的FILE封装的文件标识符是 0 ,1,2.

printf是c语言中的库函数,它是往stdout上进行输出的,而stdout封装的是文件标识符为1的文件,当我们把1关闭的的文件给关闭掉也就是关闭标准输出,然后我们在打开myfile文件,此时它的file文件指针就填充到文件标识符为1的位置,当printf时,是往文件标识符为1的文件上输入内容,所以此时侯自然就往myfile文件里输出。

本来输出到显示器上,而被显示到了没有myfile文件上。 这就是输出重定向。

输出重定向的原理

修改fd_array数组中某一个位置的内容,使包含文件的file*替换成要要重定向的文件的file*。

  🍎 dup2

既然我们知道缓冲区的原理后 ,系统给了我们一个接口,可以使我们直接对数据重定向。

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

该函数是直接将file_array中的下标为oldfd中内容直接拷贝给下标为newfd的位置的内容。

测试代码:

 

 🍎缓冲区

在c语言中,数据刷新的方式包括无缓冲,行缓冲和全缓冲。

无缓冲:没有缓冲区,数据直接往目标文件上写。

行缓冲:当遇到 '\n'时,会将'\n'前面缓冲区的所有数据刷新到目标文件上,或者程序结束后,缓冲区满了才把数据刷新目标文件上或者fflush主动刷新。

全缓冲:只有当程序结束后或者缓冲区满了才将数据刷新到往目标文件或者fflush主动刷新。

为什么会有缓冲区呢?

学过冯诺依曼体系的同学就知道,内存往外设写数据效率是很低的(文件是磁盘上的,属于外设),如果每次内存中生成一个数据就往文件中输出数据,那么内存往文件上就要跑很多次来回,很明显这效率是很低的。当我们有了缓冲区之后,内存中的数据就可以积累到一定的量之后才往外设上写入,这可以减少数据往外设写入的次数,这可以大大提高数据运输的效率。而为什么会有行缓冲,如果没有行缓冲的话,只有全缓冲的话,那么我们每次刷新数据的时候就会一大片数据刷新到我们的文件或者显示器上,这时候我们就很难去观察数据。所以行缓冲是为了提高效率的同时又便于观察,可以输出少量数据就刷新出来。

在内存中往显示器上输入数据默认是行缓冲,在文件输入数据默认是全缓冲

如果我们将上面的代码fflush给删除掉,我们在在观察现象。 

结果:显示器和文件myfile都没有数据,为什么会这样子呢?这个hello world哪去了呢?

我们用c语言接口输出的时候,需要先将数据输出到c语言的缓冲区,然后再通过fd找到得到文件的file结构体变量,通过file结构体变量就能将c语言缓冲区的内容刷新到文件上。

如下图:

 在上面的代码中,printf是往stdout上输出的,stdout上封装着c语言缓冲区,c语言缓存区会映射到我们的进程的虚拟内存上),所以输入的字符往stdout中c语言缓冲区输入,其中stdout是FILE变量,里面封装着fd=1的文件,然后我们通过系统接口将fd=1指向的file给关闭掉后,此时的stdout中的缓冲区中的数据还在,当进程结束后,stdout中的c语言缓冲区就找不到fd=1的file,所以数据就找不到对应的文件,数据就不能从c语言缓冲区刷新到文件中。

     下面是test.c的代码

当讲程序编译完成形成可执行程序test后,我们执行test并将重定向给输出给myfile。此时我们打开myfile之后,发现c语言的库函数往myfile输出两次,系统调口往myfile输出一次,这是为什么呢

printf和fprintf是c库函数,它们是自带缓冲区,而write是系统调用接口是没有自带缓冲区的 ,而我们说的库函数缓冲区是用户级别的缓冲区的,而系统也有缓冲区,系统的缓冲区是由系统管理的(我们不关心),printf和fprintf是直接将数据输入到c语言的缓冲区,(c语言缓冲区是物理内存中的,然后映射到我们进程的虚拟内存上),创建一个子进程后,此时的子进程和父进程数据(包括缓冲区)在物理内存上是共享的,当其中一个进程结束的时候,则会刷新c语言的缓冲区,时就会被认此为在该缓冲区修改数据,所以系统就会发生写实拷贝,在物理内存中创建一个一样大小的缓冲区,然后通过映射到该进程的虚拟内存上,使父子进程都有一个缓冲区,最后刷新缓冲区的时候相当于刷新两次c语言缓冲区,所以c库函数输出两次。

好啦~,今天的内容就分享到这里了,觉得写的不错的小伙伴麻烦你们动一下你们的小指头,帮忙点个赞或者关注。谢谢你们

  • 13
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值