Minix3 process scheduling Analysis
5.1minix3 process scheduling Overview
The process scheduling of minix3 is still very simple, and the scheduling algorithm is very short. Its purpose is to reflect a simple and efficient design principle. Of course, simplicity and efficiency are difficult to coexist. On the whole, is a multi-queue scheduling algorithm, which is placed in the corresponding position according to the priority.
Now let's take a look at the entire figure: (the figure below is actually the figure shown when the user has not created a process during the minix3 initialization.) Level 1 is considered as the system layer with the highest level, it is a system and clock task, and the next layer is tty (a terminal process, a process used in I/O, this analysis document is ignored for the time being), and so on, to run it up, at Layer 3, it is also the process with the lowest scheduling priority. It is called idle_q. This process is actually a NOP operation, that is, a null operation, and does not do anything. This process can only be run when all processes in a process cannot run. Generally, this possibility is very small unless the system fails.
Now let's go into an in-depth analysis and analyze a structural diagram of the entire scheduling. Now let's take a look at what
Will the scheduling program be started? First, minix3 is a time-based operating system,
Time-sharing 'OS, that is, when a clock is interrupted, it determines whether the user process is exhausted.
75
Slice. If the time slice is used up, we will start the scheduling algorithm to complete the scheduling. It can be seen that this scheduler is actually a small program attached to a clock task, and the scheduling algorithm does not exist separately as a process. This is very important.
Next, let's think about how to handle the problem if the current process is in a blocking or suspended state? We will also enter the kernel to complete related scheduling.
5.2 minix3 process scheduling source code analysis
This is basically the two cases above. We will analyze the source code internally ,!
Before analysis, we should first look at a data structure involved in scheduling. The data structure is the one above. Now let's look at the specific content of the data structure:
Extern struct proc * rdy_head [nr_sched_queues];/* ptrs to ready list headers */
Extern struct proc * rdy_tail [nr_sched_queues];/* ptrs to ready list tails */,
This is two pointer arrays, that is, the two sides of the graph. The rdy_head [] array is the opposite of the scheduling queue,
Rdy_tail [] is the end of the scheduling queue. The queue here is not a queue in the general sense, it is a deformation of the queue, the head and end of the team are directed to the process, such processing is to insert high efficiency. The efficiency is not analyzed here.
Let's continue to look at the following. Now we can see which domains connect these processes,
Struct proc * p_nextready;/* pointer to next ready process */
This domain points to the next process.
Continue to analyze the table entry fields related to the Scheduling Algorithm
Char p_priority;
// Current process priority
Char p_max_priority;
// Maximum priority of the scheduling queue
Char p_ticks_left;
// How many remaining clocks are there in the process?
Char p_quantum_size;
// Number of clocks in the process
Clock_t p_user_time;
// User time
Clock_t p_sys_time;
// System time
/* Current scheduling priority */
/* Maximum scheduling priority */
/* Number of scheduling ticks left */
/* Quantum size in ticks */
/* User time in ticks */
/* Sys time in ticks */
Now we analyze the source code, because there are few scheduling Source Code involved, mainly involved In Proc. C and restart. C. Analyze Proc. C first
Check the two functions and the external scheduling interface functions of the process: enqueue () and dequeue () functions, and lock_enqueue () scheduling functions.
The sched () and pick_proc () functions are provided to enqueue () and
76
Use the dequeue () function.
/* ===================================================== ===================================================== *
* Enqueue *
* ===================================================== ==================================
======= */
Private void enqueue (RP)
Register struct proc * RP;/* this process is now runnable */
{
/* Add 'rp 'to one of the queues of runnable processes. This function
Is responsible for inserting a process into one of the scheduling queues.
* The mechanism is implemented here. The actual scheduling policy is
* Defined in sched () and pick_proc ().
Add 'rp 'to one of the running process queues. This function is mainly responsible for inserting a process
77
Scheduling queue. This mechanism is implemented here. In fact, the true scheduling policy is defined in the sched () and pick_proc () functions.
*/
Int Q;/* scheduling queue to use */
Int front;/* Add to front or back */
# If debug_sched_check
Check_runqueues ("enqueue ");
If (RP-> p_ready) kprintf ("enqueue () already ready process \ n"); # endif
/* Determine where to insert to process .*/
This function is introduced below to determine which process to insert.
Sched (RP, & Q, & Front );
/* Now add the process to the queue .*/
// Now add the process to the queue. Q indicates the queue number, which is added by the previous sched () function.
If (rdy_head [Q] = nil_proc) {/* Add to empty queue */
// As you can see from the previous function, Q is the desired running queue.
// Process, you must create a new queue and send the process to the process for use.
Rdy_head [Q] = rdy_tail [Q] = RP;/* Create a New queue */
RP-> p_nextready = nil_proc;/* mark new end */
}
// If the queue already has a process, we need to proceed with it carefully.
// If there is time remaining for the process, add the process to the header of the team list
Else if (Front) {/* Add to head of queue */
RP-> p_nextready = rdy_head [Q];/* chain head of queue */
Rdy_head [Q] = RP;/* set new queue head */
}
// If the time is used up, it should be added to the end of the team list.
Else {/* Add to tail of queue */
Rdy_tail [Q]-> p_nextready = RP;/* chain tail of queue */
Rdy_tail [Q] = RP;/* set new queue tail */
RP-> p_nextready = nil_proc;/* mark new end */
}
/* Now select the next process to run. */select the next process to run.
Pick_proc ();
# If debug_sched_check
RP-> p_ready = 1;
78
Check_runqueues ("enqueue ");
# Endif
}
/* ===================================================== ===================================================== *
* Sched: This function and the next function are the main functions of the minix scheduler.
* Content: This function is used to calculate who should be scheduled, and then set the corresponding value in the kernel.
Another function, restart, is actually transferred from the hardware.
* ===================================================== ============================================= */
Private void sched (RP, queue, front)
Register struct proc * RP;
// Scheduled Process
Int * queue;
// Return value 1: The used queue number int * front;
// The former or the latter
{
/* Process to be scheduled */
/* Return: queue to use */
/* Return: Front or back */
/* This function determines the scheduling policy. It is called whenever
A process must be added to one of the scheduling queues to decide where
To insert it. As a side-effect the process 'priority may be updated.
This function determines the scheduling policy. This function is called to determine that a process must be added to the scheduling queue.
And decide where to insert the process. One negative effect is that the priority of the process must be changed.
Change.
*/
Static struct proc * prev_ptr = nil_proc;/* Previous without time */
Int time_left = (RP-> p_ticks_left> 0);/* quantum fully consumed */
// This variable determines whether it is consumed.
Int penalty = 0;/* Change in priority */
// This variable is used to change the priority of a process.
/* Check whether the process has time left. otherwise give a new quantum
* And possibly raise the priority. processes using multiple quantums
* In a row get a lower priority to catch infinite loops in high priority
* Processes (system servers and drivers ).
Check whether the process has time remaining. If there is no time remaining, a new value is provided and the priority of the process is displayed. Processes that are using multi-value values (in the array) Get a lower priority to capture the infinite loop of this high-priority process.
*/
// If time_left = 0, that is, if the time is used up, enter the IF.
If (! Time_left) {/* quantum consumed? */
// Assign a value to the process again
79
RP-> p_ticks_left = RP-> p_quantum_size;/* give new quantum */
// If the previous process is RP, you are ready to capture this infinite loop. However, you must reduce the priority of this process by one, that is, add the penalty value to 1.
If (prev_ptr = RP) Penalty ++;/* Catch infinite loops */
Else penalty --;/* give slow way back */
// Run the prev_ptr RP here, mainly to prepare for the following Scheduling
Prev_ptr = RP;/* store PTR for next */
}
/* Determine the new priority of this process. The bounds are determined
* By idle's queue and the maximum priority of this process. kernel task
* And the idle process are never changed in priority.
Determines the new priority of the process. The boundary is determined by the maximum priority of the idle and the process. Kernel tasks and idle processes never change their priorities.
*/
// If the penalty value is not 0 and the RP is not a kernel process, the priority can be changed.
// The method for changing is simple: add the priority of the process to the penalty value. Check whether the threshold value exceeds the upper limit and lower priority.
If (Penalty! = 0 &&! Iskernelp (RP )){
RP-> p_priority + = penalty;/* update with penalty */
If (RP-> p_priority <RP-> p_max_priority)
RP-> p_priority = RP-> p_max_priority;
Else if (RP-> p_priority> IDLE_Q-1)
RP-> p_priority = IDLE_Q-1;
}
/* Check upper bound */
/* Check lower bound */
/* If there is time left, the process is added to the front of its queue, * so that it can immediately run. the queue to use simply is always the process 'current priority.
If there is time remaining, the process is added to the frontend of its queue. So it can be put into operation immediately. This queue always uses the current priority of a simple process.
*/
* Queue = RP-> p_priority;
* Front = time_left;
}
/* ===================================================== ===================================================== *
* The pick_proc function can be seen literally: This is an option.
. *
* ===================================================== ============================================= */
Private void pick_proc ()
80
{
/* Decide who to run now. A new process is selected by setting 'Next _ PTR '. * When a billable process is selected, record it in 'bill _ PTR ', so that the clock task can tell who to bill for system time.
Decide who is running it now. A new process is selected for processing by setting it to the point pointed by next_ptr. When a payment process is selected, it is recorded in bill_ptr. So the system task can tell who pays for the system time.
*/
Register struct proc * RP;/* process to run */
Int Q;/* iterate over queues */
/* Check each of the scheduling queues for ready processes. The number of queues is defined in Proc. H, and priorities are set in the task table.
* The lowest queue contains idle, which is always ready.
Check each scheduling queue for each process being prepared. The number of queues is defined in Proc. h file.
And the priority is set in the task progress table. The lowest incoming queue includes the idle process, which is always in the ready queue.
*/
// This function is mainly used to scan from low-priority to high-priority. If only the queue header of one queue is not // nil_proc, set next_ptr to the process that points to it ----- Rp. Next_ptr will be used by the // restart function later.
For (q = 0; q <nr_sched_queues; q ++ ){
If (Rp = rdy_head [Q])! = Nil_proc ){
Next_ptr = RP;/* Run process 'rp 'Next */
If (priv (RP)-> s_flags & billable)
Bill_ptr = RP;
Return;
}
}
}
/* Bill for system time */
/* ===================================================== ===================================================== *
* Dequeue remove a running process from the scheduling queue
*
* ===================================================== ============================================= */
Private void dequeue (RP)
Register struct proc * RP;/* this process is no longer runnable */
{
/* A process must be removed from the scheduling queues, for example, because it has blocked. if the currently active process is removed, a new process is picked to run by calling pick_proc ().
81
A process must be removed from the scheduling queue. For example, it is blocked. If the current
The active process is removed and a new process needs to be selected to run. by calling the pick_proc () function
*/
Register int q = RP-> p_priority;/* queue to use */
Register struct proc ** xpp;/* iterate over queue */
Register struct proc * prev_xp;
/* Side-effect for kernel: check if the task's tack still is OK? * /// Check whether the process is a kernel process. If it is a kernel process, perform corresponding processing.
If (iskernelp (RP )){
If (* priv (RP)-> s_stack_guard! = Stack_guard)
Panic ("stack overrun by task", proc_nr (RP ));
}
# If debug_sched_check
Check_runqueues ("dequeue ");
If (! RP-> p_ready) kprintf ("dequeue () already unready Process \ n"); # endif
/* Now make sure that the process is not in its ready queue. Remove
* Process if it is found. A process can be made unready even if it is
Not running by being sent a signal that kills it.
Now we make sure that this process is not in its preparation queue. If this process is found, it will be removed. If a process is not in the running queue and a signal is sent to kill the process, the process can be removed from the waiting status.
*/
// The above comments seem to be difficult, so we can see the code below.
//
Prev_xp = nil_proc;
// Scan the Q queue, find the process to be found, and remove it from the ready queue.
For (xpp = & rdy_head [Q]; * xpp! = Nil_proc; xpp = & (* xpp)-> p_nextready)
{
// Find the process to be removed and we will enter the if
If (* xpp = RP) {/* found process to remove */
// If it is found, replace it to replace the process. This statement is implemented in combination with the last statement of this loop.
//, Pay attention to the most essential content. The removal here is only to bypass the scheduling queue. And need
The process to be removed will not be processed.
* Xpp = (* xpp)-> p_nextready;/* replace with next chain */
// If the RP is an end-to-end queue, point the end of the end-to-end queue to the previous one that has been stored in the search linked list.
Table item.
If (Rp = rdy_tail [Q])/* queue tail removed */
Rdy_tail [Q] = prev_xp;/* set new tail */
If (Rp = proc_ptr | Rp = next_ptr)/* active process
82
Removed */
// If the RP is a currently active process, call the pick_proc () function and reselect a process. //
Pick_proc ();/* pick new process to run */
Break;
}
Prev_xp = * xpp;/* save previous in chain */
// Store the previous table item of the linked list to be searched, and use it with the preceding statement to remove the team from the process.
Column
}
# If debug_sched_check
RP-> p_ready = 0;
Check_runqueues ("dequeue ");
# Endif
}
/* ===================================================== ===================================================== *
* Lock_enqueue *
* ===================================================== ==================================
======= */
Public void lock_enqueue (RP)
Struct proc * RP;/* this process is now runnable */
{
/* Safe Gateway to enqueue () for tasks .*/
Lock (3, "enqueue ");
Enqueue (RP );
Unlock (3 );
}
/* ===================================================== ===================================================== *
* Lock_dequeue *
* ===================================================== ==================================
======= */
Public void lock_dequeue (RP)
Struct proc * RP;/* this process is no longer runnable */
{
/* Safe Gateway to dequeue () for tasks .*/
Lock (4, "dequeue ");
Dequeue (RP );
83
Unlock (4 );
}
5.3 combination of minix3 process scheduling and interrupt return Processing
Now let's review the restart function mentioned in the interrupt system and further analyze the entire scheduling mechanism with the kernel scheduling instance.
First look at the restart function. Similarly, first attach the source code of the restart function:
! * ===================================================== =================================================== *
! * Restart: we enter this function from hwint_inhandler (IRQ ).
To analyze this function! Before analysis, we also need to understand the inside of the stack and related registers.
First, stack or kernel stack
Then CS and IP point to the first address of this function. Now we will go to the function for internal analysis.
*
! * ===================================================== =================================================== *
_ Restart:
! Restart the current process or the next process if it is set.
!! This is very important because it is mainly used for process scheduling.
CMP (_ next_ptr), 0! See if another process is scheduled, here _ next_ptr
The address of the process to which the restart operation points. Actually
JZ 0f
MoV eax, (_ next_ptr )! If it is not null, the execution flow starts from here.
! Save the value of _ next_ptr to eax, and then set the value of eax to the memory address of _ proc_ptr. pro_ptr points to the current process pointer. These pointers are already set, this pointer is used for the next return operation.
MoV (_ proc_ptr), eax! Schedule New Process
MoV (_ next_ptr), 0! The main reason for setting _ next_ptr to NULL is
Log will be executed next time
! Whether the above two pointers are reset during the restart routine. If not, enter the 0 flag directly. This is based on the highest code efficiency.
In this example, the next_ptr pointer is set. The pick_proc () function sets next_ptr,
Is used here. There is a reason for comparing the _ next_ptr and null values. You can imagine,
If a non-clock interruption occurs, the current process may be used as the object for the next scheduling, instead of the clock interruption.
We may not call the scheduling algorithm, so we will continue to use this process directly. And
Here _ next_ptr is a pivot of the above scheduling algorithm function and the restart. The preceding scheduling Calculation
The method function selects the _ next_ptr function. Now the _ restart function returns the _ next_ptr
Function is in use.
84
5.4minix3 relationship between time scheduling and other Kernel Modules
Where can the scheduling process scheduling function appear? The scheduling queue may be called when the clock is interrupted. Later, the relevant message mechanism will also call this scheduling queue:
Two images are used for illustration:
The first is that the clock mechanism may trigger this scheduling algorithm function:
The message mechanism triggers the scheduling process as follows:
85
The scheduling Analysis of minix3 is basically complete. For minix3, the process scheduling algorithm is simple and the effectiveness is easy to understand!
Minix3 process scheduling Analysis