文件系统和目录

文件系统和目录

理论

我们前面介绍过(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系统:

1open-openeuler

我们使用mkdir和cd命令新建一个目录,并进入这个目录。

2make-testfiledir

我们使用mkdir再创建三个文件,使用tree命令可以查看目录树结构。

3tree

我们使用cd linux进入linux目录,注意linux是相对路径,我们可以通过pwd查看绝对路径,pwd就是以绝对路径的方式显示当前工作目录:
4pwd

我们通过touch命令新建一个文本文件plain.txt,通过echo命令输入要加密明文内容。我们可以通过cat命令和od命令查看文件内容。通过od命令我们可以看到文本文件也是以二进制方式存储的。
5od

我们还可以通过file命令查看文件的类型,使用stat命令查看文件的属性。stat中的大部分内容可以通过ls -l命令显示。

6type-stat

我们可以通过ls -a看到两个隐藏的目录名,其中.表示当前目录,..表示父目录。

7lsa

我们用OpenSSL命令对明文plain.txt加密,得到密文ciphertext.bin 我们使用的是国密算法sm4. 注意Linux下不需要文件后缀的,我们这有个后缀bin,是提醒大家这是个二进制文件,file命令也显示了这一点。我们用cat命令查看密文内容时会发现都是乱码。我们可以使用od命令查看密文的每个字节都是什么。掌握od命令对大家来说非常有必要,比如后面学习密码学,很多同学大四了看到乱码都不知道如何查看。

8opensslenc

我们用OpenSSL命令对密文ciphertext.bin解密得到plain2.txt加密, 我们使用的是国密算法sm4.注意加密密钥(-K)和初始向量(-iv)要和加密过程一致。

9openssldenc

我们对目录,文件还有其他操作,涉及的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 -lstat命令查看文件的长度,我们看到明文长度是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读取了,像明文那样的文本文件我们还有其他读取方式。

  1. 一次读取一个字符

我们可以使用fgetc一次读取文本文件的一个字符,代码及运行情况如下:

  1. 一次读取一行
    我们可以使用fgets一次读取文本文件的行,代码及运行情况如下:

顺序读写,随机读写

前面我们介绍操作系统都支持文件的顺序读写,我们如果想随机读取文件怎么办?比如我们想把明文文件plaint.txt的数字串提取出来?C语言中可以使用fseek函数。

代码和运行结果如下:

到你了,怎么写文件

参考我们上面的思路,想想如何写文件,可以通过下面两个练习完成

  1. 编写一个mycp.c, 实现Linux下cp命令的功能
  2. 把上面明文中的数字串“123”提取出来,把他转化为数字(调用atoi),然后把转化后的数字写到一个二进制文件ctohex.bin中。

安全问题

作为信息安全专业、网络空间安全专业的学生,访问文件不光能完成相关功能,还要考虑代码不能出现安全问题,不能让编译后的软件具有漏洞。通过搜索引擎或chatGPT等AI工具了解哪些C函数我们不能调用的,否则会引起缓冲区溢出攻击会字符串溢出攻击。

Python程序设计

使用Python语言完成上述C语言代码中的功能,比较两种语言处理文件的异同。

欢迎关注“rocedu”微信公众号(手机上长按二维码)

做中教,做中学,实践中共同进步!

rocedu



如果你觉得本文对你有帮助,请点一下左下角的“好文要顶”和“收藏该文


posted @ 2023-11-23 16:45  娄老师  阅读(197)  评论(0编辑  收藏  举报