장난감 OS, 32 비트는 마침내 간단한 대화식 쉘을 구현합니다. 
할 일 목록 :
총 2 개의 하드 디스크가 있으며, 시스템 자체가 기본 디스크에 설치되고 MBR의 부팅 모드가 채택되며 MBR-> 부트 로더-> Kernnel의 프로세스가 사용됩니다.
MBR LBA 0号扇区시작하여 1 섹터에 위치하고 있습니다.
Boot Loader 디스크의 LBA 2号扇区부터 시작하는 4 개의 섹터에 있습니다.
Kernel 디스크 LBA 9号扇区로 시작하는 200 개의 섹터 내에 있습니다.
파일 시스템은 슬레이브 디스크에서 구현됩니다. 이것은 그리 합리적이지 않을 수 있습니다. 파일 시스템이 상용 시스템의 논리를 기반으로하는 경우 지금 구현해야하며 운영 체제가 해당 파티션에 설치됩니다.
메모리 페이징, 한 페이지는 4KB입니다
메모리 관리는 비트 맵 관리를 채택하며 메모리를 할당 할 때 크기별로 구별됩니다. 바이트가 1024 바이트 인 경우 페이지별로 직접 할당됩니다.
1024 바이트 미만인 경우 페이지별로 경기장 할당을 기준으로 할당 및 제어를 위해 경기장에서 유휴 블록 체인을 사용하십시오.
편의를 위해 페이징 메커니즘이 활성화되어 있지만 메모리 페이지 및 디스크의 스위칭 기능은 구현되지 않습니다.

PCB는 1 페이지 크기입니다
스레드 스케줄링의 핵심 본질은 클럭 인터럽트에 의한 ESP 포인터 스위칭을 제어하여 PCB를 전환하는 것입니다. 우선 순위는 각 스레드의 러닝 타임 슬라이스의 길이에 반영됩니다.
프로세스의 구현은 스레드를 기반으로하며 TSS의 선택은 단일 TSS를 백업 레벨 0 스택 및 레벨 0 스택 포인터를 사용하여 Linux를 기반으로합니다. 스레드와 가장 큰 차이점은 프로세스가 PCB에 페이지 테이블 주소가 있다는 것입니다. 이는 프로세스와 스레드의 가장 큰 차이점입니다. 프로세스에는 진정으로 고유 한 독립적 인 가상 메모리 공간이 있습니다.
스케줄링에는 효율적인 알고리즘이 없으며 큐 루프 스케줄링 만 사용하십시오.
유휴 스레드 구현
유휴 스레드의 구현은 매우 간단합니다. 처음 일정을 받으면 일정을 차단하고 CPU에서 나옵니다. 스케줄러가 디스패치를 다시 실행하면 준비된 큐에 준비된 스레드 나 프로세스가 없으면 유휴 스레드가 깨어납니다. 현재 유휴 스레드는 hlt 를 통해 CPU를 중단시킵니다. 타임 슬라이스가 사용되고 CPU가 준비된 프로세스 나 스레드를 찾지 못하면 유휴 스레드를 CPU로 계속 교체하십시오. 이 시점에서, 유휴 상태는 계속 차단 한 다음 위의 스케줄링 프로세스를 반복하기 시작합니다.
// 空载任务
static void idle ( void * arg ) {
while ( 1 ) {
thread_block ( TASK_BLOCKED );
asm volatile ( "sti; hlt" : : : "memory" );
}
}프로세스 포크
프로세스는 먼저 현재 프로세스의 PCB를 복사 한 다음 현재 프로세스의 가상 풀 비트 맵을 통해 새 페이지 테이블을 만듭니다. 가상 주소의 서신은 원래 프로세스와 정확히 동일합니다. 마지막으로 인터럽트 사이트가 위조되고 하위 프로세스가 스케줄링 큐에 추가되어 스케줄링이 실행될 때까지 기다립니다. 단조 인터럽트 사이트에서, 아동 프로세스의 PCB의 EAX는 0으로 수정되며, 이는 새 프로세스에서 포크의 반환 값이 0이지만, 부모 프로세스의 PCB의 EAX는 변경되지 않은 상태로 유지되며, 아동 프로세스의 PID를 나타냅니다. 부모 프로세스는 시스템 호출의 끝을 통해 반환되는 반면, 하위 프로세스는 인터럽트 출구 기능을 통해 직접 돌아옵니다.
프로세스 exec
Exec의 구현은 먼저 ELF 파일을 디스크에서 메모리로로드 한 다음 현재 프로세스의 PCB에서 프로세스 이름을 변경하고 프로세스에 필요한 매개 변수를 합의 된 레지스터로 실행하고 EIP를 ELF의 진입 점으로 수정하고 인터럽트 사이트의 위조를 직접 실행하여 새로운 프로세스를 직접 호출하여 새로운 프로세스를 실행합니다.
그중에서도 ELF의 진입 점은 매우 간단한 CRT 자체를 구현하여 구현되며, 이는 _START 항목을 제공하고 합의 된 매개 변수 레지스터를 3 레벨 스택으로 푸시하고 외부 명령의 주요 기능을 호출하여 매개 변수 전달을 실현합니다.
[bits 32]
extern main
extern exit
; 这是一个简易版的CRT
; 如果链接时候ld不指定-e main的话,那ld默认会使用_start来充当入口
; 这里的_start的简陋实现,充当了exec调用的进程从伪造的中断中返回时的入口地址
; 通过这个_start, 压入了在execv中存放用户进程参数的两个寄存器。然后call 用户进程main来实现了向用户进程传递参数
section .text
global _start
_start:
;下面这两个要和 execv 中 load 之后指定的寄存器一致
push ebx ;压入 argv
push ecx ;压入 argc
call main
; 压入main的返回值
push eax
call exit ; 不再返回,直接调度别的进程了,这个进程直接被回收了프로세스 대기
포크 및 로컬 명령을 실행 한 후 나타나지 않기 위해 부모 절차는 지역 대기 아동 과정에서 끝나야합니다.
여기서 구현은 SYS_WAIT 시스템 호출을 입력하고 전체 프로세스 큐를 통과하고 부모 프로세스가 자체 부유 상태 인 프로세스를 찾은 다음 PCB에서 반환 값을 얻고 PCB 및 페이지 디렉토리 테이블을 재활용하고 스케줄링 큐에서 제거하는 것입니다. Traversal 후에 보류중인 아동 과정이 발견되지 않으면 스스로 차단하고 아동 과정이 일어나기를 기다립니다.
프로세스 종료
실행 중에 외부 명령은 실제로 자체적으로 생성 된 간단한 CRT에 의해 포장됩니다. 간단한 CRT 호출 외부 명령의 메인은 끝에서 리턴 값을 얻고 종료로 전달한 다음 종료를 호출합니다.
여기서 구현은 주로 세 가지를 수행합니다.
외부 명령을로드하고 실행하는 전체 프로세스
먼저 외부 명령은 int main(int argc, char **argv) 함수를 제공해야합니다. 링크 할 때는 수제 간단한 CRT start.o 가져 와서 마지막으로 컴파일 된 외부 명령을 파일 시스템에 작성해야합니다.
외부 명령을 실행하려면 현재 프로세스는 프로세스를 포크하고, 새 프로세스에서 EXECV를 포크하고, 현재 프로세스는 대기를 실행하고, 아동 프로세스의 반환 값을 수락하기 위해 주소를 전달 한 다음, 새 프로세스가 반환 될 때까지 차단하고 기다립니다. 새로운 프로세스는 파일 시스템에서 EXEVC의 메모리로 외부 명령을로드하고 PCB의 관련 내용을 외부 명령의 정보로 변경하고 CRT의 _Start 항목으로 PCB 인터럽트 스택의 EIP를 수정합니다 (이 시점에서 새로운 프로세스는 외부 명령 프로세스로 완전히 대체되어 intr_exit exit inth intert interut interut inter interut inter and the the interute inter and the the interute 외부 명령. 외부 명령 메인 실행이 종료되고 반환되면 CRT는 기본 반환 값을 통해 종료하고 sys_exit 의 기본 반환 값을 프로세스 PCB의 해당 위치에 넣고 PCB 및 페이지 디렉토리 테이블을 제외한 모든 리소스를 재활용 한 다음 부모 프로세스를 깨우고 자식 프로세스를 차단합니다. After the parent process is awakened, the system call sys_wait gets the return value from the pcb of the pcb of the pcb of the pcb of the pcb of the pcb of the pcb of the pcb of the pcb of the pcb of the pcb of the pcb, cleans up the page directory table of the pcb of the child process, removes the child process from the dispatch queue and the global queue, and returns the child process pid. 이 시점에서 아동 과정은 실행되었으며 완전히 재활용되었습니다.
파일 시스템 구현은 유닉스와 같은 시스템의 inode를 모방합니다.
파티션은 inodes 4096의 수를 제한합니다. CPU는 블록의 크기 (클러스터)에 따라 하드 디스크를 작동시키고 하나의 블록은 512 바이트로 섹터로 설정됩니다.
Inode는 12 개의 직접 블록과 1 개의 1 단계 간접 테이블을 지원합니다. 하나의 블록은 섹터의 512 바이트이므로 단일 파일은 최대 140 * 512 바이트를 지원합니다.
inode 구조

파일 시스템 레이아웃

파일 디스크립터와 inode 사이의 대응

파이프 라인의 구현은 파일 시스템의 파일 구조에 따라 다릅니다. 그것의 본질은 파일 구조가 원래 커널 공간의 링 버퍼 공간과 일치해야한다는 잉오드를 대체하는 것입니다.
// 因为管道也是当作文件来对待,因此file结构体在针对真实文件和管道是有不同的意义
struct file {
// 文件操作的偏移指针, 当是管道是表示管道打开的次数
uint32_t fd_pos ;
// 文件的操作标志,当是管道是一个固定值0xFFFF
uint32_t fd_flag ;
// 对应的inode指针,当是管道时指向管道的环形缓冲区
struct inode * fd_inode ;
}; 커널 공간이 공유되므로 읽기 및 쓰기 파이프 라인을 통해 다른 프로세스 간의 통신을 달성 할 수 있습니다. 파이프 라인의 읽기 및 쓰기는 sys_write 및 sys_read 에 캡슐화되므로 파이프 라인 작동과 일반 파일 작동 사이에는 차이가 없습니다.
리디렉션의 본질은 PCB 파일 디스크립터 테이블에서 해당 글로벌 디스크립터 테이블의 주소를 변경 한 다음 해당 파일 디스크립터를 읽고 쓰는 작업이 새 파일을 가리키는 것입니다.
파이프 라인 문자의 구현 | 쉘에서 표준 입력 및 표준 출력을 파이프 라인에 리디렉션하여 달성됩니다.
int32_t sys_read ( int32_t fd , void * buf , uint32_t count ) {
if ( fd == stdin_no ) {
if ( is_pipe ( fd )) {
// 从已经重定向好管道中读
ret = pipe_read ( fd , buf , count );
} else {
// 从键盘获取输入
}
} else if ( is_pipe ( fd )) {
// 读管道
ret = pipe_read ( fd , buf , count );
} else {
// 读取普通文件
}
return ret ;
}
int32_t sys_write ( int32_t fd , const void * buf , uint32_t count ) {
if ( fd == stdout_no ) {
if ( is_pipe ( fd )) {
// 向已经重定向好管道中写入
return pipe_write ( fd , buf , count );
} else {
// 向控制台输出内容
}
} else if ( is_pipe ( fd )) {
// 写管道
return pipe_write ( fd , buf , count );
} else {
// 向普通文件写入
}
}