머리말
이전 작업은 이미 거의 부팅을 완료,하지만 같은 보호 모드와 같은 일부 작업의 끝에서 리눅스 운영 체제에 들어가기 전에된다. 내 자신에 FragileOS가 보호 모드에서 입력 한 부트 프로그램의 마지막에 완료됩니다.
운영 체제의 보호 모드 부분에 큰 능선 리얼 모드, 그래서 우리는 언급 할 필요가
보호 모드로 리얼 모드에서
작동 모드 리얼 모드와 보호 모드의 CPU이며, 그들의 주요 차이점은 주소입니다
리얼 모드는 초기 8088CPU 기간을 나타납니다. 이 때, CPU 성능은, 20 개의 어드레스 라인 (따라서 만 1메가바이트 주소 공간), 8 16 비트 범용 레지스터와 4 개의 16 비트 세그먼트 레지스터의 합계를 한정한다. 따라서, 16 비트 레지스터의 20 비트 메인 메모리 주소를 구성 할 수 있도록하기 위해 특별한 방법으로주의해야합니다. 메모리 액세스가된다 :
物理地址 = 段基址 << 4 + 段内偏移
CPU의 개발 1MB의 공간에서 메모리를 액세스 할 수 이제도 4기가바이트가되고, 중간 레지스터는 32이된다. 그리고 리얼 모드에서 어떤 제한없이 매우 자유의 프로그램 메모리에 대한 사용자 액세스는 부담 메모리 셀을 수정할 수 있습니다. 그래서 리얼 모드는 시대의 요구에 부응하고, 보호 모드에 와서 수 없습니다
32 비트 어드레싱 모드 세그먼트 레지스터로 보호 모드 오프셋 값은 여전히 필요하지 않지만이 더이상 세그먼트의 세그먼트 레지스터베이스 주소를 저장하지만 유사한 배열 인덱스
그리고이 배열을 수행하는 것은 글로벌 디스크립터 테이블 (GDT) 각 테이블 엔트리는 기술자라고 상황에서, GDT는 테이블 항목이 포함되어 있습니다.
그리고 우리는 당신이 해당하는 항목을 찾을 수 있습니다, 색인이 세그먼트 레지스터를 전달합니다. 세그먼트 디스크립터 세그먼트 기본 주소 세그먼트 한계 타입 메모리 세그먼트 속성 저장
내부 프로세서는 글로벌 디스크립터 테이블 레지스터 (GDTR)라고 불리는 48 비트 레지스터를 갖는다. 즉, GDT를 기록하기 위해,이다
설명자
보호 모드에서 FragileOS
- 보호 모드를 입력 할 때 위의 설명에서 먼저 GDT를 구성해야합니다
- 물론, 또한 우리가 상세히 후술 다른 중간 초기화가 필요
- 그리고 CPU가 보호 모드에 따라 특정 작업을 식별 할 수
코드의 일부
[SECTION .gdt] ; 利用宏定义定义gdt
; 段基址 段界限 属性
LABEL_GDT: Descriptor 0, 0, 0
LABEL_DESC_CODE32: Descriptor 0, 0fffffh, DA_C | DA_32 | DA_LIMIT_4K
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0fffffh, DA_DRW
LABEL_DESC_VRAM: Descriptor 0, 0fffffh, DA_DRW | DA_LIMIT_4K
in al, 92h ; 切换到保护模式
or al, 00000010b
out 92h, al
mov eax, cr0
or eax , 1
mov cr0, eax
리눅스의 시작하기 전에 최종 준비
마지막으로 일을 시작하기 전에 이제 리눅스에서 살펴보고 무엇을
시스템 데이터 보호 모드로 얻을 액세스
Setup.s 주요 작업은 시스템 BIOS에서 데이터를 얻을 수 있습니다 다음 메모리 위치에 저장
현재 커서의 위치를 가져옵니다
mov ax,#INITSEG ! this is done in bootsect already, but...
mov ds,ax
mov ah,#0x03 ! read cursor pos
xor bh,bh
int 0x10 ! save it in known place, con_init fetches
mov [0],dx ! it from 0x90000.
취득 메모리 크기
mov ah,#0x88
int 0x15
mov [2],ax
현재 디스플레이 모드를 확인
mov ah,#0x0f
int 0x10
mov [4],bx ! bh = display page
mov [6],ax ! al = video mode, ah = window width
보호 모드
보호 모드 코드에 설치되어
이 0x10000에있는 위치에 판독되기 전에 SYSTEM 제 1 코어 부, 상기 제 0 위치로 이동
mov ax,#0x0000
cld ! 'direction'=0, movs moves forward
do_move:
mov es,ax ! destination segment
add ax,#0x1000
cmp ax,#0x9000
jz end_move
mov ds,ax ! source segment
sub di,di
sub si,si
mov cx,#0x8000
rep
movsw
jmp do_move
그런 다음로드는 글로벌 디스크립터 테이블과 벡터 테이블을 중단했다 위에있다
, 언급하지 않았다 앞에 벡터 테이블 인터럽트 그러나 비교적 간단는 GDT 다소 유사한, 즉, 운영 체제가 인터럽트 벡터 테이블을 유지해야하며, 각 항목은 주소 (인터럽트 서비스 루틴 ISR) 인터럽트 핸들러를 기록
end_move:
mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-)
mov ds,ax
lidt idt_48 ! load idt with 0,0
lgdt gdt_48 ! load gdt with whatever appropriate
보호 모드 1M 재 열 어드레스 라인들 A20, A20 열지 않고 어드레스 선을하더라도 최대 주소 앞에
call empty_8042
mov al,#0xD1 ! command write
out #0x64,al
call empty_8042
mov al,#0xDF ! A20 on
out #0x60,al
call empty_8042
칩 초기화가 8259A, 8259A 및 8085A를 인터럽트 8,088분의 8,086 칩 설계를 제어하기 위해 설계되어, 상기 프로그램 인터럽트 컨트롤러를 제어 할 수있다. 단일 8259A는 여덟 개 우선 순위 인터럽트 벡터를 관리 할 수 있습니다. 하드웨어의 초기화를 들어 CPU의 고정 된 루틴에 따라 실제로
코드의 일부
mov al,#0x11 ! initialization sequence
out #0x20,al ! send it to 8259A-1
.word 0x00eb,0x00eb ! jmp $+2, jmp $+2
out #0xA0,al ! and to 8259A-2
마지막으로, 마지막, 마지막으로 공식적으로 입력 된 보호 모드, 우리는 보호 모드로 전환 여기 방법을보고 나를 CR0 도끼를 이동 위의 동일하지 않을 수 있습니다,이 방법을 사용하는 리눅스 이유는 이전의 CPU (286)과의 호환성을위한 또한주의 보호 모드에서 CPU는 리프레시 명령 대기열을 점프 사이 실행 기간을 입력 직후에 필요한 값 세그먼트 본원 점프 설명한 바와이며, 세 번째 부분은 십오 가리키는 데 사용 수단 세그먼트 디스크립터 어드레스의 첫 번째 두 개의 레코드 점프 인 인덱스 GDT (1000),
mov ax,#0x0001 ! protected mode (PE) bit
lmsw ax ! This is it!
jmpi 0,8 ! jmp offset 0 of segment 8 (cs)
GTD 번째 디스크립터는 상기 있도록하는 메모리 0으로 이동한다
.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb)
.word 0x0000 ! base address=0
.word 0x9A00 ! code read/exec
.word 0x00C0 ! granularity=4096, 386
IDT 및 페이징 관리 시스템
또한 아래 공식적으로 IDT 및 페이징 관리 시스템에 대한 언급이 필요하기 전에, 핵심 부분에 입력
IDT
각 인터럽트 디스크립터 테이블은 방해하거나 예외 처리 인터럽트 번호와 연결된 이벤트 서비스 루틴 기술자에 대한 포인터. GDT 및 LDT와 같이, IDT가 8 바이트 배열 설명이다. 그리고 GDT는 다른 LDT, 제 IDT는 디스크립터를 포함 할 수있다. IDT의의 인덱스를 형성하기 위해, 프로세서 인터럽트는 IDT 8의 인덱스를 수행하기위한 이상 플래그 후 곱한. 256 수 있기 때문에, IDT는 256 개 이상의 기술자를 포함 할 필요가 없다. 그것은 적은 256 개 항목, 인터럽트, 특별한 아이템을 사용할 필요가있는 사람 만 포함 할 수 있습니다.
IDT는 메모리에 어느 곳이 될 수 있습니다. 위치 프로세서 IDT IDT 레지스터 (IDTR)에서. SIDT LIDT 및 IDTR 운영에 대한 지침.
페이징 메커니즘
물리 어드레스의 메모리도 블록 또는 페이지 프레임 4킬로바이트) 및 숫자들로 분할하면서 논리 어드레스 공간의 사용자 프로그램 (프로세스), 페이지 (4킬로바이트) 및 숫자들로 분할되고, 그래서 모든 애플리케이션 모두를 만드는 것이다 전용 메모리처럼 시작 주소는 0이며, 마지막으로 실제 메모리 어드레스 매핑 된 페이지 프레임의 페이지에 저장된 페이지 테이블을 설정할
메모리의 페이지 테이블을 저장하는 레지스터 (PTR)
매핑 완료
- 프로세스는 논리 주소에 액세스
- 페이지 번호, 페이지 테이블 레지스터와 시작 주소,에 의해 선형 주소는 해당 페이지 테이블 엔트리를 찾기 위해 페이지 테이블을 찾을 수
- 페이지 테이블 항목의 블록 수는 물리적 메모리 블록의 수를 찾을 수
- 상기 페이지 어드레스 내의 블록 번호, 선형 주소를 물리 주소 발견
우리는 페이지 테이블이있는 경우에만, 물론, 우리는 PG 비트가 CR0 페이징 기능 설정 레지스터 열, 및 기타 작업은 CPU에 의해 수행되어야한다
두 페이지 테이블 구조
메모리 공간의 양을 줄이기 위해, 80X86 계층 적 페이지 테이블을 사용
텐 전력 페이지 디렉토리 4 바이트 항목이이 두 가지 진입 점에 해당하는 두 개의 선형 주소를 찾는 데 사용 페이지 디렉토리 테이블 인덱스로 천장 (10)
같은 인덱스 엔트리를 찾는 리니어 (10)를 사용하여 관련 개의 중간 페이지 테이블 주소를 포함하는 페이지의 페이지 테이블 엔트리의 20 개 개의 물리적 기본 주소
- 프로세스는 논리 주소에 액세스
- 페이지 번호, 페이지 테이블 레지스터 및 외측 페이지 테이블의 개시 어드레스의 외층 (CR3), 두 페이지 테이블의 선형 주소가 시작 주소를 찾을
- 개시 어드레스 개의 페이지 테이블 어드레스 플러스 외층 선형 페이지 어드레스에 의해, 대응하는 페이지 테이블 엔트리는 두 페이지 테이블을 찾는
- 페이지 테이블 항목 페이지 주소를 더한 선형 주소의 물리적 블록 번호에 의해 물리적 주소를 찾을 수
그래서 CPU는 두 단계의 총 주소가 필요합니다 :
- 첫째, 논리적 주소를 부여 (사실, 섹션 오프셋)
- CPU利用段式内存管理单元,先将为个逻辑地址转换成一个线程地址 (也就是前面说的GDT)
- 再利用其页式内存管理单元,转换为最终物理地址。(二级页表)
进入到了内核部分
head.s这部分其实已经是进入了内核部分了,但是在Linux0.12里还是把它归为Boot部分。这一部分的主要工作是重新设置GDT和IDT,然后在设置管理内存的分页处理机制 (在进入保护模式后,Linux用的就是AT&T的汇编语法了,最显著的差别就是源操作数和目的数的位置对调了)
- 设置IDT
setup_idt:
lea ignore_int,%edx
movl $0x00080000,%eax
movw %dx,%ax /* selector = 0x0008 = cs */
movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
lea idt,%edi
mov $256,%ecx
rp_sidt:
movl %eax,(%edi)
movl %edx,4(%edi)
addl $8,%edi
dec %ecx
jne rp_sidt
lidt idt_descr
ret
- 设置GDT
setup_gdt:
lgdt gdt_descr
ret
gdt_descr:
.word 256*8-1 # so does gdt (not that that's any
.long gdt # magic number, but it works for me :^)
.align 8
- 这里就是已经准备跳入C语言的main部分了,也就是汇编里的函数调用,先把main的地址压入栈中,当下一个函数执行完ret的时候,就会去执行main了
after_page_tables:
pushl $0 # These are the parameters to main :-)
pushl $0
pushl $0
pushl $L6 # return address for main, if it decides to.
pushl $main
jmp setup_paging
L6:
jmp L6 # main should never return here, but
# just in case, we know what happens.
- 最后就是设置分页机制了
STOS指令:将AL/AX/EAX的值存储到[EDI]指定的内存单元
CLD清除方向标志和STD设置方向标志,当方向标志是0,该指令通过递增的指针数据每一次迭代之后(直到ECX是零或一些其它条件,这取决于REP前缀的香味)工作,而如果该标志是1,指针递减。
setup_paging:
movl $1024*5,%ecx /* 5 pages - pg_dir+4 page tables */
xorl %eax,%eax
xorl %edi,%edi /* pg_dir is at 0x000 */
cld;rep;stosl
movl $pg0+7,pg_dir /* set present bit/user r/w */
movl $pg1+7,pg_dir+4 /* --------- " " --------- */
movl $pg2+7,pg_dir+8 /* --------- " " --------- */
movl $pg3+7,pg_dir+12 /* --------- " " --------- */
movl $pg3+4092,%edi
movl $0xfff007,%eax /* 16Mb - 4096 + 7 (r/w user,p) */
std
1: stosl /* fill pages backwards - more efficient :-) */
subl $0x1000,%eax
jge 1b
xorl %eax,%eax /* pg_dir is at 0x0000 */
movl %eax,%cr3 /* cr3 - page directory start */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* set paging (PG) bit */
ret /* this also flushes prefetch-queue */
小结
这一节主要是描述了保护模式和一些CPU需要的数据结构。这几篇文章相当于讲述了一台计算机启动的时候都发生了什么。
- 通过引导程序boot来加载真正的内核代码
- 获得一些硬件上的系统参数保存在一些内存里供后面使用
- 最后是初始化像GDT、IDT等,然后设置分页等等