I was learning how to write an operating system (C): the operating system to start the protection mode

Foreword

Previous work is already almost finished boot, but before entering the Linux operating system at the end of some operations, such as protected mode. In my own FragileOS enter in protected mode is completed at the end of the boot program.

A large ridge real mode to protected mode part of the operating system, so we need to mention

From real mode to protected mode

Operating modes are real mode and protected mode CPU, their main difference is addressing

Real Mode appears early 8088CPU period. At that time, CPU performance is limited, a total of only 20 address lines (so only 1MB address space), and eight 16-bit general purpose registers, and four 16-bit segment register. Therefore, in order to be able to constitute a 20-bit main memory address of the 16-bit register must be taken in a special way. Memory access becomes:

  物理地址 = 段基址 << 4 + 段内偏移

With the development of the CPU can access memory from 1MB space now becomes 4GB, the median register also becomes 32. And in real mode, the user access to the program memory of very free, without any restrictions, casually can modify any of a memory cell. So real mode can not meet the demands of the times, and came into protected mode

Protected mode offset value into a 32-bit addressing mode segment registers are still required, but these are no longer stored in the segment register base address of the segment, but a similar array index

And to do this array is a global descriptor table (GDT) things, the GDT contains a table entries, each table entry is called descriptor.

And we pass this segment registers in the index, you can find the corresponding entries. Storing a segment descriptor segment base address, segment limit, type attribute memory segments

Internal processor has a 48-bit register called the Global Descriptor Table Register (GDTR). That is, in order to record the GDT

Descriptor

FragileOS in protected mode

  • From the above description, when entering the protected mode you will first need to construct a GDT
  • Of course, also we need some other intermediate initialization, mentioned later in detail
  • Then allow the CPU to identify the specific operation according to the protection mode

Part of the code

[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

Final preparations before the start of Linux

Now take a look at Linux before finally start doing what

System data and obtain access to protected mode

Setup.s main task is to get the data from the system BIOS and then stored in a memory location

Gets the current cursor position

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.

Acquisition memory size

mov ah,#0x88
int 0x15
mov [2],ax

Check the current display mode

mov ah,#0x0f
int 0x10
mov [4],bx      ! bh = display page
mov [6],ax      ! al = video mode, ah = window width

Protected mode

Protected mode code are setup in

SYSTEM first core portion is moved to the first position 0, before it is read into the location at 0x10000

    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

Then load is above said global descriptor tables and interrupt vector table

Interrupt vector table in front did not mention, however relatively simple, somewhat similar to the GDT, that is, the operating system must maintain an interrupt vector table, each entry records an interrupt handler (ISR, Interrupt Service Routine) address

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

Followed by re-open the address lines A20, A20 address line without opening, even if the maximum address in the protection mode or 1M

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

Chip initialization 8259A, 8259A and 8085A is designed in order to control the interrupt 8086/8088 designed chip, it is possible to control the program interrupt controller. Single 8259A can manage eight priority interrupt vector. For the initialization of the hardware is actually in accordance with the fixed routine of CPU

Part of the code

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

Finally, the last, finally officially entered the protected mode, we can see here the method for entering protected mode and above me move cr0 ax are not the same, Linux reason for using this method is for compatibility with previous CPU 286, Also note in protected mode is required immediately after entering an execution period between the CPU jumps to make a refresh command queue , described herein jump has been described is the value segment, the third segment means used to point to the fifteenth GDT in the index (1000), which is the jump to the first two records in the segment descriptor address

mov ax,#0x0001  ! protected mode (PE) bit
lmsw    ax      ! This is it!
jmpi    0,8     ! jmp offset 0 of segment 8 (cs)

GTD second descriptor, so the above is to jump to the memory 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 and paging management system

Further down is officially entered into the core part, before this need to mention about IDT and paging management system

IDT

The interrupt descriptor table to each interrupt or exception processing interrupt number and a pointer to the event service routine descriptor linked. As with the GDT and LDT, IDT is an 8-byte array descriptor. And GDT, LDT different, the first IDT may comprise a descriptor. In order to form an index in the IDT, the processor interrupts, multiplied by the number after the abnormality flag for the index of the IDT 8 do. Because only 256 number, IDT need not contain more than 256 descriptors. It can contain fewer than 256 entries, only those who need to use interrupts, unusual items.

IDT can be anywhere in memory. By positioning processor IDT IDT register (IDTR). Instructions for operating SIDT LIDT and IDTR.

Paging mechanism

The user program (process) of the logical address space is divided into a number of pages (4KB) and number, while the memory of the physical address is also divided into a number of blocks or page frame 4KB) and number, so that is to make all applications it all like an exclusive memory, the starting address is 0, and finally set up a page table stored in the page to the page frame that is mapped real memory address

A register (PTR) to store the page tables in memory

Mapping completed

  • Process accesses a logical address
  • Linear address by the page number, the page table register and the start address, and find the page table to find the corresponding page table entry
  • A block number in the page table entries, to find the number of physical memory blocks
  • The block number within the page address, and linear address, physical address found

We opened by setting PG bit register CR0 paging functions, and other operations to be accomplished by the CPU, of course, only if we have a page table

Two page table structures

In order to reduce the amount of memory footprint, 80X86 uses a hierarchical page table

Ten power page directory 4-byte entry 2 corresponding to these two entry points, the ceiling 10 as a page directory table index used to find the two linear addresses

20 two physical base address of the page table entries of the page containing the relevant, two intermediate page table address using a linear 10 to find an index entry as

  • Process accesses a logical address
  • The linear address of the page number, the page table register and an outer layer (CR3) of the outer page table start address, two page table to find the start address
  • By the start address two page table address plus the outer layer linear page address, the page table entry corresponding to find the two page table
  • By the physical block number of the page table entry, the page address plus the linear address to find the physical address

So the CPU requires addressing a total of two steps:

  1. First, given a logical address (in fact, the section offsets)
  2. CPU利用段式内存管理单元,先将为个逻辑地址转换成一个线程地址 (也就是前面说的GDT)
  3. 再利用其页式内存管理单元,转换为最终物理地址。(二级页表)

进入到了内核部分

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等,然后设置分页等等

Guess you like

Origin www.cnblogs.com/secoding/p/11407486.html