【Linux】基础IO之文件操作(文件fd)——针对被打开的文件

news2024/12/25 12:46:57

系列文章目录


文章目录

  • 系列文章目录
  • 前言
    • 浅谈文件的共识
  • 一、 回忆c语言对文件操作的接口
    • 1.fopen接口和cwd路径
    • 2.fwrite接口和"w","a"方法
    • 3.fprintf接口和三个默认打开的输入输出流(文件)
  • 二、过渡到系统,认识文件调用
    • 2.1看一看文件的系统调用接口——open
    • 2.2 write系统接口
  • 访问文件的本质
  • 总结


前言

浅谈文件的共识

  • 1.文件 = 内容 + 属性

  • 2.文件分为打开的文件和未打开的文件

    • 1)打开的文件:进程打开的。本质上是研究进程和文件的关系。
    • 文件被打开,就必须先加载到内存中。
    • 一个进程可以打开多个文件,操作系统要对这些文件进行管理,就要先描述,再组织。在内核中,操作系统要管理好这些文件,就必须有这个文件的对象,包含很多的文件属性。
  • 2)未打开的文件有很多,操作系统要将这些文件存储好,本质上就是对这些文件进行增删查改的操作!

    • 未打开的文件,在磁盘上放着。

本文章目标:针对被打开的文件,进行各种深入剖析。

一、 回忆c语言对文件操作的接口

1.fopen接口和cwd路径

先执行一下代码:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 
  4 int main()
  5 {
  6     FILE* fp = fopen("log.txt","w");
  7     if(fp == NULL)
  8     {
  9         perror("fopen");
 10         return 1;
 11     }
 12     printf("pid: %d\n",getpid());
 13     fclose(fp);
 14     sleep(1000);                                                
 15     return 0;
 16 }

该程序运行起来后,以"w"的方式打开log.txt文件,如果该文件不存在,则会创建一个文件。

运行起来时可以看到该进程的pid已经被打印出来。
ll查看能看到的确存在一个log.txt的文件。
在这里插入图片描述
那为什么是在当前目录下创建log.txt文件呢???
这是因为一个叫做cwd的东西的存在。

在根目录下的proc目录下,有该进程的当前路径。
即通过ls /proc/进程pid -l 可以看到,该运行中的进程的cwd路径!

在这里插入图片描述并且该cwd路径就是可执行程序所在的路径!

cwd:current work directory——当前工作目录!

所以,fopen以写的方式打开文件,如果文件不存在,就会在该进程的cwd路径下创建一个log.txt的文件!

由此可以得出,如果我们自己把该进程的cwd路径改了,那么它就会在更改后的cwd路径下创建log.txt文件!

怎么改?

用一个接口:chdir()即可更改当前的cwd路径。

chdir("/home/dzt/learning");   

在上面代码的基础上,在main函数开头就增加这一句代码后。

运行起来通过查找cwd路径发现,cwd被修改了!
在这里插入图片描述

且在/home/dzt/learning路径下发现:
在这里插入图片描述
真就被创建了一个log.txt文件

且在进程对应的工作目录中,不再有log.txt文件。
在这里插入图片描述

注意:1.chdir也受权限的约束,作为普通用户,不能将路径修改到/home/dzt路径下!
2.如果fopen打开的文件带绝对路径,那就按绝对路径来,如果是相对路径,就按该进程的cwd来!

总结:这个小节讲了复习了fopen函数,并且引入了cwd当前工作目录这个概念!

2.fwrite接口和"w","a"方法

在这里插入图片描述
fwrite的使用方法是:将ptr这个字符串,以size大小,nmemb个长度写入stream文件指针指向的文件中。

在这里插入图片描述
w方法的特点是:如果该文件不存在,会创建一个文件。如果该文件存在,会先将该文件清空,再打开!

注意这里的一个细节:

6     const char* message = "Hello Linux\n";                         
17    fwrite(message,strlen(message),1,fp);

执行该函数fwrite时,是否需要strlen(message)+1

答案是不需要的,+1是为了将字符串后面的’\0’也写入文件中,可是:

字符串以’\0’结尾是c语言的规定,关文件操作什么事?!

所以并不需要+1。

而a方法的作用是,直接在文件的末尾追加字符串。

由此可知,Linux中的 “>” 和 ">>"两个符号的区别一定是一个以"w"方式打开,一个以"a’方式打开的区别!!


3.fprintf接口和三个默认打开的输入输出流(文件)

fprintf接口比我们常见的printf函数多了一个字符f,默认情况下,printf就是向显示器打印数据。

而Linux下一切皆文件,所以显示器也是一个文件。

而fprintf接口,就是向指定的文件中输入数据。

fprintf(stdout,"%s %d\n",message,123);      

在这里插入图片描述

而我们在运行该程序时,会发现显示器中出现了这些信息,这就是被打印到了显示器文件中,而不是打印到其他文件中。

在这里插入图片描述
而这三个标准输入输出流,就是对应的:

键盘文件——stdin
显示器文件——stdout
显示器文件——stderr

一旦c程序运行起来,就会默认打开这三个文件。

二、过渡到系统,认识文件调用

文件其实是在磁盘上的,磁盘是外部设备,访问磁盘文件的本质,其实是访问硬件!

2.1看一看文件的系统调用接口——open

使用man 2 手册进行查找open接口的功能
man 2 open

该函数的功能是:打开/创建一个文件或设备。

在这里插入图片描述

这里多嘴一句:c语言中的fopen函数,实现也是将这个open函数进行封装得来的。

pathname是文件路径,如果传的是相对路径,就是按进程所在的cwd路径为主。
flags是一个标志位:
在这里插入图片描述
这里的标志位有三个:
O_RDONLY:表示只读操作
O_WRONLY:表示只写操作
O_RDWR:表示可读可写

为了更好地进行后面的传参,下面来讲一个比特位传参的方式

看下面的代码:

  1 #include<stdio.h>
  2 
  3 #define ONE (1<<0)
  4 #define TWO (1<<1)
  5 #define THREE (1<<2)
  6 #define FOUR (1<<3)
  7 
  8 void show(int flags)
  9 {
 10     if(flags&ONE)   printf("hello function 1\n");
 11     if(flags&TWO)   printf("hello function 2\n");
 12     if(flags&THREE)   printf("hello function 3\n");
 13     if(flags&FOUR)   printf("hello function 4\n");
 14 
 15 }
 16 
 17 int main()
 18 {
 19     show(ONE);
 20     printf("\n");
 21     show(TWO);
 22     printf("\n");
 23     show(ONE|THREE);
 24     printf("\n");
 25     show(ONE|TWO|THREE|FOUR);                                                                                                               
 26     printf("\n");
 27 
 28     return 0;
 29 }

上图所示的代码定义了几个宏,分别表示(1<<n位)
传参时如果穿过来的flag是ONE,则会打印function1,
如果传的是ONE|TWO|THREE,则传过去的flag的二进制为:111
此时就能够匹配三个if语句,就会打印出三个function。

通过这个例子就可以理解了,open函数中的flags作为一个标志位,未来会传很多比特位为1的宏,如果传多个,就能达到不一样的效果!

下面看这个例子:

1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<string.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 
  8 int main()
  9 {
 10     // pathname, flags, modes
 11     int fd = open("log.txt",O_WRONLY); //采用八进制,默认权限位666
 12 
 13     if(fd < 0)
 14     {                                                                                                                                       
 15         printf("open file error\n");
 16         return 1;
 17     }
 18 
 19     return 0;
 20 }

接下来的操作上打开一个文件,因为传的是相对路径,如果按照c语言的fopen函数,如果该文件不存在,那它就会在该进程的cwd路径下创建log.txt文件。

运行后会发现:居然打开失败了!?

在这里插入图片描述

因为系统的open函数的O_WRONLY是只读的,并没有创建文件的功能!
要想解决这个问题:只需要

int fd = open("log.txt",O_WRONLY|O_CREAT);

增加一个比特位传参即可!
在这里插入图片描述
此时就创建出了一个log.txt文件!

注意:为什么log.txt的权限那么奇怪呢?还是一些随机的权限???

因为open函数中,第三个函数 mode是权限,我们没有传,就默认是随机的!

int fd = open("log.txt",O_WRONLY|O_CREAT, 0666); //采用八进制,默认权限位666      

再传参之后,重新试试,结果如下:

在这里插入图片描述
此时就相对正确了,可是,666权限对应的权限位应该是-rw-rw-rw-
明显不同,这是因为权限掩码的存在,默认的umask是2,根据权限掩码和权限的计算规则:

最终权限 = 起始权限 &~umask)

最终权限就是664–>-rw-rw-r--

如果非要将权限设置成666,就更改权限掩码:

umask(0); 

在全局中有一个umask(2),在该进程中也有一个umask(0),所以该文件创建之后其实是听进程中的umask(0)的,因为**就近原则,局部优先,**进程的umask会影响整个进程,但不会影响全局的。
在这里插入图片描述

此时log.txt的权限就非常正确了


总结:这个小节讲了open函数的三个参数:

pathname , flags , mode

2.2 write系统接口

在这里插入图片描述
write接口是向文件描述符对应的文件中写入。

文件描述符:file descrpitor(fd),也就是open函数的返回值,这个文件描述符就是一个文件的标识。

 8 int main()
    9 {
   10     umask(0);
   11     // pathname, flags, modes
   12     int fd = open("log.txt",O_WRONLY|O_CREAT, 0666); //采用八进制,默认权限位666
   13 
   14     if(fd < 0)
   15     {
   16         printf("open file error\n");
   17         return 1;
   18     }                                                                                                                                     
   19 
   20     const char* mesg = "Hello Linux";
   21     ssize_t id = write(fd,mesg,strlen(mesg));
   22 
   23     return 0;
   24 }

此时,向fd文件描述符对应的文件中写入Hello Linux;
结果显而易见,就不展示了,但是当我们将字符串修改成"aaa"时,结果如下:

20     const char* mesg = "Hello Linux";

在这里插入图片描述

这个结果跟fwrite函数结果完全不同,fwrite函数是每次打开文件都会清空内容再写入。

所以,只需要小小地操作:

12     int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC, 0666); //采用八进制,默认权限位666

在这里插入图片描述

O_TRUNC就是truncate的简写。

通过O_WRONLY|O_CREAT|O_TRUNC选项,就实现了如果文件不存在就创建,如果文件存在就打开并先清空的逻辑!

所以,O_APPEND,就是追加的逻辑!

访问文件的本质

由此可知,c语言,c++,java等任何其他语言,对文件的操作接口的底层一定是对这些open函数,write函数的封装!!!

在这里插入图片描述

可是还有一个问题:open系统调用的返回值是int fd,而fopen函数的返回值是FILE* fp

这两者有什么关系?

每次创建一个进程时,都会在内存中创建一个描述该进程的task_struct对象,包含进程中的各种信息,其中就有一个叫做struct file_struct* files的指针,该指针指向一个struct files struct数组,且该数组中的所有成员类型都是struct file*的指针。

为什么要这样设计呢?

来看右边:

每次打开一个文件时,都会创建一个描述该文件的struct file文件对象,该对象存储文件的各种信息。而该文件对象的地址就恰好被进程中的一个指针数组存储着!!!

在这里插入图片描述

所以,为什么open函数的返回值为int fd这个文件描述符,其实就是进程中维护的指针数组存储该文件对象的下标!!

如果该文件对象的被存储在指针数组的2号下标处,打开文件成功后就返回2!(fd)

当我们尝试着打印该文件的fd时,发现结果是3!

在这里插入图片描述

这恰好证明了, 该文件的文件描述符一定是放在进程管理的文件对象指针数组的3号下标处!!!

可是,为什么是3呢?

因为前面说过,一个进程创建后,会默认打开三个输入输出流(文件)
这三个输入输出流分别是:

stdin stdout stderr

分别对应的下标是:

0 1 2

 10 int main()
 11 {
 12     char buffer[1024];
 13     ssize_t sz = read(0,buffer,sizeof(buffer));
 14     //sz返回读取到的个数
 15     if(sz < 0)
 16     {
 17         perror("read fail");
 18         return 1;
 19     }
 20     buffer[sz] = '\0'; // read是按字节读取,如果想把它识别成字符串,就得主动加'\0'                                                          
 21     printf("%s\n",buffer);

 22 }

如上就是从0号文件中读取数据,放入到buffer数组中。

在这里插入图片描述

运行起来后会发现,结果就是等待输入,等待键盘文件的输入。

read系统接口的注意事项:返回值是返回成功读取到的字符的个数。如果想将读取到的若干字符识别成字符串,需要主动添加’\0’。


下面再看一组测试代码:

  8 int main(){
  9     close(1);
 10 
 12     const char* msg = "Hello Linux\n";
 13     write(1,msg,strlen(msg));
 14     write(2,msg,strlen(msg));                                                                                                               
 15                                       
 16 }     

首先close 1号文件后,运行结果只打印了一行msg代码。

前面说过,1号文件是stdout,对应的是显示器文件,2号文件是stderr,对应的也是显示器文件。它们本质上没有区别,那为什么关闭了1号文件,也就是关闭了显示器文件后,通过2号文件仍然能向显示器中打印呢?

1号文件和2号文件虽然都是显示器文件,但是他们对应的struct file*指针不同,也就是说,有两个指针指向显示器文件。
关闭1号文件的本质是,让1号文件对应的指针置空,同时让显示器文件对应的引用计数减减。这个就是close函数的本质操作。

综上:

C语言中将fd(文件描述符)封装成了FILE的结构体,不止是c语言,在任何其他语言中,只要是文件操作的结构体,就一定封装了fd(文件描述符)


总结

这篇文章讲述了关于文件的基础理解。
针对的是被打开的文件。

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

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

相关文章

Ha-NeRF源码解读 train_mask_grid_sample

目录 背景&#xff1a; &#xff08;1&#xff09;Ha_NeRF论文解读 &#xff08;2&#xff09;Ha_NeRF源码复现 &#xff08;3&#xff09;train_mask_grid_sample.py 运行 train_mask_grid_sample.py解读 1 NeRFSystem 模块 2 forward()详解 3 模型训练tranining_st…

人工智能-多层感知机

隐藏层 该模型通过单个仿射变换将我们的输入直接映射到输出&#xff0c;然后进行softmax操作。 如果我们的标签通过仿射变换后确实与我们的输入数据相关&#xff0c;那么这种方法确实足够了。 但是&#xff0c;仿射变换中的线性是一个很强的假设。 线性模型可能会出错 例如&…

基于ThinkPHP+MySQL实现的通用的PHP网站后台管理系统

caozha-admin 后台管理框架 1.8.3 caozha-admin是一个通用的PHP网站后台管理框架&#xff0c;基于开源的ThinkPHP开发&#xff0c;特点&#xff1a;易上手&#xff0c;零门槛&#xff0c;界面清爽极简&#xff0c;极便于二次开发。 基础功能 1、系统设置 2、管理员管理 3、…

超声波俱乐部分享:百度世界大会点燃AI创业者新希望

10月22日&#xff0c;2023年第十三期超声波俱乐部内部分享会在北京望京举行。本期的主题是&#xff1a;百度世界大会点燃AI创业者新希望。 到场的嘉宾有&#xff1a;超声波创始人杨子超&#xff0c;超声波联合创始人、和牛商业创始人刘思雨&#xff0c;中国国际经济交流中心研…

C++中的强制类型转换方式

在 C 语言中需要进行强制类型转换可以使用以下方式&#xff0c;在 C中当然还是可以使用。 ( 类型 ) 变量 ; 例如&#xff1a; int a 48 ; char * b ( char *)& a ; c除了能使用 c 语言的强制类型转换外&#xff0c;还新增了四种强制类型转换&#xff1a; st…

uni-app 解决钉钉小程序日期组件uni-datetime-picker不兼容ios问题

最近在使用uni-app开发 钉钉小程序 &#xff0c;遇到一个ios的兼容性问题 uni-datetime-picker 组件在模拟器上可以使用&#xff0c;在真机上不生效问题 文章目录 1. 不兼容的写法&#xff0c;uni-datetime-picker 不兼容IOS2. 兼容的写法&#xff0c;使用 dd.datePicker 实现。…

安企神局域网监控软件,员工电脑终端的安全管理软件

安企神局域网监控软件&#xff0c;员工电脑终端的安全管理软件 安企神局域网监控软件下载使用 公司老板其实最怕的就是公司机密遭到泄露&#xff0c;而一般泄露的方法都是通过一些通讯软件而泄露出去的&#xff0c;如微信、qq等等&#xff0c;所以很多老板都想知道有什么软件…

C语言映射表在串口数据解析中的应用

一、映射表在串口数据解析中的应用 1、数据结构 typedef struct {char CMD[CMDLen];unsigned char (*cmd_operate)(char *data); }Usart_Tab; 2、指令、函数映射表 static const Usart_Tab InstructionList[CMDMax] {{"PWON",PowOn},{"PWOFF",PowOff}…

【错误解决方案】ModuleNotFoundError: No module named ‘feather‘

1. 错误提示 在python程序中&#xff0c;尝试导入一个名为feather的模块&#xff0c;但Python提示找不到这个模块。 错误提示&#xff1a;ModuleNotFoundError: No module named feather 2. 解决方案 出现这种情况&#xff0c;有可能是因为你还没有安装这个模块&#xff0c;…

基于ECS搭建云上博客WordPress,使用Apache+MariaDB+PHP环境

文章目录 一、安装Apache二、安装MariaDB数据库三、安装PHP四、安装和配置WordPress 一、安装Apache 执行如下命令&#xff0c;安装Apache服务及其扩展包。 ssh rootECS公网地址yum -y install httpd mod_ssl mod_perl mod_auth_mysql执行如下命令&#xff0c;查看Apache是否…

吴文俊人工智能科学技术奖在哪可以查到?

吴文俊人工智能科学技术奖 “吴文俊人工智能科学技术奖&#xff08;WU WEN JUN AI SCIENCE & TECHNOLOGY AWARD&#xff09;”是我国智能科学技术领域唯一以享誉海内外的杰出科学家、数学大师、人工智能先驱、我国智能科学研究的开拓者和领军人、首届国家最高科学技术奖获…

最新itvbox如意版影视源码,支持苹果CMS,tvbox接口全解版

tvbox是一款现今非常火爆的影视APP壳子&#xff0c;免会员可观看全网所有VIP视频&#xff0c; itvbox就是tvbox的二开版本。支持会员系统、自动注册、登陆、卡密充值、在线购买套餐、试看功能、首页公告、积分兑换、点播、直播、可对接苹果CMS系统、资源站以及tvbox仓库接口等…

Anisble中的任务执行控制

一、循环 1、简单循环 使用loop赋值列表的格式&#xff1a; loop: ##赋值列表 - value1 - value2 - ... {{item}} ##迭代变量名称2、循环散列或字典列表 可以赋予不同的服务不同的状态 - name: create filehosts: 172.25.0.254tasks:- name: file moduleservice:name: &…

Windows电脑怎样修改电脑的名称?

Windows电脑在使用过程中会处于局域网络中&#xff0c;为了让局域网络中其他Windows电脑看到自己的电脑并知识是自己的电脑&#xff0c;可以将电脑名称设置为自己的名字&#xff0c;那怎样设置电脑名称呢&#xff1f; Windows电脑怎样修改电脑的名称&#xff1f; 1、打开Wind…

组件局部注册和全局注册

普通组件的注册使用-局部注册 1.特点&#xff1a; 只能在注册的组件内使用 2.实现效果 3.步骤&#xff1a; 创建.vue文件&#xff08;三个组成部分&#xff09;在使用的组件内先导入再注册&#xff0c;最后使用 4.使用方式&#xff1a; 当成html标签使用即可 <组件名&…

【vtk学习笔记4】基本数据类型

一、可视化数据的基本特点 可视化数据有以下特点&#xff1a; 离散型 计算机处理的数据是对无限、连续的空间进行采样&#xff0c;生成的有限采样点数据。在某些离散点上有精确的值&#xff0c;但点与点之间值不可知&#xff0c;只有通过插值方式获取数据具有规则或不规则的结…

const迭代器与模板构造函数

在自己实现C中list的时候&#xff0c;当实现const迭代器的时候&#xff0c;发现报错了&#xff0c;一直思考到现在 才发现是一个&#xff0c;很简单的问题&#xff0c;但是也让我有了一点感受&#xff0c;我在这里给大家分享一下。文章目录 1.当时遇到的问题2.解决方法3. 自己的…

什么是用户体验测试? 为什么很重要?

在当今数字化时代&#xff0c;用户体验(User Experience&#xff0c;简称UX)已经成为产品成功的关键因素之一。无论是应用程序、网站、硬件设备还是软件&#xff0c;提供出色的用户体验不仅能够吸引更多用户&#xff0c;还能够增加用户满意度&#xff0c;提高品牌忠诚度&#x…

残差网络ResNet

残差网络的提出,是为了解决深度学习中的退化问题。 退化问题指的是随着神经网络层数的增加&#xff0c;网络性能反而逐渐降低的现象。换句话说&#xff0c;当我们不断增加神经网络的层数时&#xff0c;神经网络的训练误差可能会持续下降&#xff0c;但是验证集误差却不断增加&…

全球发布|首个AI视角下的生态系统架构解读—《生态系统架构--人工智能时代从业者的新思维》重磅亮相!

点击可免费注册下载 &#x1f447; 人工智能时代的企业架构师必读系列 《生态系统架构--人工智能时代从业者的新思维》 Philip Tetlow、Neal Fishman、Paul Homan、Rahul著 The Open Group Press 2023年11月出版 这本书可以很好地帮助全球架构师使用人工智能来构建、开发和…