lesson5-基础IO
文件概念
- 文件 = 文件内容 + 属性(也是数据)
- 对文件的所有操作,无外乎是:a.对内容 b.对属性
- 站在系统的角度,能够被input读取,或者能够output写出的设备就叫做文件
- 狭义文件:普通的磁盘文件
- 广义文件:显示器,键盘,网卡,声卡,显卡,磁盘,几乎所有的外设,都可以称之为文件
访问文件
- 访问文件的本质:是进程在访问
- 文件在磁盘(硬件)上放着,而向硬件写入,只有操作系统才有这个权利
- 如果普通用户,也想写入那就必须让OS提供接口----文件类的系统调用接口
关于系统接口
- 一旦使用系统接口,编写所谓的文件代码,就无法再其他平台上直接运行了,
- 而像C++/Java这样的语言,它们就对文件系统接口进行了封装,包括所有访问文件的操作
- C++对所有的平台的代码都实现一遍,通过条件编译,动态裁剪实现封装性
为什么要学习OS层的文件接口
- 不同的语言,有不同的语言级别的文件访问接口(都是不一样的),但是底层都封装了系统接口
- 系统接口只有一个,OS也只有一个,
关于printf向显示器打印
- 显示器是硬件,printf向显示器打印,本质也是一种写入,和磁盘写入到文件中是没有本质区别!
C语言文件接口
当前路径
- 当一个进程运行起来,每个进程都会记录自己当前所处的工作路径,这个路径就叫做工作路径
向文件里写入:fwrite fprintf fputs
- 在fwrite写入时,传的strlen(s1),不需要加一
- \0结尾是C语言的规定,文件不用遵守,文件要保存的是有效数据!
从文件中读取: fgets
- 这里以只读的方式打开
直接使用系统接口
关于系统接口:open
- pathname表示文件名,
- flags是一种标记,标记打开文件的状态
- mode表示没有文件,创建文件时给的初始权限
flags标记
- 系统中的 flags就是宏定义
使用系统接口: open write read close
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{umask(0);// 清空过滤权限//fopen("log.txt", "w");int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);// 写入 创建 清空const char* s = "hello write\n";//const char* s = "aa\n";write(fd, s, strlen(s));flose(fd);//fopen("log.txt", "a");int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_APPEND, 0666);// 写入 创建 追加printf("open success, fd: %d\n", fd1);int fd2 = open("log2.txt", O_WRONLY|O_CREAT|O_APPEND, 0666);// 写入 创建 追加printf("open success, fd: %d\n", fd2);int fd3 = open("log3.txt", O_WRONLY|O_CREAT|O_APPEND, 0666);// 写入 创建 追加printf("open success, fd: %d\n", fd3);int fd4 = open("log4.txt", O_WRONLY|O_CREAT|O_APPEND, 0666);// 写入 创建 追加printf("open success, fd: %d\n", fd4);close(fd1);close(fd2);close(fd3);close(fd4);//fopen("log.txt", "r");int fdd = open("log.txt", O_RDONLY); // 只读char buffer[64];memset(buffer, '\0', sizeof(buffer));read(fdd, buffer, sizeof(buffer));printf("%s", buffer);flose(fdd);return 0;
}
- 我们在应用层看到一个很简单的动作,在系统接口层面甚至OS层面,可能要做非常多的动作!
系统接口的细节,引入fd(文件描述符)
这里借用一下上面程序的结果
- 这里怎么是从3开始的呢,0 1 2 去那哪里了?
- 一个文件打开默认会打开stdin stdout stderr(标准输入 标准输出 标准错误),它们也用0 1 2 表示
文件指针FILE*的认识
- FILE是一个struct结构体(内部有很多成员),是由C标准库提供的
- 而在C文件库函数内部一定要调用系统调用,站在系统角度,它只认识fd,所以可以推出FILE结构体里面必定封装了fd!!!
证明FILE内部封装了fd
fd的细节
fd的理解
- 进程要访问文件,必须先打开文件
- 文件要被访问,前提是加载到内存中,才能被直接访问
- 一般而言 进程 : 打开的文件 = 1 : n,但是如果是多个进程都要打开自己的文件呢?
- 系统中会存在大量的被打开的文件,OS面对如此的文件也要管理起来,通过先描述,再组织
- 在内核中,OS内部会为了管理一个打开的文件,构建一个file结构体
- 创建的struct file的对象,充当一个被打开的文件,如果用的很多,就用双链表组织起来,
- 上面这张是进程和文件之间的关系
- fd在内核中,本质是一个数组下标
文件的分类
磁盘文件(没有被打开)
内存文件(被进程在内存中打开)
对一个内存文件的操作流程
- fopen -> open -> fd -> FILE -> FILE *
- fwrite() -> FILE* -> fd -> write -> write(fd,...) -> 自己执行操作系统内部的write方法 -> 能找到进程的task_struct -> *fs -> files_struct -> fd_array[] -> fd_array[fd] -> struct file -> 内存文件被找到了! -> 操作
对输出重定向的理解
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{close(1);// 这里的fd的分配规则是: 最小的,没有被占用的文件描述符// 0,1,2 -> close(1) -> fd -> 1int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0){perror("open");return 1;}printf("fd: %d\n", fd); // stdout->FILE{fileno=1}->log.txtprintf("fd: %d\n", fd);fprintf(stdout, "hello fprintf\n");const char* s = "hello fwrite\n";fwrite(s, strlen(s), 1, stdout);fflush(stdout); //stdout->_fileno == 1;close(fd); //后面解释return 0;
}
- 不管是输出重定向还是输入重定向,都是在OS内部,更改fd对应的内容指向
dup2:更改fd
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char* argv[])
{if (argc != 2) {return 2;}int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);//int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND);if (fd < 0){perror("open");return 1;}dup2(fd, 1);fprintf(stdout, "%s\n", argv[1]); //stdout->1->显示器return 0;
}
- dup2使old_fd变成new_fd,就相当于更改fd对应的内容指向
理解Linux下一切皆文件
linux是用C语言写的,而C语言要实现面向对象,甚至是运行时多态,必须封装类
但是C语言的struct只能封装成员属性,要封装成员方法,就需要使用函数指针(用来调用成员方法)
- linux的设计哲学:体现在操作系统的软件设计层面
- Linux看待所有硬件都是struct file,通过函数指针调来调去,这是一种统一化
理解缓冲区
- 缓冲区就是一段内存空间
- 存在缓冲区的原因是:为了提高整机效率,提高用户的响应速度,
缓冲区的刷新策略
- 立即刷新
- 行刷新(行缓冲) \n
- 满刷新(全缓冲)
所有设备都是倾向于全缓冲的,特殊情况:a.用户强制刷新(fflush),b.进程退出
缓冲区的位置
- 第一个程序,向显示器打印输出4行文本,
- 向普通文件(磁盘上),打印的时候,变成了7行,其中:
- C语言 IO接口是打印了2次的
- 系统接口,只打印1次和向显示器打印的一样
- 第二个程序,加上了fflush(stdout)强制刷新,向显示器和向普通文件(磁盘上)输出的文本都是4行
解释上面2个程序产生不同结果的原因
- 如果向显示器打印,刷新策略就是行刷新,那么最后执行fork的时候,
一定是函数执行完了 && 数据已经被刷新了,那么fork就无意义 - 如果你对应的程序进行了重定向,要向磁盘文件打印
隐形的刷新策略就变成的了全缓冲,那么\n就无意义 - 第2个程序中的fflush(stdout)强制刷新,不管刷新策略是什么都会被强制刷新
- fork()之前的函数已经执行完了,但是并不代表数据已经刷新了!!
这些数据在当前进程对应的C标准库中的缓冲区中
这些数据也是父进程的数据,fork()创建子进程,是会发生写时拷贝的,这就是为第一个程序向普通文件(磁盘上),打印的时候,变成了7行的原因
缓冲区如果是OS统一提供的,那么我们上面的代码,表现应该是一样,所以是C标准库提供且维护的
用户级缓冲区
C标准库给我们提供的用户级缓冲区,在struct FILE 结构体中,这个结构体中不仅封装了fd
还包含了该文件fd对应的语言层的缓冲区结构
简单设计用户级缓冲区
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <stdlib.h>
#define NUM 1024struct MyFILE_ {int fd;char buffer[1024];int end; //当前缓冲区的结尾
};typedef struct MyFILE_ MyFILE;MyFILE* fopen_(const char* pathname, const char* mode)
{assert(pathname);assert(mode);MyFILE* fp = NULL;if (*mode == 'w') {// 假设是以'w'的方式打开int fd = open(pathname, O_WRONLY | O_TRUNC | O_CREAT, 0666);// 系统接口if (fd >= 0){fp = (MyFILE*)malloc(sizeof(MyFILE));memset(fp, 0, sizeof(MyFILE));fp->fd = fd;}}return fp;
}// C标准库中的实现!
void fputs_(const char* message, MyFILE* fp)
{assert(message);assert(fp);// 把数据存放在缓冲区中strcpy(fp->buffer + fp->end, message); //abcde\0fp->end += strlen(message);printf("%s\n", fp->buffer);//暂时没有刷新if (fp->fd == 0){//标准输入}else if (fp->fd == 1){//标准输出if (fp->buffer[fp->end - 1] == '\n'){write(fp->fd, fp->buffer, fp->end);fp->end = 0;}}else if (fp->fd == 2){//标准错误}else{//其他文件}
}void fflush_(MyFILE* fp)
{assert(fp);if (fp->end != 0){//暂且认为刷新了--其实是把数据写到了内核write(fp->fd, fp->buffer, fp->end);syncfs(fp->fd); //将数据写入到磁盘fp->end = 0;}
}void fclose_(MyFILE* fp)
{assert(fp);fflush_(fp);close(fp->fd);free(fp);
}int main()
{//close(1);MyFILE* fp = fopen_("./log.txt", "w");if (fp == NULL){printf("open file error");return 1;}fputs_("one: hello world", fp);fork();fclose_(fp);
}
对stdout和stderr的补充
- 1和2对应的都是显示器文件
- ./myfile > ok.txt 2>err.txt 将标准输出重定向到ok.txt中,标准错误重定向到err.txt
- ./myfile > log.txt 2>&1 将标准输出和标准错误都重定向到log.txt
理解文件系统
背景知识
- 文件系统中的磁盘->磁盘级文件是没有被打开的文件
- 学磁盘级别的文件的侧重点
- 单个文件角度 -- 这个文件位置?,多大?,其他属性...
- 站在系统角度 -- 一共有多少个文件?,各自的属性在哪?,如何快速找到,..
- 磁盘文件
- 内存 - 掉电易失存储介质
- 磁盘 -- 永久性存储介质 -- SSD,U盘,flash卡,光盘,磁带
磁盘不仅是一个外设,而且还是我们计算机中唯一的一个机械设备
4.磁盘结构
物理结构
- 磁盘的盘面上会存储数据,里面有大量的像磁铁一样的东西,它们是有正负性的
- 先磁盘里写入,本质就是改变磁盘上的正负性,毕竟计算机只认识二进制
存储结构
- 灰色的圆圈叫做磁道,磁道的一部分叫做扇区
在物理上,如何把数据写入到指定的扇区中?
- 通过一中叫CHS的寻址方案,通过这种方案所有的扇区就都能够找到了
- 1.在那一个面上(对应的就是那一个磁头)
- 2.在那一个磁道(柱面)上
- 3.在那一个扇区上
磁盘的抽象(虚拟,逻辑)结构
- 磁盘的存储数据的基本单位是512字节
- 将磁盘的盘面展开,想象成一种线性结构
- 访问一个扇区,只要通过数组的下标就可以了,
对磁盘的管理
- 将数据存储到磁盘中 -> 将数据存储到该数组
- 找到磁盘特定扇区的位置 -> 找到数组特定的位置
- 对磁盘的管理 - > 对数组的管理
对磁盘的管理 -> 对一个小分区(小数组) 的管理
- 虽然磁盘的基本单位是扇区(512字节),但是操作系统(文件系统)和磁盘进行IO的基本单位是:4KB(8*512byte),硬件和软件(OS)进行了解耦!
- 如果基本单位太小了,有可能会导致多次IO,进而导致效率的降低!
- 如果操作系统使用的是和磁盘一样的大小,万一磁盘基本大小变了的话,OS的源代码也必须发生改变,所以IO没有以512字节为基本单位
在Linux在磁盘上存储文件的时候,内容和属性是分开存储的!
- Date blocks:多个4KB(扇区*8)大小的集合,报错的都是特定文件的内容
- inode Table:inode是一个大小为128字节的空间,保存的是对应文件的属性
该块组内,所有文件的inode空间的集合,需要标识唯一性,每一个inode块,都要有一个inode编号!
一般而言,一个文件,一个inode对应node编号 - BlockBitmap:假设有10000+个blocks,10000+比特位:比特位和特定的block是一一对应的,其中比特位为1,代表该block被占用,否则表示可用
- inode Bitmap:假设有10000+个blocks,10000+比特位:比特位和特定的indoe是一一对应的,其中比特位为1,代表该inode被占用,否则表示可用
- GDT(简写): 快组描述符,这个快组多大,已经使用了多少了,有多少个inode,已经占用了多少个,还剩多少,一共多少个block,使用了多少...
- SuperBlock(简写):文件系统的属性信息
- 这些分区使一个文件的信息可追溯,可管理
上面的快组分割成为上面的内容,并且写入相关的管理数据 -> 每一个快组都这么做 -> 整个分区就被写入了文件系统信息(格式化)
- 一个文件"只"对应一个inode属性节点,inode编号,但是一个文件不只有一个block,
文件名 VS inode编号
- 找到文件: inode编号(依托于目录结构) -> 分区特定的bg -> inode -> 属性 -> 内容
- 在linux中inode属性里面,是没有文件名这样的说法的,但是目录也是文件,它也应该有自己的inode,有自己的data block,
- 文件名 : inode 编号存在一种映射关系,互为Key值
- inode是固定的,datablock是固定的!
在linux中删除文件,能恢复出来吗?
- 能恢复出来,这与inode编号有关,如果inode编号,没有被使用,inode和datablock没有被重复占用,就可以恢复删除文件
软硬连接
软连接: ln -s 源文件 目标文件
- 软连接有独立的inode -> 它是一个独立文件
- 特性: 软连接的文件内容,是指向的文件对应的路径!
- 应用: 相当于windows下的快捷方式
硬链接: ln 源文件 目标文件
- 硬连接没有独立的inode -> 它不是一个独立文件
- 创建硬链接时,仅仅只是在指定的目录下,建立了文件名 和 指定inode的映射关系
inode的引用计数
- 这个引用计数表示有多少个文件名与它相关,
- 当我们删除一个文件的时候,并不是把这个文件inode删除,而是将这个文件的inode引用计数--
当引用计数为0的时候,这个文件,才是真正的删除
- 默认创建目录:引用计数(硬链接)默认是2
- 自己目录名:inode
- 自己目录内部: inode
- 实际引用: 在目录的引用计数- 2就是目录里的文件数(进入这个目录,不包括目录中的目录)
动态库.so 与 静态库.a
写一个静态库
- 生成一个静态库的命令: ar -rc lib文件名.a 其他.o文件
- hello这个包里面有.h和静态库
使用静态库
- 第一步就是要把hello这个包拷贝到当前路径,也就是间接性的把这个静态库拷贝当前路径
- gcc 源文件 -I(大写的i) 头文件搜索路径 -L 库文件搜索路径 -l (小写的l)指定静态库
写一个动态库
gcc -c -fPIC 其他.c -o 其他.o
gcc -shared 其他.o -o lib文件名.so
使用动态库
- 动态库是一个独立的库文件,它可以和可执行程序,分批加载!!!
- 当动态库和静态库同时存在时,默认调用动态库
- 如果这时要使用静态库,需要在最后面加上-static
上面的那段代码只是给gcc说了动态库路径,系统加载器不知道,需要给它说明动态库路径
方法一: 环境变量
- export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:要使用的动态库路径
- 不过这种方式重启之后就会被清除
方法二: 新增配置文件
- sudo touch /etc/ld.so.conf.d/文件名.conf
- sudo vim /etc/ld.so.conf.d/文件名.conf ,将自己写的动态库的路径放进去
- sudo ldconfig,更新配置文件
- 这种方式设置之后,重启依旧生效
- sudo rm /etc/ld.so.conf.d/文件名.conf
- sudo ldconfig,清理缓存
方法三: 软连接
- sudo ln -s 动态库的路径 /lib64/lib目标文件名.so
lesson5-基础IO相关推荐
- java中sum=a+aa+aaa_Java面向对象基础IO系统
Java面向对象基础–IO系统 一.IO 输入:input 输出:output Java的IO主要包含三个部分 流式部分:IO的主题部分 非流式部分:主要包含一些辅助流式部分的类 文件读取部分的与安全 ...
- Java基础-IO流对象之数据流(DataOutputStream与DataInputStream)
Java基础-IO流对象之数据流(DataOutputStream与DataInputStream) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.数据流特点 操作基本数据类型 ...
- Linux中的基础IO(二)
Linux中的基础IO(二) 文章目录 Linux中的基础IO(二) 一.基本接口 二.文件描述符 三.文件描述符的分配规则 四.重定向 五.dup2系统调用 六.minishell 一.基本接口 i ...
- Linux中的基础IO(一)
Linux中的基础IO 文章目录 Linux中的基础IO 一.C语言中的文件接口 二.随机读写数据文件 三.文件读写的出错检测 一.C语言中的文件接口 写在前面 计算机文件是以计算机硬盘为载体存储在计 ...
- Linux系统编程25:基础IO之亲自实现一个动静态库
本文接:Linux系统编程24:基础IO之在Linux下深刻理解C语言中的动静态库以及头文件和库的关系 文章目录 A:说明 B:实现静态库 C:实现动态库 A:说明 前面说过,库其实就是头文件和和.a ...
- 黑马 程序员——Java基础---IO(下)
黑马程序员--Java基础---IO(下) ------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------ 一.概述 Java除了基本的字节流.字符流之外,还提供 ...
- 基础IO(文件接口、安装内核源码超详细步骤图解、静态库与动态库)
基础IO C语言的文件操作接口 fopen fclose fread fwrite fseek 系统调用文件接口 open close read write lseek 安装内核源码 文件描述符&am ...
- Java基础IO流(二)字节流小案例
JAVA基础IO流(一)https://www.cnblogs.com/deepSleeping/p/9693601.html ①读取指定文件内容,按照16进制输出到控制台 其中,Integer.to ...
- C++基础——IO库基础
C++学习--IO库基础 IO处理通过继承封装了不同输入输出流读取的细节,其继承关系为 #mermaid-svg-vfNR5uEIxowMKr5Z .label{font-family:'trebuc ...
- Linux系统:基础IO
基础IO 1. 内存文件 1.1 理解内存文件 文件是内容和属性的集合,文件不仅有内容,还有各种属性.任何文件操作都可以分为对文件内容的操作和对文件属性的操作. 文件没被打开时是放在磁盘上的,想要打开 ...
最新文章
- 数据中心内虚拟机迁移带来的网络技术难题
- c++ 数据类型转换笔记
- Linux中vi的常用命令和快捷键使用
- TensorRT学习笔记4 - 运行sampleGoogleNet
- C++之静态成员变量和静态成员函数
- F-Secure Client Security 注册机
- With you With me
- vue中用数组语法绑定class
- grub4dos命令引导自定义映像_电脑C盘过小,教你在任意磁盘下安装windows系统,应用引导即可...
- 第十八届绵竹年画节开幕 大巡游展示清末年画《迎春图》场景
- 国内统一Android应用市场,最全最干净的安卓应用市场
- 2022年的1024
- 从菜鸟到完全学不会二维傅立叶在图像处理算法中的应用【老司机教你学傅立叶】
- 必要的系统组件未能正常运行,请修复Adobe Flash Player
- 【Python表白小程序】表白神器——赶紧收藏起来~
- JHU计算机专业学费,约翰霍普金斯大学学费多少 贵不贵
- uni-app获取设备的唯一标识
- 中国数字地球行业发展态势与投资前景展望报告(新版)2022-2027年
- RSA算法和DH算法的区别
- FPGA[视频+文档+例程]170G相关资料放送(持续更新)
热门文章
- 渣本全力以赴33天,四面阿里妈妈(淘宝联盟),拿下实习岗offer
- 谈下自己的认识只掌握一门语言
- 青岛大学计算机专硕分数线,2020青岛大学研究生分数线汇总(含2016-2019历年复试)...
- Unity 项目 导出到手机后像素特别低 图片模糊解决
- 正则表达式 要求只能有汉字和数字(不能全为数字)(至少5个字符)
- 【容易打工网】于千万人之中,你是匠人
- 电子阅读+社交分享 QQ阅读中心被看好
- leetcode|割冷冻韭菜的最佳时机
- Elasticsearch(ES6)------(5)kibana的es查询、mysql查询转换和对应javaAPI使用(一)
- 日历怎样设置每月提醒