【设计题目】
二级文件系统设计
【设计目的】
(1)本实验的目的是通过一个简单多用户文件系统的设计,加深理解文件系统的内部功能和内部实现。
(2)结合数据结构、程序设计、计算机原理等课程的知识,设计一个二级文件系统,进一步理解操作系统。
(3)通过分对实际问题的分析、设计、编程实现,提高学生实际应用、编程的能力。
【设计内容】
一、任务
为Linux系统设计一个简单的二级文件系统。要求做到以下几点:
1.可以实现下列几条命令:
login 用户登录
dir 列目录
create 创建文件
delete 删除文件
open 打开文件
close 关闭文件
read 读文件
write 写文件
cd 进出目录
2.列目录时要列出文件名,物理地址,保护码和文件长度
3.源文件可以进行读写保护
二、程序设计
1.要求实现的功能:
login 登录
ls 列目录
ll 列目录(详细)
mkdir 创建目录
mk 创建文件
rmdir 删除文件夹
rm 删除文件
open -ff 打开文件到内存中(最先适应存储分配算法)
open -zj 打开文件到内存中(最佳适应存储分配算法)
open -zh 打开文件到内存中(最坏适应存储分配算法)
close i 释放内存中第i个线程
cat 读文件
write -a f context 写文件(追加)
write -c f context 写文件(覆盖)
cd 进出目录
2.额外实现的功能:
cp f1 f2 复制文件夹f1变成f2
mv f1 f2 移动文件夹f1变成f2
attrib f r 修改文件f的权限为只读
attrib f w 修改文件f的权限为读写
head f n 读取文件f的前n行内容
tail f n 读取文件f的后n行内容
clear 清除屏幕
exit 退出系统
3.列目录时列出文件名、物理地址、保护码、文件长度、读写权限、修改日期、文件类型等
4.源文件可以进行读写保护,可以通过attrib命令修改文件权限为只读或读写。
【实验环境】
IntelliJ IDEA 2021.1.1 x64
【设计思路】
一、采用的数据结构
(一)主要数据结构——FileContext类
该类存储乐多级文件系统的实际存储路径、用户名、当前路径等,linux基本操作基本围绕着这个类展开,这也是本项目最重要的一个数据结构之一。
public class FileContext {//多级文件系统的实际存储路径(该项目父结构的下一层./dir目录下)public static String real = "." + File.separatorChar+ "dir" ;//当前路径public static String now="/";//多级文件系统的用户名public static String user="";//是否使用目录public static String userDirectory = "/"+user;//字符集public static String Charset = "UTF-8";
}
(二)主要数据结构——Memory类
Memory类是Java中的模拟内存,主要用来打开、关闭文件的。其中,size是内存块大小,lastFind是上次寻址结束的下表,threds、holes分别是内存块进程的双向链表和内存块分区的双向链表,MIN_SIZE是最小剩余分区大小。这里为了侧重展示重点,去掉了非核心的get、set方法,核心方法是getMemory申请一个size大小的内存,和releaseMemory释放内存,着两个方法也是实现动态分区存储管理的核心实现方法。
public class Memory {private int size; //内存块大小private int lastFind; //上次寻址结束的下标private LinkedList<Thrd> thrds; //记录内存块中进程的双向链表private LinkedList<Hole> holes; //记录内存块分区的双向链表private static final int MIN_SIZE = 5; //最小剩余分区大小public Memory(int size) {this.size = size; //初始化内存大小this.thrds = new LinkedList<>(); //初始化两个链表this.holes = new LinkedList<>();holes.add(new Hole(0, size)); //分区链表的首项为大小是内存大小的空闲分区}//size:申请大小 location:分配分区的下标 hole:location对应的分区public Memory getMemory(int size, int location, Hole hole) {//若分配后当前分区剩余大小大于最小分区大小,则把当前分区分为两块if (hole.getSize() - size >= MIN_SIZE) {Hole newHole = new Hole(hole.getHead() + size, hole.getSize() - size);holes.add(location + 1, newHole);hole.setSize(size);}thrds.add(new Thrd(10000 + (int)(89999 * Math.random()), 1, hole)); //模拟添加一个就绪状态的进程,此进程id随机生成(忽略id重复的情况哈)hole.setFree(false); //设置当前分区为非空闲状态System.out.println("成功分配大小为" + size + "的内存");return this;}public Memory releaseMemory(int id) {Thrd thrd = null; //记录此id对应进程(忽略进程id与分区id相同,但进程不同的情况哈)if (id >= holes.size()) { //若id大于holes的表长度,则需要判断此id是否是进程idboolean flag = false;for (int i = 0; i < thrds.size(); i++) { //循环比对此id是否是thrds链表中进程的idif (thrds.get(i).getId() == id) {thrd = thrds.get(i);flag = true;break;}}if (!flag) {System.err.println("无此分区:" + id);return this;}}if (thrd != null) { //若是通过进程id释放内存,则用下列循环获取进程对应的hole对应holes链表的下标(获取分区id)for (int i = 0; i < holes.size(); i++) {Hole hole = holes.get(i);if ((thrd.getHole().getSize() == hole.getSize()) && (thrd.getHole().getHead() == hole.getHead())) {id = i;break;}}}Hole hole = holes.get(id); //此id为分区idif (hole.isFree()) {System.out.println("此分区空闲,无需释放:\t" + id);return this;}//用分区id释放thrdfor (int i = 0; i < thrds.size(); i++) {Thrd thrd2 = thrds.get(i);if ((thrd2.getHole().getSize() == hole.getSize()) && (thrd2.getHole().getHead() == hole.getHead())) {thrds.remove(i);break;}}//如果回收分区不是尾分区且后一个分区为空闲, 则与后一个分区合并if (id < holes.size() - 1 && holes.get(id + 1).isFree()) {Hole nextHole = holes.get(id + 1);hole.setSize(hole.getSize() + nextHole.getSize());holes.remove(nextHole);}//如果回收分区不是首分区且前一个分区为空闲, 则与前一个分区合并if (id > 0 && holes.get(id - 1).isFree()) {Hole lastHole = holes.get(id - 1);lastHole.setSize(hole.getSize() + lastHole.getSize());holes.remove(id);id--;}holes.get(id).setFree(true);System.out.println("内存回收成功!");return this;}
}
(三)主要数据结构——Hole类
Hole类用于表示内存中各个分区,也表示进程对应的内存块,其中,head是小内存块的起始地址,size是小内存块的大小,isFree是小内存块的空闲状态。
public class Hole {private int head; //小内存块的起始地址private int size; //小内存块的大小private boolean isFree; //小内存块的空闲状态public Hole(int head, int size) {this.head = head;this.size = size;this.isFree = true;}}
(四)主要数据结构——Thrd类
Thrd类主要用于描述进程,是用于记录进程信息和进程对应的内存块,其中id是进程号,state是进程状态,hole是进程对应的小内存块。
public class Thrd {private int id; //进程idprivate int state; //进程状态 0为空闲 1为就绪 2为执行 3为阻塞private Hole hole; //进程所对应的小内存块public Thrd(int id, int state, Hole hole) {this.id = id;this.state = state;this.hole = hole;}
}
二、主要的函数说明
(一)有哪些函数——实现Linux基本操作的接口
下面展示的是实现Linux基本操作的关键函数,完成了课设所有要求的同时,实现了约20个不同的linux命令。下面是接口部分的代码展示。我将在后面着重选择open、close、delete、write这四个函数来讲。
public interface FileManagerService {Boolean login(String username,String password);Boolean register(String username,String password);String ls() throws Exception;String ll() throws Exception;Boolean attrib(String filename,String permission) throws IOException;String cd(String arg) throws IOException;Boolean cp(String filename,String newFilename) throws IOException;Boolean mv(String filename,String newFilename) throws IOException;Boolean mk(String filename) throws IOException;Boolean rm(String filename) throws IOException;Boolean rmdir(String filename);Boolean mkdir(String filename) throws IOException;String cat(String filename) throws IOException;String help(String command) throws IOException;String help() throws IOException;String head(String filename, Integer n);String tail(String filename, Integer n);Boolean write(Integer mode,String filename,String context) throws IOException;String open(String algorithmId,String filename);String close(Integer thrdId);default String getNowPath(){String[] strings = FileContext.now.split(File.pathSeparator);if(FileContext.now.startsWith(File.separatorChar+ FileContext.user)){return FileContext.now.replace(File.separatorChar+ FileContext.user,"~");}return FileContext.now;}default String getRealPath(){return FileContext.real + FileContext.now;}default void createUserDirectory(){File file = new File(FileContext.real + File.separatorChar + FileContext.user);if(!file.exists()){file.mkdirs();}};default boolean checkUserDirectory(){File file = new File(FileContext.real + File.separatorChar + FileContext.user);if(!file.exists()){createUserDirectory();return true;}return true;}default void init(){if(checkUserDirectory()){FileContext.now = File.separatorChar+ FileContext.user;}}
}
(二)主要函数——open()方法
open方法实现的核心功能为,将磁盘中的文件调度到内存中打开,同时使用动态分区分配算法,实现模拟操作系统中,内存的优化。
这里前4行有效代码都是类中的全局变量,在open方法中都有用到。
Partition partition = new Partition();
//初始化的内存大小
private final Integer memorySize = 1000;
//初始化一个内存
Memory memory = new Memory(memorySize);//存储小内存块的链表
LinkedList<Hole> holeLinkedList = new LinkedList<>();@Override
public String open(String algorithmId,String filename) {File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);//要申请的大小long totalSpace = 0;//申请的大小if(file.isDirectory()){totalSpace = getFolderSize(file);}else{totalSpace = file.length();}Hole hole = new Hole(file.getAbsolutePath().hashCode(), (int) file.length());holeLinkedList.add(hole);//1--首次适应算法if("-ff".equals(algorithmId)){memory = partition.FF(memory, (int) totalSpace);//2--最佳适应算法}else if("-zj".equals(algorithmId)){memory = partition.ZJ(memory,(int) totalSpace);//3--最坏适应算法}else if("-zh".equals(algorithmId)){memory = partition.ZH(memory,(int) totalSpace);}String showThrds = partition.showThrds(memory);String showMemory = partition.showMemory(memory);return showThrds+"\n"+showMemory;
}
(三)主要函数——动态分区分配算法
1.首次适应算法
算法思想:每次都从低地址开始查找,找到第一个能满足大小的空闲分区。
如何实现:空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。函数如下:
public Memory FF(Memory memory, int size) {int sum = 0;//循环内存中所有分区for (int i = 0; i < memory.getHoles().size(); i++) {sum++;//为循环首次适应算法设置最后寻址的下标memory.setLastFind(i);Hole hole = memory.getHoles().get(i); //获得对应的分区//若此分区空闲且大小大于申请的大小,则申请内存if (hole.isFree() && hole.getSize() >= size) {System.out.println("查找" + sum + "次");return memory.getMemory(size, i, hole);}}System.err.println("OUT OF MEMORY!");return memory;
}
2.最佳分区分配算法
算法思想:由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区,即,优先使用更小的空闲区。
如何实现:空闲分区按容量递增次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
public Memory ZJ(Memory memory, int size) {int findIndex = -1; //最佳分区的下标int min = memory.getSize(); //min存储当前找到的最小的合适的分区大小for (int i = 0; i < memory.getHoles().size(); i++) {//memory.setLastFind(i);Hole hole = memory.getHoles().get(i);if (hole.isFree() && hole.getSize() >= size) {//若当前找到的分区大小比min还要合适(剩余空间更小),则修改其值if (min > hole.getSize() - size){min = hole.getSize() - size;findIndex = i;}}}if (findIndex != -1) { //若存在合适分区return memory.getMemory(size, findIndex, memory.getHoles().get(findIndex));}System.err.println("OUT OF MEMORY!");return memory;
}
3.最坏分区分配算法
算法思想:为了解决最佳适应算法的问题——即留下太多难以利用的小碎片,可以在每次分配时优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。
如何实现:空闲分区按容量递减次序链接。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
public Memory ZH(Memory memory, int size) {int findIndex = -1;int max = 0;for (int i = 0; i < memory.getHoles().size(); i++) {Hole hole = memory.getHoles().get(i);if (hole.isFree() && hole.getSize() >= size) {if (max < hole.getSize() - size){max = hole.getSize() - size;findIndex = i;}}}if (findIndex != -1) {return memory.getMemory(size, findIndex, memory.getHoles().get(findIndex));}return memory;
}
(四)主要函数——close()方法
主要函数有close调用的releaseMemory释放内存操作和showThrds、showMemory展示进程、内存的操作。
@Override
public String close(Integer thrdId) {memory = memory.releaseMemory(thrdId);String showThrds = partition.showThrds(memory);String showMemory = partition.showMemory(memory);return showThrds+"\n"+showMemory;
}
releaseMemory方法如下:
public Memory releaseMemory(int id) {Thrd thrd = null; //记录此id对应进程(忽略进程id与分区id相同,但进程不同的情况哈)if (id >= holes.size()) {//若id大于holes的表长度,则需要判断此id是否是进程idboolean flag = false;for (int i = 0; i < thrds.size(); i++) { //循环比对此id是否是thrds链表中进程的idif (thrds.get(i).getId() == id) {thrd = thrds.get(i);flag = true;break;}}if (!flag) {return this;}}if (thrd != null) { //若是通过进程id释放内存,则用下列循环获取进程对应的hole对应holes链表的下标(获取分区id)for (int i = 0; i < holes.size(); i++) {Hole hole = holes.get(i);if ((thrd.getHole().getSize() == hole.getSize()) && (thrd.getHole().getHead() == hole.getHead())) {id = i;break;}}}Hole hole = holes.get(id); //此id为分区idif (hole.isFree()) {return this;}//用分区id释放thrdfor (int i = 0; i < thrds.size(); i++) {Thrd thrd2 = thrds.get(i);if ((thrd2.getHole().getSize() == hole.getSize()) && (thrd2.getHole().getHead() == hole.getHead())) {thrds.remove(i);break;}}//如果回收分区不是尾分区且后一个分区为空闲, 则与后一个分区合并if (id < holes.size() - 1 && holes.get(id + 1).isFree()) {Hole nextHole = holes.get(id + 1);hole.setSize(hole.getSize() + nextHole.getSize());holes.remove(nextHole);}//如果回收分区不是首分区且前一个分区为空闲, 则与前一个分区合并if (id > 0 && holes.get(id - 1).isFree()) {Hole lastHole = holes.get(id - 1);lastHole.setSize(hole.getSize() + lastHole.getSize());holes.remove(id);id--;}holes.get(id).setFree(true); return this;
}
两个show方法如下:
public String showMemory(Memory memory) {String returnStr ="-------------------------------------------\n"+"分区编号\t分区始址\t分区大小\t空闲状态\t\n"+"-------------------------------------------\n";for (int i = 0; i < memory.getHoles().size(); i++){Hole hole = memory.getHoles().get(i);returnStr += i + "\t\t" + hole.getHead() + "\t\t" + hole.getSize() + " \t\t" + hole.isFree()+"\n";}returnStr += "-------------------------------------------\n";return returnStr;
}
public String showThrds(Memory memory) {String returnStr ="-------------------------------------------\n"+"进程编号\t进程状态\t进程起始地址\t进程大小\t\n"+"-------------------------------------------\n";if (memory.getThrds().size() > 0) {for (int i = 0; i < memory.getThrds().size(); i++) {Thrd thrd = memory.getThrds().get(i);returnStr+=thrd.getId() + " \t" + thrd.getState() + "\t\t" + thrd.getHole().getHead() + "\t\t\t" + thrd.getHole().getSize()+"\n";}} else {returnStr +="\t\t\t暂无进程!\n";}returnStr+="-------------------------------------------\n";return returnStr;
}
(五)主要函数——delete()方法
删除的方法一共有两个,在这我写成了rm和rmdir,前者用来完成删除文件的操作,后者用于删除文件夹。
@Override
public Boolean rm(String filename) {File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);if(file.exists()){file.delete();}else{return false;}return true;
}
@Override
public Boolean rmdir(String filename) {File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);if (file.exists()) {if (file.isDirectory()) {return deleteDirectory(file);} else {return file.delete();}} else {return false;}
}
private boolean deleteDirectory(File directory) {File[] files = directory.listFiles();if (files != null) {for (File file : files) {if (file.isDirectory()) {deleteDirectory(file); // 递归删除子目录} else {file.delete();}}}return directory.delete(); // 删除当前目录
}
(六)主要函数——write()方法
write方法我一共设计了两种模式,一种是-a(append)追加模式,另一种是-c(cover)覆盖模式,都可以对文件做写入操作。具体代码如下。
@Override
public Boolean write(Integer mode,String filename, String content) {content = content +"\n";File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);//0--追加,1--覆盖if(mode == 0){try (BufferedWriter writer = new BufferedWriter(new FileWriter(file,true))) {writer.write(content);return true;} catch (IOException e) {e.printStackTrace();return false;}}else if(mode == 1){try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {writer.write(content);return true;} catch (IOException e) {e.printStackTrace();return false;}}return false;
}
三、程序流程设计等
1.首次适应算法
将空闲分区链以地址递增的顺序连接;在进行内存分配时,从链首开始顺序查找,直到找到一块分区的大小可以满足需求时,按照该作业的大小,从该分区中分配出内存,将剩下的空闲分区仍然链在空闲分区链中。
图 1 首次适应算法流程图
2.最佳分区分配算法
将空闲分区链中的空闲分区按照空闲分区由小到大的顺序排序,从而形成空闲分区链。每次从链首进行查找合适的空闲分区为作业分配内存,这样每次找到的空闲分区是和作业大小最接近的,所谓“最佳”。
图 2 最佳分区分配算法流程图
3.最坏分区分配算法
与最佳适应算法刚好相反,将空闲分区链的分区按照从大到小的顺序排序形成空闲分区链,每次查找时只要看第一个空闲分区是否满足即可。
图 3 最坏分区分配算法流程图
write方法流程图如下:
先读入语句,裁剪出用户输入的命令,由第一个字符,判断是什么命令。如果是write命令,那再判断语句格式是否正确,具体为:看是否是4个字符组成的语句。再然后,看write的模式,-a是追加模式,-c是覆盖模式。最后,第4个字符是要写入文件的内容。现在已经拿到了所有需要的参数,接下来根据参数,对文件进行插入操作就行了。另外,记得在结束的时候关闭文件。流程图如下所示。
图 4 write方法流程图
【源程序清单】
先说一下我的代码结构,如下:
框出来的源码在上面章节已经给出了,为了避免文档重复性,这一章只给FileManagerServiceImpl类和LinuxTerminalGUI类。如下:
1.FileManagerServiceImpl.java
package com.ly.service;import com.ly.context.FileContext;
import com.ly.domain.Hole;
import com.ly.domain.Memory;
import com.ly.partition.Partition;
import jdk.nashorn.internal.parser.JSONParser;import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.*;
import java.text.SimpleDateFormat;
import java.util.*;
/*** @Author 林瑶* @Description TODO* @Date 2024/6/27 0:25*/
public class FileManagerServiceImpl implements FileManagerService {Partition partition = new Partition();//初始化的内存大小private final Integer memorySize = 1000;//初始化一个内存Memory memory = new Memory(memorySize);//存储小内存块的链表LinkedList<Hole> holeLinkedList = new LinkedList<>();public FileManagerServiceImpl(){File file = new File(FileContext.real);if(!file.exists()){file.mkdirs();}}public Boolean login(String username, String password) {checkUserDirectory();Boolean flag = false;//默认无用户try (BufferedReader br = new BufferedReader(new FileReader(FileContext.real + FileContext.now+"user.txt"))) {String line;while ((line = br.readLine()) != null) {String[] parts = line.split(" ");//如果查到有username,就更改标记if(parts[0].equals(username)){flag = true;}if (parts[0].equals(username) && parts[1].equals(password)) {return true; // 登录成功}}} catch (IOException e) {e.printStackTrace();}return flag; // 登录失败}public Boolean register(String username, String password) {try (FileWriter writer = new FileWriter(FileContext.real + FileContext.now+"user.txt", true)) {writer.write(username + " " + password + "\n");checkUserDirectory();return true; // 注册成功} catch (IOException e) {e.printStackTrace();}return false; // 注册失败}@Overridepublic String ls() throws Exception{File file = new File(FileContext.real + FileContext.now );StringBuilder returnFileStr = new StringBuilder();if(file.exists()){for (String f : file.list()) {returnFileStr.append(f).append(" ");}return returnFileStr.toString();}else{}return "";}@Overridepublic Boolean attrib(String filename, String permission) throws IOException {File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);if (file.exists()) {switch (permission) {//设置只读case "r":Files.setAttribute(file.toPath(), "dos:readonly", true);break;//设置读写case "w":Files.setAttribute(file.toPath(), "dos:writeonly", false);break;default:throw new IllegalArgumentException("Invalid permission value");}return true;} else {return false;}}private String getFilePermission(File file) {Path filePath = file.toPath();String permissionStr = "";//类型:文件or文件夹if(file.isDirectory()){ permissionStr +="d"; }else{ permissionStr +="-"; }//权限-读if (Files.isReadable(filePath)) { permissionStr +="r"; }else{ permissionStr+="-"; }//权限-写if (Files.isWritable(filePath)) { permissionStr +="w"; }else{ permissionStr+="-"; }//权限-执行if (Files.isExecutable(filePath)) { permissionStr +="x"; }else{ permissionStr+="-"; }return permissionStr;}public String ll() throws Exception {File file = new File(FileContext.real + FileContext.now);StringBuilder returnFileStr = new StringBuilder();if (file.exists() && file.isDirectory()) {File[] files = file.listFiles();if (files != null) {returnFileStr.append("total ").append(files.length).append("\n");for (File f : files) {returnFileStr.append(formatFileDetails(f));}}}return returnFileStr.toString();}private String formatFileDetails(File f) {String filePermission = getFilePermission(f);String ownerName = "";try {ownerName = Files.getOwner(f.toPath()).getName();} catch (IOException e) {ownerName = "N/A";}long fileSize = f.length();String formattedSize = String.valueOf(fileSize);if (f.isDirectory()) {formattedSize = getFolderSize(f)+"";} else if (fileSize > 999) {formattedSize = String.format("%.1f%s", fileSize / 1024.0, "K");}String creationTime = formatCreationTime(f.toPath());String contentType = "";if(f.isDirectory()){contentType = "<DIR>";}else if(f.isFile()){contentType = "<FILE>";}String relativePath = f.getPath().length() > 30 ? "..." + f.getPath().substring(f.getPath().length() - 27) : f.getPath();return String.format("%-10s\t%-15s\t%-2s\t%s\t%-2s\t%s\n", filePermission, ownerName, formattedSize, creationTime, contentType, relativePath);}//获取文件大小private long getFolderSize(File folder) {long length = 0;File[] files = folder.listFiles();if (files != null) {for (File file : files) {if (file.isFile()) {length += file.length();} else {length += getFolderSize(file);}}}return length;}private String formatCreationTime(Path path) {try {BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);FileTime creationTime = attributes.creationTime();SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd HH:mm",Locale.ENGLISH);return dateFormat.format(new Date(creationTime.toMillis()));} catch (IOException e) {return "N/A";}}public String cd2(String arg) {File file = new File(FileContext.real + FileContext.now + File.separatorChar + arg);if("..".equals(arg)){file.getParent().replace(FileContext.real,"");}else if(file.exists() && file.isDirectory()){FileContext.now = FileContext.now + arg;}else{System.out.println("cd: "+arg+": No such file or directory");}return FileContext.now;}@Overridepublic String cd(String arg) {if ("../".equals(arg)) {File parentDir = new File(FileContext.real + FileContext.now).getParentFile();if (parentDir != null) {FileContext.now = parentDir.getPath().substring(FileContext.real.length());} else {System.out.println("cd: " + arg + ": No such file or directory");}}else if("/".equals(arg)){FileContext.now = "/";}else if("".equals(arg.trim())){FileContext.now = "";} else {File file = new File(FileContext.real + FileContext.now + File.separatorChar + arg);if (file.exists() && file.isDirectory()) {FileContext.now = FileContext.now + arg;} else {System.out.println("cd: " + arg + ": No such file or directory");}}return FileContext.now;}@Overridepublic Boolean cp(String filename, String newFilename) throws IOException {File srcFile = new File(FileContext.real + FileContext.now + File.separatorChar + filename);File destFile = new File(FileContext.real + FileContext.now + File.separatorChar + newFilename);
// 如果源文件不存在if(!srcFile.exists()){throw new RuntimeException("找不到源文件");}
// 如果目标文件不存在if(!destFile.exists()){destFile.mkdirs();}
// 如果源文件是文件if(srcFile.isFile()){
// 如果目标文件是文件if(destFile.isFile()){Files.copy(srcFile.toPath(),destFile.toPath());}// 如果目标文件是文件夹else if(destFile.isDirectory()){File newFile = new File(destFile,srcFile.getName());Files.copy(srcFile.toPath(),newFile.toPath());}}
// 如果源文件是文件夹else if(srcFile.isDirectory()){if(destFile.isFile()){throw new RuntimeException("源文件是文件夹,目标文件是文件,无法进行复制");}else if(destFile.isDirectory()){File fs[] = srcFile.listFiles();for(File f:fs){File newFile = new File(destFile,f.getName());
// 如果子级文件是文件夹,则递归if(f.isDirectory()){cp(f.getAbsolutePath(),newFile.getAbsolutePath());}if(f.isFile()){Files.copy(f.toPath(),newFile.toPath());}}}}return true;}@Overridepublic Boolean mv(String filename, String newFilename) {File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);if(file.exists()){file.renameTo(new File(FileContext.real + FileContext.now + File.separatorChar + newFilename));file.delete();}else{return false;}return true;}@Overridepublic Boolean mk(String filename) throws IOException {File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);if(!file.exists()){file.createNewFile();}return true;}@Overridepublic Boolean rm(String filename) {File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);if(file.exists()){file.delete();}else{return false;}return true;}@Overridepublic Boolean rmdir(String filename) {File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);if (file.exists()) {if (file.isDirectory()) {return deleteDirectory(file);} else {return file.delete();}} else {return false;}}private boolean deleteDirectory(File directory) {File[] files = directory.listFiles();if (files != null) {for (File file : files) {if (file.isDirectory()) {deleteDirectory(file); // 递归删除子目录} else {file.delete();}}}return directory.delete(); // 删除当前目录}@Overridepublic Boolean mkdir(String filename) {File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);if(!file.exists()){file.mkdirs();}return true;}@Overridepublic String cat(String filename) throws IOException {File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);if(file.exists()){StringBuilder content = new StringBuilder();try (BufferedReader reader = new BufferedReader(new FileReader(file))) {String line;while ((line = reader.readLine()) != null) {content.append(line);content.append(System.lineSeparator());}}return content.toString();}return null;}// write函数@Overridepublic Boolean write(Integer mode,String filename, String content) {content = content +"\n";File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);//0--追加,1--覆盖if(mode == 0){try (BufferedWriter writer = new BufferedWriter(new FileWriter(file,true))) {writer.write(content);return true;} catch (IOException e) {e.printStackTrace();return false;}}else if(mode == 1){try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {writer.write(content);return true;} catch (IOException e) {e.printStackTrace();return false;}}return false;}@Overridepublic String open(String algorithmId,String filename) {File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);//要申请的大小long totalSpace = 0;//申请的大小if(file.isDirectory()){totalSpace = getFolderSize(file);}else{totalSpace = file.length();}Hole hole = new Hole(file.getAbsolutePath().hashCode(), (int) file.length());holeLinkedList.add(hole);//1--首次适应算法if("-ff".equals(algorithmId)){memory = partition.FF(memory, (int) totalSpace);//2--最佳适应算法}else if("-zj".equals(algorithmId)){memory = partition.ZJ(memory,(int) totalSpace);//3--最坏适应算法}else if("-zh".equals(algorithmId)){memory = partition.ZH(memory,(int) totalSpace);}String showThrds = partition.showThrds(memory);String showMemory = partition.showMemory(memory);return showThrds+"\n"+showMemory;}@Overridepublic String close(Integer thrdId) {memory = memory.releaseMemory(thrdId);String showThrds = partition.showThrds(memory);String showMemory = partition.showMemory(memory);return showThrds+"\n"+showMemory;}@Overridepublic String head(String filename, Integer n) {File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);StringBuilder result = new StringBuilder();if(file.exists()){try {FileReader fr = new FileReader(file);BufferedReader bf = new BufferedReader(fr);// 读取文件的前n行内容String line;int count = 0;while ((line = bf.readLine()) != null && count < n) {result.append(line).append("\n");count++;}bf.close();fr.close();} catch (IOException e) {e.printStackTrace();}}return result.toString();}@Overridepublic String tail(String filename, Integer n) {File file = new File(FileContext.real + FileContext.now + File.separatorChar + filename);String result = "";if(file.exists()){try {FileReader fr = new FileReader(file);BufferedReader bf = new BufferedReader(fr);// 读取文件的所有行List<String> lines = new ArrayList<>();String line;while ((line = bf.readLine()) != null) {lines.add(line);}bf.close();fr.close();// 输出倒数第n行到最后一行的内容for (int i = Math.max(0, lines.size() - n); i < lines.size(); i++) {result += lines.get(i) + "\n";}} catch (IOException e) {e.printStackTrace();}}return result;}@Overridepublic String help(String command) {if(command.equals("cd")){return "cd directory1:进入名为directory1的文件夹中";}else if(command.equals("ls")){return "ls:列出该目录下所有文件名称";}else if(command.equals("ll")){return "ll:列出该目录下所有文件详细信息";}else if(command.equals("cp")){return "cp filename1 filename2:把filename1文件复制成filename2文件";}else if(command.equals("mv")){return "mv filename1 filename2:把filename1修改名称、移动为filename2";}else if(command.equals("mkdir")){return "mkdir directory1:创建名为directory1的文件夹";}else if(command.equals("mk")){return "mk filename1:创建名为filename1的文件";}else if(command.equals("rmdir")){return "rmdir directory1:删除名为directory1的文件夹";}else if(command.equals("rm")){return "rm filename1:删除名为filename1的文件";}else if(command.equals("cat")){return "cat filename1:展示filename1文件里的内容";}else if(command.equals("head")){return "head filename1 n:展示filename1文件里前n行的内容";}else if(command.equals("tail")){return "tail filename1 n:展示filename1文件里后n行的内容";}else if(command.equals("attrib")){return "attrib filename1 r:修改filename1文件为只读\n"+"attrib filename1 w:修改filename1文件为读写";}else if(command.equals("write")){return "write filename1 context:把context写入文件filename1";}else if(command.equals("open")){return "open -ff filename1:打开文件f到内存中(最先适应存储分配算法)"+"open -zj filename1:打开文件f到内存中(最佳适应存储分配算法)"+"open -zh filename1:打开文件f到内存中(最坏适应存储分配算法)";}else if(command.equals("close")){return "close 进程id:关闭内存中进程号为id的进程";}return "这里是help方法";}@Overridepublic String help(){String returnTxt ="-----------------------------------------------------\n"+" 功能目录 \n"+"-----------------------------------------------------\n"+"【cd f\t进入文件夹f中】\n"+"【ls\t展示当前目录下文件名】\n"+"【ll\t展示当前目录下文件物理地址】\n"+"【cp f1 f2\t复制文件夹f1变成f2】\n"+"【mv f1 f2\t移动文件夹f1变成f2】\n"+"【mkdir f\t创建文件夹f】\n"+"【mk f\t创建文件f】\n"+"【rmdir f\t删除文件夹f】\n"+"【rm f\t删除文件f】\n"+"【cat f\t查看文件f的内容】\n"+"【attrib f r\t修改文件f的权限为只读】\n"+"【attrib f w\t修改文件f的权限为读写】\n"+"【write -a f context\t把context内容写入文件f(追加)】\n"+"【write -c f context\t把context内容写入文件f(覆盖)】\n"+"【head f n\t读取文件f的前n行内容】\n"+"【tail f n\t读取文件f的后n行内容】\n"+"【open -ff f\t打开文件f到内存中(最先适应存储分配算法)】\n"+"【open -zj f\t打开文件f到内存中(最佳适应存储分配算法)】\n"+"【open -zh f\t打开文件f到内存中(最坏适应存储分配算法)】\n"+"【close i\t释放内存中第i个线程】\n"+"【clear\t清除屏幕】\n"+"【exit\t退出系统】\n"+"-----------------------------------------------------\n";return returnTxt;}}
2.LinuxTerminalGUI.java
package com.ly;import com.ly.context.FileContext;
import com.ly.service.FileManagerService;
import com.ly.service.FileManagerServiceImpl;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import lombok.SneakyThrows;public class LinuxTerminalGUI extends Application {FileManagerService fileManagerService;private TextArea textArea;private String lastCommand = "";private String username;private String password,password2;private int lineIdx = 0;//命令private String cmd;@Overridepublic void start(Stage primaryStage) throws Exception{primaryStage.setTitle("struggilr@"+System.getenv().get("COMPUTERNAME")+"~");primaryStage.setWidth(1250);primaryStage.setHeight(650);fileManagerService = new FileManagerServiceImpl();textArea = new TextArea();textArea.setStyle("-fx-control-inner-background:#000000; -fx-text-fill: #FFFFFF; -fx-font-size: 18;");// 监听文本区域的键盘按键事件textArea.setOnKeyPressed(this::handleKeyPressed);//模拟按下 Enter 键,从而触发 LoginF 方法LoginF(new KeyEvent(KeyEvent.KEY_PRESSED, "", "", KeyCode.ENTER, false, false, false, false));//检查用户目录,不存在就创建一下。fileManagerService.checkUserDirectory();// 创建一个栈面板,并将文本区域添加到栈面板中StackPane root = new StackPane();root.getChildren().add(textArea);Scene scene = new Scene(root, Color.BLACK);primaryStage.setScene(scene);primaryStage.show();}@SneakyThrowsprivate void handleKeyPressed(KeyEvent event) {//1--检查按下的键是否为字母键、数字键或空格键if (event.getCode().isLetterKey() || event.getCode().isDigitKey() || event.getCode().isWhitespaceKey()) {// 如果光标位置在命令行提示符后面,则禁止输入if (textArea.getCaretPosition() < textArea.getText().lastIndexOf("$ ") + 2) {event.consume(); // 禁止在命令行中输入}//2--检查按下的键是否为回车键} else if (event.getCode() == KeyCode.ENTER) {// 如果光标位置在命令行提示符后面或者在提示符上,则禁止换行if (textArea.getCaretPosition() <= textArea.getText().lastIndexOf("$ ") + 2) {event.consume(); // 禁止在命令行中换行} else {// 在文本区域末尾添加新的命令提示符并保存最后一条命令
// textArea.appendText("\n$ ");textArea.insertText(textArea.getLength(), "\n"+command());lastCommand = textArea.getText();}} //3--检查按下的键是否为退格键else if (event.getCode() == KeyCode.BACK_SPACE && textArea.getCaretPosition() <= textArea.getText().lastIndexOf("$ ") + 2) {event.consume(); // 禁止删除上一行命令}else if (event.getCode() == KeyCode.BACK_SPACE) {// 如果光标位置在 "login as: " 之后,则正常处理退格键if (textArea.getCaretPosition() > textArea.getText().indexOf("login as: ") + "login as: ".length()) {// 允许删除光标位置之前的字符return;}// 如果光标位置在 "login as: " 之前,则禁止删除textArea.positionCaret(textArea.getText().indexOf("login as: ") + "login as: ".length()); // 设置光标位置在 "login as: " 之后event.consume(); // 禁止删除 "login as: " 之前的文本}if (event.getCode() == KeyCode.ENTER) {// 阻止默认的回车键行为event.consume();// 获取 "login as:" 后面换行之前的字符串String inputCommand = textArea.getText();lineIdx = textArea.getParagraphs().size();System.out.println("【lineIdx:"+lineIdx+"】");if(lineIdx==1&&inputCommand.startsWith("login as: ")){username = inputCommand.substring("login as: ".length());FileContext.user = username;// 在文本区域末尾添加新的命令提示符并保存最后一条命令textArea.insertText(textArea.getLength(), "\nlogin password: ");}else if(lineIdx==2&&getLineText(inputCommand,lineIdx).startsWith("login password: ")){password = getLineText(inputCommand,lineIdx).substring("login password: ".length());if(fileManagerService.login(username,password)){textArea.insertText(textArea.getLength(), "\n"+command());}else{textArea.insertText(textArea.getLength(), "\ncheck password: ");}}else if(lineIdx==3&&getLineText(inputCommand,lineIdx).startsWith("check password: ")){password2 = getLineText(inputCommand,lineIdx).substring("check password: ".length());if(password2.equals(password)){Boolean registerStatus = fileManagerService.register(username, password);if(registerStatus){textArea.insertText(textArea.getLength(), "\n"+command());}}else{textArea.insertText(textArea.getLength(), "\n两次输入密码不一致!check password: ");}}else{if(inputCommand.length()>0&&lineIdx>0){cmd = getLineText(inputCommand,lineIdx).substring(command().length());}else{cmd = "";}String[] args = cmd.split(" ");if(cmd == null || "".equals(cmd)){textArea.insertText(textArea.getLength(), "\n"+command());}else if(args.length>1 && "--help".equals(args[1])){String helpTxt = fileManagerService.help(args[0]);textArea.insertText(textArea.getLength(), "\n"+helpTxt);textArea.insertText(textArea.getLength(), "\n"+command());}else if(cmd.startsWith("cd")){if(args.length>1){textArea.insertText(textArea.getLength(), "\n"+fileManagerService.cd(args[1]));}else{textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("cd"));}textArea.insertText(textArea.getLength(), "\n"+command());}else if("ls".equals(cmd)){textArea.insertText(textArea.getLength(), "\n"+fileManagerService.ls());textArea.insertText(textArea.getLength(), "\n"+command());}else if("ll".equals(cmd)){textArea.insertText(textArea.getLength(), "\n"+fileManagerService.ll());textArea.insertText(textArea.getLength(), "\n"+command());}else if(cmd.startsWith("cp")){if(args.length>2){fileManagerService.cp(args[1],args[2]);}else{textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("cp"));}textArea.insertText(textArea.getLength(), "\n"+command());}else if(cmd.startsWith("mv")){if(args.length>2){fileManagerService.mv(args[1],args[2]);}else{textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("mv"));}textArea.insertText(textArea.getLength(), "\n"+command());}else if(cmd.startsWith("mkdir")){if(args.length>1){fileManagerService.mkdir(args[1]);}else{textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("mkdir"));}textArea.insertText(textArea.getLength(), "\n"+command());}else if(cmd.startsWith("mk")){if(args.length>1){fileManagerService.mk(args[1]);}else{textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("mk"));}textArea.insertText(textArea.getLength(), "\n"+command());}else if(cmd.startsWith("rmdir")){Boolean rmdirStatus = false;if(args.length>1) {rmdirStatus = fileManagerService.rmdir(args[1]);}else{textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("rmdir"));}System.out.println(rmdirStatus?"删除文件夹成功":"删除文件夹失败");textArea.insertText(textArea.getLength(), "\n"+command());}else if(cmd.startsWith("rm")){Boolean rmStatus = false;if(args.length>1) {rmStatus = fileManagerService.rm(args[1]);}else{textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("rm"));}System.out.println(rmStatus?"删除文件成功":"删除文件失败");textArea.insertText(textArea.getLength(), "\n"+command());}else if(cmd.startsWith("cat")){String catRst = "";if(args.length>1) {catRst = fileManagerService.cat(args[1]);}else{textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("cat"));}textArea.insertText(textArea.getLength(), "\n"+catRst);textArea.insertText(textArea.getLength(), "\n"+command());}else if(cmd.startsWith("head")){String headTxt = "";if(args.length>2){int n = Integer.parseInt(args[2]);headTxt = fileManagerService.head(args[1], n);}else{textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("head"));}textArea.insertText(textArea.getLength(), "\n"+headTxt);textArea.insertText(textArea.getLength(), "\n"+command());}else if(cmd.startsWith("tail")){String tailTxt = "";if(args.length>2){int n = Integer.parseInt(args[2]);tailTxt = fileManagerService.tail(args[1], n);}else{textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("tail"));}textArea.insertText(textArea.getLength(), "\n"+tailTxt);textArea.insertText(textArea.getLength(), "\n"+command());}else if(cmd.startsWith("attrib")){fileManagerService.attrib(args[1],args[2]);textArea.insertText(textArea.getLength(), "\n"+command());}else if(cmd.startsWith("write")){if(args.length>3){if("-a".equals(args[1])){fileManagerService.write(0,args[2],args[3]);}else if("-c".equals(args[1])){fileManagerService.write(1,args[2],args[3]);}}else{textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("write"));}textArea.insertText(textArea.getLength(), "\n"+command());}else if(cmd.startsWith("open")){if(args.length>2){textArea.insertText(textArea.getLength(), "\n"+fileManagerService.open(args[1],args[2]));}else{textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("open"));}textArea.insertText(textArea.getLength(), "\n"+command());}else if(cmd.startsWith("close")){if(args.length>1){Integer thrdId = Integer.parseInt(args[1]);textArea.insertText(textArea.getLength(), "\n"+fileManagerService.close(thrdId));}else{textArea.insertText(textArea.getLength(), "\n"+missOpeErrMsg("close"));}textArea.insertText(textArea.getLength(), "\n"+command());}else if(cmd.startsWith("help")){textArea.insertText(textArea.getLength(), "\n"+fileManagerService.help());textArea.insertText(textArea.getLength(), "\n"+command());}else if(cmd.startsWith("exit")){System.exit(0);}else if(cmd.startsWith("clear")){textArea.setText(command());textArea.positionCaret(textArea.getText().length());event.consume();}else{// 在文本区域末尾添加新的命令提示符并保存最后一条命令textArea.insertText(textArea.getLength(), "\n"+cmd+": command not found");textArea.insertText(textArea.getLength(), "\n"+command());}}// 检查用户输入的命令是否为 "exit",如果是则退出程序if (inputCommand.trim().equalsIgnoreCase("exit")) {System.exit(0); // 退出程序}}}public void LoginF(KeyEvent event){// 设置文本的同时,把光标设置到末尾textArea.appendText("login as: ");textArea.positionCaret(textArea.getText().length());event.consume();}//获取一行的输入(lineIdx从1开始)public String getLineText(String text, int lineIdx) {if (lineIdx < 1) {return "err"; // 行索引必须从1开始}String[] lines = text.split("\n");if (lineIdx > lines.length) {return "err"; // 行索引超出范围}return lines[lineIdx - 1]; // 返回指定行的内容}private String command(){return FileContext.user+"@"+System.getenv().get("COMPUTERNAME")+":"+fileManagerService.getNowPath()+"$ ";}private String missOpeErrMsg(String operandStr){return operandStr+": missing operand\nTry '"+operandStr+" --help' for more information";}public static void main(String[] args) {launch(args);}
}
【测试结果】
【设计总结】
这个学期末的16-18周,一共是有1个大作业,2个课设,对我而言就相当于3个课设,其中,操作系统课设是我耗时最久完成的一个,共计用时两天两夜。
说实话写的挺崩溃的,一开始想内卷一下,所以就选择了用JavaGUI来复刻Linux终端,做出来黑框框还行,挺好做的,但真要写成linux那种的一样,可就有点麻烦了。除了得考虑每次换行的行首都是用户id和主机名,还得考虑光标位置在textArea的最后,以及不能让用户删除$ 之前的内容。由于我并没有系统学习过JavaGUI,所以前期屡屡碰壁,先是自己写了个lineIdx,用自增的方式来记录textArea中行数,结果发现报了好多错,找了好久才发现,呀,有内置函数直接或切换行数。所以就很顺利的把bug解决掉了。
第二个难点是我做完所有开发之后再开始做的附加题,这就导致我要改的代码量巨大无比,同时,还得高度专注,尽可能规避掉因为小错误导致的bug。再加上上学期学的东西基本都还给老师了,所以都还得重新学,从理清楚逻辑,到代码实现,还是有那么一丢丢难度的。
第三个难点本来不应该是登录注册的,但因为我凌晨写的时候逻辑有点乱套了,导致删删改改写了将近3个小时才完成。用户的账号密码用明文存储在txt里了,这种解决方案我是不很满意的,如果真认真写,除了加密以外,还可以加上转json操作、3次输密码机会的操作等等。另外还应该对登录失败的各种情况做判断,但实在没精力写了,这一块的工作就放掉了。
最后一个难点就是写文档了。还是有点小懒,没有在开发过程中就写好文档的习惯,于是,现在是早上7点,连续通了两天宵的我还在这码字,不过早上过去答个辩应该就能溜了吧,真得睡觉了。
总而言之,这次的操作系统课设还是非常有意思的,这也是我第一次独立完成操作系统的开发,虽然难点有好几个,但也基本是逐个攻破了。搜了很多资料,发现全网貌似没人做过类似的JavaGUI仿Linux终端,一方面觉得自己挺厉害的,还真让我做出来了,另一方面也知道,作为练习使用尚可,但真在实战中开发操作系统,用Java开发是绝对不行的,这也是我目前存在的问题之一,掌握的语言数量太少,没法灵活根据需求调节。
最后,感谢这10天里坚持不懈写了3个课设的自己,感觉自己进步非常大,也感谢老师的指导,期待大四还能有您的课!