【Linux】文件描述符 - fd

文章目录

  • 1. open 接口介绍
    • 1.1 代码演示
    • 1.2 open 函数返回值
  • 2. 文件描述符 fd
    • 2.1 0 / 1 / 2
    • 2.2 文件描述符的分配规则
  • 3. 重定向
    • 3.1 dup2 系统调用函数
  • 4. FILE 与 缓冲区

在这里插入图片描述

1. open 接口介绍

使用 man 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: 追加写	
返回值:成功:新打开的文件描述符失败:-1

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

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

1.1 代码演示

操作文件,除了使用 C 语言的接口【Linux】回顾 C 文件接口,还可以采用系统接口来进行文件访问;

写文件:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main()
{umask(0);int fd = open("myfile", O_WRONLY | O_CREAT, 0644);if (fd < 0){perror("open");return 1;}int count = 5;const char* msg = "hello open!\n";int len = strlen(msg);while (count--){write(fd, msg, len);// fd : 下面介绍// msg : 缓冲区首地址// len : 本次读取,期望写入多少个字节的数据// 返回值 : 实际写了多少字节数据}close(fd);return 0;
}

读文件:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main()
{int fd = open("myfile", O_RDONLY);if (fd < 0){perror("open");return 1;}const char* msg = "hello open!\n";char buf[1024];while (1){ssize_t s = read(fd, buf, strlen(msg)); // 类比writeif (s > 0){printf("%s", buf);}else{break;}}close(fd);return 0;
}

1.2 open 函数返回值

在认识返回值之前,先来认识两个概念:系统调用库函数

  • fopen fclose fread fwrite 都是 C 标准库当中的函数,我们称之为库函数(libc);
  • open close read write lseek 都属于系统提供的接口,称之为系统调用接口;

在这里插入图片描述

  • 系统调用接口与库函数的关系如上图;
  • 所以,可以认为,f# 系列的函数,都是对系统调用的封装,方便二次开发。

2. 文件描述符 fd

  • 文件描述符的本质,就是数组下标!!!

2.1 0 / 1 / 2

  • Linux 进程默认情况下会有 3 个缺省打开的文件描述符,分别是标准输入 0,标准输出 1,标准错误 2
  • 0,1,2 对应的物理设备一般是:键盘,显示器,显示器;
  • 所以输入输出也可以采用如下方式:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>int main()
{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));}return 0;
}

在这里插入图片描述

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

2.2 文件描述符的分配规则

直接看代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd = open("myfile", O_RDONLY);if (fd < 0){perror("open");return 1;}printf("fd: %d\n", fd);close(fd);return 0;
}

输出发现是 fd: 3

关闭 0 或者 2,再看:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{close(0);//close(2);int fd = open("myfile", O_RDONLY);if (fd < 0){perror("open");return 1;}printf("fd: %d\n", fd);close(fd);return 0;
}

发现结果是:fd: 0 或者 fd: 2

可见,文件描述符的分配规则:在 files_struct 数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符,会分配给最新打开的文件。

3. 重定向

那如果关闭 1 呢?看代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>int main()
{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);exit(0);
}

此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中 fd = 1。这种现象叫做输出重定向。

常见的重定向有:>>><

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

在这里插入图片描述

3.1 dup2 系统调用函数

函数原型如下:

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

函数简介:

makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following:
将newfd设置为oldfd的副本,并在必要时先关闭newfd,但请注意以下事项:*	If oldfd is not a valid file descriptor, then the call fails, and newfd is not closed.如果oldfd不是有效的文件描述符,则调用失败,newfd不会关闭。*	If oldfd is a valid file descriptor, and newfd has the same value as oldfd, then dup2() does nothing, and returns newfd.如果oldfd是一个有效的文件描述符,并且newfd与oldfd具有相同的值,那么dup2()什么都不做,并返回newfd。

示例代码:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>int main()
{int fd = open("./log", O_CREAT | O_RDWR, 0644);if (fd < 0){perror("open");return 1;}close(1);dup2(fd, 1);int i = 0;for (i = 0; i < 5; i++){char buf[1024] = { 0 };ssize_t read_size = read(0, buf, sizeof(buf) - 1);if (read_size < 0){perror("read");break;}printf("%s", buf);fflush(stdout);}return 0;
}
  • printf 是 C 库当中的 IO 函数,一般往 stdout 中输出,但是 stdout 底层访问文件的时候,找的还是 fd:1
  • 但此时 fd:1 下标所表示的内容已经变成了 log 的地址,不再是显示器文件的地址;
  • 所以,输出的任何消息都会往文件中写入,进而完成输出重定向。

4. FILE 与 缓冲区

  • 因为 IO 相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过 fd 访问的。
  • 所以 C 库当中的 FILE 结构体内部,必定封装了 fd。
  • 缓冲区就是一块内存区域,其存在目的是为了提升使用者的效率(用空间换时间)。
  • 我们这里说的缓冲区是语言层面的缓冲区,也就是 C 自带的缓冲区,跟内核中的缓冲区没有关系。
  • 缓冲区刷新方式:
    • 无缓冲 - 无刷新;
    • 行缓冲 - 行刷新 :写满一行才刷新,我们平时写代码经常会遇到缓冲区的问题;
    • 全缓冲 - 全部刷新:在普通文件中写入时,缓冲区被写满,才刷新!
    • 强制刷新:使用各种方法让缓冲区强制刷新,如:fflush() 函数;
    • 自动刷新:程序退出的时候会自动刷新。

来段代码研究一下:

#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

但如果对进程实现输出重定向呢?./a.out > file ,我们发现结果变成了:

hello write
hello printf
hello fwrite
hello peintf
hello fwrite

我们发现 printffwrite(库函数)都输出了 2 次,而 write 只输出了一次(系统调用)。

为什么呢?肯定和 fork 有关:

  • 一般 C 库函数写入文件是全缓冲的,而写入显示器是行缓冲。
  • printf fwrite 库函数会自带缓冲区(进度条例子可以说明【Linux】编写第一个小程序:进度条),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
  • 而我们放在缓冲区中的数据,就不会被立即刷新,即使是 fork 之后;
  • 但是进程退出之后,会统一刷新,写入文件当中。
  • 但是 fork 的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
  • write 没有变化,说明没有所谓的缓冲。

综上:printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS 也会提供相关内核级缓冲区,不过不在我们讨论范围之内。那这个缓冲区谁提供呢?printf fwrite 是库函数,writre 是系统调用,库函数在系统调用的“上层”,是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是 C,所以由 C 标准库提供。


END

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/281148.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

02. Java 中的关键字、标识符、运算符、分隔符和注释

关键字 Java 的关键字(keyword、保留字)是 Java 语言中具有特殊含义的单词&#xff0c;它们被保留供 Java 自身使用&#xff0c;不能被用作标识符。例如 public、class、void、int 等都是关键字。 关键字在 Java 语法中起着重要的作用&#xff0c;它们定义了编程的结构、控制…

Python 深度学习第二版(GPT 重译)(一)

前言 序言 如果你拿起这本书&#xff0c;你可能已经意识到深度学习在最近对人工智能领域所代表的非凡进步。我们从几乎无法使用的计算机视觉和自然语言处理发展到了在你每天使用的产品中大规模部署的高性能系统。这一突然进步的后果几乎影响到了每一个行业。我们已经将深度学…

【数据结构与算法】(13):冒泡排序和快速排序

&#x1f921;博客主页&#xff1a;Code_文晓 &#x1f970;本文专栏&#xff1a;数据结构与算法 &#x1f63b;欢迎关注&#xff1a;感谢大家的点赞评论关注&#xff0c;祝您学有所成&#xff01; ✨✨&#x1f49c;&#x1f49b;想要学习更多数据结构与算法点击专栏链接查看&…

揭秘2024云渲染平台优惠陷阱!有些看似划算实则很坑

近年来&#xff0c;随着云渲染技术的飞速发展&#xff0c;越来越多的人开始关注并使用云渲染平台。然而其中隐藏着一些消费陷阱&#xff0c;需要我们谨慎小心。有时候一些平台看似优惠&#xff0c;实际上可能是一个深不见底的坑。 今天小编就来对比分析2024年市面上主流的五款云…

MT管理器 使用手册

MT管理器 论坛&#xff1a;https://bbs.binmt.cc/ 使用技巧系列教程&#xff1a;https://www.52pojie.cn/thread-1259872-1-1.html MT管理器 使用手册 &#xff1a;https://mt2.cn/guide/&#xff1a;https://www.bookstack.cn/read/mt-manual/80b8084f6be128c0.md&#xff…

外包干了5天,技术退步明显。。。。

说一下自己的情况&#xff0c;本科生&#xff0c;19年通过校招进入广州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试&a…

exporter方式监控达梦数据库

蓝鲸监控 随着国产化和信创的深入&#xff0c;开始普遍使用国产化数据库–如达梦数据库&#xff0c;蓝鲸平台默认没有对其进行监控&#xff0c;但是平台了提供监控告警的能力。比如脚本采集&#xff0c;脚本的是一种灵活和快速的监控采集方式&#xff0c;不同层的监控对象都可…

SqlServer数据库复习总结资料

基于课堂上学到的以及书上的看到的&#xff0c;总结出的数据库复习资料 一、数据库概述 基本概念 1.数据 数据&#xff08;Data&#xff09;是事物的符号表示&#xff0c;可以是声音、图像、文字、数字&#xff0c;也可以是计算机代码。 2.数据库 数据库&#xff08;DataBase…

pytorch之诗词生成6--eval

先上代码&#xff1a; import tensorflow as tf from dataset import tokenizer import settings import utils# 加载训练好的模型 model tf.keras.models.load_model(r"E:\best_model.h5") # 随机生成一首诗 print(utils.generate_random_poetry(tokenizer, model)…

WebXR实践——利用aframe框架浏览器展示全景图片

一、效果 话不多说&#xff0c;先上效果 二、代码 index.html <!DOCTYPE html> <html><head><meta charset"utf-8"><title>360&deg; Image</title><meta name"description" content"360&deg; Imag…

JavaSE:数据类型与变量

目录 一、前言 二、数据类型与变量 &#xff08;一&#xff09;字面常量 &#xff08;二&#xff09;数据类型 &#xff08;三&#xff09;变量 1.变量概念 2.语法格式 3.整型变量 3.1整型变量 3.2长整型变量 3.3短整型变量 3.4字节型变量 4.浮点型变量 4.1双精…

REDHAWK——连接(续)

文章目录 前言一、突发 IO1、数据传输①、输入②、输出 2、突发信号相关信息 (SRI)3、多输出端口4、使用复数数据①、在 C 中转换复数数据 5、时间戳6、端口统计①、C 二、消息传递1、消息生产者①、创建一个消息生产者②、发送消息 2、消息消费者①、创建消息消费者②、注册接…

01mysql

登陆mysql 默认数据库 进入&#xff0c;展示&#xff0c;删除 &#xff0c;查看当前正使用的库 select version()查看版本 查看表结构desc 查询 not in不会忽略空 in会自动忽略 like模糊查询 %o%中间带o的 _A%第二个字母是A的 查名字是下划线的 %\_% 排序 order …

罗技G29游戏方向盘试玩拆解,带震动力反馈

1.正好有时间记录下 自己的爱好 一千多的罗技G29游戏方向盘试玩拆解&#xff0c;带震动力反馈&#xff0c;值这个价吗_哔哩哔哩_bilibili 一千多的罗技G29游戏方向盘试玩拆解&#xff0c;带震动力反馈&#xff0c;值这个价吗_哔哩哔哩_bilibili 2.拆解 3.2个大电机 4.主控芯…

docker的部署与安装以及部署一个docker(容器)应用及docker容器常出现的问题

docker 架构图 一、docker的部署与安装 1、在 CentOS 上安装 Docker 移除旧版本&#xff08;如果有的话&#xff09;&#xff1a;sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-…

MySQL 索引的分类和优化

​ 优质博文&#xff1a;IT-BLOG-CN 索引是什么 &#xff1a; MySQL 官方对索引的定义&#xff1a;索引&#xff08;Index&#xff09;是帮助 MySQL 高效获取数据的数据结构。可以得到索引的本质&#xff1a;索引是数据结构。索引的目的在于提高查询效率。可以简单理解为&#…

【记录39】html element-ui 加载

环境 html使用element-ui组件、用vue框架搭建 方法一&#xff1a; 方法二&#xff08;推荐&#xff09; 将相关资源下载下来&#xff0c;在对应的html文件中相对路径引入。注意&#xff1a;css加载放在js之前

java框架 2 springboot 过滤器 拦截器 异常处理 事务管理 AOP

Filter 过滤器 对所有请求都可以过滤。 实现Filter接口&#xff0c;重写几个方法&#xff0c;加上WebFilter注解&#xff0c;表示拦截哪些路由&#xff0c;如上是所有请求都会拦截。 然后还需要在入口处加上SvlterComponentScan注解&#xff0c;因为Filter是javaweb三大组件之…

基于深度学习的心律异常分类系统设计——算法设计

基于深度学习的心律异常分类系统——算法设计 第一章 研究背景算法流程本文研究内容 第二章 心电信号分类理论基础心电信号产生机理MIT-BIH 心律失常数据库 第三章 心电信号预处理心电信号噪声来源与特点基线漂移工频干扰肌电干扰 心电信号读取与加噪基于小波阈值去噪技术的应用…

PP-Matting——支持多场景精细化高精度人像抠图(C++模型推理)

简介 Matting和分割是图像处理中两个重要的任务&#xff0c;它们在抠图和图像分析中起着不同的作用。 分割方法将图像分成不同的区域&#xff0c;并为每个像素分配一个分类标签&#xff0c;因此其输出是一个像素级别的分类标签图&#xff0c;通常是整型数据。这种方法适用于将…