当前位置: 首页 > news >正文

[Linux]文件与 fd

一、文件

1.打开文件

文件存储在磁盘中。进程需要读写文件时需要先“打开”文件。

“打开”文件的操作其实就是把文件从磁盘加载到内存。因为进程是在CPU上运行的,而根据冯诺依曼体系,CPU无法直接读取到磁盘内容只能直接读取内存,所以读写文件前需要先“打开”文件。

进程运行时会默认打开三个输入输出流,在C语言中是stdin(标准输入)、stdout(标准输出)、stderr(标准错误)。

2.打印内容到显示器

打印内容到显示器,有以下几种方法:

#include <stdio.h>printf("%s", "aaa");
fputs("bbb", stdin);
fwrite("ccc", 1, 3, stdin);
fprintf(stdin, "ddd");

二、open

除了C语言提供的接口外,我们还可以直接调用系统接口来读、写、创建文件:

1.参数

参数 flags 是一个位图,传入以下的宏:

O_RDONLY、O_WRONLY、O_RDWR、O_CREAT、O_APPEND、O_TRUNC

分别是只读、只写、读写、若文件不存在则创建、追加写入、若文件已存在则清空内容

这些宏只有一个比特位为 1,所以在传参时,可以通过按位或的方式组合起来,用一个参数位置来传入多条信息:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{open("log.txt", O_WRONLY | O_CREAT);return 0;                                                                                                                                            
}

我们运行后发现,log.txt 的权限位不正常,并不是按照一般的默认权限设置的。

这是因为在系统层面,创建文件和调整文件权限分属不同的模块,并没有耦合起来。系统在创建文件的时候,没有办法同时赋予这个文件权限位,所以会出现这种杂乱的权限位的情况。

在需要创建文件的时候,我们一般采用第二个 open 接口,也就是传入 3 个参数,最后一个参数就是在指定文件的权限位。而在只需要读写,不需要创建一个新文件时,采用第一个 open 接口。

#include <sys/types.h>                                                                                                                                   
#include <sys/stat.h>
#include <fcntl.h>int main()
{open("log.txt", O_WRONLY | O_CREAT, 0666);return 0;
}

我们再运行后发现 log.txt 的权限实际是 0664,这是因为系统还用权限掩码 umask 修正了它,导致最终权限是 0666 - 0002 = 0664(实际系统运算过程并非如此,只是等价于这样相减)

#include <sys/types.h>                                                                                                                                   
#include <sys/stat.h>
#include <fcntl.h>int main()
{umask(0);open("log.txt", O_WRONLY | O_CREAT, 0666);return 0;
}

这样我们可以把权限掩码设为 0000,再创建出的 log.txt 的权限就是0666 了。

这里设置的 umask 是本进程的 umask,只会影响本进程,不会影响到整个系统以及其他进程创建的文件。

2.mytouch

我们可以用 open 接口自己实现一个 touch:

3.返回值(文件描述符 fd)

创建失败返回 -1 并立刻设置错误码,创建成功返回一个整型 int,被称为文件描述符。

我们发现我们自己打开的文件,描述符是从 3 开始依次递增的。

没有 0、1、2 是因为,进程启动时会默认启动三个标准输入输出流:stdin、stdout、stderr,他们分别占用了 0、1、2。

三、close、read、write、lseek

1.close

关闭文件描述符对应的文件。

关闭失败返回 -1 并设置错误码 errno,关闭成功返回 0。

2.read

从 fd 代表的文件中,读取 count 字节数据到 buf 指针指向的空间中。

读取失败返回 -1 并设置错误码,读取成功返回读取的字节数。

count 是我们期望读取到的字节数,说明最多会读取到 count 个字节,也可能读不到这么多,所以会有一个返回值,告诉我们实际读入了多少字节。

从标准输入中读取数据到 s 数组中并打印:

输入 abcd 并回车,我们发现果然打印了 abcd。

s[n-1] 中原本存放着读入的 \n,把它置为 \0 是为了补上字符串结束标志并且避免多打一个空行。

如果用 s[n] = '\0' 则 printf 函数中就不需要再加 \n 了,否则会打出两个空行。

3.write

从 buf 指向的数组中,读取 count 个字节,写入到 fd 代表的文件中(从读写偏移量指向的位置开始写入)。

写入失败返回 -1 并设置错误码,写入成功返回写入的字节数。 

在 log.txt 中已存在 helloworld 内容的情况下,用 O_WRONLY 方式打开 log.txt,再用 write 写入字符串 aaa,结果 log.txt 内容是 aaaloworld。

这是因为 O_WRONLY 打开后文件的读写偏移量被设置在文件开头,导致了覆盖式的写入。

如果希望内容只有 aaa,则要在 open 接口中指定用 O_WRONLY | O_TRUNC 方式打开,让 open 接口先清空 log.txt 中的内容。

如果希望内容是 helloworldaaa,则要传入 O_WRONLY | O_APPEND,让 open 把读写偏移量设置到文件末尾。

语言层的 fopen 其实就是对系统调用 open 的封装,fopen 传入的 "w" 参数会转换到 open 中的 O_WRONLY | O_CREAT | O_TRUNC(文件不存在则创建,存在则清空内容)。

我们知道 strlen 函数只会统计字符串的有效字符数,不会计算字符串末尾的 \0 结束标志。那么当向文件中写入字符串时,需不需要用 strlen()+1,把 \0 也写入进去呢?

我们发现 log.txt 文件结尾出现了乱码。

这是因为字符串以 \0 结尾只是 C 语言的规定,和文件毫无关系,写入到文件就会出现不可显示字符,所以向文件中写入字符串的时候,如果使用 strlen 函数来统计字符串大小,是不需要再 +1 多计算 \0 的。

我们在上面 三、2. 中之所以给字符数组 s 补上 \0,是为了使字符串一直符合 C 语言的规范,而不是因为文件写入的要求。

4.lseek

修改文件的读写位置偏移量,fd 是文件描述符,offset 是偏移量(以字节为单位),whence是偏移量对应的参考值(填入宏:SEEK_SET、SEEK_CUR、SEEK_END)。

SEEK_SET:读写偏移量将指向,从文件开头开始,向后偏移 offset 个字节的位置。

SEEK_CUR:从当前位置开始,偏移 offset 个字节处,正值向后偏移,负值向前偏移。

SEEK_END:从文件末尾开始,偏移 offset 个字节处,正值向后偏移,负值向前偏移。

修改失败返回 -1 并设置错误码,修改成功返回从文件开头开始计算的文件偏移量。

四、理解文件描述符 fd

1.文件描述符联系了进程和文件

我们知道,进程在内存中对应着一个 struct task_struct 结构体,这是内核用来描述进程的,许多这样的结构体组成一张 task_list 链表,构成了内核的进程管理。

而文件在内核中也是这样组织的,每个被打开的文件对应一个 struct file 结构体,这个结构体描述了文件的属性等信息,所有这些结构体又被组织成一张 file_list 链表形成内核的文件管理。

这时进程和文件之间还毫无关联,而我们要在进程中向文件写入,就必须在两者之间建立起联系。

在 struct task_struct 中,有一个指针 struct files_struct *files,它指向一张表,叫做文件描述符表,在这张表里有还有一个指针 struct file *fd_array[N],里面存放的就是某个 struct file 的地址。

所以文件描述符 fd,其实就是文件描述符表中 fd_array[N] 的数组下标,进程通过这样一个下标,就可以拿到文件的地址,进而对文件进行操作。

在系统层面,文件描述符是进程访问文件的唯一方式。

2.FILE 和 FILE*

FILE 和 FILE* 都是 C 语言提供的,是语言级的。

FILE 是一个结构体 struct FILE,其中包含着文件描述符 fd。因为 fopen 是对 open 的封装,fopen 返回 FILE*,open 返回 int fd,而 fd 又是进程访问文件的唯一方式,所以 FILE 必定要包含 fd。

stdin、stdout、stderr 就是 FILE* 类型,里面包含了许多成员。其中 _fileno 就对应着文件描述符:

http://www.dtcms.com/a/394186.html

相关文章:

  • FFmpeg 深入精讲(二)FFmpeg 初级开发
  • 睡眠脑电技术文章大纲
  • 计算机等级考试Python语言程序设计备考•第二练
  • 【Python】面向对象(一)
  • Jetson 设备监控利器:Jtop 使用方式(安装、性能模式、常用页面)
  • 「数据获取」《商洛统计年鉴》(2001-2024)
  • 链表的探索研究
  • 2025年工程项目管理软件全面测评
  • JAVA算法练习题day17
  • Nacos:服务注册和配置中心
  • Linux 命令行快捷键
  • EasyClick JavaScript Number
  • LeetCode:42.将有序数组转化为二叉搜索树
  • 海外代理IP网站有哪些?高并发场景海外代理IP服务支持平台
  • JavaScript数据交互
  • 11.2.5 自定义聊天室
  • 力扣:字母异味词分组
  • Linux视频学习笔记
  • 2014/12 JLPT听力原文 问题四
  • Elasticsearch面试精讲 Day 21:地理位置搜索与空间查询
  • 华为数字化实战指南:从顶层设计到行业落地的系统方法论
  • 外部 Tomcat 部署详细
  • 【回文数猜想】2022-11-9
  • 216. 组合总和 III
  • Bugku-请攻击这个压缩包
  • 2. NumPy数组属性详解:形状、维度与数据类型
  • 【css特效】:实现背景色跟随图片相近色处理
  • vuex原理
  • 内存泄露怎么排查?
  • nginx配置防盗链入门