Now that the project is ready, I forgot a lot of things in front of it. I still need to use makefile to re-understand how the entire project is compiled:
Now we have nine files:
- Ipl10.nas initialprogramloader occupies the first sector of the floppy disk and complies with the boot disk specifications. The default load address is 0x7c00 to 0x7e00, reads 10 cylinders into 0x8200 to 0x34fff (10 cylinders in total 10*2*18 = 360 sectors, but the first is not read );
- Asmhead. NAS contains some unknown settings;
- Naskfun. NAS contains assembly functions for C language programs;
- Bootpack. h constant definitions and function definitions;
- Hankaku.txt font file;
- Implement a complete cyclic queue in FIFO. C for interrupt buffering;
- Graphic. C provides plotting functions to draw system interfaces and pointers and print characters;
- Dsctbl. c gdt (Global symbol Descriptor Table) and IDT (Interrupt Descriptor Table) settings;
- Processing of Int. c interrupt service routines.
System memory allocation:
- 0x7c00 ~ 0x7e00 ipl10.nas
- 0x26f800 ~ 0x26ffff IDT
- 0x270000 ~ 0x27ffff gdt
- 0x280000 ~ 0x2fffff bootpack. h
Although the book has simplified gdt IDT settings as much as possible, there are quite a few details that need to be noted. It is not a blog article, so I decided not to record the entire process, it sums up the major knowledge points that need attention. After all, this is just a note...
Gdt (Global Descriptor Table) is a data structure in order to define the characteristics of the varous memory areas used during program execution,
Including the base address, the size and access privileges like executability and writability.
Gdt stands for the global segment descriptor table, which is used to provide various information about memory for program execution. The table entry is gdt entry, which is 8 bytes in size and contains the segment size; the start address of the segment, management attribute of the segment, and other information. its structure is as follows:
The C language is as follows:
Struct segment_descriptor {short limit_low, base_low; char base_mid, access_right; char limit_high, base_high; }__ attribute _ (packed )); /* The author omitted the memory compression instruction in the Code, which should be implicitly executed by NASK. */
It can also be represented by a bit field:
Struct desc_struct {Union {struct {unsigned int A; unsigned int B ;}; struct {b2limit0; // service.unsigned int b2base0; unsigned base1: 8, type: 4, s: 1, DPL: 2, P: 1; unsigned limit: 4, aVL: 1, L: 1, D: 1, G: 1, base2: 8 ;};}}_ _ attribute _ (packed); // the attributes of such segments are clearer.
We can see that limit is divided into limit_low and limit_high, and base is divided into low, mid, and high, which makes assignment of this structure very troublesome, the reason for this is to be compatible with the system earlier than 286.
You noticed that I didn't gave a real structure for gdt [], didn't you? That's on purpose. the actual structure of descriptors is a little messy for backwards compatibility with the 286's gdt. base address are split on 3 different fields and you cannot encode any limit you want. plus, here and there, you have flags that you need to set up properly if you want things to work.
This also causes the trouble of filling in the segment_descriptor structure. The code for filling in the segment_dscriptor is as follows (I can see it very well ...) :
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar){ if (limit > 0xfffff) { ar |= 0x8000; /* G_bit = 1 */ limit /= 0x1000; } sd->limit_low = limit & 0xffff; sd->base_low = base & 0xffff; sd->base_mid = (base >> 16) & 0xff; sd->access_right = ar & 0xff; sd->limit_high = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0); sd->base_high = (base >> 24) & 0xff; return;}
// When I suddenly feel that my mind is not enough, I just need to understand the following. I think these complicated rules are justOnly one skill.
A gdt must have at least the following entries:
- The null descriptor which is never referenced by the processor. certain emulators, like bochs, will complain about limit limits tions if you do not have one present. some use this descriptor to store a pointer to the gdt itself (to use with the lgdt instruction ). the null descriptor is 8 bytes wide and the pointer is 6 bytes wide so it might just be the perfect place for this. // an empty Descriptor
- A code segment descriptor (for your kernel, it shocould have type = 0x9a) // code segment
- A data segment descriptor (you can't write to a code segment, so add this with type = 0x92) // data segment
- A TSS segment descriptor (trust me, keep a place for at least one). // task status segment
However, after initializing all the segments, the author only fills in the Code and data segments:
Set_segmdesc (gdt + 1, 0 xffffffff, 0x00000000, ar_data32_rw); set_segmdesc (gdt + 2, limit_botpak, adr_botpak, ar_code32_er );
// The code segment is bootpack. HRB.
- GDTR is a 48-bit register used to tell the CPU gdt location in the memory. The lgdt command can be used to write the address to GDTR. The Code is as follows:
_load_gdtr: ; void load_gdtr(int limit, int addr); MOV AX,[ESP+4] ; limit MOV [ESP+6],AX LGDT [ESP+6] RET
- PIC
Http://wiki.osdev.org/PIC
The 8259 programmable interrupt controller (PIC) is one of the most important chips making up the X86 architecture. without it, the X86 architecture wocould not be an interrupt driven architecture. the function of the 8259a is to manage hardware interrupts and send them to the appropriate system interrupt. this allows the system to respond to devices needs without loss of time (from polling the device, for instance ).
Due to the limitation of the CPU Structure, only one interrupt can be handled. Therefore, PIC is designed to assist the CPU in handling multiple interruptions. The full name of PIC is the programmable interrupt controller, 15 interruptions can be controlled through PIC. In modern operating systems, the 8259pic seems to be replaced by APIC.
The main trigger is connected to the CPU pin, and the trigger is connected to the IRQ 2 of the main trigger.
Irq x is responsible for transmitting the interrupt signal.
- Initialize PIC (edge trigger mode or something reminds me of the digital logic ...) :
- PIC trigger:
IMR is the interrupt shielding register used to shield IRQ signals;
ICW 1 and ICW 4 declare the main board wiring method (= Not familiar );
ICW 3 is the master-slave setting, which indicates the slave trigger of the trigger; (generally irq2 );
ICW 2 determines which interrupt Number of IRQ notifies the CPU, int 0x0 ~ 0x19 cannot be used.
The Code is as follows:
Void init_pic (void)/* PIC initialization */{io_out8 (pic0_imr, 0xff); // disable all io_out8 (pic1_imr, 0xff) interruptions of the main PIC ); // disable all interruptions from the pic. // set io_out8 (pic0_icw1, 0x11) for the main pic. // edge trigger mode (edge trigger mode) io_out8 (pic0_icw2, 0x20); // irq0 ~ 7 from INT 20 ~ 27 receive io_out8 (pic0_icw3, 1 <2); // pic1 connects io_out8 (pic0_icw4, 0x01) from the PIC by irq2; // no buffer Mode
// Set io_out8 (pictureicw1, 0x11) from the PIC; // edge trigger mode io_out8 (pictureicw2, 0x28); // irq0 ~ 7 from int 28 ~ 2f receives io_out8 (pic1_icw3, 2); // pic1 connects io_out8 (pic1_icw4, 0x01) by irq2; // no buffer mode io_out8 (pic0_imr, 0xfb ); // disable io_out8 (pictureimr, 0xff) for all messages other than pic1; // disable return for all messages in 11111011 ;}
- ISR
Http://wiki.osdev.org/ISR
ISR (interrupt service routines) is the interrupt processing program. When the interrupt occurs, this program will be called. note that the return of Isr cannot use the RET command, but the iretd command is not implemented in the C language. Of course there are many ways to avoid this problem, the author uses functions implemented in C language in functions implemented in assembly.
The writing method is skipped.
- IDT
Http://wiki.osdev.org/IDT
The Interrupt Descriptor Table (IDT) is specific to the i386 architecture. it is the Protected Mode counterpart to the real mode interrupt vector table (IVT) telling where the interrupt service routines (ISR) are located. it is similar to the Global Descriptor Table in structure. the IDT entries are called gates. it can contain Interrupt Gates, task gates and trap gates.
IDT is an Interrupt Descriptor Table in protected mode. It is used to tell the cpu isr location. The structure is similar to that of gdt. The IDT entry is also called a gate, including an interrupt gate, trap Door and task door... Here, the door should be interrupted.
set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
The implementation of other functions is similar to that of gdt.
This time, we mainly understand concepts rather than code writing, and the interface has not changed significantly. Therefore, we will not post code or images.