The last one says that process scheduling boils down to calling the Timer_interrupt function in SYSTEM_CALL.S:
# # # # Int32--(int 0x20) clock interrupt handler. Interrupt frequency is set to 100Hz (include/linux/sched.h,5), # Timing chip 8253/8254 is initialized at (kernel/sched.c,406). So here jiffies every 10 milliseconds plus 1. # This code will increase jiffies by 1, send an end interrupt instruction to the 8259 controller, and then call the # C function Do_timer (long CPL) with the current privilege level as a parameter. When the call returns, it goes to detect and process the signal: Align 2_timer_interrupt:push%ds # save ds,es and put kernel data Spacepush%es # into them. %fs is used by _system_callpush%FSPUSHL%edx # We save%eax,%ecx,%edx as GCC doesn ' tpushl%ecx # save those across Functi On calls. %EBXPUSHL%EBX # is saved as we use this in RET_SYS_CALLPUSHL%eaxmovl $0x10,%eax # Ds,es to point to the kernel data segment. mov%ax,%dsmov%AX,%ESMOVL $0x17,%eax # FS is set to point to the local data segment (the data segment of the error program). MOV%ax,%fsincl _jiffies# due to the initialization of the interrupt control chip is not used automatic EOI, so here need to send instructions to end the hardware interrupt. Movb $0x20,%al # EOI to interrupt Controller #1outb%al,$0x20 # operation command Word OCW2 send 0x20 port. # The following 3 sentences remove the current privilege level (0 or 3) from the selector and press it into the stack as a parameter to the Do_timer. MOVL CS (%ESP),%eaxandl $3,%eax #%eax is CPL (0 or 3, 0=supervisor) PUSHL%eax# Do_timer (CPL) perform task switching, timing, etc., in KERNEL/SHCHED.C, 305 Line implementation. Call _do_timer # ' Do_timer (long CPL) ' does everything froMADDL $4,%esp # Task switching to accounting ... jmp ret_from_sys_call
The previous heap of PUSH commands saves the current register and then pops up in the Ret_from_sys_call.
MOVL $0x10,%eax the Segment selection sub-0x10 that is, the kernel data segment selection sub-assignment to EAX, and then assigned to DS, ES;
Then _jiffies plus 1,jiffies is defined in Sched.h:
extern long volatile jiffies;//The number of ticks (10ms/ticks) from the start of the boot.
The next three sentences are more critical:
MOVL CS (%ESP),%eaxandl $3,%eax #%eax is CPL (0 or 3, 0=supervisor) PUSHL%eax
From the above push register to remove the value of CS register, that is, the code segment selector, according to the structure of choice, 0-1 bits is a privileged level, Andl $3,%eax is to take eax in the 0-1-bit value, and then the EAX stack as a do_timer parameter transfer, 4 bytes.
OK, now go into the Do_timer function, in SCHED.C:
The clock interrupts the C function handler, which is called in _timer_interrupt (176 rows) in Kernel/system_call.s. The parameter CPL is the current privileged level 0 or 3,0 indicates that the kernel code is executing. For a process, task switching occurs because the execution time slice is exhausted. and perform a timed update work. void Do_timer (Long cpl) {extern int beepcount;//loudspeaker audible time tick count (kernel/chr_drv/console.c,697) extern void Sysbeepstop (void )///Turn off the speaker (kernel/chr_drv/console.c,691)//If the sounder counts several times, turn off the audible. (Send command to 0x61 port, reset bits 0 and 1.) Bit 0 Control 8253//Counter 2 work, bit 1 control loudspeaker). if (Beepcount) if (!--beepcount) sysbeepstop (); If the current privilege level (CPL) is 0 (the highest, that is, the kernel program is working), the kernel program run time stime increments;//[Linus the kernel program is referred to as Super User (supervisor) program, see system_call.s,193 The English note on the line]//If CPL > 0, means that the general user program is working, adding utime. if (CPL) current->utime++; else current->stime++; If a user's timer exists, the value of the 1th timer in the list is reduced by 1. If it is equal to 0, the appropriate processing//program is called and the handler pointer is set to NULL. Then remove the timer. if (Next_timer) {//Next_timer is the head pointer of the timer list (see 270 lines). next_timer->jiffies--; while (Next_timer && next_timer->jiffies <= 0) {void (*FN) (void);//insert a function pointer definition here!!? fn = next_timer->fn; NEXT_TIMER->FN = NULL; Next_timer= next_timer->next; (FN) ();//Call handler function. }}//If the motor start bit in the digital output register of the current floppy disk controller FDC is set, the floppy timer program (245 lines) is executed. if (Current_dor & 0xf0) Do_floppy_timer (); if ((--current->counter) > 0) return;//exits if the process runs out of time. Current->counter = 0; if (!cpl) return;//is not dependent on the counter value for the Superuser program (kernel state program). Schedule ();}
The role of the passed parameter CPL is that if it is 0, that is, the kernel program, then stime plus 1, otherwise is a normal user program, then utime plus 1.
User timer and so on to use to say.
Next determine the time slice counter, in the Sched.h process descriptor:
Long counter;//Long counter task run time count (decrement) (tick count), run time slice.
If there is a time slice then do not call the Dispatch function schedule (), then the time slice minus 1 and exit this function.
If the time slice is exhausted (<=0), the time slice is 0, then the privilege level is determined, and if the kernel-level program exits the function directly. Otherwise, enter the most central dispatch function schedule:
/* * ' schedule () ' is a dispatch function. This is a very good code! There is no reason to modify it because it can work in all environments (such as being able to respond well to io-boundaries). There's only one thing worth noticing, and that's the signal here. * Handling code. * Attention!! Task 0 is an idle (' idle ') task that is called only if no other task can run. It cannot be killed, nor can it sleep. The status information ' state ' in task 0 is never used. */void Schedule (void) {int I, next, C; struct TASK_STRUCT A pointer to the **P;//task structure pointer. /* Check Alarm, wake up any interruptible task that has got a signal */* detects alarm (process alarm timing value), wakes any interruptible tasks that have been signaled *//from Task The last task in the array starts to detect alarm. for (p = &LAST_TASK; p > &FIRST_TASK;--p) if (*p) {//If the timed value of the task is set alarm and has expired (alarm<jiffies), the Signal bitmap in the SIGALRM signal,//is to send sigalarm signal to the task. Then Qing alarm. The default action for this signal is to terminate the process. Jiffies is the number of ticks (10ms/ticks) from the start of the system. defined on line 139th of Sched.h. if ((*p)->alarm && (*p)->alarm < jiffies) {(*p)->signal |= (1 << (SIGALRM-1)); (*p)->alarm = 0; }//If there are other signals in the signal bitmap other than the blocked signal, and the task is in a interruptible state, the task is in the ready state. where ' ~ (_blockable & (*p)->blocked) ' is used to ignore blocked signals, but Sigkill and Sigstop cannot be blocked. if ((*p)->signal & ~ (_blockable & (*p)->blocked)) && (*p)->state = = task_interruptible) (*p)->state = task_running;//is set to Ready (executable) state. }/* This is the scheduler proper: */* Here is the main part of the scheduler */while (1) {c =-1; Next = 0; i = nr_tasks; p = &task[NR_TASKS]; This code also loops through the last task in the task array and skips the array slots that do not contain the task. Compare the counter (descending tick count for task run time) value for each readiness/status task, which is large and not running long, and which task number next//points to. while (--) {if (!*--p) continue; if ((*p)->state = = task_running && (*p)->counter > c) c = (*p)->counter, next = i;} If you compare a result with a counter value greater than 0, exit the loop starting with line 124 and perform a task switch (141 rows). if (c) break; Otherwise, update the counter value of each task according to the priority value of each task, and then return to the 125 row to re-compare. The counter value is calculated as Counter = COUNTER/2 + priority. [Right counter=0??] The calculation process here does not take into account the state of the process. for (p = &LAST_TASK; p > &FIRST_TASK;--p) if (*p) (*p)->counter = ((*p)->counter >> 1) + (*P) Priority }//Switch to task number for next task run. In line 126 Next is initialized to 0. So if there are no other tasks in the system//can run, then next is always 0. Therefore, the dispatch function executes task 0 when the system is idle. Task 0 only executes/pause at this time() system call, and this function is called again. Switch_to (next);//Switch to task number for next task and run it. }
A good understanding of the previous, direct analysis of the main part, this part of the main task is to find out all the tasks of the time slice the biggest task, it means that the running time is less, next point to this task and jump out of the loop to switch tasks.
If all tasks have a time slice of 0, the time slice counter value for each task is updated based on the priority value of each task. Then find next again, and finally switch tasks, call Switch_to (next):
A macro definition that calculates the index number (selector) of the TSS descriptor for the nth task in the global table. #define _TSS (N) (((unsigned long) n) <<4) + (first_tss_entry<<3))/** switch_to (n) will switch the current task to the task NR, or N. First detect task n is not the current task, * If yes then do nothing to quit. The TS flag in the control register CR0 also needs to be reset if we switch to a task that used the math coprocessor recently (last run). *///Input:%0-offset address for new TSS (*&__TMP.A),%1-selector value for new TSS (*&__TMP.B),//dx-Selector for new task N, ecx-new task pointer task[n]. Where the temporary data structure is __tmp, the value of a is a 32-bit offset value and B is the new TSS selector. The A value//is not used (ignored) when the task is toggled. When judging whether a new task was last executed using a coprocessor, it is done by comparing the address of the new task State segment with the address of the task state segment that was saved in the Last_task_used_math variable using the coprocessor. #define SWITCH_TO (n) {struct {long B,} __tmp; __asm__ ("Cmpl%%ecx,_current\n\t" \//task n is the current task? Current ==task[n]?) "Je 1f\n\t" \//is, then do nothing, quit. "MOVW%%dx,%1\n\t" \//the new task's selector?? *&__tmp.b. "Xchgl%%ecx,_current\n\t" \//current = TASK[N];ECX = The task being switched out. "LJMP%0\n\t" \//CEO jumps to *&__tmp, causing the task to switch. The following statement will not continue until the task has been switched back. "Cmpl%%ecx,_last_task_used_math\n\t" \//new task last used coprocessor? "Jne 1f\n\t" \//no jump, exit. "Clts\n" \//the new task last used the coprocessor, the CR0 TS flag is cleared. "1:":: "M" (*&__tmp.a), "M" (*&__tmp.b), "D" (_TSS (N)), "C" ((long) task[n]));
Before analyzing this code, be aware that in 32-bit protected mode, there are 2 ways to directly initiate a task switch:
1.call 0x0010:0x00000000
2.JMP 0x0010:0x00000000
in both cases, the operand of the call and the JMP instruction is the TSS descriptor selector for the task or the task gate. When the processor executes these two instructions, it first selects the sub-access GDT with the descriptor given in the instruction and parses its descriptor type . If it is a generic code snippet descriptor, it is executed as a normal inter-segment transfer rule, and if it is called, it is executed by the rule calling the gate, and if it is a TSS descriptor or a task gate, a task switch is performed. At this point, the 32-bit offset given in the instruction is ignored because the state of all processors can be obtained from the TSS when performing a task switch .
When a task switch occurs, the contents of the TR register are followed by the TSS that points to the new task. The process is this: first, the processor saves the field information for the current task to the TSS pointed to by the TR register, and then the TR register points to the TSS of the new task and resumes the site from the TSS of the newly-assigned task.
Note: The task Gate descriptor can be installed in the Interrupt Descriptor table, or can be installed in the GDT or the LDT.
Know the theoretical knowledge, the above code is not difficult to analyze, the key is the new task of the TSS select sub-assignment to%1 is *&_tmp.b, now the value of B is the TSS selector, note here ljmp%0 is equivalent to ljmp *%0, said to be indirect jump, equivalent to "ljmp *__tmp.a ", that is, jump to the 48bit logical address contained in address &__tmp.a. By the definition of struct _tmp, this means that __TMP.A is the offset portion of the logical address, __tmp.b the low 16bit is seg_selector (high 16bit useless) part.
Until this line of command is finished, the real task switch is counted! This concludes the process scheduling analysis.
Linux0.11 kernel--Process scheduling analysis 2. Scheduling