1966 /* Here we just switch the register state and the stack. */1967 switch_to(prev, next, prev);1968 1969 barrier();
The Linux kernel process scheduler is based on two functions: The periodic scheduler function and the main scheduler function.
Periodic Scheduler
The so-called periodic scheduler is implemented in scheduler_tick. If the system is active, the kernel automatically calls this function according to Hz. In fact, this function is called in the handler of each tick answer. If no process is waiting for scheduling, you can disable the scheduler to reduce power consumption when the computer power supply is insufficient. This function activates the periodic Scheduling Method for the scheduling class of the current process.
if (curr != rq->idle) /* FIXME: needed? */ curr->sched_class->task_tick(rq, curr);
Because of the modular structure of the scheduler, it is easy to implement the scheduler itself, because the main work can be delegated to a specific scheduler class.
The implementation of task_tick relies entirely on the underlying scheduler class. For example, the CFS scheduler class checks whether the process has been running for too long in the method to avoid too long delay. If scheduling is required, the set_tsk_need_resched function is called to set the tif_need_resched flag to indicate the request.
Master Scheduler
In many parts of the kernel, if you need to allocate the CPU to another process with different active processes, the main scheduler function schedule will be called directly. After returning from the system call, the kernel will check whether the current process has set a rescheduling flag tif_need_resched flag (for example, scheduler_tick mentioned above may set this flag). If this flag is set, call the schedule function.
Let's take a look at the implementation of schedule.
3616 /*3617 * schedule() is the main scheduler function.3618 */3619 asmlinkage void __sched schedule(void)3620 {3621 struct task_struct *prev, *next;3622 long *switch_count;3623 struct rq *rq;3624 int cpu;3625 3626 need_resched:3627 preempt_disable();3628 cpu = smp_processor_id();3629 rq = cpu_rq(cpu);3630 rcu_qsctr_inc(cpu);3631 prev = rq->curr;3632 switch_count = &prev->nivcsw;
3630 obtain the currently running process and save it in Prev.
3639 /*3640 * Do the rq-clock update outside the rq lock:3641 */3642 local_irq_disable();3643 __update_rq_clock(rq);3644 spin_lock(&rq->lock);3645 clear_tsk_need_resched(prev);
3643 update RQ-> prev_clock_raw and RQ-> clock
3645 clear the tif_need_sched flag
3647 if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {3648 if (unlikely((prev->state & TASK_INTERRUPTIBLE) &&3649 unlikely(signal_pending(prev)))) {3650 prev->state = TASK_RUNNING;3651 } else {3652 deactivate_task(rq, prev, 1);3653 }3654 switch_count = &prev->nvcsw;3655 }
If the current process is in an interrupted sleep state, it must be upgraded to a running process again. Otherwise, call deactivate_task to stop the process.
3660 prev->sched_class->put_prev_task(rq, prev);3661 next = pick_next_task(rq, prev);
The current process of the put_prev_task notification scheduler class that calls the scheduler class should be replaced by another process. This operation does not mean that the prev is removed from the ready queue, but provides an opportunity to perform some accounting work.
Pick_next_task: select the next process to be executed. Note that the newly selected process may be the original process, for example, if there is only one process in the ready queue.
3665 if (likely(prev != next)) {3666 rq->nr_switches++;3667 rq->curr = next;3668 ++*switch_count;3669 3670 context_switch(rq, prev, next); /* unlocks the rq */3671 } else3672 spin_unlock_irq(&rq->lock);
If the new process is not the same as the old process, we call context_switch to switch the process context.
Context switching
1929 static inline void1930 context_switch(struct rq *rq, struct task_struct *prev,1931 struct task_struct *next)1932 {............... 1945 if (unlikely(!mm)) {1946 next->active_mm = oldmm;1947 atomic_inc(&oldmm->mm_count);1948 enter_lazy_tlb(oldmm, next);1949 } else1950 switch_mm(oldmm, mm, next);
Switch_mm is an architecture-specific function. The global directory is displayed to install a new address space. For the ARM platform, it is to set the TTB register of the CP15 coprocessor as the new PGD; for x86, it is to set the new PGD as the new PGD. Someone may ask, if the new PGD is switched here, will the code execution be discontinuous? It doesn't matter, because it is now the kernel space, the page ing of the kernel address space will not change with PGD.
1966 /* Here we just switch the register state and the stack. */1967 switch_to(prev, next, prev);1968 1969 barrier();1970 /*1971 * this_rq must be evaluated again because prev may have moved1972 * CPUs since it called schedule(), thus the 'rq' on its stack1973 * frame will be invalid.1974 */1975 finish_task_switch(this_rq(), prev);1976 }
The hardware context for process switching is the shared CPU register. During the process switching, the hardware context is stored in the task_struct-> thread field. Note that the thread is of a specific architecture, therefore, not every architecture needs to save registers to this structure. The code after swtich is executed only when the current process is selected for the next execution.
Barrier ensures that switch_to and finish_task_switch are not executed in disorder.