The previous article was boot. S. This is head. S. The role of head. S is
The head. s program runs in 32-bit protection mode, including the initial setting code, the Process Code for clock interruption int 0x08, and the system call interruption Int.
Process Code of 0x80 and code and data of task a and Task B. The initial settings mainly include: ① resetting the gdt table; ② setting the system timer chip; ③ resetting the IDT table and setting
Set the clock and system call interruption gate; ④ move to task a for execution.
In the virtual address space, the kernel code and task code of the head. s program are shown in Figure 4-41. In fact, all the code and data segments in this kernel example correspond to the same region of the physical memory.
, That is, the area starting from the physical memory 0. In gdt, the content of the global code segment and data segment descriptor is set to: The base address is 0x0000, and the segment length is 0x07ff. Because the granularity is 1
The actual segment length is 8 Mb. The global display data segment is set to: Base Address: 0xb8000; segment length limit: 0x0002, so the actual segment length is 8 KB, corresponding to the Display memory area.
|
Figure 4-41 kernel and task allocation in virtual address space |
The content of the code segment and data segment descriptor of the two tasks in LDT is also set to: The base address is 0x0000, the segment length is 0x03ff, and the actual segment length is 4 MB. Therefore, the linear address is empty.
The code and data segments of this "kernel" and the task code and data segments both start from linear address 0, and because the paging mechanism is not used, they both directly correspond to physical address 0. In head Program
The code and data organization in the compiled target file and the final image file of the floppy disk is 4-42.
|
Figure 4-42 head code and data distribution in kernel image files and memory |
By
Code at the privileged level 0 cannot directly transfer control to the code at the privileged level 3 for execution, but the return operation can be interrupted. Therefore, when the initialization of gdt, IDT, and scheduled chip ends, we are using
The iret command is returned to start the first 1st tasks. The specific implementation method is to manually set a return environment in the init_stack of the initial stack, that is, load the TSS segment selector of task 0 to task storage.
After the LTR and LDT segment selector are loaded into ldtr, the user Stack pointer (0x17: init_stack) and Code pointer (0x0f: task0) of task 0 and the flag
The register value is pushed into the stack, and the T is returned when the interrupt command is executed. This command will pop up the stack pointer on the stack as the task 0 user Stack pointer, restore the hypothetical task 0 flag register content, and pop up the stack
The Code pointer is put into the Cs: EIP register, and the code of task 0 is executed, and the Control Transfer from privileged level 0 to privileged level 3 code is completed.
In order to switch the running tasks every 10 ms, the head. s Program sets the channel 0 of the timer chip 8253 to send a clock to the interrupt control chip 8259a every 10 ms.
Request disconnection signal. PC Rom
During BIOS boot, the clock interruption request signal has been set to interrupt vector 8 in 8259a. Therefore, we need to perform task switching during the process of Interrupt 8. The task switching implementation method is to view
The current running task number in the current variable. If the current value is 0, the TSS selector of Task 1 is used as the operand to execute the remote jump command and switch to Task 1 for execution. Otherwise
Otherwise.
When each task is executed, the ASCII code of one character is first put into the register Al, and then the int
0x80, and the system calls the processing process to call a simple character writing screen subroutine to display the characters in register Al on the screen, at the same time, record the next position of the screen where the characters are displayed
The position of the next display character. After a single character is displayed, the task code will be delayed for a period of time using the loop statement, and then jump to the start of the task code to continue the loop execution until it runs for 10 ms
The scheduled interruption occurs, and the code will be switched to another task to run. For task a, the character "a" is always stored in the register Al, and the character "B" is always stored in the Al when Task B is running ". Therefore
During the runtime, we will see a series of characters "a" and a series of characters "B" continuously display on the screen at intervals, as shown in 4-43.
|
(Click to view the big picture) Figure 4-43 display of the running status of a simple Kernel |
Figure 4-43 shows the screen when we run this kernel sample in the bochs simulation software. Careful readers will find that a character "C" is displayed on the bottom line in the figure ". This is because PC
An unexpected interruption occurs, instead of clock interruption or system call interruption. Because we have installed a default interrupt handler for all other interrupts in the program. When there is another interruption, the system will
Run the default interrupt handler, so a character "C" is displayed on the screen, and then exit the interrupt.
**************************************** **************************************** **************************************** **********************
Head. s
01 # Head. S contains the 32-bit Protection Mode Initialization setting code, clock interruption code, system call interruption code, and two task code.
02 # After initialization, the program moves to task 0 to start execution, and switches between task 0 and Task 1 under the control of clock interruption.
03 latch = 11930 # The initial counting value of the timer, that is, an interrupt request is sent every 10 ms.
04 scrn_sel = 0x18 # memory segment selection character displayed on the screen.
05 tss0_sel = 0x20 # TSS segment selection operator of task 0.
06 ldt0_sel = 0x28 # LDT segment selection operator of task 0.
07 tss1_sel = 0x30 # TSS segment selection operator of Task 1.
08 ldt1_sel = 0x38 # LDT segment selection operator of Task 1.
09. Text
10 startup_32:
11 # load the data segment register ds, the stack segment register SS, And the stack pointer ESP. The base linear address of all segments is 0.
12 movl $0x10, % eax #0x10 is the data segment selection character in gdt.
13mov % ax, % DS
14lss init_stack, % ESP
15 # reset the IDT and gdt tables at the new location.
16 call setup_idt # Set IDT. First, fill in the descriptor of the default processing process for all the 256 interrupt doors.
17 Call setup_gdt # Set gdt.
18 movl $0x10, % eax # Reload all segment registers after gdt is changed.
19mov % ax, % DS
20mov % ax, % es
21mov % ax, % FS
22mov % ax, % GS
23lss init_stack, % ESP
24 # Set the 8253 timing chip. Set counter channel 0 to send an interrupt request signal to the interrupt controller every 10 ms.
25 movb $0x36, % Al # control word: Set channel 0 to work in Mode 3. The initial count value is binary.
26 movl $0x43, % edX #8253 Chip Control Word register write port.
27 outb % Al, % DX
28 movl $ latch, % eax # The initial count value is set to latch (1193180/100), that is, the frequency is 100Hz.
29 movl $0x40, % edX # port of channel 0.
30 outb % Al, % DX # Write the initial count value to channel 0 twice.
31 movb % ah, % Al
32 outb % Al, % DX
33 # Set the timed interrupt gate Descriptor and System Call trap gate descriptor respectively in IDT table 8th and 128th (0x80.
34 movl $0x00080000, % eax # The interrupt program belongs to the kernel, that is, the eax is the kernel code segment identifier 0x0008.
08 h |
Irq0: implemented by the system timing component; called 18.2 times per second (once every 55 MS) by the PIC |
35 movw $ timer_interrupt, % ax # sets the timed interrupt gate descriptor. The address of the scheduled interrupt handler.
36 movw $0x8e00, % DX # The interrupt door type is 14 (blocked interrupt), privileged level 0 or hardware usage.
37 movl $0x08, % ECx # indicates the clock interrupt vector number 8 set by the BIOS during startup. Use it directly here.
38lea IDT (, % ECx, 8), % ESI # Put the IDT descriptor 0x08 address into ESI, and then set this descriptor.
39 movl % eax, (% Esi)
40 movl % edX, 4 (% Esi)
41 movw $ system_interrupt, % ax # sets the system call trap gate descriptor. The system call handler address.
42 movw $0xef00, % DX # the trap door type is 15, and the privileged level 3 program can be executed.
43 movl $0x80, % ECx # The system call Vector number is 0x80.
44lea IDT (, % ECx, 8), % ESI # Put the IDT descriptor 0x80 address into ESI, and then set this descriptor.
45 movl % eax, (% Esi)
46 movl % edX, 4 (% Esi)
47 # well, now we want to move the stack content to task 0 (Task A) for execution, and create a scenario for manual interruption return in the stack.
48 pushfl # The nested task flag in the reset flag register eflags.
4 down the stack pointer (if the current operand size attribute is 32), and press all the content of the eflags Register into the stack; or decrease the stack pointer
2 (if the current operand size attribute is 16), and the low 16 bits (namely, the flags register) of the eflags register are pushed into the stack. (These commands are executed
Popf/popfd command ). When the entire eflags register is copied to the stack, the VM and RF flag (bit 16 and
17); on the contrary, the values of these labels are cleared in the eflags image stored in the stack.
49 andl $0 xffffbfff, (% ESP)
50 popfl
51 movl $ tss0_sel, % eax # load the TSS segment selector of task 0 to the task register tr.
52ltr % ax
53 movl $ ldt0_sel, % eax # load the LDT segment selector of task 0 to the Local Descriptor Table register ldtr.
54 lldt % ax # Tr and ldtr are manually loaded once, And the CPU will automatically process them later.
55 movl $0, current # Save the current task number 0 in the current variable.
56sti # enable interrupt now and create an interrupt return scenario in the stack.
57 pushl $0x17 # import the data segment (stack segment) of task 0 to the stack.
58 pushl $ init_stack # import the stack pointer into the stack (you can also directly import ESP into the stack ).
59 pushfl # import the flag register value into the stack.
60 pushl $ 0x0f # select the code segment of the current local space into the stack.
61 pushl $ task0 # code pointer into the stack.
62 iret # execute the interrupt return command to switch to task 0 in privileged level 3 for execution.
63
64 # The following is a subroutine used to set descriptor items in gdt and IDT.
65 setup_gdt: # Use the 6-byte operand lgdt_opcode to set the location and length of the gdt table.
66 lgdt lgdt_opcode
67ret
# This Code temporarily sets all the 256 interrupt gate descriptors in the IDT table to the same default value, and uses the default interrupt processing process.
# Ignore_int. The specific setting method is as follows: first, set the default interrupt gate descriptor 0 ~ In the eax and EDX registers respectively ~ 3
# Bytes and 4 ~ 7-byte content, and then use this register to fill the default interrupt gate descriptor content in the loop to the IDT table.
68 setup_idt: # Set all 256 interrupt gate descriptors to use the default processing process.
69lea ignore_int, % edX # The setting method is the same as setting the timed interrupt gate descriptor.
70 movl $0x00080000, % eax # The selector is 0x0008.
71 movw % dx, % ax
72 movw $0x8e00, % DX # interrupt door type, privileged level 0.
73lea IDT, % EDI
74mov $256, % ECx # cyclically set all 256 gate descriptor items.
75 rp_idt: movl % eax, (% EDI)
76 movl % edX, 4 (% EDI)
77 addl $8, % EDI
78dec % ECx
79jne rp_idt
80 LIDT lidt_opcode # the last 6-byte operand is used to load the idtr register.
81ret
82
83 # display the character subroutine. Take the current cursor position and display the characters in Al on the screen. The screen can contain 80x25 characters.
84 write_char:
85 push % GS # first, save the registers to be used. The caller is responsible for saving eax.
86 pushl % EBX
87mov $ scrn_sel, % EBX # Then point the Gs to the Display memory segment (0xb8000 ).
88mov % BX, % GS
89 movl scr_loc, % BX # And then obtain the current position value of the character from the variable scr_loc.
90shl $1, % EBX # Because each character on the screen has an attribute byte, the character
91 movb % Al, % GS :( % EBX) # The Memory offset address corresponding to the actual display location must be multiplied by 2.
92shr $1, % EBX # Place the character in the display memory and divide the position value by 2 and 1.
93 incl % EBX # The Next display position. If the position is greater than 2000, it is reset to 0.
94 CMPL $2000, % EBX
95jb 1f
96 movl $0, % EBX
97 1: movl % EBX, scr_loc # Save the position value (scr_loc ),
98 popl % EBX # And the saved register content is displayed.
99pop % GS
100ret
101
102 # The following are three interrupt handlers: Default interrupt, scheduled interrupt, and system call interrupt.
103 # ignore_int is the default interrupt handler. If the system has other interruptions, a character "C" is displayed on the screen ".
104. Align 2
105 ignore_int:
106 push % DS
107 pushl % eax
108 movl $0x10, % eax # first point ds to the kernel data segment because the interrupt program belongs to the kernel.
Using mov % ax, % DS
110 movl $67, % eax # code for storing the character "C" in Al. The call display program is displayed on the screen.
111 call write_char
112 popl % eax
113pop % DS
114 iret
115
116 # This is a scheduled interrupt processing program. The task switchover is mainly performed.
117. Align 2
118 timer_interrupt:
119 push % DS
120 pushl % eax
121 movl $0x10, % eax # First let DS point to the kernel data segment.
122mov % ax, % DS
123 movb $0x20, % Al # Then immediately allow other hardware interruptions, that is, sending The EOI command to 8259a.
124 outb % Al, $0x20
125 movl $1, % eax # Then judge the current task. If Task 1 is run, the task 0 is executed, or vice versa.
126 CMPL % eax, current
127je 1f
128 movl % eax, current # If the current task is 0, save 1 to current and jump to Task 1
129 ljmp $ tss1_sel, $0. Note that the Offset Value of the jump is useless, but you need to write it.
130jmp 2f
131 1: movl $0, current # If the current task is 1, save 0 to current and jump to task 0
132 ljmp $ tss0_sel, $0.
133 2: popl % eax
134pop % DS
135 iret
136
137 # The system calls the interrupt int 0x80 handler. This example only has one character display function.
138. Align 2
139 system_interrupt:
140 push % DS
141 pushl % edX
142 pushl % ECx
143 pushl % EBX
144 pushl % eax
145 movl $0x10, % edX # First let DS point to the kernel data segment.
146mov % dx, % DS
147 call write_char # Call the show character subroutine write_char to display the characters in Al.
148 popl % eax
149 popl % EBX
150 popl % ECx
151 popl % edX
152pop % DS
153 iret
154
155 /************************************** *******/
156 current:. Long 0 # current task number (0 or 1 ).
157 scr_loc:. Long 0 # current position on the screen. It is displayed sequentially from the upper left corner to the lower right corner.
158
159. Align 2
160 lidt_opcode:
161. Word 256*8-1 #6-byte operands for loading the idtr register: The table length and base address.
162. Long IDT
163 lgdt_opcode:
164. Word (end_gdt-gdt)-1 #6-byte operands loading the GDTR register: Table length and base address.
165. Long gdt
166
167. Align 3
168 IDT:. Fill, 0 # IDT space. A total of 256 gate descriptors, each 8 bytes, occupy 2 kb.
169
170 gdt:. Quad 0x0000000000000000 # gdt table. 1st descriptors are not used.
171. Quad 0x00c09a00000007ff # 2nd is the kernel code segment descriptor. Its selection character is 0x08.
172. Quad 0x00c09200000007ff # 3rd are kernel data segment descriptors. The value is 0x10.
173. Quad 0x00c0920b80000002 # 4th is the display memory segment descriptor. The value is 0x18.
174. Word 0x68, tss0, 0xe900, 0x0 # 5th are tss0 descriptors. Its selection character is 0x20
175. Word 0x40, ldt0, 0xe200, 0x0 # 6th are ldt0 segment descriptors. Its selection character is 0x28
176. Word 0x68, tss1, 0xe900, 0x0 # 7th are tss1 descriptor. The value is 0x30.
177. Word 0x40, ldt1, 0xe200, 0x0 # 8th are ldt1 descriptors. Its selection character is 0x38
178 end_gdt:
179. Fill, 4, 0 # initial Kernel stack space.
180 init_stack: # used to load the SS: ESP Stack pointer value when entering the protection mode.
181. Long init_stack # stack segment offset position.
182. Word 0x10 # The stack segment is the same as the kernel data segment.
183
184 # The following is the partial segment descriptor in the LDT table segment of task 0.
185. Align 3
186 ldt0:. Quad 0x0000000000000000 # 1st descriptors, no.
187. Quad 0x00c0fa00000003ff # 2nd partial code segment descriptors, with the corresponding selector 0x0f.
188. Quad 0x00c0f200000003ff # 3rd Local Data Segment descriptors, corresponding to 0x17.
189 # The following is the TSS section of task 0. Note that the fields such as the label do not change during task switching.
190 tss0:. Long 0/* back link */
191. Long krn_stk0, 0x10/* esp0, ss0 */
192. Long 0, 0, 0, 0, 0/* esp1, SS1, esp2, ss2, 3303 */
193. Long 0, 0, 0, 0, 0/* EIP, eflags, eax, ECx, EDX */
194. Long 0, 0, 0, 0, 0/* ebx esp, EBP, ESI, EDI */
195. Long 0, 0, 0, 0, 0, 0/* es, Cs, SS, DS, FS, GS */
196. Long ldt0_sel, 0x8000000/* LDT, trace bitmap */
197
198. Fill, 4, 0 # This is the kernel stack space of task 0.
199 krn_stk0:
200
201 # the content of the LDT table segment and the content of the TSS segment of Task 1 is as follows.
202. Align 3
203 ldt1:. Quad 0x0000000000000000 # 1st descriptors, no.
204. Quad 0x00c0fa00000003ff # The selector is 0x0f and the base address is 0x00000.
205. Quad 0x00c0f200000003ff # The selector is 0x17 and the base address is 0x00000.
206
207 tss1:. Long 0/* back link */
208. Long krn_stk1, 0x10/* esp0, ss0 */
209. Long 0, 0, 0, 0, 0/* esp1, SS1, esp2, ss2, 3303 */
210. Long task1, 0x200/* EIP, eflags */
211. Long 0, 0, 0, 0/* eax, ECx, EDX, EBX */
212. Long usr_stk1, 0, 0, 0/* ESP, EBP, ESI, EDI */
213. Long 0x17, 0x0f, 0x17,0x17,0x17,0x17/* es, Cs, SS, DS, FS, GS */
214. Long ldt1_sel, 0x8000000/* LDT, trace bitmap */
215
216. Fill, 4, 0 # This is the kernel stack space of Task 1. Its user Stack directly uses the initial stack space.
217 krn_stk1:
218
219 # The following are the programs for task 0 and Task 1, which show the characters "a" and "B" cyclically ".
220 task0:
221 movl $0x17, % eax # first point ds to the local data segment of the task.
222 movw % ax, % DS # the two statements can be omitted because the task does not use local data.
223 movl $65, % Al # Put the character "a" to be displayed into the Al register.
224int $0x80 # execute the system call and display characters.
225 movl $0 xfff, % ECx # executes the loop, which is delayed.
226 1: loop 1b
227jmp task0 # Jump to the start of the task code and continue to display characters.
228 task1:
229 movl $66, % Al # Put the character "B" to be displayed into the Al register.
230int $0x80 # execute the system call and display characters.
231 movl $0 xfff, % ECx # delay for a period of time, and jump to the start to continue the loop display.
232 1: loop 1b
233jmp task1
234
235. Fill, 4, 0 # This is the user stack space of Task 1.
236 usr_stk1:
**************************************** **************************************** **************************************** ****