Absrtact: Process scheduling is one of the most important parts of the operating system, in this paper, we mainly explain the process model, the data structure involved, how to go from the kernel state to the user process. This section mainly completes the analysis of the process data structure, and the content from the beginning of the ring0>>ring1 process.
1. ProcessLet's take a look at what data structures and program modules are required to complete the process switch:
1) First, a process must have code, data (and stacks): Related data are LDT, segment descriptor, TSS, etc.
2) for the rest of the process, we need to let it wake up to remember the status of the pending, so that the original task can continue to execute-so we need to save the program state, which is the PCB (Program control block)
3) Process switcher, is the process scheduling module of the operating system
4) clock interrupt handler to help us complete the process switch
2. Assembly instructions related to process switching
Pushad:Because process switching requires the process context to be saved, and it is cumbersome to save each register using push, Intel provides an instruction Pushad to hold the values of all common registers
IRET and iretdis the mnemonic for the same opcode. The iretd mnemonic (interrupt return double word) is used to return an interrupt that uses a 32-bit operand size, but most assemblers use the IRET mnemonic interchangeably for both operands.
3. Process scheduling Approximate process
PCB:The PCB is used to describe the process, it is independent of the process, when we put the context into the PCB, is already in the Process Management module.
esp points to: In the Process Scheduler module, the stack is used, and the register is pushed to the process table, and the ESP points to a location on the PCB-the next stack operation destroys the PCB. To address these issues, you need to point esp to a dedicated kernel stack. So, during process switching, esp points to three times: the process stack--pcb--the kernel stack.
Privilege Level Transformation: from the outer layer to the inner level, obtains the SS:ESP from the TSS; when initialized, from Ring0>>>ring1, this and the recovery process executes a bit like, we need to complete the context initialization, Then use the IRETD directive to complete the transfer. recovery : First, we need to restore the value of the register from the PCB, then instruct iretd, set Cs:ip and EFlags, so the program goes back to process B.
4. Initialize--ring0>>>ring1
In order to have a perceptual understanding of the transformation from kernel to process, let's look at the code for the conversion moment:
Chapter6/i/kernel/kernel.asm
; ============================================================================ ========
; Restart
; ====================================================================================
Restart:
mov esp, [p_proc_ready] ; esp points to LPCB, ready when the process is running
Lldt [esp + p_ldt_sel]
lea EAX, [ESP + P_ Stacktop];eax=esp+p_stacktop
mov DWORD [TSS + tss3_s_sp0], eax; In this way, the address TSS + TSS3_S_SP, where the SS0 address is stored
restart_ Reenter:
Dec DWORD [k_reenter]
pop gs
pop fs pop
es
pop ds
popad
add ESP, 4
Iretd
So where does this part jump from?
/kernel/mai.c/kernel_main () >>restart ()
Analyze the code execution process:
1) P_proc_ready, is a PCB pointer, pointing to the next will be executed PCB;
2) Okay, here, let's go check the definition of the PCB and the definition of P_ldt_sel:
Definition of PCB:
typedef struct S_PROC { stack_frame regs; /* Process registers saved in stack frame */ U16 Ldt_sel; /* GDT selector giving LDT base and limit */ descriptor ldts[ldt_size];/* Local descriptors for code and data * *-
notoginseng int ticks; /* remained ticks */+ int priority; u32 pid; /* Process ID passed in from MM
/ p_name[16 [char]; /* Name of the process */
}process;
P_ldt_sel represents the index of the Ldt_sel, and look at some column definitions of the total PCB related variables:
8 p_stackbase equ 0
9 gsreg equ p_stackbase
ten fsreg equ gsreg + 4
esreg equ fsreg + 4
dsreg equ esreg + 4
edireg equ dsreg + 4 esireg equ edireg + 4< c18/>15 ebpreg equ esireg + 4
kernelespreg equ ebpreg + 4 ebxreg equ Kernelespreg + 4
edxreg equ ebxreg + 4
ecxreg equ edxreg + 4
eaxreg< c34/>equ Ecxreg + 4
retadr equ eaxreg + 4
eipreg equ retadr + 4
csreg< c43/>equ Eipreg + 4
eflagsreg equ csreg + 4
espreg equ eflagsreg + 4
26 Ssreg equ Espreg + 4
p_stacktop equ ssreg + 4
p_ldt_sel
equ p_stacktop P_ldt equ P_ldt_sel + 4
tss3_s_sp0 equ 4
Well, see here, perhaps you have understood the PCB structure, as follows:
>ring1 (i) ">
Related definitions of TSS:
A typedef struct S_TSS { u32 backlink;
Panax Notoginseng u32 esp0; /* stack pointer to use during interrupt */ u32 ss0; /* "segment" "" " */
....
....
} Tss
3) To summarize, the first two sentences are the setting of the LDT, then the two sentences are the sp0 of the TSS.
Let's take a look at the context of the calling code:
*/* Initialize 8253 PIT * /out_byte (Timer_mode, rate_generator); Out_byte (TIMER0, (U8) (timer_freq/hz)); Out_byte (TIMER0, (U8) ((timer_freq/hz) >> 8)); Put_irq_handler (CLOCK_IRQ, Clock_handler);/* Set Clock Interrupt handler * /ENABLE_IRQ (CLOCK_IRQ); /* Allow 8259A to receive clock interrupt
* /restart (); (1) {}
Combining the start chart of the process table, we draw the following conclusions:
The next time the interrupt occurs, the pop Sregs, then the pop regs, then skips retaddr, then executes the iretd. The next time an outage occurs, the work that needs to be done is to restore the values of each register, SS0 in TSS, and set LDTR.
4.1 Clock Interrupt HandlerClock interrupt just to complete the process switch, we do not use complex scheduling here, just complete the ring0>>ring1, so use Iret.
ALIGN
151 hwint00: ; Interrupt routine for IRQ 0 (the clock). iretd
4.2PCB, process body, GDT and TSSFor the initialization of the PCB, we only need to set Sregs, EIP, ESP and eflags. In addition, CS and DS correspond to the LDT at this time, so it is necessary to initialize the LDT. In addition, we also need to initialize SS0 and esp0 in TSS.
OK, let's take a look at the process table, PCB, GDT, TSS, the data relationship between them: (see figure above)
Next, let's do the initialization of these four parts:
1) Process body:
is a function that keeps printing letters A:CHAPTER6/A/KERNEL/MAIN.C
Wuyi void TestA () () () . the while (1) { disp_str ("A"); Disp_int (i++); disp_str ("."); (1);
60}
Think about it, Testa is just a process, and it's an object that's being interrupted, apparently not part of the kernel. How to transfer control to the process. In the previous chapters, Kernel_main is the kernel function, the jump process:
There is a jmp kernel_main directive in Kernel.asm, Kernel_main is a function in Main.c, and the last sentence of Kernel.main is the while (1) {}, so the kernel enters the wait mode, Requests for processing modules and process scheduling modules will be interrupted accordingly.
2) Process table
It is not difficult to define the relevant structure of the PCB according to the intent of the process above:
9 typedef struct S_STACKFRAME {U32 gs; /* \ */U32 FS; /* | * * U32 es; /* | * * U32 DS; /* | */U32 EDI; /* | * * u32 ESI; /* | Pushed by Save () */U32 EBP; /* | * * U32 Kernel_esp; /* <-' popad ' would ignore it */u32 ebx; /* | * * U32 edx; /* | * * u32 ecx; /* | * * u32 eax; /*/* u32 retaddr; /* return addr for Kernel.asm::save () */u32 EIP; /* \ */U32 CS; /* | * * U32 eflags; /* | Pushed by CPU during interrupt */U32 ESP; /* | * * U32 SS;
/*/*}STACK_FRAME; The typedef struct S_PROC {stack_frame regs; /* Process registers saved in stack frame */U16 Ldt_sel; /* GDT selector giving LDT base and limit */descriptor ldts[ldt_size]; /* Local descriptors for code and data */U32 pid; /* Process ID passed in from MM */PNs char p_name[16]; /* Name of the process */}process;
Know the data structure, and then take a look at its initialization a/kernel/main.c
P_proc->ldt_sel = Selector_ldt_first;
memcpy (&p_proc->ldts[0], &gdt[selector_kernel_cs>>3], sizeof (descriptor)); P_PROC->LDTS[0].ATTR1 = Da_c | Privilege_task << 5;
Change the DPL memcpy (&p_proc->ldts[1], &gdt[selector_kernel_ds>>3], sizeof (descriptor)); P_PROC->LDTS[1].ATTR1 = DA_DRW | Privilege_task << 5; Change the DPL to P_proc->regs.cs = (0 & sa_rpl_mask & sa_ti_mask) | Sa_til |
Rpl_task; P_proc->regs.ds = (8 & sa_rpl_mask & Sa_ti_mask) | Sa_til |
Rpl_task; p_proc->regs.es = (8 & sa_rpl_mask & Sa_ti_mask) | Sa_til |
Rpl_task; P_proc->regs.fs = (8 & sa_rpl_mask & Sa_ti_mask) | Sa_til |
Rpl_task; P_PROC->REGS.SS = (8 & sa_rpl_mask & Sa_ti_mask) | Sa_til |
Rpl_task; PNS P_proc->regs.gs = (Selector_kernel_gs & sa_rpl_mask) |
Rpl_task;
p_proc->regs.eip= (u32) TestA; 39 p_proc->regs.esp= (u32) Task_stack + stack_size_total; P_proc->regs.eflags = 0x1202;
If=1, iopl=1, bit 2 is always 1.
P_proc_ready = proc_table; Restart ();
Where the macro used above is defined in Protect.h, refer to: a/include/protect.h
#define INDEX_DUMMY 0 */*/#define INDEX_FLAT_C 1/* | LOADER has been identified in the * * #define INDEX_FLAT_RW 2/* | * * #define INDEX_VIDEO 3 */*/#define INDEX_TSS 4 #define Index_ldt_fir ST 5 73/* Select Child */#define SELECTOR_DUMMY 0/* \ */#define Selector_flat_c 0x08/* | LOADER has been identified in the * * #define SELECTOR_FLAT_RW 0x10/* | * * #define SELECTOR_VIDEO (0x18+3)///<--rpl=3 */#define SELECTOR_TSS 0x20/* T SS */#define SELECTOR_LDT_FIRST 0x28 #define SELECTOR_KERNEL_CS selector_flat_c 82 #define SELECTOR_KERNEL_DS SELECTOR_FLAT_RW #define SELECTOR_KERNEL_GS selector_video 84 85/* Each task has a separate LDT, each Number of descriptors in the LDT: */#define LDT_SIZE 2 87 88 * * Select Subtype Value Description */89/* where Sa_: SelectoR Attribute * * #define SA_RPL_MASK 0xFFFC #define SA_RPL0 0 #define SA_RPL1 1 #define SA_RPL2 2 94 #define SA_RPL3 3 #define SA_TI_MASK 0xFFFB #define SA_TIG 0 98 #define SA_TIL 4
Descriptor for process Ldt in the filled GDT: a/kernel/protect.c
109 Init_descriptor (&gdt[index_ldt_first], Vir2phys (Seg2phys (selector_kernel_ds), PROC_ Table[0].ldts),
111 ldt_size * sizeof (Descriptor)-1, Da_ldt);
The implementation of this function:
149 PRIVATE void Init_descriptor (descriptor *p_desc,u32 base,u32 limit,u16 attribute) (151 p_desc-) >limit_low = limit & 0x0ffff; P_desc->base_low = base & 0x0ffff;
153 P_desc->base_mid = (base >>) & 0x0ff;
154 p_desc->attr1 = attribute & 0xFF;
155 p_desc->limit_high_attr2= ((limit>>16) & 0x0F) | (attribute>>8) & 0xF0;
156 P_desc->base_high = (base >>) & 0x0ff;
157}
3) Prepare GDT and TSS
Now, all that remains is the initialization of the TSS and the padding of the corresponding descriptor in the GDT:
Initialize TSS:
99/* Fill in the TSS in the GDT this descriptor * /-memset (&tss, 0, sizeof (TSS));
101 tss.ss0 = Selector_kernel_ds;
102 Init_descriptor (&GDT[INDEX_TSS],
103 Vir2phys (Seg2phys (Selector_kernel_ds), &TSS),
104 sizeof (TSS)-1, da_386tss);
106 tss.iobase = sizeof (TSS);/* There is no I/O license Bitmap/*
below, fill tr: $ xor eax, eax
131 mov ax, SELECTOR_TSS Ltr Ax
4.3iretd
Here, let's start with a simple restart function:
294 Restart:
295 mov esp, [p_proc_ready]
296 Lldt [esp + P_ldt_sel]
297 lea EAX, [ ESP + p_stacktop]
298 mov DWORD [TSS + tss3_s_sp0], eax
299
pop gs
301 Pop fs< c36/>302 pop es
303 pop ds
304 popad
305
306 add ESP, 4
307
308 iretd
Well, using iretd will load Cs:ip, think about what the value of CS and IP is. Note that the test function in the compiled MAIN.C is in the 32b code snippet, which we need to look at in disassembly. 4.4 Process Initiation and review Let's recall the startup process for the first process:
Initialization process: TestA; Initializes the two characters of TSS and Ldt in the GDT, initializes the TSS (in Init_prot ()), prepares the process table (in Kernel.main ()), and completes the Jump (kernel.asm)
However, we are now just completing the process from the kernel to the user, but how to complete the process switch, obviously, we need to turn on the clock interrupt and set the EOI bit of 8259A.
To summarize:
Kernel Workflow:
Kernel.asm:
_start: Kernel entry, sequential execution down
Cstart (): Copy GDT from loader to kernel, set GDT and LDT, initialize interrupt vector table
Init_prot (): Initialize 8259A, initialize each interrupt gate
Set TR
Main.c/tinix_main ():
Set up PCB information
Restart (): Lldt, SS0, recovery segment Register and Universal Register, enter ring1 (iretd), perform testa--infinite loop
while (1)
Here, let's introduce a trick: How to debug the system kernel.
Our original debugging, is in the assembly program state, how to follow the C language line level to debug the kernel. Here, we dig a hole and backfill the place later.