19 文件接口

文件概念

文件指的是文件内容+属性,对文件的操作无外乎就是对内容或者属性的操作

为什么平时不用文件接口

我们运行程序访问文件,本质是进程在访问文件,向硬件写入内容,只有操作系统有这个权限。普通用户想写入内容呢?所以操作系统提供了文件类的系统调用,为什么我们没有接触过

一个是因为比较难,c语言这些对接口做了封装,为了更好的使用,这也导致了不同的语言有不同的文件访问接口,但是都是对系统接口的封装。这样的os文件接口只有一套,因为os只有一个

另一个是方便跨平台,每个os都有不同的接口,一旦使用了系统接口编写代码,就无法在其他平台运行,不具备跨平台性。各类语言把平台的代码都实现一遍,条件编译,动态裁剪,这样到不同的平台都可以运行

一切皆文件(感性)

显示器也是硬件,向显示器打印也是一种写入,和磁盘写入文件没有本质区别
曾经理解的文件的操作就是read和write。而显示器的printf是一种write,键盘的scanf是一种read,站在内存的角度,都是input和output
在这里插入图片描述

什么叫做文件?
站在系统的角度,能够被input读取,或者能够output写出的设备叫做文件
狭义的文件:普通的磁盘文件
广义的文件:显示器,键盘,网卡,声卡,显卡,磁盘,几乎所有外设,都可以被称为文件

fopen函数

在这里插入图片描述

权限说明

在这里插入图片描述
r+和w+都为可读可写,区别为文件不在会不会创建文件
w写的方式每次都会清空文件内容,a会在原有内容后面追加

当前路径

一个w权限打开文件的程序,文件会创建在哪里

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>int main()
{FILE* fd = fopen("log.txt", "w+");if (fd == NULL){perror("fopen");return 0;}fclose(fd);return 0;
}

在这里插入图片描述
log文件创建在了代码源文件的地方,但当把test程序放在上级目录,又会生成在上级目录里。也就是会和程序在同一个目录吗?

将程序改为死循环,进程里查找这个程序,用id找到进程目录
在这里插入图片描述
exe代表可执行程序在磁盘的路径
cwd是进程的当前工作路径

当一个进程启动的时候,会记录这两个路径。进程知道程序的路径,在cwd后面加上需要创建的文件,就是文件创建的路径

c语言函数

在之前学过的写文件函数中,有三个,用这些函数向log文件里写入内容

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
int fprintf(FILE *stream, const char *format, …);
int fputs(const char *s, FILE *stream);

int main()
{FILE* fd = fopen("log.txt", "w+");if (fd == NULL){perror("fopen");return 0;}char* str1 = "hello fwrite\n";fwrite(str1, strlen(str1), 1, fd);char* str2 = "hello fprintf\n";fprintf(fd, "%s", str2);char* str3 = "hello fputs\n";fputs(str3, fd);\fclose(fd);return 0;
}

c语言字符串规定最后必须是\0,上面的strlen需要加1保存\0吗?
不需要,\0是c语言的规定,写入文件后,文件不需要遵守,只保存有效数据,读的时候也只读有效数据

在这里插入图片描述

将三个写入注释掉,只用w方式打开文件看看
在这里插入图片描述

文件什么内容都没有输出,w权限打开文件先清空原内容,才会写入。情况文件的功能就可以这样,w权限打开直接关闭

当打开权限改为a后,不会情况内容,会往后面追加
在这里插入图片描述

实现cat命令

利用main传入参数,作为打开的文件,然后用r权限读取内容打印输出

int main(int argc, char* argv[])
{if (argc != 2){printf("参数过少\n");return 0;}FILE* fd = fopen(argv[1], "r");if (fd == NULL){perror("fopen");return 0;}//按行读char line[64];while (fgets(line, sizeof(line),fd) != NULL)  //fgets,string,自动加\0{// printf("%s", line);fprintf(stdout, "%s", line);}fclose(fd);return 0;
}

在这里插入图片描述

fopen 以w方式打开,默认先情况文件
fopen 以a方式打开,追加,不断向文件最后新增内容

上面的fprintf传入的stdout是标准输出流,将内容打印在显示器上,一个进程默认会打开三个流,标准输入,输出和错误,键盘和显示器都是硬件,是怎么通过FILE*写入到硬件上的

stdin
stdout
stderr

系统接口

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

这些宏其实都是一个个标志位,可以打开查看

vim /usr/include/asm-generic/fcntl.h

在这里插入图片描述

如何给函数传标志位

上面的全大写的参数就是宏,open函数控制打开方式的参数只有一个int,这一个标志位int就可以实现各种功能的选择。怎么样用宏控制函数的不同功能?

#define ONE 0x1
#define TWO 0x2
#define THREE 0x4
void show(int flag)
{if (flag & ONE){printf("ONE\n");}if (flag & TWO){printf("TWO\n");}if (flag & THREE){printf("THREE\n");}
}
int main()
{show(ONE);show(TWO);show(ONE | TWO);show(ONE | TWO | THREE);return 0;
}

定义了三个宏,二进制分别为只有每一位有1,show函数通过和每个标志位&判断,可以得到有没有这个参数。传入的时候想传多个参数或以下宏参数,这样就可以只用一个int传入多个标志参数,实现不同功能
在这里插入图片描述

返回值

为了研究open的返回值是什么意思,我们像开始一样写一个简单的打开文件,然后输出返回值
在这里插入图片描述

报错没有这个文件。之前的fopen没有文件会自动创建,这里的系统接口并不会。在应用层看到的一个简单的动作,在系统接口层面甚至os层面,可能需要很多动作才能完成

参数只有一个O_CREAT,文件不存在会创建,所以加上该参数
在这里插入图片描述

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd = open("log.txt", O_WRONLY|O_CREAT);if (fd < 0){perror("open");return 1;}printf("fd = %d", fd);return 0;
}

文件是创建成功了,可是发现权限是随机的。这时候就需要用open函数第二个形式,加入权限的参数。第一个形式一般用来读取文件
在这里插入图片描述

s,表示set UID或set GID。位于user或group权限组的第三位置。如果在user权限组中设置了s位,则当文件被执行时,该文件是以文件所有者UID而不是用户UID 执行程序。如果在group权限组中设置了s位,当文件被执行时,该文件是以文件所有者GID而不是用户GID执行程序。

int fd = open(“log.txt”, O_WRONLY|O_CREAT, 0666); //rw- rw- rw-

在这里插入图片描述

发现权限还是和设置的不一样,这时因为有默认的umask默认掩码的存在,可以用函数设置掩码
在这里插入图片描述

在打开文件前先设置uamsk,设为0
在这里插入图片描述
在这里插入图片描述

返回值是3,打开成功,小于0才是失败。那么012去哪了,为什么是3

先看看打开后怎么关闭

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

close

在这里插入图片描述

write

在这里插入图片描述

往打开的文件里写入一个字符串

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{umask(0);int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);if (fd < 0){perror("open");return 1;}char* str = "hello write\n";write(fd, str, strlen(str));printf("fd = %d", fd);close(fd);return 0;
}

在这里插入图片描述

再写入aa看看结果
在这里插入图片描述
并不像w的权限一样会清空写入,而是从开始位置覆盖
有一个选项可以清空内容
在这里插入图片描述

加入选项后就会清空内容
在这里插入图片描述

a的权限追加实际上是把情况换为APPEND
在这里插入图片描述

在这里插入图片描述

read

在这里插入图片描述
返回值是实际读到的字符串个数
修改代码为从文件读取内容,read读取后不会自动添加\0,需要自己添加

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{umask(0);int fd = open("log.txt", O_RDONLY, 0666);if (fd < 0){perror("open");return 1;}// char* str = "aa\n";char buff[64];memset(buff, '\0', sizeof(buff));  //read不会加\0// write(fd, str, strlen(str));read(fd, buff ,sizeof(buff));printf("%s\n", buff);printf("fd = %d\n", fd);close(fd);return 0;
}

在这里插入图片描述

open的返回值

认识返回值之前,回顾一下操作系统的图
在这里插入图片描述

文件描述符fd

创建方式打开四个文件,看看它们的返回值

int main()
{umask(0);int fd1 = open("log.txt", O_WRONLY|O_CREAT, 0666);int fd2 = open("log1.txt", O_WRONLY|O_CREAT, 0666);  int fd3 = open("log2.txt", O_WRONLY|O_CREAT, 0666);  int fd4 = open("log3.txt", O_WRONLY|O_CREAT, 0666);  if (fd1 < 0){perror("open");return 1;}printf("fd1 = %d\n", fd1);printf("fd2 = %d\n", fd2);printf("fd3 = %d\n", fd3);printf("fd4 = %d\n", fd4); close(fd1);close(fd2);close(fd3);close(fd4);return 0;
}

在这里插入图片描述
返回值从3开始,依次增加。既然这样,那么0,1,2这三个去哪了。前面我们说过,进程会默认打开三个文件,stdin,stdout,stderr,类型是FILE*,这三个刚好对应这里的0,1,2
在这里插入图片描述

怎么证明上面的说法,可以用两次输出到标准输出的函数对比

int main()
{fprintf(stdout, "%s", "hello fprintf\n");write(1, "hello write\n", strlen("hello write") + 1);return 0;
}

在这里插入图片描述
1也实现了输出到stdout相同的结果,所以三个标准文件刚好对应了前三个数

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

FILE*

fopen的返回值是FILE*,,是一个结构体,库函数内部一定调用了系统调用,系统角度只认fd,不认FILE,所以FILE内部一定对fd进行了封装

int main()
{printf("%d\n",stdin->_fileno);printf("%d\n", stdout->_fileno);printf("%d\n", stderr->_fileno);return 0;
}

在这里插入图片描述

打印出了stdin,stdout,stderr的三个值,正好是对应的三个文件标识符

fd是什么

进程要访问文件,必须先打开文件,一个进程可以打开多个文件,一般而言,进程:打开的文件都是1:n的。文件要被访问,就要加载到内存中,进如果多个进程都打开自己的文件呢?系统中就会存在大量被打开的文件,os要把这些文件管理起来,需要先描述,再组织。内核中构建了file的文件结构,包含了文件的属性和下一个文件的地址

在这里插入图片描述

用双链表将所有的文件组织了起来,那么怎么管理和访问呢?
进程的PCB结构里面保存文件的数组下标,所有文件结构的地址保存在一个数组中,open打开一个文件后先创建这个文件结构,在数组里找一个没有用的下标写入,然后返回下标,fd在内核中本质就是数组下标,进程的PCB保存了指针,指向了这个文件数组
在这里插入图片描述

文件fd结构

在这里插入图片描述
PCB里保存了files的指针,指向的结构体里保存了一个fd_array数组,类型是file的数组,这个file就保存了文件的所有属性。所以fd的本质就是fd_array的下标,通过fd就可以索引到进程的每个文件

在这里插入图片描述

对于一个文件的操作,系统内部其实是做了很多工作才找到了对应的文件

fd分配规则

关闭fd为0的文件,然后打开创建一个文件,输出fd看看是多少

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{close(0);int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);printf("%d\n", fd);return 0;
}

在这里插入图片描述

关闭后分配的fd是0
创建文件时,每次都在文件下标数组里遍历,从0开始,找到没有用的返回,所以关掉0后返回了0

重定向

如果我们关闭1的文件,然后创建一个文件,再往标准输出打印内容呢?

int main()
{close(1);int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);printf("%d\n", fd);printf("%d\n", fd);printf("%d\n", fd);fprintf(stdout, "%d\n", 33);fwrite("hello\n",6, 1, stdout);return 0;
}

在这里插入图片描述

当打开文件的下标返回了1后,本来往标准输出打印的东西都打印到了文件里,这个现象就叫做重定向

进程会默认打开3个文件,再次打开文件时会在file_struct里遍历,找到没有使用的下标填入这个文件的地址,因为关闭了1下标,所以就将地址填入了1下标。linux下一切皆文件,无论是键盘还是显示器,调用库函数,向stdout打印,并不关心stdout里面是什么,只知道里面封装了文件标识符的值是1,write也只是向1里面的文件打印,所以本应该显示到屏幕里的内容却打印到了文件里

在这里插入图片描述

事实上重定向并不是这样先关闭再打开写入实现的,只是利用了fd分配的规则实现了功能相同的重定向

dup2系统调用

函数原型

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

函数描述
在这里插入图片描述

newfd作为oldfd的拷贝,意思就是newfd会和oldfd的内容一样,用old拷贝new

这里面有oldfd和newfd,意思就是替换fd里面的内容,假设文件下面是3,想把输出重定向到3里,哪个是old,哪个是new?只需要想清楚1里面的要和3里面的一样才可以重定向,所以1就是new,3就是old

重写一下前面的重定向功能

int main(int argc, char* argv[])
{int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);if (fd < 0){perror("open");return 0;}dup2(fd, 1);// printf("hello dup2\n");fprintf(stdout, "%s\n", "hello dup2");//close(fd);return 0;
}

将内容重定向输入到log.txt文件中

虚拟文件系统

虚拟文件系统(VFS)是由Sun microsystems公司在定义网络文件系统(NFS)时创造的。它是一种用于网络环境的分布式文件系统,是允许和操作系统使用不同的文件系统实现的接口。

再进一步理解一切皆文件,这时linux设计的哲学,体现在操作系统的软件设计层面
linux是用c语言写的,如何用c语言实现面向对象,甚至运行时多态

我们都知道类有成员属性和方法,struct可以保存属性,那怎么保存方法。可以利用函数指针来指针一个个功能函数
在这里插入图片描述

在驱动层面,底层不同的 硬件都是外设。对应不同的操作方法,每一个设备的核心访问函数,都可以是read,write,也就是I/O。所有的设备都有自己的read和write,但是,代码的实现一定是不一样的。文件结构里只需要保存函数指针就可以指向不同设备的功能,这就是Os和驱动的耦合

在这里插入图片描述

在linux的文件结构里,有一个结构体指针,指向的结构里面保存很多功能函数的函数指针,用来指向和调用不同的文件功能。这就是一切皆文件的设计和管理模式

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

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

相关文章

【一】学习TDengine-总结新技术学习的思考

学习TDengine-总结新技术学习的思考 概要 因业务场景需要我们开始接触时序数据库&#xff0c;于是开始根据以往的学习经验着手熟悉这一项新技术&#xff0c;学习也是一种技能&#xff0c;成功的人越容易成功&#xff0c;因为他们掌握了一套成功的方法&#xff0c;这里提到学习经…

蓝桥杯第十三届电子类单片机组决赛程序设计

前言 一、决赛题目 1.比赛题目 2.题目解读 二、功能实现 1.关于定时器资源 1&#xff09;超声波和NE555需要的定时器资源 2&#xff09;定时器2 2.单位切换 3.数据长度不足时&#xff0c;高位熄灭 4.AD/DA多通道的处理 5.PWM输出 6.长按功能的实现 三、完整代码演…

【QT】pro文件里添加又删除LIBS不影响运行的原因

我发现个问题啊&#xff0c;如果运行项目&#xff0c;发现报错&#xff0c;缺少某dll&#xff0c;接着你在pro文件里加上win32:LIBS -lOpengl32&#xff08;举个例子&#xff09;&#xff0c;接着可以运行了&#xff0c;接着把这行删掉&#xff0c;再运行&#xff0c;仍然可以…

瑞_23种设计模式_访问者模式

文章目录 1 访问者模式&#xff08;Visitor Pattern&#xff09;1.1 介绍1.2 概述1.3 访问者模式的结构1.4 访问者模式的优缺点1.5 访问者模式的使用场景 2 案例一2.1 需求2.2 代码实现 3 案例二3.1 需求3.2 代码实现 4 拓展——双分派4.1 分派4.2 动态分派&#xff08;多态&am…

安卓刷机fastboot分段传输

win10 fastboot 无法识别&#xff0c;驱动下载地址GitHub - xushuan/google_latest_usb_driver_windows 把inf文件更新到设备管理器驱动更新即可 问题 archive does not contain super_empty.img Sending vbmeta_a (4 KB) OKAY [ 0.117s] Writing …

antd+Vue 3实现table行内upload文件图片上传【超详细图解】

目录 一、背景 二、效果图 三、代码 一、背景 一名被组长逼着干前端的苦逼后端&#xff0c;在一个晴天霹雳的日子&#xff0c;被要求前端订单产品实现上传产品图片并立刻回显图片。 二、效果图 三、代码 <template><a-table :dataSource"dataSource" :c…

使用单点登录(SSO)如何提高安全性和用户体验

什么是单点登录&#xff08;SSO&#xff09; 对于所有大量采用云应用程序的组织来说&#xff0c;有效的身份管理是一个巨大的挑战&#xff0c;如果每个 SaaS 应用程序的用户身份都是独立管理的&#xff0c;则用户必须记住多个密码&#xff0c;技术支持技术人员在混合环境中管理…

官网下载IDE插件并导入IDE

官网下载IDEA插件并导入IDEA 1. 下载插件2. 导入插件 1. 下载插件 地址&#xff1a;https://plugins.jetbrains.com/plugin/21068-codearts-snap/versions 说明&#xff1a;本次演示以IDEA软件为例 操作&#xff1a; 等待下载完成 2. 导入插件 点击File->setting->Pl…

Linux下使用C语言实现高并发服务器

高并发服务器 这一个课程的笔记 相关文章 协议 Socket编程 高并发服务器实现 线程池 使用多进程并发服务器时要考虑以下几点&#xff1a; 父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)系统内创建进程个数(与内存大小相关)进程创建过多是否降低整体…

docker从入门到熟悉

一、什么是docker&#xff1f; Docker是一个用于开发&#xff0c;交付和运行应用程序的开放平台。Docker使您能够将应用程序与基础架构分开&#xff0c;从而可以快速交付软件。借助Docker&#xff0c;您可以以与管理应用程序相同的方式来管理基础架构。通过利用Docker的快速交付…

Java——数组练习

目录 一.数组转字符串 二.数组拷贝 三.求数组中元素的平均值 四.查找数组中指定元素(顺序查找) 五.查找数组中指定元素(二分查找) 六.数组排序(冒泡排序) 七.数组逆序 一.数组转字符串 代码示例&#xff1a; import java.util.Arrays int[] arr {1,2,3,4,5,6}; String…

政安晨:【深度学习神经网络基础】(三)—— 激活函数

目录 线性激活函数 阶跃激活函数 S型激活函数 双曲正切激活函数 修正线性单元 Softmax激活函数 偏置扮演什么角色&#xff1f; 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 政安晨的机器学习笔记 希望政安晨的博客能够对您有所裨…

超越ChatGPT,国内快速访问的强大 AI 工具 Claude

claude 3 opus面世后&#xff0c;网上盛传吊打了GPT-4。网上这几天也已经有了许多应用&#xff0c;但竟然还有很多小伙伴不知道国内怎么用gpt&#xff0c;也不知道怎么去用这个据说已经吊打了gpt-4的claude3。 今天我们想要进行的一项尝试就是—— 用claude3和gpt4&#xff0c…

GeoServer:忘记密码重置

操作步骤 1. 找到data_dir/security/usergroup/default目录下的users.xml文件&#xff0c; 2.修改password为plain:geoserver&#xff0c; 这里无论原来的密码是什么&#xff0c;改为plain:geoserver之后&#xff0c;就可以通过admin&#xff1a;geoserver默认账户密码登录了。…

[计算机效率] 鼠标手势工具:WGestures(解放键盘的超级效率工具)

3.22 鼠标手势工具&#xff1a;WGestures 通过设置各种鼠标手势和操作进行绑定。当用户通过鼠标绘制出特定的鼠标手势后就会触发已经设置好的操作。有点像浏览器中的鼠标手势&#xff0c;通过鼠标手势操纵浏览器做一些特定的动作。这是一款强大的鼠标手势工具&#xff0c;可以…

2024年能源环境、材料科学与人工智能国际会议(ICEEMSAI2024)

2024年能源环境、材料科学与人工智能国际会议(ICEEMSAI2024) 会议简介 2024国际能源环境、材料科学和人工智能大会&#xff08;ICEEMSAI 2024&#xff09;主要围绕能源环境、物质科学和人工智慧等研究领域&#xff0c;旨在吸引能源环境、先进材料和人工智能专家学者、科技人员…

C++入门语法(命名空间缺省函数函数重载引用内联函数nullptr)

目录 前言 1. 什么是C 2. C关键字 3. 命名空间 3.1 命名空间的定义 3.2 命名空间的使用 4. C输入和输出 5. 缺省函数 5.1 概念 5.2 缺省参数分类 6. 函数重载 6.1 概念 6.2 为何C支持函数重载 7. 引用 7.1 概念 7.2 特性 7.3 常引用 7.4 引用与指针的区别 7…

最近一些前端面试问题整理

最近一些前端面试问题整理 4月8号1. TS 中的 类型别名 和接口的区别是什么&#xff1f;2. 什么是深拷贝和浅拷贝&#xff1f;深浅拷贝的方法有哪些&#xff1f;浅拷贝&#xff08;Shallow Copy&#xff09;深拷贝&#xff08;Deep Copy&#xff09;区别总结 3. 使用 JSON.strin…

Python中Python-docx 包的run介绍

先对run做一个简单地介绍。每个paragraph对象都包含一个run对象的列表。举例&#xff1a; 这是一个简短的段落。 from docx import Document doc Document("1.docx") #上面这段话保存在1.docx中 print("这一段的run个数是&#xff1a;",len(doc.paragr…

GitHub repository - Code - Issues - Pull Requests - Wiki

GitHub repository - Code - Issues - Pull Requests - Wiki 1. Code2. Issues3. Pull Requests4. WikiReferences 1. Code 显示该仓库中的文件列表。仓库名下方是该仓库的简单说明和 URL. 2. Issues 用于 BUG 报告、功能添加、方向性讨论等&#xff0c;将这些以 Issue 形式进…