Linux:文件管理(一)——文件描述符fd

目录

一、文件基础认识

二、C语言操作文件的接口

1.> 和 >>

2.理解“当前路径”

三、相关系统调用

1.open

2.文件描述符

3.一切皆文件

4.再次理解重定向


一、文件基础认识

  • 文件 = 内容 + 属性。换句话说,如果在电脑上新建了一个空白文档,它虽然没有内容,但也是占据磁盘空间的。
  • 想要修改一个文件的内容,比如用WPS这样的软件操作文件内容,本质上都需要CPU完成相关的指令,而CPU又只与内存交互,所以,打开文件的含义其实就是把文件加载到内存中
  • 在我们眼里,我们双击了一个文件就是打开了文件,但是在操作系统看来,并不是我们打开了文件,而是某一个正在运行的进程,文件是由进程打开的
  • 一个进程可以打开多个文件。
  • 操作系统管理多个被打开文件,必然也会像操作系统管理多个进程一样,利用面向对象和数据结构,因此,内核中必然定义了结构体来描述被打开的文件。
  • 从操作系统管理文件的角度看,文件被区分为被打开的文件(在内存中)和没有打开的文件(在磁盘中)。

二、C语言操作文件的接口

        fopen"w"方法打开一个文件。

#include <stdio.h>
#include <stdlib.h>
int main()
{FILE* pf = fopen("aaa.txt","w");if(pf == NULL){perror("fopen:");return 1;}const char* str = "aaaaaaaaaaaaaaaaaaaaaa\n";fputs(str,pf);fclose(pf);return 0;
}
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ ./a.out 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ cat aaa.txt 
aaaaaaaaaaaaaaaaaaaaaa
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ 

        结果显示,文件aaa.txt中已经写入了一段字符串。修改源代码,将写入字符串的代码删除后,再执行编译运行一次。

#include <stdio.h>
#include <stdlib.h>
int main()
{FILE* pf = fopen("aaa.txt","w");if(pf == NULL){perror("fopen:");return 1;}
//	const char* str = "aaaaaaaaaaaaaaaaaaaaaa\n";
//	fputs(str,pf);fclose(pf);return 0;
}
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ cat aaa.txt 
aaaaaaaaaaaaaaaaaaaaaa
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ gcc file.c 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ ./a.out 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ cat aaa.txt 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ 

        结果表明,aaa.txt文件中的内容都消失了。原因在于fopen打开文件的方式"w",使用man手册查看fopen打开文件方式的说明。

        "w"方式打开文件时,会先清空文件中的所有内容。如果想保留文件中原来的内容做写入操作,就应该使用"a"的方式打开文件。


1.> 和 >>

utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ cat aaa.txt
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ echo aaaaaaaaaaaa > aaa.txt 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ cat aaa.txt
aaaaaaaaaaaa
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ echo bbbbbbbbb > aaa.txt 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ cat aaa.txt
bbbbbbbbb
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ 

        通过echo做重定向操作向aaa.txt文件中先后写入两次,最终效果并不是有两段字符串,说明重定向操作符">"打开文件的方式本质上也是"w"的方式。(需要一提的是,echo重定向到文件中,本质上也要修改文件的内容,所以一定会打开文件)。


utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ echo aaaaaaaaaaaaaaaa >> aaa.txt 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ cat aaa.txt 
aaaaaaaaaaaaaaaa
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ echo bbbbbbbbbbbbbbbb >> aaa.txt 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ cat aaa.txt 
aaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbb
utocoo@utocoo-virtual-machine:~/Desktop/linux/241121$ 

        而追加重定向操作符" >> "先后向aaa.txt文件写入两次后,最终效果是两段字符串都被保留了下来,说明 " >> "其实和"a"方式类似,是一种追加的形式。


2.理解“当前路径”

        在使用C接口操作文件的时候,经常会听到说,“如果没有这个文件,则在当前路径下新建这个文件”,如何理解这个“当前路径”

        最简单直接的理解,就是我们当前程序的路径

//file.c
#include <stdio.h>
#include <stdlib.h>
int main()
{FILE* pf = fopen("aaa.txt","w");if(pf == NULL){perror("fopen:");return 1;}fclose(pf);return 0;
}

        当前路径就是file.c文件所在路径,编译运行前,该路径下没有aaa.txt文件,编译运行后,该路径下存在名为aaa.txt的文件。

utocoo@utocoo-virtual-machine:~/Desktop/linux/241122$ ll
总计 16
drwxrwxr-x  2 utocoo utocoo 4096 11月 22 12:22 ./
drwxrwxr-x 16 utocoo utocoo 4096 11月 22 12:19 ../
-rw-rw-r--  1 utocoo utocoo  233 11月 22 12:19 file.c
-rw-rw-r--  1 utocoo utocoo   64 11月 22 12:21 Makefile
utocoo@utocoo-virtual-machine:~/Desktop/linux/241122$ make
gcc -o file file.c
utocoo@utocoo-virtual-machine:~/Desktop/linux/241122$ ./file 
utocoo@utocoo-virtual-machine:~/Desktop/linux/241122$ ll
总计 32
drwxrwxr-x  2 utocoo utocoo  4096 11月 22 12:23 ./
drwxrwxr-x 16 utocoo utocoo  4096 11月 22 12:19 ../
-rw-rw-r--  1 utocoo utocoo     0 11月 22 12:23 aaa.txt
-rwxrwxr-x  1 utocoo utocoo 16048 11月 22 12:23 file*
-rw-rw-r--  1 utocoo utocoo   233 11月 22 12:19 file.c
-rw-rw-r--  1 utocoo utocoo    64 11月 22 12:21 Makefile

         在文件基础认识部分,已经提到过,文件是由进程打开的,那么新建一个文件也是由进程完成,进程是如何知道在哪条路径下新建一个文件呢。

        在源代码中打印出进程的PID,运行后,再在/proc路径下找到对应进程的所在目录。

while(1)
{printf("PID:%d\n",getpid());sleep(2);
}
PID:2930
PID:2930
PID:2930
PID:2930
PID:2930
PID:2930
PID:2930

        当前路径在进程的属性中其实已经保存好了,是cwd这条信息。因此新建一个文件要被存放到哪里也是确定的。但是进程的工作路径是可以修改的,虽然进程的前身是一个可执行程序,可执行程序的路径是确定,但是当可执行程序被操作系统管理起来后变成进程,进程的工作路径是可以通过chdir指令修改的,那么修改路径后,再新建一个文件,这个文件的所在路径不再是修改前的路径了,而是修改后的路径。

        这就表明,所谓的当前路径,其实是进程在运行的时候的工作路径,这个路径是由进程自己记录的,就是那条cwd信息。

三、相关系统调用

        系统默认打开三个流,stdin,stdout,stderr,这三个流对应的外设分别为键盘、显示器显示器。而Linux管理外设,是以文件的方式,即必然存在系统调用system call。因此,C语言的fopen、fclose、fwrite等函数本质是调用了system call

        下面就来认识Linux下文件相关的system call。

1.open

 

  • pathname就是路径,传参方法和C语言的fopen的参数差不多。
  • flags类型为int,传参的可选项如下所示

        这些值都是C语言定义的宏,目的是为了实现,只定义一个函数,却可以同时“传两个参数”。比如

#include <stdio.h>
#define ONE 1
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
#define FIVE (1<<4)void Print(int flags)
{if(flags & ONE)printf("1\n");if(flags & TWO)printf("2\n");if(flags & THREE)printf("3\n");if(flags & FOUR)printf("4\n");if(flags & FIVE)printf("5\n");
}
int main()
{Print(ONE);printf("-----------------\n");Print(TWO);printf("-----------------\n");Print(ONE|TWO);printf("-----------------\n");Print(ONE|FOUR|FIVE);return 0;
}


        如果使用两个形参的open接口,一般是操作已经存在了的文件,比如bbb.txt文件必须存在,否则会报错。

int main()
{int fd = open("bbb.txt",O_WRONLY);if(fd == -1){perror("open\n");return 1;}close(fd);return 0;
}
由于bbb.txt不存在,则fd=-1

         用open接口实现fopen的"w"方式,文件如果不存在,则新建。而新建一个文件会有权限的初始化,一般普通用户新建一个文件的权限是0666(-rw-rw-rw-),而普通用户的权限掩码umask为0002,实际权限等于初始化权限减去权限掩码,即(-rw-rw-r--)

        mode即初始化权限码,一般传0666,只有flags带O_CREAT时,mode传参才有效。

        一般新建一个文件,在open的第二个参数上,应该传新建、可写、写入时清零,等同于fopen的"w"方式。

int main()
{int fd = open("bbb.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);if(fd == -1){perror("open\n");return 1;}const char* msg = "this is open to w\n";write(fd,msg,strlen(msg));close(fd);return 0;
}

        原来不存在的文件bbb.txt被创建了出来,并且o的权限少了w,符合预期。


2.文件描述符

        再来理解open的返回值——文件描述符(int fd)——Linux用整型值描述被打开的文件。

        这些整型值其实是数组下标,我们知道系统默认打开三个流,其实是三个文件,stdin、stdout、stderr,它们的下标对应为0、1、2,如果先后有序的打开1.txt、2.txt、3.txt,则它们的下标也是有序的为3、4、5。

        这段话似乎让你很懵,不过我马上就要阐述具体的内容。

        在此之前,要明确,操作文件只能由操作系统来做,因此有C语言的fopen封装open接口,有C语言定义的FILE指针的流封装文件描述符fd。

        实际上,FILE类型是结构体类型,也是封装了文件描述符int fd。


         对int fd的理解。

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

  • OS管理进程,这一板块叫做进程管理,有PCB,Linux下被定义为task_struct。
  • OS管理文件,这一板块叫做文件管理,在之前介绍了,文件区分为内存中的文件和磁盘中的文件,被加载到内存中的文件,OS要对它们做管理,就必然做“面向对象”和“数据结构”的工作,“面向对象”就是定义结构体,“数据结构”就是把对象存储到链表或者其他数据结构里面。Linux下把这个结构体类型定义为file,结构体内容大致有属性、方法集、缓冲区、mode(权限码)、flag、pos以及指向下一个结点的next等。
  • 进程管理和文件管理是两个独立的板块,但是又有关联。进程可以打开多个文件,那么一个进程打开了哪些文件,该进程必然要做记录。于是Linux下,task_struct结构体中有一个结构体指针,指向的结构体类型为files_struct,而这个结构体中,有一个数组,数组的每个元素类型为结构体指针,指针指向的结构体类型为file,这个数组被称为文件描述符表

        一个进程打开文件后,进程在这个数组中保存指向这个文件的指针,默认这个数组的前三个位置已经被stdin、stdout、stderr这三个文件占用了。 

        而数组下标,就是文件描述符,为什么close、write等这些接口都用int类型的文件描述符来操作文件,原因很简单,数组下标式访问,仅仅是O(1)复杂度

3.一切皆文件

        硬件一层,由于各种原因,设备的操作方法各不相同,因此每台计算机都需要装载相应的驱动。而对于每台设备的操作函数,它们的函数类型相同,函数内容各不相同。

        file结构体定义了方法集,本质就是函数指针

  • 每一台设备被视为一个结构体,方法集指向了该设备的操作方法。
  • 当系统调用read读取某个外设的内容,实际上就是函数回调的形式,用函数指针调用外设的读函数。

4.再次理解重定向

        文件描述符的分配规则:一定会把最小的数组下标利用起来,如果存在没有被利用的较小下标,则会分配给最新打开的文件,比如打开b文件前,将已经打开的a文件关闭,则打开b文件后,a文件的较小fd会分配给b文件。

        上面这段话,其实就是重定向的实现原理。


        输出重定向:本该输出到屏幕的语句却输出到了bbb.txt。 

int main()
{close(1);int fd = open("bbb.txt",O_WRONLY);printf("这段话本该输出到屏幕\n");return 0;
}

        原因就是在执行完close(1)语句后,当前进程的文件描述符表中数组下标为1的位置不再是指向屏幕文件的指针,而又打开了bbb.txt文件,则1号下标的指针指向了bbb.txt文件printf底层封装的write传参的fd值还是1,因此,这句字符串被写进了1位置指向的bbb.txt文件的缓冲区。

        所以,重定向的本质,就是文件指针在文件描述表中的下标发生了变化


         有一个专门用来拷贝文件描述符的系统调用——dup

         想把打印到屏幕的内容重定向到bbb.txt,可以用dup2来实现。

        大致意思是用oldfd的值覆盖到newfd

int main()
{int fd = open("bbb.txt",O_WRONLY);dup2(fd,1);printf("----\n");return 0;
}

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

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

相关文章

mac上的建议xftp 工具

mac上的建议xftp 工具 最近使用mac比较频繁了&#xff0c;但是第一次重度使用mac里面有很多的工具都是新的&#xff0c;有的window版本的工具无法使用。 xftp 的平替 Cyberduck 从它的官网上下载是免费的&#xff0c;但是如果使用 Apple store 要花费198呢。这不就剩下一大笔…

IC数字后端实现之大厂IC笔试真题(经典时序计算和时序分析题)

今天小编给大家分享下每年IC秋招春招必考题目——静态时序分析时序分析题。 数字IC后端笔试面试题库 | 经典时序Timing计算题 时序分析题1&#xff1a; 给定如下图所示的timing report&#xff0c;请回答一下几个问题。 1&#xff09;这是一条setup还是hold的timing report?…

警钟长鸣,防微杜渐,遨游防爆手机如何护航安全生产?

近年来&#xff0c;携非防爆手机进入危险作业区引发爆炸的新闻屡见报端。2019年山西某化工公司火灾&#xff0c;2018年延安某煤业瓦斯爆炸&#xff0c;均因工人未用防爆手机产生静电打火引发。涉爆行业领域企业量大面广&#xff0c;相当一部分企业作业场所人员密集&#xff0c;…

Redis与MySQL如何保证数据一致性

Redis与MySQL如何保证数据一致性 简单来说 该场景主要发生在读写并发进行时&#xff0c;才会发生数据不一致。 主要流程就是要么先操作缓存&#xff0c;要么先操作Redis&#xff0c;操作也分修改和删除。 一般修改要执行一系列业务代码&#xff0c;所以一般直接删除成本较低…

Linux宝塔部署wordpress网站更换服务器IP后无法访问管理后台和打开网站页面显示错乱

一、背景&#xff1a; wordpress网站搬家&#xff0c;更换服务器IP后&#xff0c;如果没有域名时&#xff0c;使用服务器IP地址无法访问管理后台和打开网站页面显示错乱。 二、解决方法如下&#xff1a; 1.wordpress搬家后&#xff0c;在新服务器上&#xff0c;新建站点时&am…

探秘嵌入式位运算:基础与高级技巧

目录 一、位运算基础知识 1.1. 位运算符 1.1.1. 与运算&#xff08;&&#xff09; 1.1.2. 或运算&#xff08;|&#xff09; 1.1.3. 异或运算&#xff08;^&#xff09; 1.1.4. 取反运算&#xff08;~&#xff09; 1.1.5. 双重按位取反运算符&#xff08;~~&#xf…

MySQL底层概述—3.InnoDB线程模型

大纲 1.InnoDB的线程模型 2.IO Thread 3.Purge Thread 4.Page Cleaner Thread 5.Master Thread 1.InnoDB的线程模型 InnoDB存储引擎是多线程的模型&#xff0c;因此其后台有多个不同的后台线程&#xff0c;负责处理不同的任务。 后台线程的作用一&#xff1a;负责刷新内存…

pikachu平台xss漏洞详解

声明&#xff1a;文章只是起演示作用&#xff0c;所有涉及的网站和内容&#xff0c;仅供大家学习交流&#xff0c;如有任何违法行为&#xff0c;均和本人无关&#xff0c;切勿触碰法律底线 文章目录 概述&#xff1a;什么是xss一、反射型XSS1. get2. post 二、存储型XSS三、DOM…

Easyexcel(7-自定义样式)

相关文章链接 Easyexcel&#xff08;1-注解使用&#xff09;Easyexcel&#xff08;2-文件读取&#xff09;Easyexcel&#xff08;3-文件导出&#xff09;Easyexcel&#xff08;4-模板文件&#xff09;Easyexcel&#xff08;5-自定义列宽&#xff09;Easyexcel&#xff08;6-单…

通用网络安全设备之【防火墙】

概念&#xff1a; 防火墙&#xff08;Firewall&#xff09;&#xff0c;也称防护墙&#xff0c;它是一种位于内部网络与外部网络之间的网络安全防护系统&#xff0c;是一种隔离技术&#xff0c;允许或是限制传输的数据通过。 基于 TCP/IP 协议&#xff0c;主要分为主机型防火…

对于GC方面,在使用Elasticsearch时要注意什么?

大家好&#xff0c;我是锋哥。今天分享关于【对于GC方面&#xff0c;在使用Elasticsearch时要注意什么&#xff1f;】面试题。希望对大家有帮助&#xff1b; 对于GC方面&#xff0c;在使用Elasticsearch时要注意什么&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java…

[仓颉Cangjie刷题模板] 优先队列(含小顶堆实现)

[TOC]([仓颉Cangjie刷题模板] 优先队列(含小顶堆实现) ) 一、 算法&数据结构 1. 描述 堆是一个可以维护实时最大/最小值的数据结构&#xff0c;相比treeset等常数优很多。 常用于维护一组数据的极值贪心问题。2. 复杂度分析 初始化O(n)查询O(1)修改O(lgn) 3. 常见应用…

解决 MySQL 5.7 安装中的常见问题及解决方案

目录 前言1. 安装MySQL 5.7时的常见错误分析1.1 错误原因及表现1.2 错误的根源 2. 解决方案2.1 修改YUM仓库配置2.2 重新尝试安装2.3 处理GPG密钥错误2.4 解决依赖包问题 3. 安装成功后的配置3.1 启动MySQL服务3.2 获取临时密码3.3 修改root密码 4. 结语 前言 在Linux服务器上…

计算机网络 网络安全基础——针对实习面试

目录 网络安全基础你了解被动攻击吗&#xff1f;你了解主动攻击吗&#xff1f;你了解病毒吗&#xff1f;说说基本的防护措施和安全策略&#xff1f; 网络安全基础 网络安全威胁是指任何可能对网络系统造成损害的行为或事件。这些威胁可以是被动的&#xff0c;也可以是主动的。…

oracle小技巧-解决特殊密码字符而导致的exp错误

在使用oracle数据库的时候&#xff0c;我们经常会利用exp工具对某些表进行导出。但有些时候&#xff0c;因我们用户密码为安全性设有特殊字符&#xff0c;导致exp导出时候报&#xff1a;“EXP-00056和ORA-12154”&#xff0c;今天我们就分享下如何通过设置符号隔离的小技巧解决…

Taro 鸿蒙技术内幕系列(三) - 多语言场景下的通用事件系统设计

基于 Taro 打造的京东鸿蒙 APP 已跟随鸿蒙 Next 系统公测&#xff0c;本系列文章将深入解析 Taro 如何实现使用 React 开发高性能鸿蒙应用的技术内幕 背景 在鸿蒙生态系统中&#xff0c;虽然原生应用通常基于 ArkTS 实现&#xff0c;但在实际研发过程中发现&#xff0c;使用 C…

带有悬浮窗功能的Android应用

android api29 gradle 8.9 要求 布局文件 (floating_window_layout.xml): 增加、删除、关闭按钮默认隐藏。使用“开始”按钮来控制这些按钮的显示和隐藏。 服务类 (FloatingWindowService.kt): 实现“开始”按钮的功能&#xff0c;点击时切换增加、删除、关闭按钮的可见性。处…

ML 系列:第 36 节 — 统计学中的抽样类型

ML 系列&#xff1a;第 36 天 — 统计学中的抽样类型 文章目录 一、说明二、抽样方法三、简单随机抽样四、 Stratified Sampling分层抽样五、 Cluster Sampling 整群抽样六、Systematic Sampling系统抽样七、Convenience Sampling便利抽样八、结论 一、说明 统计学中的抽样类型…

CGMA – Cloth Creation and Simulation for Real-Time

CGMA – 实时布料创建和模拟 Info&#xff1a; 本课程介绍如何将 Marvelous Designer 整合到布料工作流程中以实时创建角色&#xff0c;从软件基础知识到创建逼真和风格化服装的高级技术。本课程将首先介绍软件&#xff0c;通过创建现代、现代的服装&#xff0c;然后深入探讨使…

Springboot组合SpringSecurity安全插件基于密码的验证Demo

Springboot组合SpringSecurity安全插件基于密码的验证Demo!下面的案例&#xff0c;都是基于数据库mysql&#xff0c;用户密码&#xff0c;验证登录的策略demo。 1&#xff1b;引入maven仓库的坐标 <dependency><groupId>org.springframework.boot</groupId>…