GeekOS Project2



Geekos Project2



1.1プロジェクト2の設計要件

このプロジェクトでは、次のドキュメントを変更する必要があります。

(1) 'src / GeekOS / user.c'ファイル内の関数Spawn()。この関数は、新しいユーザーレベルのプロセスを生成することです。



(2) 'src / GeekOS / user.c'ファイルの関数Switch_To_User_Context()、スケジューラーはこの関数を呼び出して、新しいプロセスを実行する前にユーザーアドレス空間を切り替えます。

(3) 'src / GeekOS / elf.c'ファイル内の関数Parse_ELF_Executable()。この機能の実装要件は、プロジェクト1の場合と同じです。



(4)「src / GeekOS / userseg.c」ファイルは、主に「src / GeekOS /user.c」の高レベルの操作サポートを実装するためのいくつかの関数を実装しています。

Destroy_User_Context()関数の関数は、ユーザー状態プロセスによって占有されているメモリリソースを解放することです。

Load_User_Program()関数の関数は、実行可能イメージをロードすることにより、新しいプロセスのUser_Context構造を作成します。



Copy_From_User()関数とCopy_To_User()関数の関数は、ユーザーアドレス空間とカーネルアドレス空間の間でデータをコピーすることです。セグメントメモリ管理モードでは、セグメントが有効である限り、memcpy関数を呼び出して、これら2つの関数の関数を実装できます。

Switch_To_Address_Space()関数の機能は、プロセスのLDTをLDTレジスタにロードすることにより、ユーザーのアドレス空間をアクティブ化することです。

(5)「src / GeekOS /kthread.c」ファイルのStart_User_Thread関数とSetup_User_Thread関数。

Setup_User_Thread()関数の関数は、プロセスのカーネルスタックを初期化することです。スタックは、プロセスが初めてユーザー状態に入るときにプロセッサ状態を設定するために使用されるデータです。

Start_User_Thread()は、User_Contextオブジェクトを使用して新しいプロセスを開始する高レベルの操作です。

(6) 'src / GeekOS / kthread.c'ファイルは主に、ユーザープログラムがカーネルにサービスを提供するために必要なシステムコール関数定義を実装するために使用されます。ユーザーは、Sys_Exit()関数、Sys_PrintString()関数、Sys_GetKey()、Sys_SetAttr()、Sys_GetCursor()、Sys_PutCursor()、Sys_Spawn()関数、Sys_Wait()関数、およびSys_GetPID()関数を実装する必要があります。

(7)main.cファイルで最初のユーザー状態プロセスを生成する関数呼び出しを書き直します:Spawn_Init_Process(void)。最初のユーザー状態プロセスを作成し、それを使用して他のプロセスを作成します。

1.2プロジェクト2の設計原則

(1)GeekOSでは、ユーザー状態プロセスとカーネルプロセスを区別するために、ユーザー状態プロセスコンテキストを指すフィールドuserContextがKernel_Thread構造体に設定されます。カーネルプロセスの場合、このポインターは空であり、ユーザーモードプロセスには独自のユーザーコンテキスト(User_Context)があります。したがって、GeekOSでは、userContextフィールドが空である限り、プロセスがカーネルプロセスであるかユーザーモードプロセスであるかを判断します。

図1ユーザー状態のプロセス構造

(2)プロジェクト2はユーザープロセスを実装する必要があります。実際、ユーザープロセスは、カーネルプロセスに基づく改善です。カーネルプロセス制御ブロック構造Kernel_ThreadにはフィールドUser_Contextがあり、カーネルプロセスの機能を初期化するときに、システムはそれをゼロに初期化します。User_Contextフィールドは、実際には次のように定義されるコンテキストデータ構造です。

StructUser_Context {

#defineNUM_USER_LDT_ENTRIES 3

StructSegment_Descriptor ldt [NUM_USRE_LDT_ENTRIES]

Structsegment_Descriptor * ldtDsecriptor

Char *メモリ

Ulong_tサイズ

Ushort_tldtSelector

Ushort_t csSelector

Ushort_tdsSelector

Pde_t * pageDir

Ulong_tentryAddr

Ulong_targBlockAddr

Ulong_tstackPointerAddr

Int refCount

#if 0

Int *セマフォ

#endif

}

構造体セグメント記述子ldt [NUM_USER_LDT_ENTRIES]:2つの要素のみを持つセグメント記述子配列です。1つはユーザープロセスデータ用のセグメントで、もう1つはユーザープロセスコード用のセグメントです。 ldtDescriptorはLDTセグメント記述子の説明であり、memoryはユーザーメモリスペースの実際のアドレスです。サイズはユーザースペースのサイズです。 entryAddrはユーザーコードの実際のアドレスであり、プロセスはこのアドレスから開始されます。

(3)各ユーザー状態プロセスには、コードセグメント、データセグメント、スタックセグメントなど、独自のメモリセグメントスペースがあります。各セグメントにはセグメント記述子があり、各プロセスにはセグメント記述子テーブルがあります。 (LocalDescriptor Table)、プロセスのすべてのセグメント記述子を保存するために使用されます。グローバル記述子テーブル(GDT、グローバル記述子テーブル)もオペレーティングシステムに設定され、システム内のすべてのプロセスのlDT記述子を記録します。

図2GDT、LDT、およびUser_Contextの関係

1.3プロジェクト2の分析

Geekosシステムの初期カーネルはユーザー状態プロセスをサポートしていませんが、カーネルプロセス制御ブロックKernel_Threadには、プロセスがカーネルプロセスであるかユーザー状態プロセスであるかを示すフィールドUser_Contextがあります。コアプロセスの場合、フィールドには値0が割り当てられます。

user.hから、User_Context構造、ユーザーのLDT、および各記述子の選択とコードエントリがこの構造に格納されていることがわかります。したがって、ユーザープログラムをロードするときは、最初にUser_Context構造体プロセスを初期化してから、ユーザープロセスの初期化を実行する必要があります。以下は、ユーザープロセスの作成プロセスの説明です。

(1)Spawn関数がユーザープログラムにインポートされ、初期化されます。Load_User_Programが呼び出されて、User_Contextが初期化され、ユーザースペースが割り当てられ、ユーザープログラムの各セグメントがロードされます。

(2)Spawn関数は、Start_User_Thread関数を呼び出して、ユーザーもプロセスを初期化します。

(3)スポーンが終了し、ユーザープロセスが呼び出され、スケジュールできる

スポーン関数のプロトタイプは次のとおりです。

int Spawn(const char * program、const char * command、struct Kernel_Thread ** pThread)

パラメータの説明:プログラムは読み取られる実行可能ファイルに対応し、コマンドはユーザーがプログラムを実行するときのコマンドライン文字列であり、PTreadはプロセスへのポインタです。

Spawnが完了する主な機能は次のとおりです。

(1)Read_Fully関数を呼び出して、実行可能ファイルをメモリバッファに読み込みます。

(2)Parse_ELF_Executable関数を呼び出して、ELF形式のファイルを分析します。

(3)Load_User_Program関数を呼び出して、実行可能ファイルのプログラムセグメントとデータセグメントをメモリにロードし、User_contextデータ構造を初期化します。

(4)Start_User_Thread関数を呼び出してプロセスを作成し、そのプロセスを準備キューに入れます。

1.4プロジェクト2の実装

(1)Parse_ELF_Executable関数は項目1を参照してください

(2)Spawn関数の具体的な実装は次のとおりです。

int Spawn(const char * program、const char * command、structKernel_Thread ** pThread)

{{

int結果

char * exeFileData = 0

ulong_t exeFileLength

structUser_Context * UserContext = 0

structExe_Format exeFormat

structKernel_Thread *スレッド

if((result = Read_Fully(program、(void **)&exeFileData、&exeFileLength))!= 0)

{{

Print( 'Oh、mygod!ファイル%sの読み取りに失敗しました n'、プログラム)

後藤失敗

}

if((result = Parse_ELF_Executable(exeFileData、exeFileLength、&exeFormat))!= 0)

{{

Print( 'Oh、mygod!ELFファイルの解析に失敗しました n')

後藤失敗

}

if((result = Load_User_Program(exeFileData、exeFileLength、&exeFormat、command、&UserContext))!= 0)

{{

Print( 'Oh、mygod!Failed to Load User Program n')

後藤失敗

}

Free(exeFileData)

exeFileData = 0

スレッド= Start_User_Thread(UserContext、false)

if(thread!= 0)

{{

* pThread =スレッド

結果=スレッド-> pid

}

そうしないと

{{

結果= ENOMEM

}

結果を返す

不合格:

if(exeFileData!= 0)

Free(exeFileData)

if(UserContext!= 0)

Destroy_User_Context(UserContext)

結果を返す

}

(3)Switch_To_User_Context()関数は次のように実装されます。

void Switch_To_User_Context(struct Kernel_Thread * kthread、struct Interrupt_State * state)

{{

struct User_Context * UserContext = kthread-> userContext

if(UserContext == 0)

{{

戻る

}

(4)Load_User_Program関数は次のように実装されます。

int Load_User_Program(char * exeFileData、ulong_t exeFileLength、struct Exe_Format * exeFormat、const char * command、struct User_Context ** pUserContext)

{{

ulong_t maxva = 0

署名されていないnumArgs

ulong_t argBlockSize

ulong_t virtSize、argBlockAddr

struct User_Context * UserContext = 0

int i

for(i = 0 i numSegments i ++)

{{

struct Exe_Segment * segment =&exeFormat-> segmentList [i]

ulong_t topva = segment-> startAddress + segment-> sizeInMemory

if(topva> maxva)

maxva = topva

}

Get_Argument_Block_Size(command、&numArgs、&argBlockSize)

virtSize = Round_Up_To_Page(maxva)+ DEFAULT_USER_STACK_SIZE

argBlockAddr = virtSize

virtSize + = argBlockSize

UserContext = Create_User_Context(virtSize)

if(UserContext == 0)

-1を返す

for(i = 0 i numSegments ++ i)

{{

struct Exe_Segment * segment =&exeFormat-> segmentList [i]

memcpy(UserContext-> memory + segment-> startAddress、

exeFileData + segment-> offsetInFile、

セグメント-> lengthInFile)

}

Format_Argument_Block(UserContext-> memory + argBlockAddr、numArgs、argBlockAddr、command)

UserContext-> argBlockAddr = argBlockAddr

UserContext-> stackPointerAddr = argBlockAddr

UserContext-> entryAddr = exeFormat-> entryAddr

* pUserContext = UserContext

0を返す

}

Spawn_With_Path関数を次のように変更します。

int Spawn_With_Path(const char * program、const char * command、

const char * path)

{{

int pid

char exeName [(CMDLEN * 2)+5]

/ *指定どおりにプログラムを実行してみてください* /

pid = Spawn_Program(program、command

)。

if(pid == ENOTFOUND && strchr(program、 '/')== 0){

Print( '/ cまたは/ aでファイルを検索してみてください...... n')

/ *パス上のプログラムを検索します。 * /

ために () {

char * p

while(* path == ':')

++パス

if(strcmp(path、 '')== 0)

ブレーク

p = strchr(path、 ':')

if(p!= 0){

memcpy(exeName、path、p -path)

exeName [p-パス] = ' 0'

パス= p + 1

} そうしないと {

strcpy(exeName、path)

パス= ''

}

strcat(exeName、 '/')

strcat(exeName、program)

if(!Ends_With(exeName、 '。exe'))

strcat(exeName、 '。exe')

/ * Print( 'exeName =%s n'、exeName)* /

pid = Spawn_Program(exeName、command

)。

if(pid!= ENOTFOUND)

ブレーク

}

}

pidを返す

}

操作の結果は次のとおりです。

図3プロジェクト2のテストインターフェイス

1.5プロジェクトの概要

このプロジェクトで実際に構築される最初のユーザーレベルのプロセスは、実際にはシェルであり、それを介して他のプロセスが作成されます。シェルは、入力を分析する単純なコマンドパーサーです。シェルは多くのコマンドを事前に作成します。たとえば、pidコマンドは現在のプロセスのpidを照会できます。

ここには2つの問題があります。 1つの問題は、GeekOSの起動後、システムがシェル(Main、Idle、Repair、Floppy_Request_Thread、IDE_Request_Thread)を含む6つのプロセスで初期化されたため、入力pidが6として表示されることです。もう1つのポイントは、pidコマンドがGet_PID関数を呼び出して、現在のプロセスのpidを返すことであるため、複数のプロセス(b.exe、c.exeなど)を確立した後、「錯覚」が発生します。 )シェルを介して、pidコマンドを入力します。戻り値はまだ6です。これは実際にはシェルのpidコマンドであり、現在のプロセスのpidを返すことであり、シェルは現在のプロセスです。シェルがシェルで実行されると、返されるpidコマンドは6ではなくなります。

もう1つの問題は、シェルがユーザープロセスを動的に作成するときに、ユーザーがデフォルトで入力するexeファイルのパスです。シェル(/ cディレクトリのc.exeファイル)にcと入力すると、最初にファイルを読み取り(上記のように)、次に/ cおよび/ aディレクトリ(実際にはIt)でファイルを検索するように求められます。パスに追加して再度読み取る必要があります。入力mmから確認できます。このファイルがある場合は、ロードしてください。/c/c.exeと入力しても、ファイルの読み取りに失敗したというエラーメッセージは表示されません。