07_实验七_拓展实验一

拓展实验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操作系统整个运行过程有了更加深刻的了解。

posted @ 2023-12-07 23:26  彬彬zhidao  阅读(754)  评论(0编辑  收藏  举报