【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)); // 类比write
        if (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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI系统性学习06—开源中文语言大模型

1、ChatGLM ChatGLM-6B的github地址&#xff1a;https://github.com/THUDM/ChatGLM-6B ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。结合模型量化技术&#xff0c;用户可以在消费级…

透视未来工厂:山海鲸可视化打造数字孪生新篇章

在信息化浪潮的推动下&#xff0c;数字孪生工厂项目正成为工业制造领域的新宠。作为一名山海鲸可视化的资深用户&#xff0c;我深感其强大的数据可视化能力和数字孪生技术在工厂管理中的应用价值&#xff0c;同时我们公司之前也和山海鲸可视化合作制作了一个智慧工厂项目&#…

OceanBase生产环境安装部署的最优实践

关于生产环境&#xff0c;为了尽量确保性能和稳定性&#xff0c;我们比较建议采用标准化的配置进行部署&#xff0c;例如接下来会提到的服务初始化、日志管理和数据分盘等关键步骤。而在非生产环境中&#xff0c;如果条件满足&#xff0c;同样建议遵循规范部署的原则。 前期准备…

项目实战-开发工具入门/基本框架搭建/项目初始化/引入组件库

上周更新完了之前vue3的shopping项目&#xff0c;接下来&#xff0c;将会开启一个新的项目&#xff0c;效果是类似于移动端的一个伙伴匹配项目&#xff0c;今天这篇文章从需求分析到架构设计再到项目初始化&#xff0c;基本框架搭建几个部分来为大家详细介绍。 从这个项目开始…

YOLOV5 改进:替换backbone为Swin Transformer

1、前言 本文会将YOLOV5 backbone更换成Swin Transformer 具体为什么这样实现参考上文:YOLOV5 改进:替换backbone(MobileNet为例)-CSDN博客 这里只贴加入的代码 训练结果如下: 2、common文件更改 在common文件中加入下面代码: 这里是swin transformer的实现,参考:…

VMware部署银河麒麟遇到的问题记录

1. 解决VMware Workstation安装VMware Tools显示灰色的办法 1.关闭虚拟机; 2.在虚拟机设置分别设置CD/DVD、CD/DVD2和软盘为自动检测三个步骤; 3.再重启虚拟机,灰色字即点亮。 2.Linux安装vmTool

Pytest接口自动化测试框架搭建模板

auto_api_test 开发环境: Pycharm 开发语言&版本: python3.7.8 测试框架: Pytest、测试报告: Allure 项目源码Git地址 项目目录结构 api – 模仿PO模式, 抽象出页面类, 页面类内包含页面所包含所有接口, 并封装成方法可供其他模块直接调用config – 配置文件目录data…

打造高效自动化渗透测试系统:关键步骤与实践

随着当前网络安全威胁的不断扩展与升级&#xff0c;开展渗透测试工作已经成为广大企业组织主动识别安全漏洞与潜在风险的关键过程。然而&#xff0c;传统的人工渗透测试模式对测试人员的专业能力和经验水平有很高的要求&#xff0c;企业需要投入较大的时间和资源才能完成。在此…

品牌纷纷推出“穷鬼套餐”的原因竟然有这些?

被大众戏称为“千元店”的山姆也开穷鬼套餐了&#xff1f;&#xff1f;该套餐包括1.59公斤售价21.9元的鸡蛋、7个装售价23.9元的原味贝果以及16片装售价59.8元的瑞士卷。 穷鬼套餐最开始是麦当劳和肯德基的推出的概念&#xff0c;由麦当劳推出的11随心配、肯德基超值午餐等。为…

python(django)之产品后台管理功能实现

1、添加新项目 在命令行输入以下代码 python manage.py startapp prroduct 2、添加路径和代码结构 在新项目目录下admin.py中加入以代码 from .models import Product class ProductAdmin(admin.ModelAdmin):list_display [product_name, product_desc,producter,created_…

CC-DefineTag:一个简单好用的标签组件,支持自动换行、自适应高度,且可设置行数、标签文字颜色等属性

CC-DefineTag&#xff1a;一个简单好用的标签组件&#xff0c;支持自动换行、自适应高度&#xff0c;且可设置行数、标签文字颜色等属性 摘要&#xff1a; 在前端开发中&#xff0c;标签组件是常见的UI组件之一&#xff0c;用于显示一组相关的标签。然而&#xff0c;传统的标签…

Postman Runner 使用指南

什么是 Postman Runner&#xff1f; 而 Postman Runner 是 Postman 中的一个模块&#xff0c;它提供了一种批量运行 API 请求的方式&#xff0c;这些请求可以是已经保存的历史请求、集合中的请求或者手动添加的请求。在批量运行 API 请求的过程中&#xff0c;Postman Runner 可…

4.1 用源文件写汇编代码

汇编语言 1. 源程序 1.1 伪指令 汇编指令是有对应的机器码的指令&#xff0c;可以被编译为机器指令&#xff0c;最终为CPU所执行伪指令没有对应的机器指令&#xff0c;最终不被CPU所执行伪指令是由编译器来执行的指令&#xff0c;编译器根据伪指令来进行相关的编译工作 1.2…

arm-linux实现onvif server+WS-UsernameToken令牌验证

目录 一、环境搭建 1、安装openssl 2、安装bison 3、安装flex 二、gsoap下载 三、编译x86版本gsoap 四、编译arm-linux版本gsoap 1、交叉编译openssl 1.1、下载openssl 1.2、交叉编译 2、交叉编译zlib 2.1、下载zlib 2.2、交叉编译 3、交叉编译gsoap 3.1、编译过…

Day75:WEB攻防-验证码安全篇接口滥用识别插件复用绕过宏命令填入滑块类

目录 图片验证码-识别插件-登录爆破&接口枚举 登录爆破 接口枚举 图片验证码-重复使用-某APP短信接口滥用 滑块验证码-宏命令-某Token&Sign&滑块案例 知识点&#xff1a; 1、验证码简单机制-验证码过于简单可爆破 2、验证码重复使用-验证码验证机制可绕过 3、…

TypeScript在学习(0)

1.什么是TypeScript? 答:TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。 个人浅见,我一直把ts简单理解成,其实就是javascript上多了的类型限制; 2.优势和缺点 答…

文件编码探测与原理、Java实现与构造让探测器失效文件

文章目录 构造让探测器失效的文件文件编码探测原理探测器Java实现版本测试 构造让探测器失效的文件 我们用vscode打开一个文本文件的时候&#xff0c;默认会使用UTF-8编码&#xff0c;所以当文件不是UTF-8编码的时候就会乱码。 但是&#xff0c;好像notepad–这类编辑器就似乎…

评估单细胞数据聚类指标 AvgBIO

从scGPT的报告中看到的&#xff1a; 从ChatGPT到scGPT 生成式AI助力单细胞生物学_哔哩哔哩_bilibili

深入理解Mysql索引底层原理(看这一篇文章就够了)

目录 前言 1、Mysql 索引底层数据结构选型 1.1 哈希表&#xff08;Hash&#xff09; 1.2 二叉查找树(BST) 1.3 AVL 树和红黑树 1.4 B 树 1.5 B树 2、Innodb 引擎和 Myisam 引擎的实现 2.1 MyISAM 引擎的底层实现&#xff08;非聚集索引方式&#xff09; 2.2 Innodb 引…

Kubernetes的Namespace使用

在 Kubernetes 中&#xff0c;命名空间提供了一种用于隔离单个集群中的资源组的机制。资源名称在命名空间内必须是唯一的&#xff0c;但不能跨命名空间。基于命名空间的作用域仅适用于命名空间物体 &#xff08;例如部署、服务等&#xff09;而不是集群范围的对象&#xff08;例…