Linux: 详解基础IO(C库文件操作函数、系统调用文件接口、文件描述符和文件流指针)(图文并茂)(一)


1. C库文件操作函数

1.1 fopen函数

FILE *fopen(const char *path, const char *mode);

参数:

  • path:带路径的文件名称(待打开的文件)
  • mode:打开的模式

返回值:

若打开成功,则返回一个文件流指针,否则就返回一个NULL。

1.2 fread函数

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

参数:

-ptr:要将读到的数据保存在哪里去,ptr会保存用户准备的一个空间(缓冲区)的地址

  • size:表示块的大小,单位为Byte
  • nmemb:块的个数

e.g.:size = 2,nmemb = 5,则读取的字节数为10个Byte。
比较常见的用法就是令size = 1,nmemb则可以指定任意的块

  • stream:文件流指针,表示要从哪里开始读

返回值

返回读到的块的个数

1.3 fwrite函数

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

参数:

  • ptr:要写入到文件当中的内容
  • size:块的大小,单位为Byte
  • nmemb:块的个数

总写入的字节数量 = size * nmemb

  • stream:文件流指针,从哪里开始写

返回值:

返回写入块的个数

1.4 fseek函数

int fseek(FILE *stream, long offset, int whence);

参数:

  • stream:文件流指针,表示需要重新定位文件指针的文件
  • offset:偏移量,单位为Byte,是相对于whence而言
  • whence:重置的文件指针位置,通常有3个选项
  • SEEK_SET:表示重置到文件的头部
  • SEEK_CUR:表示当前文件流指针的位置
  • SEEK_END:表示重置到文件的尾部

1.5 fclose函数

int fclose(FILE *fp);

参数:

fp:需要关闭的文件流指针

返回值:

成功返回0,失败返回EOF

2. 系统调用文件接口

2.1 open函数

int open(const char *pathname, int flags);

int open(const char *pathname, int flags, mode_t mode);

open函数有两个,它们的区别就是当文件存在时用①,当文件不存在时就用②

参数:

  • pathname:待打开的文件
  • flag:是对应打开文件的文件描述符所具有的属性
  • O_RDONLY:表示只读
  • O_WRONLY:表示只写
  • O_RDWR:表示读写

以上三种是互斥的,只能选择一个

  • O_CREAT:当文件不存在时则创建(需要告知权限,用8进制来表示,需使用②)
  • O_TRUNC:截断文件 (清空文件)
  • O_APPEND:追加

以上三种是可以任意组合的,且这些属性的定义都包含在include<fcntl.h>库中

  • mode:权限码,当文件不存在时使用O_CREAT要创建文件时,需要赋予其对应得权限,使用8进制来表示

组合使用的方式:按位或
例如:

//打开test.c文件,若不存在则创建文件,并赋予权限为rw- rw- r--,并且只能写,且每次写的时候都会截断文件(清空)
open("test.c",O_WRONLY|O_CREAT|O_TRUNC,0664) // 0664 - rw- rw- r--

返回值:

返回一个正整数,其含义为文件描述符

在内核源码中查看flag参数中的属性声明

在这里插入图片描述

  • 那么这些属性之间连接为什么要进行按位或呢?

将这些属性按照位图的方式来使用,如果想要某一个属性,则使用按位与的方式,将属性对应的比特位设置为1,通过按位或之后就可以拿到对应的属性值。

int fcntl(int fd, int cmd, ... /* arg */ );

该函数可以获取文件描述符的属性,它会将其属性通过返回值返回回来.

  • 文件描述符:本质上是一个正整数(小整数)

在这里插入图片描述

2.2 read函数

ssize_t read(int fd, void *buf, size_t count);

参数:

  • fd:文件描述符
  • buf:将读到的内容存入 buf 这个缓冲区中
  • count:表示最大能读多少个字节,和 buf 的大小有关,通常需要在 buf 中预留一个 ‘\0’ 的位置

返回值:

  • >0:表示读到的字节数
  • -1 :表示读取失败

2.3 write函数

ssize_t write(int fd, const void *buf, size_t count);

参数:

  • fd:文件描述符
  • buf:写入的内容放在 buf 中
  • count:写入的字节数量

返回值:

  • >0:表示写入文件中真实的字节数量
  • -1:表示写入失败

2.4 lseek函数

off_t lseek(int fd, off_t offset, int whence);

参数:

  • fd:文件描述符
  • offsetwhence参数均和上面写的 fseek函数 中的参数属性一样。

返回值:

成功完成后,lseek()返回结果偏移位置,以偏移量为单位,从文件开头开始。 错误时,值返回-1,并且将errno设置为指示错误。

2.5 close函数

int close(int fd);

参数:

  • fd:待关闭的文件的文件描述符

返回值:

成功返回0,失败返回-1

3.文件描述符和文件流指针

3.1 文件描述符

在说文件描述符之前,我们先来讲讲一个程序在运行时是怎样访问到磁盘上的文件的。这次我们从Linux内核源码上来对其进行刨析。

Linux内核源码的下载与安装,请看:CentOS安装相应版本的内核源码

① 首先,程序在运行时,操作系统为了管理该程序,会产生一个进程来描述它,这时候进程就会产生一个struct task_struct的结构体,即进程控制块(PCB)。

在这里插入图片描述

②在task_struct这个结构体中,具有一个struct files_struct* files的这样一个结构体指针。

在这里插入图片描述
其中,files为指针,并不会指向数组,为的是同进程的线程的 files指向相同 file_struct,这是线程间共享打开的文件的本质。

③而在files_struct这个结构体里,又具有一个struct file __rcu * fd_array[NR_OPEN_DEFAULT]的结构体类型的指针数组。

在这里插入图片描述

④fd_array数组中存放都是一些 struct file __rcu* 。

其中__rcu是一种同步机制,可避免使用锁原语,而多个线程同时读取和更新通过指针链接并属于共享数据结构的元素;对于被RCU保护的共享数据结构,读操作不需要获得任何锁就可以访问,但写操作在访问它时首先拷贝一个副本,然后对副本进行修改,最后在适当的时机把指向原来数据的指针重新指向新的被修改的数据。这个时机就是所有引用该数据的CPU都退出对共享数据的操作。
Linux内核中内存管理大量的运用到了RCU机制。为每个内存对象增加了一个原子计数器用来继续该对象当前访问数。当没有其他进程在访问该对象时(计数器为0),才允许回收该内存。

更为详细请看:READ-COPY-UPDATE

在这里我们不对__rcu做更多的解释,只要把它理解成为是操作系统的一种同步机制就可以了。

因此,fd_array数组中实际存放都是一些 struct file*。而且fd_array数组的下标就是我们所说的文件描述符

在这里插入图片描述
struct file:保存的是一些文件元信息,如文件名称、文件大小、文件所有者、文件权限等等,它通过一个struct inode指针指向磁盘中具体对应的那一块物理内存了,这些,我会在后面在进行详解。

如果感觉关系太乱记不住,没关系,直接看下图:

在这里插入图片描述

fd_array[ ]数组下标的0,1,2号文件描述符是固定的标准输入标准输出标准错误

小结:

  • 文件描述符就是内核当中fd_array[ ]数组的下标,是一个正整数
  • 文件描述符分配的规则为最小占用原则
  • 一个进程最大可以打开100001(默认的),且是可以修改的,使用ulimit -n [修改值]即可

可使用ulimit -a命令查看``
在这里插入图片描述

3.2 文件流指针

同样,这次我们直接从C源码中查看文件流指针FILE到底是个什么。

①我们进入到/user/include/stdio.h文件中对FILE进行查看,发现它是一个struct _IO_FILE类型的结构体

在这里插入图片描述
再往后看,就会发现我们的三个最常见的文件流指针:标准输入(stdin)、标准输出(stdout)、标准错误(stderr)
在这里插入图片描述

②查看struct _IO_FILE,可以发现六个有关读写的指针和一个int _fileno的整型

在这里插入图片描述

  • _IO_read开头的是读缓冲区
  • _IO_write开头的是写缓冲区
  • int _fileno保存了文件描述符的数值

用一个图来总结一下就是:

在这里插入图片描述
和上面的文件描述符对应一下,就可以清晰的看出整个IO的过程
在这里插入图片描述
怎么样,是不是很壮观,这就是一个小小的IO体系图

在进程终止章节的时候,我们提到了刷新缓存区的概念,其中这个缓存区就是C库维护的读写缓冲区。

再探_exit函数:

_exit函数之所以不会刷新缓冲区,是因为_exit是内核代码直接对内核进行操作的,进程一结束,并不会通知上层的C库,因此,C库维护的读写缓冲区再毫无感知的情况下就被关闭了。

3.3 区别

  • 文件流指针是一个结构体,它在内部保存了文件描述符
  • 文件描述符是一个正整数,其含义为fd_array[ ]数组的下标
  • 文件流指针维护了读写缓冲区
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值