After analyzing the 2.6.29 kernel scheduling program, it took a lot of time, but I still don't think it is very clear, and some of it may be incorrect.
Process Scheduling Analysis
-- Kernel source code 2.6.29
Reason: Linux is a large, efficient, and complex operating system. Its kernel includes five parts: process scheduling, memory management, inter-process communication, Virtual File System, and network interface, process Scheduling is the core of a multi-task operating system.
1. Timing of Process Scheduling
Process status conversion
Current process time slice used up
When the device driver is running
From kernel state to user State
Returned from interruptions or system calls;
Process Re-allows preemption;
Active sleep (for example, wait_event_interruptible () Interface)
2. Basis for Process Scheduling
When a scheduler is running, select the most worthwhile processes from all processes that are in the running state to run. There is a basis for selection.
1. Policy)
Sched_normal (sched_other): Common time-sharing process
Sched_fifo: first enters the Advanced Real-Time Process
Sched_rr: Real-time process of time slice Rotation
Sched_batch: the batch processing process, which is similar to the normal process that is not preemptible.
Sched_idle:
The scheduling policy is defined as follows in Linux:
# Define sched_normal 0
# Define sched_fifo 1
# Define sched_rr 2
# Define sched_batch 3
# Define sched_idle 5
2. CFs Scheduler
(1) CFS scheduler is used in Linux 2.9.26. CFS is completely fair scheduler, Which is Ingo Molnar.
A new desktop process scheduler is added to Linux 2.6.23. It can be said that CFS basically simulates an ideal and precise multi-task CPU on real-time hardware.
(2) In CFS, the virtual running time is expressed and tracked by the P-> Se. vruntime value of each task (Per-task. In this way, you can determine the timestamp and the expected CPU time of a task. (On an ideal hardware, all tasks should have the same p-> Se. vruntime value at any time ). The selection of CFS is also very simple. Always select P-> Se. vruntime to run the task first. It always splits the CPU time between runable tasks and tries its best to keep it close to the ideal multitasking hardware ".
(3) CFS does not use the original data structure of runqueue. It uses the red-black tree sorted by time to construct a timeline for future task execution ). CFS contains RQ-> CFS. min_vruntime is a monotonically increasing value that tracks the minimum virtual running time in the running queue. As the system runs, min_vruntime is used to place the newly activated process on the left of the tree as much as possible.
(4) the total number of running tasks is calculated using RQ-> CFs. Load. The P-> Se. vruntime value increases until the "leftmost task" in the red/black tree of a task in the tree ". At this time, the new leftmost task is selected and put into operation.
Latency is special. CFs uses nanosecond timing and does not rely on any jiffies or Hz. Therefore, CFS does not have the concept of timeslices and does not have any form of detection.
3. Process Scheduling Algorithm
The scheduling algorithm of Linux 2.6.29 kernel adopts the red-black tree algorithm. The red-black tree is a type of balanced binary tree. It has a good nature. The nodes in the tree are ordered, and because it is a balance, the search will not be very bad, the time complexity of binary tree-based operations is O (log (n )).
4. Main Data Structure
1. run queue RQ
Struct RQ {
Spinlock_t lock; // protects the spin lock of the process linked list (runqueue ).
Unsigned long nr_running; // number of processes that can be run in the running queue linked list
Unsigned long cpu_load [cpu_load_idx_max]; // CPU load factor based on the average number of runqueue Processes
Unsigned char idle_at_tick; // idle
Unsigned long last_tick_seen;
Unsigned char in_nohz_recently;
Struct load_weight load; // The task that loads on the local CPU
Unsigned long nr_load_updates; // Number of server load updates
U64 nr_switches; // number of times the CPU executes the process Switch
Struct cfs_rq CFS; // pointer to the structure of the CFS running queue
Struct rt_rq RT; // pointer to a real-time process
Struct list_head leaf_cfs_rq_list; // Add the CFS leaf to the local CPU
Struct list_head leaf_rt_rq_list; // Add real-time leaves to the local CPU
Unsigned long nr_uninterruptible; // Number of uninterruptible Processes
Struct task_struct * curr, * idle; // pointer to the current process and idle process unsigned long next_balance; // The next balance
Struct mm_struct * prev_mm; // pointer to the mm of the current process
U64 clock; // Number of clocks
Atomic_t nr_iowait; // number of processes that have been in the runqueue but are currently waiting for the I/O operation to complete
Struct root_domain * RD; // Root Domain
Struct sched_domain * SD; // point to the basic scheduling domain of the current CPU
Int active_balance; // indicates that some processes will be transferred from one local running queue to another.
Int push_cpu; // not used
Int CPU; // The CPU number corresponding to the running queue
Int online;
Unsigned long avg_load_per_task; Average number of load tasks
Struct task_struct * migration_thread; // process descriptor pointer of the migration kernel thread
Struct list_head migration_queue; // list of processes deleted from the running queue
}
2. Add part in task_struct
Int Prio, static_prio, normal_prio; // process priority
Unsigned int rt_priority; // priority of the real-time process
Struct list_head children; // linked list pointing to a child node
Struct list_head sibling; // linked list of Children on the same parent node
Struct sched_entity * parent; // pointer to the scheduler entry
Struct cfs_rq * cfs_rq; // pointer to the running queue
Struct cfs_rq * my_q; // pointer to the group of the (CFS) Running queue
5. Implementation of the scheduling function schedule ()
The schedule () function implements the scheduler. Its task is to find a process from the linked list of the running queue and allocate the CPU to the process. It can be called by Multiple kernel control methods, and can be called directly or delayed.
Several important variables during implementation:
RQ-> cur: global variable, indicating the currently running process
PREV: local variable, indicating the process executed before scheduling, used to save the current value
Next: local variable, indicating the process to be executed after scheduling.
The key operation of the Schedule () function is to set the next local variable so that it points to the selected process, which replaces the current process. If the system does not have a runable process with a higher priority than the current process, the next and current processes are eventually equal, and no process switchover will occur.
First, the schedule () function disables kernel preemption and initializes some local variables:
Need_resched:
Preempt_disable (); // disable kernel preemption
CPU = smp_processor_id (); // obtain the ID of the current CPU and assign it to the local variable CPU
RQ = cpu_rq (CPU); // point RQ to the running queue corresponding to the CPU (runqueue)
Rcu_qsctr_inc (CPU );
Prev = RQ-> curr; // point the prev to the currently executed process.
Switch_count = & Prev-> nivcsw; // record the switching times
Release_kernel_lock (prev); // release the large kernel lock
Need_resched_nonpreemptible:
Schedule_debug (prev); // If kernel preemption is disabled and cond_resched is called, an error occurs. This is used to capture the error.
Next, close the local interrupt and obtain the spin lock of the runqueue to be protected to prepare for the search for the runable process.
Spin_lock_irq (& RQ-> lock); // get the spin lock
Update_rq_clock (RQ); // update the protected runqueue lock
Clear_tsk_need_resched (prev); // indicates the tif_need_resched flag.
Next, check the prev status. If the prev process is not runable and is not preemptible in the kernel state, you should delete the prev process from the running queue. However, if it is a non-blocking pending signal and the status is task_interruptible, the function sets the status of the process to task_running and inserts it into the running queue. This operation is different from assigning a processor to Prev. It only gives Prev a chance to select and execute it.
If (prev-> State &&! (Preempt_count () & preempt_active )){
If (unlikely (signal_pending_state (prev-> state, Prev) // The process is not blocked, and the status is task_interruptible.
Prev-> state = task_running; // set the status to task_running.
Else
Deactivate_task (RQ, Prev, 1); // Delete the process from the running queue
Switch_count = & Prev-> nvcsw;
}
If no running process exists in the running queue, the function calls idle_balance () to migrate some processes from another running queue to the local running queue.
If (unlikely (! RQ-> nr_running ))
Idle_balance (CPU, rq );
If Prev and next are not the same process, exchange them.
If (likely (prev! = NEXT )){
Sched_info_switch (prev, next );
RQ-> nr_switches ++;
RQ-> curr = next;
+ * Switch_count;
Context_switch (RQ, Prev, next);/* unlocks the RQ */
// Create an address space for next. If next is a kernel thread, it uses the address space used by perv. If next is a common process, replace the prev address space with the next address space. Space swap may flip the bottom of the stack to update local variables.
CPU = smp_processor_id (); // update a local variable
RQ = cpu_rq (CPU );
} Else // If Prev and next are in the same process, no switchover is performed.
Spin_unlock_irq (& RQ-> lock); // release the optional lock of the interrupt request and accept the interrupt request
Finally, schedule () re-acquires the large kernel lock as needed, re-enables kernel preemption, and checks whether some other processes have set the current tif_need_resched flag. If yes, the entire schedule () function is re-executed. Otherwise, the function ends.
If (unlikely (reacquire_kernel_lock (current) <0) // the current process occupies a large kernel lock
Goto need_resched_nonpreemptible;
Preempt_enable_no_resched ();
If (unlikely (test_thread_flag (tif_need_resched) // The process has set the tif_need_resched flag of the current process.
Goto need_resched;