广州大学学生实验报告
开课学院及实验室:计算机科学与网络工程学院 软件实验室 2023年6月2日
学院 | 计算机科学与网络工程学院 | 年级/专业/班 | 姓名 | 学号 |
| ||
实验课程名称 | 操作系统实验 | 成绩 | |||||
实验项目名称 | 系统文件 | 指导老师 |
实验四 系统文件
一、实验目的
1、熟悉Linux文件系统的文件和目录结构,掌握Linux文件系统的基本特征;
2、模拟实现Linux文件系统的简单I/O流操作:备份文件。
二、实验环境
WSL-Ubuntu-18.04、gcc编译器
三、实验内容
1、浏览Linux系统根目录下的子目录,熟悉每个目录的文件和功能;
2、设计程序模拟实现Linux文件系统的简单I/O流操作:备份文件。
四、实验原理,实验中用到的系统调用函数(包括实验原理中介绍的和自己采用的),实验步骤
1、实验原理:
Linux文件系统:Linux 中允许众多不同的文件系统共存,如 ext2, ext3, vfat 等。通过使用同一套文件 I/O 系统 调用即可对 Linux 中的任意文件进行操作而无需考虑其所在的具体文件系统格式;更进一步,对文件的 操作可以跨文件系统而执行。“一切皆是文件”是 Unix/Linux 的基本哲学之一。不仅普通的文件,目录、字符设备、块设备、 套接字等在 Unix/Linux 中都是以文件被对待;它们虽然类型不同,但是对其提供的却是同一套操作界面。
虚拟文件系统(Virtual File System, 简称 VFS),是Linux内核中的一个软件层,用于给用户空间的程序提供文件系统接口;同时,它也提供了内核中的一个抽象功能,允许不同的文件系统共存。系统中所有的文件系统不但依赖VFS共存,而且也依靠VFS协同工作。为了能够支持各种实际文件系统,VFS 定义了所有文件系统都支持的基本的、概念上的接口和数据结构;同时实际文件系统也提供 VFS 所期望的抽象接口和数据结构,将自身的诸如文件、目录等概念在形式上与VFS的定义保持一致。换句话说,一个实际的文件系统想要被Linux支持,就必须提供一个符合VFS标准的接口,才能与VFS协同工作。实际文件系统在统一的接口和数据结构下隐藏了具体的实现细节,所以在VFS层和内核的其他部分看来,所有文件系统都是相同的。
2、系统调用函数:
fopen(“source.dat”,“r”); //打开一个文件,以只读文件形式。
fread(); //读取数据从一个文件到buff中,
fwrite(); //把数据从buff中写入另一个文件
fclose(); //关闭文件
3、C库函数
int open(char *path,int flags,mode_t mode); //参数path 是指向所要打开的文件的路径名指针。参数falgs 规定如何打开该文件它必须包含以下三个值之一,O_RDONLY只读打开,O_WRONLY只写打开,O_RDWR读/写打开,参数mode规定对该文件的访问权限,定义在<sys/stst.h>中。
int read(int fd,void *buf,size_t nbytes); //该系统调用从文件描述符fd所代表的文件中读取nbytes 个字节,buf指定的缓冲区内。所读取的内容从当前的读/写指针所指示的位置开始,这个位置由相应的打开文件描述中的偏移值(off_set)给出,调用成功后文件读写指针增加实际读取的字节数。
int write(int fd,void *buf,size_t nbytes); //该调用从buf所指的缓冲区中将nbytes 个字节写到描述符fd所指的文件中。
int close(int fd); //每打开一个文件,系统就给文件分配一个文件描述符,同时为打开文件描述符的引用计数加1。Linux文件系统最多可以分配255个文件描述符。当调用close()时,打开文件描述符的引用计数值减1,最后一次对close()的调用将使应用计数值为零。
4、使用的命令:
cd命令:用来切换当前目录至别的地方。例如,cd /表示进入系统根目录。cd ../表示进行目录回退。
ls命令:用来查看用户有执行权限的任意目录中的文件列表。
sudo命令:全称,super user do。通过此命令,可以让一些非root用户运行只有root才有权限执行的命令。sudo最常用的功能就是提升一个命令的执行权限。
chmod命令:全称,change mode。通过此命令,可以设置用户对文件的权限。
gcc 文件名.后缀 -o文件名:对程序进行编译。
./文件名:对程序进行执行操作。
5、备份文件过程图:
五、实验结果分析(截屏的实验结果,与实验结果对应的实验分析)
(一)实验结果与实验程序、实验步骤、实验原理、操作系统原理的对应分析;
(二)不同条件下的实验结果反应的问题及原因;
(三)实验结果的算法时间、效率、鲁棒性等性能分析。
1、浏览Linux系统根目录下的子目录,熟悉每个目录的文件和功能
①使用命令cd / 打开Linux系统根目录下的子目录,并使用ls命令打印该目录下的所有文件。
②使用命令cd bin 打开该目录中的bin文件,并使用ls命令打印该文件的内容。
③使用cd boot打开该目录中的boot文件,并使用ls命令打印该文件中的内容。
④使用cd dev打开该目录中的dev文件,并使用ls命令打印该文件中的内容。
⑤使用cd etc打开该目录中的etc文件,并使用ls命令打印该文件中的内容。
⑥使用cd home打开该目录中的home文件,并使用ls命令打印该文件中的内容。
⑦使用cd lib打开该目录中的 lib 文件,并使用ls命令打印该文件中的内容。
⑧使用cd lost+found打开该目录中的文件,并使用ls命令打印该文件中的内容。
打印内容出错。原因:LOST+FOUND文件是存储发生以外后丢失的文件的,只有root用户才能打开。其它用户权限不足。
解决:
- sudo +原来的指令,结果:出错。但并不是所有的指令都可以用sudo,这里直接使用语句sudo lost+found还是显示错误。
2、使用语句:sudo chmod -R 777 lost+found;其中,-R 是指级联应用到目录里的所有子目录和文件,777 是所有用户都拥有最高权限。
正确运行结果:
2、设计程序模拟实现Linux文件系统的简单I/O流操作:备份文件
编译并运行文件lab4.c,运行结果如上图所示:
生成的备份文件如下图所示:
双击打开CopyFile1.txt,出错,如下图所示:
原因:通过系统调用函数来进行备份文件后产生的文件是无法打开的。
双击打开CopyFile2.txt,结果如下图所示:
其内容与备份源文件file.txt的内容相同。
在执行过一次文件备份后,接着再进行备份文件,就会报目标文件打开失败的错误,如下图所示:
猜想这是权限不足的原因。
解决方法:执行sudo chmod -R 777 CopyFile1.txt,授予最高权限。
然后再次进行文件备份时,就不会发生文件打开错误了。
运行结果如下图所示:
六、实验总结
(一) 实验思考题的回答
1、使用系统调用函数open(),read(),write(),close()实现简单文件备份的原理是什么?
①在进行系统调用open()函数时,需要指定一个有效的文件路径,该函数用于打开指定路径下的文件,并返回一个大于0的文件描述符,表示文件打开成功。该文件描述符可以被其他系统调用函数使用,如read()函数和write()函数等。当open()函数返回值为-1时,表示打开文件失败,一般是由于文件不存在、权限不足或者其他文件系统错误引起。
②系统调用read()函数是用于从指定文件描述符对应的文件中读取指定数量的数据,并将其存储到指定的内存缓冲区中。该函数使用时需要传入文件描述符、读取数据的大小以及指向缓冲区的指针等参数。当读取成功时,read()函数返回实际读取的字节数;当读取失败时,返回-1并设置errno标志,以便用户进一步处理错误。
③系统调用write()函数是用于将指定内存缓冲区中的数据写入到指定文件描述符对应的文件中。该函数使用时需要传入文件描述符、写入数据的大小以及指向缓冲区的指针等参数。当写入成功时,write()函数返回实际写入的字节数;当写入失败时,返回-1并设置errno标志,以便用户进一步处理错误。
④系统调用close()函数是用于关闭指定文件描述符对应的文件,并释放相关资源。该函数使用时需要传入文件描述符参数,用于指定需要关闭的文件。当关闭成功时,close()函数返回0;当关闭失败时,返回-1并设置errno标志,以便用户进一步处理错误。
2、用C库函数fopen(), fread(), fwrite(), fclose() 来实现简单文件备份的原理是什么?
①通过调用C库函数fopen(),可以实现以指定的文件路径打开文件,并进行读取操作,同时可以将文件备份到指定的备份文件中。
②fread()函数是C标准库中提供的文件读取函数,可以从指定的文件流中读取指定数量的数据,并将其存储到指定的缓冲区中。
③fwrite()函数是C标准库中提供的文件写入函数,可以将指定缓冲区中的数据写入到指定的文件流中,写入数据的大小由参数size和nmemb指定。
④fclose()函数是C标准库中提供的文件流关闭函数,可以关闭指定的文件流,并释放相关的资源。
上述四个函数的实现均依赖于操作系统提供的文件IO系统调用函数,通过调用这些函数,实现对原始文件对文件读取和备份、对文件的读取、对文件的写入以及对文件流的关闭操作。
3、上述二者的区别在哪里?
①open(), read(), write(), close()是操作系统提供给用户程序的系统调用函数,这些函数需要在用户态和内核态之间进行切换来执行,这种切换过程会对系统的性能产生一定的影响。因此,在使用这些函数时需要注意其对系统性能的影响,并进行优化。
②fopen(), fread(), fwrite(), fclose()是C语言标准库提供的函数,这些函数只能在用户态中调用和执行,不需要进行用户态和内核态之间的切换。因此,这些函数的执行效率比系统调用函数要高很多。
③由于系统调用函数需要在用户态和内核态之间来回切换执行,因此其执行效率相对较低,对系统性能的影响也相对较大。而C库函数的执行效率比系统调用函数要高,因此在编写程序时应尽可能地使用C库函数,以提高程序的执行效率和系统的性能。
④系统调用函数的具体实现方式在不同的操作系统中可能会有所不同,因此这些函数在不同的操作系统上的移植性较差。而C库函数是标准化的函数库,这些函数在不同的操作系统中都有相同的实现方式和接口规范,因此具有很好的移植性。
以上是它们原理上的区别,它们在功能上并无区别。
(二)个人总结
1、在本次实验中,我通过实验步骤一明白了文件权限的含义,通过这个实例,我上网搜集了解决文件权限不足的方法,通过执行sudo chmod -R 777 目标文件,就可以授予文件最高权限,从而可以对该文件进行读写操作。
2、本次实验中的文件备份采用库函数或者采用系统调用函数,它们在代码书写上并没有展示出较大的区别,但是其中深层中它们的效率是不同的。作为程序员,我们应该考虑到代码实现的效率。
七、实验数据及源代码(学生必须提交自己设计的程序源代码,并有注释,源代码电子版也一并提交),包括思考题的程序。
注意: 实验报告文件名 学号-姓名-实验1-班级
实验数据与源代码 一个压缩包,名字和实验报告规则一样,需要有一个说明文件解释各个文件是什么文件。
测试数据文件:file.txt 其目录为:/home/pan/file.txt
#include<iostream>
using namespace std;
int main()
{
cout<<"hello world!"<<endl;
}
使用系统调用函数备份出的文件:CopyFile1.txt 其目录为:/home/pan/CopyFile1.txt
#include<iostream>
using namespace std;
int main()
{
cout<<"hello world!"<<endl;
}
使用库函数备份出的文件:CopyFile2.txt 其目录为:/home/pan/CopyFile2.txt
#include<iostream>
using namespace std;
int main()
{
cout<<"hello world!"<<endl;
}
代码源程序: lab4.c 其目录为:/home/pan/lab4.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
const char* source = "/home/pan/file.txt"; //需要备份的源文件的路径
//使用系统调用函数open(),read(),write(),close()
void Copy1() {
int sourceFlag, destinationFlag, size;
char str[128];
sourceFlag = open(source, O_RDONLY);//打开需要备份的文件,只读。返回文件描述符
destinationFlag = open("/home/pan/CopyFile1.txt", O_WRONLY | O_CREAT); //文件存在,则可以打开文件进行写操作;文件不存在,则创建文件再打开该文件进行写操作
if (sourceFlag == -1) { //刚开始这个文件没有权限打开
printf("Source file open failed!\n");
return;
}
if (destinationFlag == -1) {
printf("Destination file open failed!\n");
close(sourceFlag);
return;
}
while ((size = read(sourceFlag, str, sizeof(str)))) { //read()用于从指定文件描述符对应的文件file.txt中读取指定数量的数据,并将其存储到指定的内存缓冲区中
write(destinationFlag, str, size); //read()返回实际读取的字节数
}//write()将指定内存缓冲区中的数据写入到指定文件描述符对应的文件中
close(sourceFlag);//关闭指定文件描述符对应的文件
close(destinationFlag);
printf("System calls:The file.txt copy complete.New file is called CopyFlie1.txt\n");
}
//使用c库函数fopen(),fread(),fwrite(),fclose()
void Copy2() {
char str[128];
FILE* sourceFlag, * destinationFlag;
int size;
sourceFlag = fopen(source, "r");//读取源文件
destinationFlag = fopen("/home/pan/CopyFile2.txt", "w");
if (!sourceFlag) {
printf("Source file open failed!\n");
return;
}
if (!destinationFlag) {
printf("Destination file open failed!\n");
fclose(sourceFlag);
return;
}
//循环读取内容,直到读取到文件结尾
while ((size = fread(str, sizeof(char), 128, sourceFlag))) {
fwrite(str, sizeof(char), size, destinationFlag);//从文件开头写 若已存在该文件,则会覆盖。
}
fclose(sourceFlag);
fclose(destinationFlag);
printf("Library function:The file.txt copy complete.New file is called CopyFlie2.txt\n");
}
int main() {
Copy1(); //使用系统命令调用
Copy2(); //使用库函数调用
return 0;
}