[山东大学操作系统课程设计]实验四+实验五

news2024/11/28 18:40:19

0.写在前面:

为什么这次把两个实验放在一起写了,因为实验五的要求就是在实验四的基础上完成实现的。但是我得实现说明,我的实验四虽然完成了要求,但是无法在我自己的实验四的基础上完成实验五,这是一个很大的问题,所以在这里将直接将实验四和实验五统一进行讲解和处理。

由于后续时间不足,因此实验四开始,全部的东西将完全使用报告的形式来进行演示。在一些我遇到坑的地方,我会告诉大家怎么样处理,当然仅此而已了。。。。。

对不起啦:D

小三爷,你大胆的往前走,莫回头

其实说真的,我的本职工作是写小说的,或许未来的某天我会试着重新开始写我想写的东西。

1.实验要求(四五)

1.实验四

实验四的要求,按照2021级的要求,大概就是这些了

1. 扩展nachos的文件大小

2. 增加文件的修改时间

其实对于这个实验来说,这个实验就是一个分水岭了,接下来该怎么做,就算是抄也要对源码部分有一定的理解才可以了。

2.实验五

实验五的要求,总结一下大致如下:

0.在实验四的基础上实现

1.实现二级索引

2.增加-DI指令,可以输出磁盘的占用状态

3.尝试并解释如何为nachos加上权限实现(不要求代码实现)

其实可以看出来,核心就在于两个实验各自的第一条,但是这个实验难搞的点就在于,我们需要百分百保证实验四的可行性,而实验手册对于这个的讲解其实非常混乱,因此我们在这里的实验步骤,是一次性完成实验四五的!大家可以按需所需,这个过程中需要什么可以直接选择,如果是全篇,可以拿去当实验五的完整步骤

ok,那么我们开始?

2.实验步骤

首先,将图中对应文件移动进入lab5文件夹

filehdr类

在h头文件中,加入以下声明:

并且在对应的类中,加入如下属性以及方法

(注意,这里给print加上了一个参数默认,这个参数我们会后续说明怎么修改,print下面的方法属于我们的添加)

在filehdr.cc文件中,实现对于这些功能的声明:

在这里直接写出对于对应方法的修改,自行取用

方法新增:

对于构造和解析方法:

FileHeader::FileHeader(){
    memset(dataSectors, 0, sizeof(dataSectors));
    bHdrChange=false;
    lastModTime=0;
}
FileHeader::~FileHeader(){
    if(bHdrChange){
        WriteBack(sectorNo);
    }
}

对于expendSize方法:

//申请新的扇区的方法
bool 
FileHeader::ExtendSpace(int newFileSize){  
    int i;

    if(newFileSize<=numBytes)
        return true;
   
    int numSectorsSet=divRoundUp(newFileSize,SectorSize); //计算新的扇区位置
    if(numSectorsSet==numSectors){
        numBytes = newFileSize;
        bHdrChange = true;
        return true;
    }

    BitMap * freeMap= new BitMap(NumSectors);
    OpenFile * bitMapFile=new OpenFile(0);
    freeMap->FetchFrom(bitMapFile);

    if(numSectorsSet>MaxFileSectors||freeMap->NumClear()<numSectorsSet-numSectors){ //超过最大限度或者是不合适
        //经过答案提示需要删除点东西==========维持系统的稳定性吧=============
        delete bitMapFile;
        delete freeMap;
        return false;
    }
    if(numSectorsSet<=NumDirect){                       //不需要二级索引的情况 
        for(int i=numSectors;i<=numSectorsSet;i++){     
            dataSectors[i] = freeMap->Find();     
        }
    }else{                                       //需要二级索引的情况
        if(numSectors<NumDirect){
            for(int i=numSectors;i<=NumDirect;i++){
                 dataSectors[i] = freeMap->Find();    
            }
            numSectors=NumDirect;
        }
        int dataSectors2[NumDirect2];
        synchDisk->ReadSector(dataSectors[NumDirect],(char *)dataSectors2);
        for(i=0;i< numSectorsSet-numSectors;i++){
            dataSectors2[i+numSectors-NumDirect]=freeMap->Find();
        }
        synchDisk->WriteSector(dataSectors[NumDirect],(char *)dataSectors2);
    }
    freeMap->WriteBack(bitMapFile);
    numBytes=newFileSize;
    numSectors=numSectorsSet;
    bHdrChange=true;
    delete bitMapFile;
    delete freeMap;
    return true;
}

关于修改时间的参数

//关于时间新增的方法如下
//这两个方法为文件头这个对象加上了些许时间属性。
time_t FileHeader::GetModTime(){
    return (time_t)lastModTime;
}
void FileHeader::SetModTime(time_t modTime){
    lastModTime=(unsigned)modTime;
}

关于代码的重写

首先是空间分配的方法

bool  
FileHeader::Allocate(BitMap *freeMap, int fileSize)        //这个方法可能只是用于最开始分配空间而已了。。。。
{ 
    printf("\n=========分配空间================\n");
    int i;
    numBytes = fileSize;                                                          //确定文件的大小
    numSectors  = divRoundUp(fileSize, SectorSize);                              //根据文件大小和每个盘的尺寸确定要分几个盘

    
    if (freeMap->NumClear() < numSectors)
	    return FALSE;		    // not enough space  空间不足
    else if(NumDirect2+NumDirect<numSectors)
        return FALSE;           // 超过了二级索引所能容纳的最大部

    
    //然后接下来分成两种情况,也就是看看是否超过了最后一层的索引,根据这种情况来完成需要的分配        //应该就是这里设置了-1这个东西
    if(numSectors<=NumDirect){
        for (int i = 0; i < numSectors; i++)
	        dataSectors[i] = freeMap->Find();
        //这个时候,按照要求,因为没有用上二级的索引,因此最后一个指向二级索引的块应该为-1
        dataSectors[NumDirect]=-1;
        //打印当前信息
        printf("numSector:%d . numBytes:%d . lastIndex:%d\n",numSectors,numBytes,NumDirect);
    }else{
        for (int i = 0; i <=NumDirect; i++)                //更改2,这个位置写错了
	        dataSectors[i] = freeMap->Find();
        
        int dataSectors2[NumDirect2];
        for (int i = 0; i < numSectors - NumDirect; i++)
	        dataSectors2[i] = freeMap->Find();
        //将结果写回,就算成功了
        synchDisk->WriteSector(dataSectors[NumDirect],(char *)dataSectors2);
    }
    //=================================================================================

    return true;
}

撤销空间分配的方法

void 
FileHeader::Deallocate(BitMap *freeMap)                     //在释放空间这一步需要回收二级列表
{
    int lastIndex=NumDirect;
    if(dataSectors[lastIndex]==-1){                         //
        for (int i = 0; i < numSectors; i++) {
            ASSERT(freeMap->Test((int) dataSectors[i]));  // ought to be marked!
            freeMap->Clear((int) dataSectors[i]);
        }
    }else{
        for (int i = 0; i < lastIndex; i++) {
            ASSERT(freeMap->Test((int) dataSectors[i]));  // ought to be marked!
            freeMap->Clear((int) dataSectors[i]);
        }
        int dataSectors2[NumDirect2];
        synchDisk->ReadSector(dataSectors[lastIndex],(char *)dataSectors2);
        freeMap->Clear(dataSectors[lastIndex]);
        for (int i = lastIndex; i < numSectors; i++) {
            ASSERT(freeMap->Test((int) dataSectors2[i-lastIndex]));  // ought to be marked!
            freeMap->Clear((int) dataSectors2[i-lastIndex]);
        }
    }
    
}

根据扇区号码,拉取和写回的方法

void
FileHeader::FetchFrom(int sector)
{
    synchDisk->ReadSector(sector, (char *)this);
    numSectors=divRoundUp(numBytes,SectorSize);
    sectorNo=sector;
    bHdrChange=false;
}

//----------------------------------------------------------------------
// FileHeader::WriteBack
// 	Write the modified contents of the file header back to disk. 
//
//	"sector" is the disk sector to contain the file header
//----------------------------------------------------------------------

void
FileHeader::WriteBack(int sector)
{
    //在这里修改时间即可
    synchDisk->WriteSector(sector, (char *)this); 
    sectorNo=sector;
    bHdrChange=false;
}

print方法

void                                            //这个是打印具体文件信息的方法
FileHeader::Print(bool bPrintTime)
{
    int i, j, k;
    char *data = new char[SectorSize];

    //临时测试
    //lastTime=(unsigned)std::time(nullptr);  //段错误就这段代码引起的

    if(bPrintTime){
        printf("FileHeader contents==========\n.  File size: %d. lastModTime: %u .  File blocks:\n",numBytes,lastModTime);
    }else{
        printf("FileHeader contents==========\n.  File size: %d.  File blocks:\n", numBytes);
    }  
    
    //这里加上两种情况,来输出所占的块的数目=========================================================
    int lastIndex=NumDirect;
    if(dataSectors[lastIndex]==-1){  //仅仅占用了一个索引
       for (i = 0; i < numSectors; i++)
	       printf("%d ", dataSectors[i]);
       printf("\n没有使用二级索引\n");
    }else{                           //占用了所有的索引        
        printf("一级索引内容\n");
        for (i = 0; i < lastIndex; i++)
	       printf("%d ", dataSectors[i]);
        printf("\n二级索引内容\n");
        int dataSectors2[NumDirect2];
        synchDisk->ReadSector(dataSectors[lastIndex],(char*)dataSectors2);
        for (; i < numSectors; i++)
	       printf("%d ", dataSectors2[i-lastIndex]);  //为什么这里会一直认为是-1呢
        printf("\n确定使用二级索引,索引地址为:%d\n",dataSectors[lastIndex]);
    }
    //==========================================================================================

    printf("\nFile contents:\n");

    for (i = k = 0; i < numSectors; i++) {
	synchDisk->ReadSector(dataSectors[i], data);
        for (j = 0; (j < SectorSize) && (k < numBytes); j++, k++) {
	    if ('\040' <= data[j] && data[j] <= '\176')   // isprint(data[j])
		printf("%c", data[j]);
            else
		printf("\\%x", (unsigned char)data[j]);
	}
        printf("\n"); 
    }
    delete [] data;
}

然后是对于openfile类的修改:

openfile:

首先是对于头文件中的声明

新增一个写回方法

属性增加这些:

接下来是对实现的一个修改

首先是对于构造方法的修改,如下

OpenFile::OpenFile(int sector)             //文件读取器的创建
{ 
    hdr = new FileHeader;
    hdr->FetchFrom(sector);
    seekPosition = 0;
    // 修改时间=======================================================================================================
    hdr->SetModTime((unsigned)std::time(nullptr));
    hdrSector=sector;
}

读和写的方法,大概是这些,我在这里主要添加了关于时间修改的函数

int
OpenFile::Read(char *into, int numBytes)            //读写文件
{
   int result = ReadAt(into, numBytes, seekPosition);
   seekPosition += result;
   // 修改时间=======================================================================================================
   hdr->SetModTime((unsigned)std::time(nullptr));

   return result;
}

int
OpenFile::Write(char *into, int numBytes)
{
   int result = WriteAt(into, numBytes, seekPosition);
   seekPosition += result;

   // 修改时间=======================================================================================================
   hdr->SetModTime((unsigned)std::time(nullptr));
   return result;
   
}

在writeAt方法上作出改进

int
OpenFile::WriteAt(char *from, int numBytes, int position)   //最后是在这个方法上进行一些小小的改进
{
    int fileLength = hdr->FileLength();
    int i, firstSector, lastSector, numSectors;
    bool firstAligned, lastAligned;
    char *buf;

    //if ((numBytes <= 0) || (position >= fileLength))  // For original Nachos file system
    // For lab4 ...

    if ((numBytes <= 0)|| position > fileLength)    return 0;	


    if ((position+numBytes) >= fileLength)
        if(!(hdr->ExtendSpace(position+numBytes)))
           numBytes=fileLength-position;

    if(fileLength==0)       return 0;

    DEBUG('f', "Writing %d bytes at %d, from file of length %d.\n", 	
			numBytes, position, fileLength);

    firstSector = divRoundDown(position, SectorSize);
    lastSector = divRoundDown(position + numBytes - 1, SectorSize);
    numSectors = 1 + lastSector - firstSector;

    buf = new char[numSectors * SectorSize];

    firstAligned = (bool)(position == (firstSector * SectorSize));
    lastAligned = (bool)((position + numBytes) == ((lastSector + 1) * SectorSize));

// read in first and last sector, if they are to be partially modified
    if (!firstAligned)
        ReadAt(buf, SectorSize, firstSector * SectorSize);	
    if (!lastAligned && ((firstSector != lastSector) || firstAligned))
        ReadAt(&buf[(lastSector - firstSector) * SectorSize], 
				SectorSize, lastSector * SectorSize);	

// copy in the bytes we want to change 
    bcopy(from, &buf[position - (firstSector * SectorSize)], numBytes);

// write modified sectors back
    for (i = firstSector; i <= lastSector; i++)	
        synchDisk->WriteSector(hdr->ByteToSector(i * SectorSize), 
					&buf[(i - firstSector) * SectorSize]);
    delete [] buf;
   hdr->SetModTime((unsigned)std::time(nullptr));   // 修改时间==============
    return numBytes;
}

最后实现写回方法

void 
OpenFile::WriteBack(void){     //给文件头部设置扇区号
    hdr->WriteBack(hdrSector);
}

directory:

我好像是直接加入了这个方法

int Directory::GetHdrSecByIndex(int index){
    if(table[index].inUse){
        return table[index].sector;
    }else{
        return -1;
    }
}

filesys:

在这个类中,我们只需要实现一个打印的方法,来帮助我们实现需要的-DI指令获取文件信息的操作

首先在头文件的声明中,加入

然后直接实现这个方法即可

void FileSystem::PrintInfo(){
    BitMap *freeMap=new BitMap(NumSectors);
    Directory *directory=new Directory(NumDirEntries);
    FileHeader *fileHdr= new FileHeader();
    int i, nBytes;
    int fileHdrSector;
    int nUsedSector, nFiles =0, nBytesInFiles=0,nSectorsOfFiles=0,nFragSectors=0;

    printf("\n磁盘尺寸:%d个扇区,字节数目:%d\n",NumSectors,NumSectors*SectorSize);

    freeMap->FetchFrom(freeMapFile);

    nUsedSector = NumSectors - freeMap->NumClear();
    printf("\n(已经使用):%d个扇区,字节数目:%d\n",nUsedSector,nUsedSector*SectorSize);
    printf("\n(未使用):%d个扇区,字节数目:%d\n",freeMap->NumClear(),freeMap->NumClear()*SectorSize);

    directory->FetchFrom(directoryFile);
    for(i = 0; i < NumDirEntries; i++) {
        fileHdrSector = directory->GetHdrSecByIndex(i);
        if(fileHdrSector != -1) {
            nFiles++;
            fileHdr->FetchFrom(fileHdrSector);
            nBytes = fileHdr->FileLength();
            nBytesInFiles += nBytes;
            nSectorsOfFiles += divRoundUp(nBytes, SectorSize);
            if(nBytes % SectorSize)
                nFragSectors++;
        }
    }

    printf("%d bytes in %d files, occupy %d bytes(%d sectors).\n", \
            nBytesInFiles, nFiles, nSectorsOfFiles * SectorSize, nSectorsOfFiles);
    printf("%d bytes of internal fragmentation in %d sectors.\n", \
            nSectorsOfFiles * SectorSize - nBytesInFiles, nFragSectors);

    delete freeMap;
    delete directory;
    delete fileHdr;
}

main:

最后在这个入口文件中,新增这样一个分支

到此为止,所有的代码全部修改完毕:

3.实验汇总

关于代码的执行:

首先使用如下指令,初始化并且复制一个big文件

make
rm -f DISK

//初始化磁盘
./nachos -f

然后使用cp指令创建big文件

./nachos -cp test/big big

使用./nachos -D查询当前文件的状态

可以看到已经创建了big文件

此时使用指令

./nachos -ap test/big big

执行多次以后,可以检查到最后的修改时间被更新,并且已经实现了二级索引

最后使用指令

./nachos -D

查看占用情况:

最后是关于为nachos添加wxz等权限:

  • 在Nachos的文件系统中,每个文件都有一个相应的文件描述符(File Descriptor),用于标识和操作该文件。

  • 使用文件描述符打开文件,并获取文件的当前权限。在Nachos中,可以使用 OpenFile::Open 函数打开文件,并使用 OpenFile::GetFileHeader 函数获取文件的文件头(File Header)信息。

  • 在文件头中,找到与权限相关的位(比如读、写、执行位),并根据需要修改这些位。可以使用位操作来设置或清除相应的权限位。

  • 将修改后的文件头写回到文件系统中,以保存更改后的权限。可以使用 OpenFile::WriteAt 函数将文件头写回磁盘中的相应位置。

  • 关闭文件并释放相关资源。使用 OpenFile::Close 函数关闭文件

4.可能会踩坑的问题

我这里只说一个,我在实验四里面遇到的一个大问题,就是这个3到底含义是什么

答案就是,nachos的文件头需要存储在一个sector的内部,但是一个磁盘内的容量仅仅为128字节,而我们需要存入的是这些东西

这些东西是按照顺序存储的,如果这些东西超过了128字节,那就没救了

因此我们需要适当修改数组的大小,因此在定义NumDirect的时候,是根据我们需要存储的其他变量的大小,修改数组的长度

5.补充,我自己的实验四实现(等待补充)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1300194.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能优化算法应用:基于被囊群算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于被囊群算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于被囊群算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.被囊群算法4.实验参数设定5.算法结果6.参考文…

linux权限管理以及shell

1.shell 1.1什么是shell? shell即外壳&#xff0c;是运行在linux系统上的一个脚本语言&#xff0c;包裹在linux内核的外面。我们常说的linux操作系统实际上是linux内核。我们使用的所有指令都是一个个程序&#xff0c;而shell指令就是一个将我们用户的操作翻译给linux内核的程…

layui日历插件

layui日历插件: 在已开源的layui日历插件的基础上的改版&#xff08;原版插件地址&#xff1a;https://gitee.com/smalldragen/lay-calender-mark&#xff09;https://gitee.com/tangmaozizi/layui-calendar-plugin.gitjava后台代码并没有把项目完整结构上传上去&#xff0c;因…

小黑子——springBoot基础

springBoot简单学习 一、SpringBoot简介1.1 springBoot快速入门1.1.1 开发步骤1.1.2 对比1.1.3 官网构建工程1.1.3 SpringBoot工程快速启动 1.2 springBoot概述1.2.1 起步依赖I. 探索父工程II. 探索依赖III. 小结 1.2.2 程序启动1.2.3 切换web服务器-jetty 二、配置文件2.1 配置…

Redis权限管理体系(一):客户端名及用户名

在Redis6之前的版本中&#xff0c;因安全认证的主要方式是使用Redis实例的密码进行基础控制&#xff0c;而无法按照不同的应用来源配置不同账号以及更细粒度的操作权限控制来管理。本文先从client list中的信息入手&#xff0c;逐步了解Redis的客户端名设置、用户设置及权限控制…

simulink MATLABFunction模块中实时函数调用函数的使用

样例 function Predyy matlabceshi(input, Time_s) input1 input; Time_s1 Time_s; Predyy ee(input1) mm(Time_s1); end 上面是主要部分&#xff0c;下面是被调用部分 function A ee(input1) A input1 * 100; end function B mm(Time_s1) B Time_s1 * 100; end 模型…

每日一练【盛最多水的容器】

一、题目描述 11. 盛最多水的容器 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明…

Threejs发光闪烁提示特效

一、导语 发光闪烁特效应该在我们的项目中是经常需要去封装的一个特效吧&#xff0c;一般用于点击选择&#xff0c;选中物体&#xff0c;或者一些特效加持于中心物体&#xff0c;物体碰撞检测后的发光特效等等 二、分析 我们可以合理的使用后处理特效&#xff0c;上步骤&am…

深度学习与计算机视觉技术的融合

深度学习与计算机视觉技术的融合 一、引言 随着人工智能技术的不断发展&#xff0c;深度学习已经成为了计算机视觉领域的重要支柱。计算机视觉技术能够从图像和视频中提取有用的信息&#xff0c;而深度学习则能够通过学习大量的数据来提高计算机视觉技术的性能。本文将探讨深…

简易加减运算器的制作----数字电路设计(含proteus仿真)

简易加减运算器的制作 一、功能要求—基本功能 1、自制0-9按键&#xff0c;在一个LED数码管上稳定地显示当前按下的值。&#xff08;基本功能&#xff09; 2、增加、两个按键&#xff0c;实现0-9两个一位数的加法运算&#xff0c;同时在两位LED上稳定地显示运算结果。&#…

[MySQL--进阶篇]存储引擎的体系结构、简介、特点、选择

前言 ⭐Hello!这里是欧_aita的博客。 ⭐今日语录&#xff1a;不要在乎别人怎么看你&#xff0c;因为他们根本就没有时间&#xff0c;他们只关心他们自己。 ⭐个人主页&#xff1a;欧_aita ψ(._. )>⭐个人专栏&#xff1a; 数据结构与算法 MySQL数据库 存储引擎 前言MySQL体…

class066 一维动态规划【算法】

class066 一维动态规划 算法讲解066【必备】从递归入手一维动态规划 code1 509斐波那契数列 // 斐波那契数 // 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 // 该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。…

049:VUE 引入jquery的方法和配置

第049个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

PWN动态调试

这篇文章就是来教大家学习怎么动态调试的&#xff0c;然后我还写了一篇关于动态调试的文章&#xff0c;不是buu上面的题&#xff0c;就是两道简单的栈溢出问题&#xff0c;那两道题挺有特点的。大家可以去看看。 每日3道PWN之课外2道&#xff08;第2.5天&#xff09;-CSDN博客 …

uniApp项目的创建,运行到小程序

一、项目创建 1. 打开 HBuilder X 2. 右击侧边栏点击新建&#xff0c;选择项目 3. 填写项目名&#xff0c;点击创建即可 注&#xff1a;uniapp中如果使用生命周期钩子函数&#xff0c;建议使用哪种 ?(建议使用Vue的) 二、运行 1. 运行前先登录 2. 登录后点击 manifest.js…

基于SSM的酒店管理旅店系统(Java毕业设计)

大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的方法。无论你是…

模块一——双指针:202.快乐数

文章目录 题目描述简单证明补充知识算法原理代码实现 题目描述 题目链接&#xff1a;202.快乐数 为了方便叙述&#xff0c;将对于⼀个正整数&#xff0c;每⼀次将该数替换为它每个位置上的数字的平方和这⼀个操作记为x操作&#xff1b; 题目告诉我们&#xff0c;当我们不断重…

Python之random和string库学习

一、random库 random是python中用来生存随机数的库。具体用法如下&#xff1a; 1、生成一个0到1随机浮点数 random.random() 2、生成一个a到b的随机浮点数 random.uniform(1,2) 3、生成一个a到b之间的整数 random.randint(a,b) 4、随机从序列元素中取出一个值&#xff0c;…

基于SpringBoot+Vue的学校在线学习系统

开发环境 IDEA JDK1.8 MySQL8.0Node 系统简介 本系统拥有管理员&#xff0c;教师&#xff0c;学生三种身份登录&#xff0c;管理员登录可以查看所有信息&#xff0c;教师登录可以发布作业&#xff0c;查看试卷&#xff0c;回答问题等&#xff0c;学校登录可以查看作业&…

低代码还是好用的,我持有这个观念

低代码开发是近年来迅速崛起的软件开发方法&#xff0c;让编写应用程序变得更快、更简单。 有人说它是美味的膳食&#xff0c;让开发过程高效而满足&#xff0c;但也有人质疑它是垃圾食品&#xff0c;缺乏定制性与深度。 你认为低代码到底是美味的膳食还是垃圾食品呢&#xff0…