文件系统和目录
文件系统和目录
理论
我们前面介绍过(Computer.Science.Illuminated.7th.CN.计算机科学概论第五章)冯·诺伊曼体系结构包含五个构件:
- 存放数据和指令的内存单元
- 对数据执行算术和逻辑运算的算术/逻辑单元
- 把数据从外部世界转移到计算机中的输入单元
- 把结果从计算机内部转移到外部世界的输出单元
- 担当舞台监督,确保其他部件都参与了表演的控制单元
我们也介绍过(Computer.Science.Illuminated.7th.CN.计算机科学概论第十章)操作系统的两个重要角色:
- 管家婆:通过文件、虚存、进程三个概念管理计算机的硬件
- 服务生:通过GUI、shell、系统调用等提供服务。
文件
下面我们介绍文件相关的几个概念,这里的文件指的是磁盘文件,后面我们可以看到在Linux中,设备也被抽象成文件了,访问设备文件和磁盘文件可以使用相同的API:
- 文件(file)∶数据的有名集合,用于组织二级存储设备。
- 文件系统(file system)∶操作系统为它管理的文件提供的逻辑视图。
- 目录(directory)∶文件的有名分组。
文件可以分为二进制文件和文本文件两种类型:
- 文本文件(text file)∶包含字符的文件。
- 二进制文件(binary file)∶包含特定格式的数据的文件,要求给位串一个特定的解释。
大家注意:“文本文件”和“二进制文件”会令人产生误解。听起来就像文本文件中的信息不是以二进制数据的形式存储的。其实计算机上的所有数据最终都是以二进制数字存储的。
上面说的类型指的是格式化位串的方式:
- 文本文件:如8位或16位的位块将被解释为字符
- 二进制文件:用专用的格式组织。
文件还可以根据应用进行分类:
- 文件类型(file type)∶文件(如Java程序或Microsoft文档)中存放的关于类型的信息。
- 文件扩展名(file extension)∶文件名中说明文件类型的部分。
大家注意:Windows下扩展名跟文件类型绑定,Linux文件类型跟扩展名没有直接关系。
文件可以进行如下操作:
- 创建文件
- 删除文件
- 打开文件
- 关闭文件
- 从文件中读取数据
- 把数据写入文件
- 重定位文件中的当前文件指针
- 把数据附加到文件结尾
- 删减文件(删除它的内容)
- 重命名文件
- 复制文件
- ...
在操作系统中进行文件访问有两种方式:
- 顺序文件访问(sequential file access):把文件看作一种线性结构。这要求按顺序处理文件中的数据。读写操作根据读写的数据量移动当前文件指针。有些系统允许把文件指针重置到文件的开头,还允许向前或向后跳过几个记录
- 直接文件访问(direct file access):文件会被概念性地划分为带编号的逻辑记录。直接访问允许用户指定记录编号,从而把文件指针设置为某个特定的记录。
操作还要保护文件,比如每个文件都由一个特定用户所拥有,通常是文件的创建者。Owner通常具有文件的最高访问许可。一个文件可能具有一个相关的组名,分组只是一个用户列表。一个关联组中的用户都具有Group 许可。
目录
目录:目录是文件的有名集合,是一种按照逻辑方式对文件分组的方法。
- 大多数操作系统都用文件表示目录。目录文件存放的是关于目录中的其他文件的数据。
- 对于任何指定的文件,目录中存放有文件名、文件类型、文件存储在硬盘上的地址以及文件的当前大小。此外,目录还存放文件的保护设置的信息,以及文件是何时创建的,何时被最后修改的。
一个文件目录还可以包含另一个目录。包含其他目录的目录叫作父目录,被包含的目录叫作子目录。
目录树(directory tree)∶展示文件系统的嵌套目录组织的结构
根目录(root directory)∶包含其他所有目录的最高层目录。
工作目录(working directory)∶当前活动的子目录。
路径(path):文件或子目录在文件系统中的位置的文本名称。
绝对路径(absolute path):从根目录开始,包括所有后继子目录的路径。
相对路径(relative path):从当前工作目录开始的路径。
文件系统相关Linxu命令
我们在openEuler操作系统上通过Linux命令来说明上述概念,我们通过一个OpenSSL对文件进行加解密流程说明。
我们打开openEuler系统:
我们使用mkdir和cd命令新建一个目录,并进入这个目录。
我们使用mkdir再创建三个文件,使用tree命令可以查看目录树结构。
我们使用cd linux进入linux目录,注意linux是相对路径,我们可以通过pwd查看绝对路径,pwd就是以绝对路径的方式显示当前工作目录:
我们通过touch命令新建一个文本文件plain.txt,通过echo命令输入要加密明文内容。我们可以通过cat命令和od命令查看文件内容。通过od命令我们可以看到文本文件也是以二进制方式存储的。
我们还可以通过file命令查看文件的类型,使用stat命令查看文件的属性。stat中的大部分内容可以通过ls -l
命令显示。
我们可以通过ls -a看到两个隐藏的目录名,其中.
表示当前目录,..
表示父目录。
我们用OpenSSL命令对明文plain.txt加密,得到密文ciphertext.bin 我们使用的是国密算法sm4. 注意Linux下不需要文件后缀的,我们这有个后缀bin,是提醒大家这是个二进制文件,file命令也显示了这一点。我们用cat命令查看密文内容时会发现都是乱码。我们可以使用od命令查看密文的每个字节都是什么。掌握od命令对大家来说非常有必要,比如后面学习密码学,很多同学大四了看到乱码都不知道如何查看。
我们用OpenSSL命令对密文ciphertext.bin解密得到plain2.txt加密, 我们使用的是国密算法sm4.注意加密密钥(-K
)和初始向量(-iv
)要和加密过程一致。
我们对目录,文件还有其他操作,涉及的Linux命令希望大家参考别出心裁的Linux命令学习法、https://www.cnblogs.com/rocedu/p/14891816.html自学。
考虑到后面的教学内容,我们补充一下如何把数字123(大家一定要深刻理解数字123和字符串(文本)“123”的不同)通过echo命令写入到二进制文件中。注意我们写入的是123的16进制值,最后我们使用bc命令把16进制转为10进制,确认无误。
C程序设计
我们把明文和密文拷贝到C文件夹,注意我们这使用的是相对路径。
我们现在思考一下,如何用C语言编程读取明文pain.txt,密文ciphertext.bin中的内容?参考别出心裁的Linux命令学习法,我们可以搜索相关的C函数的。大家需要了解到C语言程序对文件的处理采用文件流(stream)的形式,程序运行在内存中的,而文件存储在外部存储介质上,例如硬盘、光盘、U盘等。可以这样想像,在程序运行时,就会在指定的文件之上建立一条管道,当读取文件时,数据就会像流水一样从文件端流向程序端,而写入文件时,数据就会像流水一样从程序端流向文件端。从文件端向程序端的文件流称为输入流,从程序端向文件端的文件流称为输出流
读取文本文件和二进制文件的通用方式
我们使用 "man -k stream | grep read | grep 3" 就可以搜到读取文件流的函数 fread。
我们查看一下fread的帮助文档,可以看到调用这个函数需要#include <stdio.h>
,但是fread函数中的FILE * stream
参数是个什么东西?
我们可以看到man fread
给出了使用fread的范例,调用fread是要和fopen和fclose配套使用的,大家要熟记这个应用范式:首先打开文件,然后读写文件,然后关闭文件。我们后面读写明文、密文文件,就可以参考这个代码,在读懂代码的基础上修改代码达到自己的预期目的,是我们学习编程非常重要的方法和能力。
我们可以看到,调用fread需要传递参数指明读取的数据长度(字节数),我们可以通过ls -l
或stat
命令查看文件的长度,我们看到明文长度是6字节,密文长度是16字节。
参考man fread
给的范例代码,我们编写readplain.c,readcipher.c程序来读取明文,密文的内容。
readplain.c代码如下:
#include <stdio.h>
#include <stdlib.h>
#define FILESIZE 6
int main(void)
{
FILE *fp = fopen("./plain.txt", "r");
if (!fp) {
perror("fopen");
return EXIT_FAILURE;
}
unsigned char buffer[20];
size_t ret = fread(buffer, 1, FILESIZE, fp);
if (ret != FILESIZE) {
fprintf(stderr, "fread() failed: %zu\n", ret);
exit(EXIT_FAILURE);
}
for (int i = 0; i < FILESIZE; i++)
printf("%x ", buffer[i]);
printf("\n");
for (int i = 0; i < FILESIZE; i++)
printf("%c ", buffer[i]);
printf("\n");
fclose(fp);
exit(EXIT_SUCCESS);
}
readcipher.c代码如下:
#include <stdio.h>
#include <stdlib.h>
#define FILESIZE 16
int main(void)
{
FILE *fp = fopen("./ciphertext.bin", "rb");
if (!fp) {
perror("fopen");
return EXIT_FAILURE;
}
unsigned char buffer[20];
size_t ret = fread(buffer, 1, FILESIZE, fp);
if (ret != FILESIZE) {
fprintf(stderr, "fread() failed: %zu\n", ret);
exit(EXIT_FAILURE);
}
for (int i = 0; i < FILESIZE; i++)
printf("%x ", buffer[i]);
printf("\n");
fclose(fp);
exit(EXIT_SUCCESS);
}
我们不少同学代码写的非常杂乱,希望大家养成使用Linux的indent
命令格式化代码的习惯,如果使用完indent
,你的代码没有变化,说明你的编码已经比较专业了,我也希望大家举一反三学学IDE(code::blocks,Visual Studio、Dev C++等)中如何格式化代码的:
格式化代码有很多风格,我个人喜欢K&R风格(学习C语言,不能不知道什么是K&R,如果不知道,搜索引擎里面查找一下吧,或者问问chatGPT等AI工具)上面代码就是使用下面命令格式化后的结果:
大家注意代码中使用fopen打开文本文件和二进制文件的不同。
我们编译运行程序,通过和od命令对比,我们确认编写代码无误。
读取文本文件的方式
像密文那样的二进制文件我们只能通过fread读取了,像明文那样的文本文件我们还有其他读取方式。
- 一次读取一个字符
我们可以使用fgetc一次读取文本文件的一个字符,代码及运行情况如下:
- 一次读取一行
我们可以使用fgets一次读取文本文件的行,代码及运行情况如下:
顺序读写,随机读写
前面我们介绍操作系统都支持文件的顺序读写,我们如果想随机读取文件怎么办?比如我们想把明文文件plaint.txt的数字串提取出来?C语言中可以使用fseek函数。
代码和运行结果如下:
到你了,怎么写文件
参考我们上面的思路,想想如何写文件,可以通过下面两个练习完成
- 编写一个mycp.c, 实现Linux下cp命令的功能
- 把上面明文中的数字串“123”提取出来,把他转化为数字(调用atoi),然后把转化后的数字写到一个二进制文件ctohex.bin中。
安全问题
作为信息安全专业、网络空间安全专业的学生,访问文件不光能完成相关功能,还要考虑代码不能出现安全问题,不能让编译后的软件具有漏洞。通过搜索引擎或chatGPT等AI工具了解哪些C函数我们不能调用的,否则会引起缓冲区溢出攻击会字符串溢出攻击。
Python程序设计
使用Python语言完成上述C语言代码中的功能,比较两种语言处理文件的异同。
欢迎关注“rocedu”微信公众号(手机上长按二维码)
做中教,做中学,实践中共同进步!
-
版权声明:自由转载-非商用-非衍生-保持署名| Creative Commons BY-NC-ND 3.0
如果你觉得本文对你有帮助,请点一下左下角的“好文要顶”和“收藏该文”