Linux基础IO(上):文件理解与文件操作


理解“文件”

狭义理解

  • 文件存储在磁盘里
  • 因为磁盘是永久性的存储介质,所以文件在磁盘上的存储是永久性的
  • 磁盘是外设(即是输出设备也是输入设备)
  • 因此,对磁盘的文件的所有操作,都属于是对外设的输入(I)或输出(O)

广义理解

Linux下一切皆文件,对于键盘、显示器、网卡、磁盘……都可以将它们抽象化为文件。
后面我们会体会出来。

共识原理

  1. 文件 = 内容 + 属性
  2. 文件分为打开的文件未打开的文件
  3. 打开的文件:谁打开呢?进程打开!因此,我们本质上是研究进程和文件的关系!
    • 根据冯诺依曼体系结构,文件被打开,必须先被加载到内存
    • 一个进程,可以打开多个文件,进程和文件是1对n的关系
    • 操作系统内部,一定存在大量被打开的文件,OS要不要对这些打开的文件进行管理呢?怎么管理?
      先描述,再组织!, 再OS内核中,一个被打开的文件都必须有自己的文件打开对象(struct XXX),包含文件的很多属性和指向下一个被打开文件的指针(struct XXX *next
  4. 未打开的文件:放在哪呢?在磁盘上。我们最需要关注什么问题呢?
    • 未打开的文件非常多,文件如何被分门别类的存储好?
    • 如何快速的对文件进行增删查改,快速的找到文件?
      这里的问题,在后续讲解文件系统时,会揭晓。

注意:

  • 对于0KB的空文件,也是占用磁盘空间的
  • ⽂件的读写本质不是通过C语⾔/C++/Java等语言的库函数来操作的,这些库函数本质是通过对⽂件相关的系统调⽤接⼝封装来实现的。

回顾C语言文件接口

在学习 系统级文件操作 前,需要先回顾一下 C语言 中的文件操作

文件打开

输入指令:man 3 fopen,即可查看man手册

FILE *fopen(const char *path, const char *mode);
  • 目标文件名(参数1)
    • 直接使用文件名,默认当前路径下的文件
      • 进程打开该文件时,如何得知当前路径?
      • 进程的cwd存储了当前路径,如果我们更改了当前进程的cwd,就可以把打开或新建文件到其他目录
    • 如果想指定目录存放,可以使用绝对路径
  • 打开方式(参数2)
    • w只写,如果文件不存在,会新建文件,文件写入前,会清空内容
    • a追加,如果文件不存在,会新建文件,在文件末尾,对文件进行追加写入,追加前不会清空内容
    • r只读,打开已存在的文件进行读取,若文件不存在,会打开失败
    • w+a+r+读写兼具,r+不会新建文件

注意:若文件打开失败,会返回NULL,可以在打开后判断一下是否成功

文件关闭

文件打开并使用后需要关闭,就像动态内存申请后需要释放一样

int fclose(FILE *fp);

关闭已打开文件,只需通过 FILE* 指针进行操作即可

注意:只能对已打开的文件进行关闭,若文件已关闭或不存在,会报错,就像动态内存申请后只能释放一次一样

文件写入

C语言的文件写入接口有:

//写入一个字符
int fputc ( int character, FILE * stream );
//行写入,写入一个字符串
int fputs ( const char * str, FILE * stream );
//格式化写入
int fprintf(FILE *stream, const char *format, ...);
//块写入,可以一次性写入大批数据
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );

int snprintf ( char * s, size_t n, const char * format, ... );

这里主要介绍一下过去我们不常用的两个接口:fwritesnprintf

  • 对于fwrite
    • 参数1:要写入数据的缓冲区的指针
    • 参数2:每个要写入的元素的大小,单位为字节
    • 参数3:要写入的元素个数
    • 参数4:文件指针
    • 返回值:返回实际写入的元素个数(可能小于参数3)

注意:fwrite为二进制格式的写入,也就是说,C语言中的格式符(\0\n\t等)不必传入,因为这个东西C语言认识,但文件并不认识,否则会导致写入我们看不懂的符号。
特别注意这种情况:

#include <stdio.h>
#include <string.h>

int main()
{
	FILE* fp = fopen("log.txt", "w");
	if(fp == NULL) return 1;

	const char* msg = "hello linux!";
	//这里strlen(msg)不能加1,因为文件不认识\0
	fwrite(msg, strlen(msg), 1, fp);
	
	fclose(fp);
}
  • 对于snprintf,其实是sprintf的升级版,添加了读取字符长度的控制,更加安全
    • 参数1:目标字符数组(缓冲区),常写做 buffer 数组
    • 参数2:缓冲区的最大容量(包括终止空字符)
    • 参数3:格式化输入

使用 snprintf 函数写入数据至缓冲区后,可以再次通过 fputs/fwrite 函数,将缓冲区中的数据真正写入文件中

文件读取

读取与写入配套出现

int fgetc ( FILE * stream );

char * fgets ( char * str, int num, FILE * stream );

size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );

int fscanf ( FILE * stream, const char * format, ... );

int sscanf ( const char * s, const char * format, ...);

函数参数与写入差不多,平移使用即可。

如需详细学习更多C文件接口,推荐大佬文章:C语言进阶——文件操作


过度到系统

我们知道,文件是在磁盘上的,磁盘是外部设备,访问磁盘文件其实就是在访问硬件。而用户访问硬件的的途径如图:
在这里插入图片描述
上文回顾的C文件接口就是用户操作接口层的libc,而库函数是通过调用系统调用接口,让操作系统来获取硬盘中的文件并对其进行操作。

认识Linux系统级文件操作

现在我们知道,各类语言的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);	//可以修改文件权限
  • 返回值:不同于fopen的返回值类型是FILE*,open的返回值是int类型,称为文件描述符( file descriptor ),文件打开失败返回-1。
    看似文件描述符与FILE没有一点关系,其实它们之间有着莫大的关系。文件描述符非常重要,下篇文章我会重点讲解。
  • 参数1pathname 待操作文件名,和fopen一样
  • 参数2flags 打开选项,使用标记比特位的方式传递选项信号,下文会重点讲解
  • 参数3mode权限设置,通常在需要创建文件时使用,文件的起始权限位0666

参数2有点复杂,使用了 位图 的方式进行多参数传递。这种方式甚是玄妙,值的我们去学习这个方法,日后定有大用。
一个int整型,大小为4个字节,就用32个比特位,每个比特位对应一个开关(0为关,1为开),就可以构成32个选项(开关信号),如图:
在这里插入图片描述
利用这个特性,我们来写一个关于位图的小demo

位图demo
#include <stdio.h>
#include <stdlib.h>

#define ONE 1//标记低第1位
#define TWO 2//标记低第2位
#define THREE 4//标记低第3位

void Test(int flags)
{
  //模拟实现三种选项传递
  if(flags & ONE)
    printf("This is one\n");

  if(flags & TWO)
    printf("This is two\n");

  if(flags & THREE)
    printf("This is three\n");
}

int main()
{
  Test(ONE | TWO | THREE);
  printf("-----------------------------------\n");
  Test(ONE);  //位图使得选项传递更加灵活
  return 0;
}

在open中,提供了宏,便于我们传参:

 O_RDONLY	//只读
 O_WRONLY	//只写
 O_APPEND	//追加
 O_CREAT	//新建
 O_TRUNC	//清空

C语言 中的 fopen 调用 open 函数,其中的选项对应关系如下:

  • w -> O_WRONLY | O_CREAT | O_TRUNC
  • a -> O_WRONLY | O_CREAT | O_APPEND
  • r -> O_RDONLY
  • 其他

注意

  • 如果文件不存在,open的参数3一定要设置,否则创建出来的文件权限为随机值,不安全。
  • 掩码默认为 0002,我们也可以通过umask()系统调用自定义掩码。

close——关闭文件

close 函数根据文件描述符关闭文件

#include <unistd.h>

int close(int fildes);

write——写入文件

write 函数的返回值类型(ssize_t)有点特殊,但使用方法与 fwrite 基本一致

#include <unistd.h>

ssize_t write(int fildes, const void *buf, size_t nbyte);

ssize_t与size_t的区别:
在这里插入图片描述

read——读取文件

read 读取很淳朴,只支持指定字符数读取

#include <unistd.h>

ssize_t read(int fildes, void *buf, size_t nbyte);


下篇预告:重定向与缓冲区
有错误欢迎指出,万分感谢
创作不易,三连支持一下吧~
不见不散!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值