首页 > 其他分享 >07_实验七_拓展实验一

07_实验七_拓展实验一

时间:2023-12-07 23:34:30浏览次数:48  
标签:Bpb StdHandle 07 实验 拓展 线程 pVcb fprintf TODO

拓展实验1

本拓展实验的任务和目标是为了更好的理解和认识EOS操作系统的内核程序。EOS的内核程序的代码在codecode平台已经给出。参考上面之前已经完成的6个基础实验的调试过程可以更好的理解内核程序的代码。

然后调试一个应用程序的执行过程,详细了解了EOS操作系统的所有重要模块,包括进程线程管理模块、存储器管理模块、输入输出管理模块、对象管理模块等

111

操作系统启动之后,应用程序执行之前,操作系统中有哪些进程和线程,它们是如何创建的?

打开 Ke/start.c 文件,KiSystemStartup 函数的函数体是空的,是留给读者完成的。要求读者先阅读注释,然后根据注释和对操作系统原理的理解在源代码中查找相关函数的声明和实现(这些需要调用的函数在源代码中已实现),最后编写代码实现该函数的功能。

补充start.c文件

/***

Copyright (c) 2008 北京英真时代科技有限公司。保留所有权利。

只有您接受 EOS 核心源代码协议(参见 License.txt)中的条款才能使用这些代码。
如果您不接受,不能使用这些代码。

文件名: start.c

描述: EOS 内核的入口函数。



*******************************************************************************/

#include "ki.h"
#include "mm.h"
#include "ob.h"
#include "ps.h"
#include "io.h"

VOID
KiSystemStartup(
	PVOID LoaderBlock
	)
/*++

功能描述:
	系统的入口点,Kernel.dll被Loader加载到内存后从这里开始执行。

参数:
	LoaderBlock - Loader传递的加载参数块结构体指针,内存管理器要使用。

返回值:
	无(这个函数永远不会返回)。

注意:
	KiSystemStartup在Loader构造的ISR栈中执行,不存在当前线程,所以不能调用任何可
	能导致阻塞的函数,只能对各个模块进行简单的初始化。

--*/
{
	//
	// 初始化处理器和中断。
	//
	KiInitializeProcessor();
	KiInitializeInterrupt();

	//
	// 初始化可编程中断控制器和可编程定时计数器。
	//
	KiInitializePic();
	KiInitializePit();

	//
	// 对各个管理模块执行第一步初始化,顺序不能乱。
	//
	MmInitializeSystem1(LoaderBlock);
	ObInitializeSystem1();
	PsInitializeSystem1();
	IoInitializeSystem1();

	//
	// 创建系统启动进程。
	//
	PsCreateSystemProcess(KiSystemProcessRoutine);

	//
	// 执行到这里时,所有函数仍然在使用由 Loader 初始化的堆栈,所有系统线程
	// 都已处于就绪状态。执行线程调度后,系统线程开始使用各自的线程堆栈运行。
	//
	KeThreadSchedule();

	//
	// 本函数永远不会返回。
	//
	ASSERT(FALSE);
}

练习

(1) 说明 PsCreateThread 函数和 PspCreateThread 函数的区别。

PsCreateThread函数是创建系统进程,他包括创建进程环境和创建进程的主线程,PspCreateThread函数是创建进程的主线程,PsCreateThread函数创建一个系统进程后,他之后的实现调用需要利用PspCreateThread函数来为该系统进程创建主线程。

(2) 在下面的表格中填写系统线程相关的信息。

系统线程名称 默认优先级 线程处理函数 功能
闲逛进程 0 PspCreateThread 当没有优先级大于0的线程占用处理器时,空闲线程就会占用处理器
控制台派遣线程 24 PspCreateThread 将键盘事件派遣到活动的控制台线程
控制台1线程 24 PspCreateThread 操作控制台1
控制台2线程 24 PspCreateThread 操作控制台2
控制台3线程 24 PspCreateThread 操作控制台3
控制台4线程 24 PspCreateThread 操作控制台4

222查看软盘的使用情况和软盘包含的文件列表

FAT12 文件系统设备对象的扩展结构体——卷控制块(VCB)。

typedef struct _VCB {
PDEVICE_OBJECT DiskDevice; // 文件系统下层的软盘卷设备对象
BIOS_PARAMETER_BLOCK Bpb; // 文件系统的参数。
PVOID Fat; // 文件分配表缓冲区。
ULONG FirstRootDirSector; // 根目录起始扇区
ULONG RootDirSize; // 根目录大小
LIST_ENTRY FileListHead; // 根目录文件链表头。
ULONG FirstDataSector; // 文件数据区的起始扇区
USHORT NumberOfClusters; // 簇的总数
}VCB, *PVCB;

补全代码

ke/sysproc.c 文件中ConsoleCmdScanDisk 函数的补全

	//
	// 得到 FAT12 文件系统设备对象,然后得到卷控制块 VCB
	//
	FatDevice = (PDEVICE_OBJECT)ObpLookupObjectByName(IopDeviceObjectType, "A:");
	pVcb = (PVCB)FatDevice->DeviceExtension;
	// TODO:
	
	//
	// 将卷控制块中缓存的 BIOS Parameter Block (BPB) ,以及卷控制块中的其它重要信息输出
	//
	
	// TODO:		
	fprintf(StdHandle, "******** BIOS Parameter Block (BPB) ********\n");
	fprintf(StdHandle, "Bytes Per Sector   : %d\n", pVcb->Bpb.BytesPerSector);
	fprintf(StdHandle, "Sectors Per Cluster: %d\n", pVcb->Bpb.SectorsPerCluster);
	fprintf(StdHandle, "Reserved Sectors   : %d\n", pVcb->Bpb.ReservedSectors);
	fprintf(StdHandle, "Fats               : %d\n", pVcb->Bpb.Fats);
	fprintf(StdHandle, "Root Entries       : %d\n", pVcb->Bpb.RootEntries);
	fprintf(StdHandle, "Sectors            : %d\n", pVcb->Bpb.Sectors);
	fprintf(StdHandle, "Media              : 0x%X\n", pVcb->Bpb.Media);
	fprintf(StdHandle, "Sectors Per Fat    : %d\n", pVcb->Bpb.SectorsPerFat);
	fprintf(StdHandle, "Sectors Per Track  : %d\n", pVcb->Bpb.SectorsPerTrack);
	fprintf(StdHandle, "Heads              : %d\n", pVcb->Bpb.Heads);
	fprintf(StdHandle, "Hidden Sectors     : %d\n", pVcb->Bpb.HiddenSectors);
	fprintf(StdHandle, "Large Sectors      : %d\n", pVcb->Bpb.LargeSectors);
	fprintf(StdHandle, "******** BIOS Parameter Block (BPB) ********\n\n");

	fprintf(StdHandle, "First Sector of Root Directroy: %d\n", pVcb->FirstRootDirSector);
	fprintf(StdHandle, "Size of Root Directroy        : %d\n", pVcb->RootDirSize);
	fprintf(StdHandle, "First Sector of Data Area     : %d\n", pVcb->FirstDataSector);
	fprintf(StdHandle, "Number Of Clusters            : %d\n\n", pVcb->NumberOfClusters);
	
	//
	// 扫描 FAT 表,统计空闲簇的数量,并计算软盘空间的使用情况
	//
	FreeClusterCount = 0;
	for (i = 2; i < pVcb->NumberOfClusters + 2; i++) {
		if (0 == FatGetFatEntryValue(pVcb, i))
			FreeClusterCount++;
	}
	UsedClusterCount = pVcb->NumberOfClusters - FreeClusterCount;
	fprintf(StdHandle, "Free Cluster Count: %d (%d Byte)\n", FreeClusterCount, FreeClusterCount*pVcb->Bpb.SectorsPerCluster*pVcb->Bpb.BytesPerSector);
	fprintf(StdHandle, "Used Cluster Count: %d (%d Byte)\n", UsedClusterCount, UsedClusterCount*pVcb->Bpb.SectorsPerCluster*pVcb->Bpb.BytesPerSector);
	
	// TODO:

image-20231207202127983

ke/sysproc.c 文件的ConsoleCmdDir 函数

	
	//
	// 得到 FAT12 文件系统设备对象,然后得到卷控制块 VCB
	//
	FatDevice = (PDEVICE_OBJECT)ObpLookupObjectByName(IopDeviceObjectType, "A:");
	pVcb = (PVCB)FatDevice->DeviceExtension;
	
	// TODO:
			
	//
	// 分配一块虚拟内存做为缓冲区,然后将整个根目录区从软盘读入缓冲区。
	//
	pBuffer = NULL;		// 不指定缓冲区的地址。由系统决定缓冲区的地址。
	BufferSize = pVcb->RootDirSize;	// 申请的缓冲区大小与根目录区大小相同。
	MmAllocateVirtualMemory(&pBuffer, &BufferSize, MEM_RESERVE | MEM_COMMIT, TRUE);
	
	RootDirSectors = pVcb->RootDirSize / pVcb->Bpb.BytesPerSector;	// 计算根目录区占用的扇区数量
	for(i=0; i<RootDirSectors; i++) {
		// 将根目录区占用的扇区读入缓冲区
		IopReadWriteSector( pVcb->DiskDevice,
							pVcb->FirstRootDirSector + i,
							0,
							(PCHAR)pBuffer + pVcb->Bpb.BytesPerSector * i,
							pVcb->Bpb.BytesPerSector,
							TRUE);
	}
	// TODO:
	
	//
	// 扫描缓冲区中的根目录项,输出根目录中的文件和文件夹信息
	//
	
	// TODO:
	fprintf(StdHandle, "Name        |   Size(Byte) |    Last Write Time\n");
	for(i=0; i<pVcb->Bpb.RootEntries; i++) {
	
		pDirEntry = (PDIRENT)(pBuffer + 32 * i);
		
		//
		// 跳过未使用的目录项和被删除的目录项
		//
		if(0x0 == pDirEntry->Name[0]
			|| (CHAR)0xE5 == pDirEntry->Name[0])
			continue;
		
		FatConvertDirNameToFileName(pDirEntry->Name, FileName);
		
		fprintf(StdHandle, "%s        %d         %d-%d-%d %d:%d:%d\n",
			FileName, pDirEntry->FileSize, 1980 + pDirEntry->LastWriteDate.Year,
			pDirEntry->LastWriteDate.Month, pDirEntry->LastWriteDate.Day,
			pDirEntry->LastWriteTime.Hour, pDirEntry->LastWriteTime.Minute,
			pDirEntry->LastWriteTime.DoubleSeconds);
	}
	//
	// 释放缓冲区
	//
	BufferSize = 0;	// 缓冲区大小设置为 0,表示释放全部缓冲区
	MmFreeVirtualMemory(&pBuffer, &BufferSize, MEM_RELEASE, TRUE);
	

	// TODO:

调试截图

image-20231207202744765

image-20231207202821453

image-20231207202839609

333操作系统如何从控制台窗口获取用户输入的命令

补全代码

keyboard.c 文件KbdRead 函数

键盘驱动程序提供的 Read 功能


	//
	// 读写大小须是键盘事件结构体大小的整数倍。
	//
	// TODO:
	Request -= Request % sizeof(KEY_EVENT_RECORD);

	while (Count < Request) {

		//
		// 阻塞等待直到缓冲区非空。
		//
		// TODO:
		PsWaitForEvent(&Ext->BufferEvent, INFINITE);
		//
		// 读取缓冲区,如果缓冲区被读空了则复位非空事件。
		// 注意,要和键盘中断服务程序互斥访问键盘事件缓冲区,要禁止中断。
		//
		IntState = KeEnableInterrupts(FALSE);
		
		Count += IopReadRingBuffer(Ext->Buffer, Buffer + Count, Request - Count);

		if (IopIsRingBufferEmpty(Ext->Buffer)) {
			PsResetEvent(&Ext->BufferEvent);
		}
		
		KeEnableInterrupts(IntState);
		// TODO:
	}
	

keyboard.c 文件KbdIsr 函数

键盘中断服务程序

VOID
KbdIsr(
	VOID
	)
{
	static UCHAR ScanCode[3];
	static UCHAR i = 0;
	KEY_EVENT_RECORD KeyEventRecord;
	ULONG ControlKeyStateMask;
	PKEYBOARD_DEVICE_EXTENSION Ext = (PKEYBOARD_DEVICE_EXTENSION)KbdDevice[0]->DeviceExtension;

	//
	// 从8042数据端口读取键盘扫描码。
	//
	ScanCode[i] = READ_PORT_UCHAR((PUCHAR)KEYBOARD_PORT_DATA);
	i++;
	// TODO:

练习

  1. 记录输入命令hello的5次按键对应的键盘扫描码。

第一次h:0x23

第二次e:0x12

第三次l:0x26

第四次l:0x26

第五次o:0x18

444应用程序进程是如何在操作系统内核中创建的

补全代码create.c 文件PsCreateProcess 函数

		//
		// 创建一个进程环境(进程的控制块以及进程的地址空间和句柄表)。
		//
		// TODO:
		Status = PspCreateProcessEnvironment(8, ImageName, CmdLine, &ProcessObject);

		if (!EOS_SUCCESS(Status)) {
			break;
		}

		//
		// 在新建进程的句柄表中为标准输入输出对象创建句柄。
		// 因为新建进程的句柄表目前还是空的,所以创建操作肯定不会失败。
		//
		Status = ObCreateHandleEx( ProcessObject->ObjectTable,
								   StdInputObject,
								   &ProcessObject->StdInput);
		ASSERT(EOS_SUCCESS(Status));

		Status = ObCreateHandleEx( ProcessObject->ObjectTable,
								   StdOutputObject,
								   &ProcessObject->StdOutput);
		ASSERT(EOS_SUCCESS(Status));

		Status = ObCreateHandleEx( ProcessObject->ObjectTable,
								   StdErrorObject,
								   &ProcessObject->StdError);
		ASSERT(EOS_SUCCESS(Status));

		StdInputObject = NULL;
		StdOutputObject = NULL;
		StdErrorObject = NULL;

		//
		// 加载可执行映像(程序的指令和数据)到新建进程的用户地址空间中。
		//
		Status = PspLoadProcessImage( ProcessObject,
									  ProcessObject->ImageName,
									  &ProcessObject->ImageBase,
									  (PVOID*)&ProcessObject->ImageEntry );

		// TODO:

		if (!EOS_SUCCESS(Status)) {
			break;
		}

		//
		// 创建新进程的主线程,所有进程的主线程都从函数PspProcessStartup开始执行。
		//
		Status = PspCreateThread( ProcessObject,
								  0,
								  PspProcessStartup,
								  NULL,
								  CreateFlags,
								  &ThreadObject );
	
		// TODO:

		if (!EOS_SUCCESS(Status)) {
			break;
		}

555 应用程序进程的主线程如何创建的,创建后是如何进行状态转换的

补全代码

补全ps/peldr.c文件中PspLoadProcessImage函数,其功能是加载 EOS 应用程序的可执行文件到内存。

冗余代码太多,于是我用图片

image-20231207205700897

image-20231207205727586

image-20231207205753594

image-20231207205824714

image-20231207205905733

666 应用程序的执行结果如何输出到控制窗口中

补全代码

补全io/console.c文件中IopWriteConsoleOutput函数,其功能是写控制台的输出缓冲区。


STATUS
IopWriteConsoleOutput(
	IN PCONSOLE Console,
	IN PVOID Buffer,
	IN ULONG NumberOfBytesToWrite,
	OUT PULONG NumberOfBytesWritten
	)
{

	//
	// TODO:
	//
	ULONG i;

	PsWaitForMutex(&Console->AccessMutex, INFINITE);
	
	for (i = 0; i < NumberOfBytesToWrite; i++) {

		IopWriteScreenBuffer( Console->ScreenBuffer,
							  &Console->CursorPosition,
							  ((PCHAR)Buffer)[i],
							  Console->TextAttributes);
	}

	//
	// 如果控制台是激活的,那么同时还要更新显示器上的光标位置。
	// 注意:要互斥访问变量IopActiveConsole。
	//
	PsWaitForMutex(&IopActiveMutex, INFINITE);

	if (IopActiveConsole == Console) {
		IopSetScreenCursor(Console->ScreenBuffer, Console->CursorPosition);
	}

	PsReleaseMutex(&IopActiveMutex);

	PsReleaseMutex(&Console->AccessMutex);

	*NumberOfBytesWritten = NumberOfBytesToWrite;

	return STATUS_SUCCESS;	
}

调试过程

在ke/sysproc.c中第326行添加一个断点,F5输入hello.exe在断点位置中断。

在api/eosapi.c文件中第 498 行添加一个断点,调用WriteFile函数向标准输出设备(屏幕)写数据,会命中这个断点。

F5命中WriteFile函数中的断点后,按F11进入ObWrite函数内部。

调试并进入到IopWriteConsoleOutput函数的内部。

调试并进入IopWriteScreenBuffer函数,该函数的功能是将字符写入到屏幕缓冲区。

调试到到342行,刷新控制台1可以看到字符H已经写入到输出缓冲区中。激活控制台窗口,可以看到已经输出字符 H。

777 应用程序进程是如何退出的

补全代码

补全ps/delete.c文件中PspTerminateProcess函数,其功能是结束指定进程。

{
	BOOL IntState;
	PTHREAD Thread;

	IntState = KeEnableInterrupts(FALSE);

	if (NULL != Process->PrimaryThread) {

		//
		// 设置进程结束标志(主线程指针为NULL)和结束码。
		//
		Process->PrimaryThread = NULL;
		Process->ExitCode = ExitCode;
		// TODO:

		//
		// 唤醒等待进程结束的所有线程。
		//
		
		// TODO:
		while (!ListIsEmpty(&Process->WaitListHead)) {
			PspWakeThread(&Process->WaitListHead, STATUS_SUCCESS);
		}
		//
		// 结束进程内的所有线程。
		// 注意:并不急于在结束每个线程后都立刻执行线程调度,所有线程都被结束后再执
		// 行一次调度即可。
		//
		while (!ListIsEmpty(&Process->ThreadListHead)) {
			Thread = CONTAINING_RECORD(Process->ThreadListHead.Next, THREAD, ThreadListEntry);
			PspTerminateThread(Thread, ExitCode, TRUE);
		}
		// TODO:

		//
		// 删除进程环境。
		//
		PspDeleteProcessEnvironment(Process);
		// TODO:

		//
		// 执行线程调度。
		//
		PspThreadSchedule();
		// TODO:
	}

	KeEnableInterrupts(IntState);
}

结果分析:

通过在 EOS 内核中调试一个应用程序的执行过程,详细了解了EOS操作系统的所有重要模块,包括进程线程管理模块、存储器管理模块、输入输出管理模块、对象管理模块等,对 EOS内核的主要模块已经进行了深入的学习和研究,这也让我对EOS操作系统整个运行过程有了更加深刻的了解。

标签:Bpb,StdHandle,07,实验,拓展,线程,pVcb,fprintf,TODO
From: https://www.cnblogs.com/binbinzhidao/p/17884255.html

相关文章

  • 06_实验六_读文件和写文件
    读文件和写文件实验目的了解在EOS应用程序中读文件和写文件的基本方法。通过为FAT12文件系统添加写文件功能,加深对FAT12文件系统和磁盘存储器管理原理的理解。文件系统驱动程序的作用用户对文件的读写请求转换为对磁盘扇区的读写请求,并负责对磁盘扇区进行管理。实验内容......
  • 09_实验八_拓展实验三
    拓展实验三:线程调度算法改进实验目的实现多级反馈队列调度算法实验步骤实现时间片轮转调度算法。修改时间片的大小TICKS_OF_TIME_SLICE为100,方便观察执行后的效果。在控制台命令“rr”的处理函数中,将Sleep时间更改为200*1000,这样可以有充足的时间查看优先级降......
  • 实验四 Web服务器2
    实验四Web服务器2基于华为鲲鹏云服务器CentOS中(或Ubuntu),使用LinuxSocket实现:1.Web服务器的客户端服务器,提交程序运行截图2.实现GET即可,请求,响应要符合HTTP协议规范3.服务器部署到华为云服务器,浏览器用本机的4.把服务器部署到试验箱。(加分项)web_server.c1#include......
  • 实验四 Web服务器2
    server.c#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#definePORT8080#defineBUFFER_SIZE1024intmain(){   int......
  • 20231207
    今天上课完成的测试 个人观点非正确答案 软件需求与分析课堂测试之九-面向对象设计与分析      阅读下列图和文字材料 ,回答问题1至问题3。某物品拍卖网站为参与者提供物品拍卖平台,组织拍卖过程,提供在线或线下交易服务。网站的主要功能描述如下:(1)拍卖参与者分......
  • 实验四 Web服务器1
    Web服务器1-socket编程实验内容基于华为鲲鹏云服务器CentOS中(或Ubuntu),使用LinuxSocket实现:time服务器的客户端服务器,提交程序运行截图echo服务器的客户端服务器,提交程序运行截图,服务器把客户端传进来的内容加入“服务器进程pid你的学号姓名echo:”返回给客户端服务器......
  • ARC076D Yakiniku Restaurants
    题意有\(n\)个商店。每个商店有\(m\)个物品。每个物品的价值为\(b_{i,j}\)。每种物品只能被购买一次。你可以选择一个起点,在任意商店结束购买。获得的价值为\(m\)个物品之和减去路程。求最大可获得的价值。\(n\le5e3,m\le200\)Sol不难发现,最优的方案一定是一个......
  • 每日总结20231207
    代码时间(包括上课)5h代码量(行):100行博客数量(篇):1篇相关事项:1、今天是周四,这周的课到此结束,上课的随堂测试也是回答的十分顺利,全部正确,并且在最后的一节课上查了软件设计师的成绩,让人十分高兴,我顺利的通过了,而且每科均达到五十分以上。2、今天下午的时候把我们班的发展团员的相关......
  • ###聪明办法学python Task07:debug调试
    debug的调试1.调试理论的简单介绍在计算机中,我们将机器看作状态机,同时我们遵循计算机不会犯错的原则,因此,如果程序运行不对劲,好好想想是不是自己的问题2.看懂报错信息编译器的报错要看懂,看不懂用翻译调试方法1.print调试:将程序分段后添加print,锁定问题发生地2.assert调试:表......
  • 每日总结_20231207
    UML(UnifiedModelingLanguage)是一种用于软件系统建模的标准化语言,它提供了一组图形符号和规范,以便开发人员可以更好地理解、设计和构建复杂的软件系统。UML包括多种图表,每种图表都有不同的目的和应用场景。1.用例图(UseCaseDiagrams)特点:用例(UseCase)是描述系统功能的一......