Linux 进程调度之schdule主调度器

考虑到文章篇幅,在这里我只讨论普通进程,其调度算法采用的是cfs(完全公平)调度算法。 至于cfs调度算法的实现后面后专门写一篇文章,这里只要记住调度时选择一个优先级最高的任务执行

一、调度单位简介1.1 task_struct 结构体简介

对于Linux内核来说,调度的基本单位是任务,用 struct task_struct 表示,定义在include/linux/sched.h文件中,这个结构体包含一个任务的所有信息,结构体成员很多,在这里我只列出与本章内容有关的成员:

代码语言:javascript代码运行次数:0运行复制

struct task_struct {......(1)volatile long state;(2)const struct sched_class *sched_class;(3)void *stack;struct thread_struct thread;struct mm_struct *mm, *active_mm;......}

(1)state :表示任务当前的状态,当state为TASK_RUNNING时,表示任务处于可运行的状态,并不一定表示目前正在占有CPU,也许在等待调度,调度器只会选择在该状态下的任务进行调度。该状态确保任务可以立即运行,而不需要等待外部事件。

简单来说就是:任务调度的对象是处于TASK_RUNNING状态的任务。

处于TASK_RUNNING状态的任务,可能正在执行用户态代码,也可能正在执行内核态的代码。

(2)sched_class :表示任务所属的调度器类,我们这里只讲CFS调度类。

代码语言:javascript代码运行次数:0运行复制

//  kernel/sched/sched.hstruct sched_class {......//将任务加入可运行的队列中void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);//将任务移除可运行的队列中void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);//选择一下将要运行的任务struct task_struct * (*pick_next_task) (struct rq *rq,struct task_struct *prev,struct pin_cookie cookie);......}extern const struct sched_class fair_sched_class;

代码语言:javascript代码运行次数:0运行复制

//  kernel/sched/fair.cconst struct sched_class fair_sched_class;/* * All the scheduling class methods: */const struct sched_class fair_sched_class = {.......enqueue_task= enqueue_task_fair,    //CFS 的 enqueue_task 实例.dequeue_task= dequeue_task_fair,//CFS 的 dequeue_task 实例.pick_next_task= pick_next_task_fair,//CFS 的 pick_next_task 实例......}

(3)任务上下文:表示任务调度时要切换的任务上下文(任务切换只发生在内核态)。 stack:当前任务的内核态堆栈(用户态的sp,用户态的ip,在内核栈顶部的 pt_regs 结构里面) thread :也叫任务的硬件上下文,主要包含了大部分CPU寄存器(如:内核栈sp)。 struct mm_struct *mm :切换每个任务用户态的虚拟地址空间(每个任务的用户栈都是独立的,都在内存空间里面,切换任务的虚拟地址空间,也就切换了任务的用户栈)。

1.2 task_struct 结构体的产生

任务(task_struct) 的来源有三处: (1) fork():该函数是一个系统调用,可以复制一个现有的进程来创建一个全新的进程,产生一个 task_struct,然后调用wake_up_new_task()唤醒新的进程,使其进入TASK_RUNNING状态。 (2) pthread_create():该函数是Glibc中的函数,然后调用clone()系统调用创建一个线程(又叫轻量级进程),产生一个 task_struct,然后调用wake_up_new_task()唤醒新的线程,使其进入TASK_RUNNING状态。 (3)kthread_create():创建一个新的内核线程,产生一个 task_struct,然后wake_up_new_task(),唤醒新的内核线程,使其进入TASK_RUNNING状态。

其实这三个API最后都会调用 _do_fork(),不同之处是传入给 _do_fork() 的参数不同(clone_flags),最终结果就是进程有独立的地址空间和栈,而用户线程可以自己指定用户栈,地址空间和父进程共享,内核线程则只有和内核共享的同一个栈,同一个地址空间。因此上述三个API最终都会创建一个task_struct结构。

备注:这里没有讨论vfok()。

总结:上述三个方式都产生了一个任务 task_struct,然后唤醒该任务使其处于TASK_RUNNING状态,然后这样调度器就可以调度 任务(task_struct)了。

还有一个来源就是0号进程(又叫 idle 进程),每个逻辑处理器上都有一个,属于内核态线程,只有在没有其他的任务处于TASK_RUNNING状态时(系统此时处于空闲状态),任务调度器才会选择0号进程,然后重复执行 HLT 指令。 HLT 指令 :停止指令执行,并将处理器置于HALT状态。简单来说让该CPU进入休眠状态,低功耗状态。 (该指令只能在 privilege level 0执行,且CPU指的是逻辑CPU而不是物理CPU,每个逻辑CPU都有一个idle进程)。

Linux 进程调度之schdule主调度器

1.3 struct rq 结构体

目前的x86_64都有多个处理器,那么对于所有处于TASK_RUNNING状态的任务是应该位于一个队列还有每个处理器都有自己的队列? Linux采用的是每个CPU都有自己的运行队列,这样做的好处: (1)每个CPU在自己的运行队列上选择任务降低了竞争 (2)某个任务位于一个CPU的运行队列上,经过多次调度后,内核趋于选择相同的CPU执行该任务,那么上次任务运行的变量很可能仍然在这个CPU缓存上,提高运行效率。

在这里我只讨论普通任务的调度,因为linux大部分情况下都是在运行普通任务,普通任务选择的调度器是CFS完全调度。

在调度时,调度器去 CFS 运行队列找是否有任务需要运行。

代码语言:javascript代码运行次数:0运行复制

DEFINE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);

代码语言:javascript代码运行次数:0运行复制

struct rq {    .......unsigned int nr_running;   //运行队列上可运行任务的个数struct cfs_rq cfs;         // CFS 运行队列struct task_struct *curr,  //当前正在运行任务的 task_struct 实例struct task_struct *idle,  //指向idle任务的实例,在没有其它可运行任务的时候执行......}

二、schedule函数详解2.1 schedule函数简介

上文说到任务调度器是对于可运行状态(TASK_RUNNING)的任务进行调度,如果任务的状态不是TASK_RUNNING,那么该任务是不会被调度的。

调度的入口点是schedule()函数,定义在 kernel/sched/core.c 文件中,这里删掉了很多代码,只留了重要的代码:

代码语言:javascript代码运行次数:0运行复制

asmlinkage __visible void __sched schedule(void){......preempt_disable();//禁止内核抢占__schedule(false);//任务开始调度,调度的过程中禁止其它任务抢占sched_preempt_enable_no_resched();//开启内核抢占......}

这里注意schedule调用__schedule函数是传入的是false参数,表示schedule函数主调度器。 调度分为主动调度和抢占调度。

__schedule的参数preempt是bool类型,表示本次调度是否为抢占调度:

__schedule的参数preempt等于0表示不是抢占调度,即主动调度,代表此次调度是该进程主动请求调度,主动调用了schedule函数(任务主动让出处理器),比如该进程进入了阻塞态。 而schedule函数参数固定传入的参数是false,也就是0,就是调用schedule函数就是主动发起调度,不是抢占调度,因此schedule函数称为主调度器。

直接调用主调度器schdule函数的场景有3种: (1)当前进程需要等待某个条件满足才能继续运行时,调用一个wait_event()类函数将自己的状态设为TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE,挂接到某个等待队列,然后根据情况设置一个合适的唤醒定时器,最后调用schedule()发起调度;

代码语言:javascript代码运行次数:0运行复制

/* * The below macro ___wait_event() has an explicit shadow of the __ret * variable when used from the wait_event_*() macros. * * This is so that both can use the ___wait_cond_timeout() construct * to wrap the condition. * * The type inconsistency of the wait_event_*() __ret variable is also * on purpose; we use long where we can return timeout values and int * otherwise. */#define ___wait_event(wq, condition, state, exclusive, ret, cmd)({__label__ __out;wait_queue_t __wait;long __ret = ret;/* explicit shadow */init_wait_entry(&__wait, exclusive ? WQ_FLAG_EXCLUSIVE : 0);or (;;) {long __int = prepare_to_wait_event(&wq, &__wait, state);if (condition)reak;if (___wait_is_interruptible(state) && __int) {__ret = __int;goto __out;}cmd;}inish_wait(&wq, &__wait);__out:__ret;})#define __wait_event(wq, condition)(void)___wait_event(wq, condition, TASK_UNINTERRUPTIBLE, 0, 0,    schedule())/** * wait_event - sleep until a condition gets true * @wq: the waitqueue to wait on * @condition: a C expression for the event to wait for * * The process is put to sleep (TASK_UNINTERRUPTIBLE) until the * @condition evaluates to true. The @condition is checked each time * the waitqueue @wq is woken up. * * wake_up() has to be called after changing any variable that could * change the result of the wait condition. */#define wait_event(wq, condition)do {might_sleep();if (condition)reak;__wait_event(wq, condition);} while (0)

(2)当前进程需要睡眠一段特定的时间(不等待任何事件)时,调用一个sleep()类函数将自己的状态设为TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE但不进入任何等待队列,然后设置一个合适的唤醒定时器,最后调用schedule()发起调度;

(3)当前进程单纯地想要让出CPU控制权时,调用yield()函数将自己的状态设为TASK_RUNNING并依旧处于运行队列,然后执行特定调度类的yield_task()操作,最后调用schedule()发起自愿调度。

代码语言:javascript代码运行次数:0运行复制

/** * sys_sched_yield - yield the current processor to other threads. * * This function yields the current CPU to other tasks. If there are no * other threads running on this CPU then this function will return. * * Return: 0. */SYSCALL_DEFINE0(sched_yield){struct rq *rq = this_rq_lock();schedstat_inc(rq->yld_count);current->sched_class->yield_task(rq);/* * Since we are going to call schedule() anyway, there's * no need to preempt or enable interrupts: */__release(rq->lock);spin_release(&rq->lock.dep_map, 1, _THIS_IP_);do_raw_spin_unlock(&rq->lock);sched_preempt_enable_no_resched();schedule();return 0;}

__schedule的参数preempt等于1表示是抢占调度,有处于运行态的任务发起的抢占调度。 例举几处发起抢占调度的地方:

代码语言:javascript代码运行次数:0运行复制

static void __sched notrace preempt_schedule_common(void){do {/* * Because the function tracer can trace preempt_count_sub() * and it also uses preempt_enable/disable_notrace(), if * NEED_RESCHED is set, the preempt_enable_notrace() called * by the function tracer will call this function again and * cause infinite recursion. * * Preemption must be disabled here before the function * tracer can trace. Break up preempt_disable() into two * calls. One to disable preemption without fear of being * traced. The other to still record the preemption latency, * which can also be traced by the function tracer. */preempt_disable_notrace();preempt_latency_start(1);//抢占调度__schedule(true);preempt_latency_stop(1);preempt_enable_no_resched_notrace();/* * Check again in case we missed a preemption opportunity * between schedule and now. */} while (need_resched());}#ifdef CONFIG_PREEMPT/* * this is the entry point to schedule() from in-kernel preemption * off of preempt_enable. Kernel preemptions off return from interrupt * occur there and call schedule directly. */asmlinkage __visible void __sched notrace preempt_schedule(void){/* * If there is a non-zero preempt_count or interrupts are disabled, * we do not want to preempt the current task. Just return.. */if (likely(!preemptible()))return;preempt_schedule_common();}NOKPROBE_SYMBOL(preempt_schedule);EXPORT_SYMBOL(preempt_schedule);/** * preempt_schedule_notrace - preempt_schedule called by tracing * * The tracing infrastructure uses preempt_enable_notrace to prevent * recursion and tracing preempt enabling caused by the tracing * infrastructure itself. But as tracing can happen in areas coming * from userspace or just about to enter userspace, a preempt enable * can occur before user_exit() is called. This will cause the scheduler * to be called when the system is still in usermode. * * To prevent this, the preempt_enable_notrace will use this function * instead of preempt_schedule() to exit user context if needed before * calling the scheduler. */asmlinkage __visible void __sched notrace preempt_schedule_notrace(void){enum ctx_state prev_ctx;if (likely(!preemptible()))return;do {/* * Because the function tracer can trace preempt_count_sub() * and it also uses preempt_enable/disable_notrace(), if * NEED_RESCHED is set, the preempt_enable_notrace() called * by the function tracer will call this function again and * cause infinite recursion. * * Preemption must be disabled here before the function * tracer can trace. Break up preempt_disable() into two * calls. One to disable preemption without fear of being * traced. The other to still record the preemption latency, * which can also be traced by the function tracer. */preempt_disable_notrace();preempt_latency_start(1);/* * Needs preempt disabled in case user_exit() is traced * and the tracer calls preempt_enable_notrace() causing * an infinite recursion. */prev_ctx = exception_enter();//抢占调度__schedule(true);exception_exit(prev_ctx);preempt_latency_stop(1);preempt_enable_no_resched_notrace();} while (need_resched());}EXPORT_SYMBOL_GPL(preempt_schedule_notrace);#endif /* CONFIG_PREEMPT *//* * this is the entry point to schedule() from kernel preemption * off of irq context. * Note, that this is called and return with irqs disabled. This will * protect us against recursive calling from irq. */asmlinkage __visible void __sched preempt_schedule_irq(void){enum ctx_state prev_state;/* Catch callers which need to be fixed */BUG_ON(preempt_count() || !irqs_disabled());prev_state = exception_enter();do {preempt_disable();local_irq_enable();//抢占调度__schedule(true);local_irq_disable();sched_preempt_enable_no_resched();} while (need_resched());exception_exit(prev_state);}

__schedule()是主调度器的主要函数,__schedule在内核源码中有很多注释,如下所示: 驱使调度器并因此进入此函数的主要方法有: 1.显式阻塞:互斥、信号量、等待队列等。 2.中断和用户空间返回路径上检查TIF_NEED_RESCHED标志。例如,请参考arch/x86/entry_64.S。 为了驱动任务之间的抢占,调度程序在定时器中断处理程序scheduler_tick()中设置标志。 3.唤醒不会真正马上调用schedule(),只是将一个任务添加到运行队列中,设置任务标志位为TIF_NED_RESCHED,也就是将唤醒的进程加入的CFS就绪队列中(将唤醒的进程调度实体加入到红黑树中),仅此而已。

现在,如果添加到运行队列的新任务抢占了当前任务,则设置TIF_NED_RESCHED,并在以下的可能情况下调用schedule(),也就是唤醒的进程什么时候调用schedule()函数,分为以下两种情况:

(1)如果内核可抢占(CONFIG_PREMPT=y): 在系统调用或异常上下文中,在下一次调用preempt_enable()时检查是否需要抢占调度。 在IRQ上下文中,从中断处理程序返回到可抢占上下文。硬件中断处理返回前会检查是否要抢占当前进程。

(2)如果内核不可抢占(未设置CONFIG_PREMPT) 调用cond_resched()。 显式调用schedule()。 从syscall或异常返回到用户空间。 从中断处理程序返回到用户空间。

2.2 __schedule 代码解图代码语言:javascript代码运行次数:0运行复制

//__schedule() is the main scheduler function.static void __sched notrace __schedule(bool preempt){    (1)struct task_struct *prev, *next;unsigned long *switch_count;struct pin_cookie cookie;struct rq *rq;int cpu;    (2)cpu = smp_processor_id();rq = cpu_rq(cpu);prev = rq->curr;(3)if (!preempt && prev->state) {if (unlikely(signal_pending_state(prev->state, prev))) {prev->state = TASK_RUNNING;} else {deactivate_task(rq, prev, DEQUEUE_SLEEP);prev->on_rq = 0;}}(4)next = pick_next_task(rq, prev, cookie);clear_tsk_need_resched(prev);clear_preempt_need_resched();(5)if (likely(prev != next)) {rq->nr_switches++;rq->curr = next;++*switch_count;rq = context_switch(rq, prev, next, cookie); /* unlocks the rq */} else {lockdep_unpin_lock(&rq->lock, cookie);raw_spin_unlock_irq(&rq->lock);}}

(1)prev局部变量表示要切换出去的任务,next局部变量表示要切换进来的任务。

代码语言:javascript代码运行次数:0运行复制

struct task_struct *prev, *next;

(2)找到当前CPU的运行队列 struct rq,把当前正在运行的任务curr 赋值给 prev。

代码语言:javascript代码运行次数:0运行复制

int cpu = smp_processor_id();struct rq = cpu_rq(cpu);struct task_struct *prev = rq->curr;

(3) preempt 用于判断本次调度是否为抢占调度,如果发生了调度抢占(preempt =1),那么直接跳过,不参与判断,直接调用pick_next_task。抢占调度通常都是处于运行态的任务发起的抢占调度。

如果本次调度不是抢占调度(preempt = 0),并且该进程的state不等于 TASK_RUNNING (0),也就是不是运行态,处于其他状态。代表此次调度是该进程主动请求调度,主动调用了schedule函数,比如该进程进入了阻塞态。

进程在操作外部设备的时候(网络和存储则多是和外部设备的合作),往往需要让出 CPU,发起主动调度。

由于进程不是运行态:TASK_RUNNING了,那么就不能在CFS就绪队列中了,那么就调用 deactivate_task 将陷入阻塞态的进程移除CFS就绪队列,并将进程调度实体的 on_rq 成员置0,表示不在CFS就绪队列中了。 通常主动请求调用之前会提前设置当前进程的运行状态为 TASK_INTERRUPTIBLE 或者 TASK_UNINTERRUPTIBLE。

代码语言:javascript代码运行次数:0运行复制

#define TASK_RUNNING0#define TASK_INTERRUPTIBLE1#define TASK_UNINTERRUPTIBLE2......

代码语言:javascript代码运行次数:0运行复制

if (!preempt && prev->state) {if (unlikely(signal_pending_state(prev->state, prev))) {prev->state = TASK_RUNNING;} else {deactivate_task(rq, prev, DEQUEUE_SLEEP);prev->on_rq = 0;}}

(4)选择下一个将要执行的任务,通过CFS调度算法选择优先级最高的一个任务。 clear_tsk_need_resched将被抢占的任务prev(也就是当前的任务)需要被调度的标志位(TIF_NEED_RESCHED)给清除掉,表示 prev 接下来不会被调度。 clear_preempt_need_resched 被抢占的任务prev(也就是当前的任务)的 PREEMPT_NEED_RESCHED 标志位给清除掉。

代码语言:javascript代码运行次数:0运行复制

if (likely(prev != next)) {        //大概率事件,进行任务切换rq->nr_switches++;  //可运行队列切换次数更新rq->curr = next;    //将当前任务curr设置为将要运行的下一个任务 next ++*switch_count;    //任务切换次数更新//任务上下文切换rq = context_switch(rq, prev, next, cookie); /* unlocks the rq */} else {     //小概率事件,不进行任务切换lockdep_unpin_lock(&rq->lock, cookie);raw_spin_unlock_irq(&rq->lock);}

(5)如果选择的任务next和 原任务prev不是同一个任务,则进行任务上下文切换 如果是同一个任务,则不进行上下文切换。 注意这里是 用 likely()修饰(这是gcc内建的一条指令用于优化,编译器可以根据这条指令对分支选择进行优化),表示有很大的概率 选择的任务next 和 原任务prev不是同一个任务。 由我们程序员来指明指令最可能的分支走向,以达到优化性能的效果。

代码语言:javascript代码运行次数:0运行复制

if (likely(prev != next)) {        //大概率事件,进行任务切换rq->nr_switches++;  //可运行队列切换次数更新rq->curr = next;    //将当前任务curr设置为将要运行的下一个任务 next ++*switch_count;    //任务切换次数更新//任务上下文切换rq = context_switch(rq, prev, next, cookie); /* unlocks the rq */} else {     //小概率事件,不进行任务切换lockdep_unpin_lock(&rq->lock, cookie);raw_spin_unlock_irq(&rq->lock);}

2.3 context_switch 代码解读

任务切换主要是任务空间即虚拟内存(用户态的虚拟地址空间,包括了用户态的堆栈)、CPU寄存器、内核态堆栈。

后面context_switch 还会专门一篇进行描述,这里限于篇幅,只是简单描述一下。

用伪代码表示:

代码语言:javascript代码运行次数:0运行复制

switch_mm();switch_to(){switch_register();switch_stack();}

代码语言:javascript代码运行次数:0运行复制

/* * context_switch - switch to the new MM and the new thread's register state. */static __always_inline struct rq * context_switch(struct rq *rq, struct task_struct *prev,       struct task_struct *next, struct pin_cookie cookie){struct mm_struct *mm, *oldmm;(1)prepare_task_switch(rq, prev, next);(2)mm = next->mm;oldmm = prev->active_mm;if (!mm) {next->active_mm = oldmm;atomic_inc(&oldmm->mm_count);enter_lazy_tlb(oldmm, next);} elseswitch_mm_irqs_off(oldmm, mm, next);    (3)/* Here we just switch the register state and the stack. */switch_to(prev, next, prev);(4)return finish_task_switch(prev);}

(1)开始任务切换前,需要做的准备工作,这里主要是提供了2个接口给我们内核开发者,当任务切换时我们可以自己添加一些操作进去,任务被重新调度时我们也可以自己添加一些操作进去。 同时通知我们任务被抢占(sched_out)。

代码语言:javascript代码运行次数:0运行复制

prepare_task_switch(rq, prev, next);-->fire_sched_out_preempt_notifiers(prev, next);   -->__fire_sched_out_preempt_notifiers(curr, next);      -->hlist_for_each_entry(notifier, &curr->preempt_notifiers, link)notifier->ops->sched_out(notifier, next);

这里的 sched_in()函数和 sched_out函数是内核提供给我们开发者的接口。我们可以通过在这两个接口里面添加一些操作。 sched_in :任务重新调度时会通知我们。 sched_out:任务被抢占时会通知我们。

备注:调度器运行调度相关的代码,但其自身并不作为一个单独的 process 存在,在进程切换时,执行 switch out 的代码就是在被换出的 process 中,执行 switch in 的代码就是在被换入的 process 中,因此 scheduler 没有一个对应的 PID。

具体请参考:Linux 进程调度通知机制

代码语言:javascript代码运行次数:0运行复制

struct preempt_ops {void (*sched_in)(struct preempt_notifier *notifier, int cpu);void (*sched_out)(struct preempt_notifier *notifier,  struct task_struct *next);};struct preempt_notifier {struct hlist_node link;struct preempt_ops *ops;};

(2) 切换任务的用户虚拟态地址(不切换内核态的虚拟地址),也包括了用户态的栈,主要就是切换了任务的CR3, CR3寄存器放的是 页目录表物理内存基地址。

代码语言:javascript代码运行次数:0运行复制

mm = next->mm;oldmm = prev->active_mm;if (!mm) {   //mm == NULL,代表任务是内核线程//  直接用 被切换进程prev的active_mmnext->active_mm = oldmm;atomic_inc(&oldmm->mm_count);//通知处理器不需要切换虚拟地址空间的用户空间部分,用来加速上下文切换enter_lazy_tlb(oldmm, next);} else//不是内核线程,那么就要切换用户态的虚拟地址空间,也就是切换任务的CR3switch_mm_irqs_off(oldmm, mm, next);-->load_cr3(next->pgd); //加载下一个任务的CR3
Linux 进程调度之schdule主调度器

(3)切换任务的寄存器和内核态堆栈,保存原任务(prev)的所有寄存器信息,恢复新任务(next)的所有寄存器信息,当切换完之后,并执行新的任务。

代码语言:javascript代码运行次数:0运行复制

switch_to(prev, next, prev);-->__switch_to_asm((prev), (next)))-->ENTRY(__switch_to_asm)/* switch stack */movq%rsp, TASK_threadsp(%rdi)movqTASK_threadsp(%rsi), %rspjmp__switch_toEND(__switch_to_asm)-->__switch_to()

(4) 完成一些清理工作,使其能够正确的释放锁。这个清理工作的完成是第三个任务,系统中随机的某个其它任务。同时通知我们任务被重新调度(sched_in)。 这里其实也有点复杂,我们后面在 context_switch 篇重点描述。

代码语言:javascript代码运行次数:0运行复制

finish_task_switch(prev)-->fire_sched_in_preempt_notifiers(current);-->__fire_sched_in_preempt_notifiers(curr)-->hlist_for_each_entry(notifier, &curr->preempt_notifiers, link)notifier->ops->sched_in(notifier, raw_smp_processor_id());

总结 这篇文章主要讲了schdule主调度器的工作流程,即一个运行中的进程主动调用 _schedule 让出 CPU,总结来说就是: (1)通过调度算法选择一个优先级最高的任务。 (2)进行任务上下文切换,上下文切换又分用户态进程空间的切换和内核态的切换。 保存原任务的所有寄存器信息,恢复新任务的所有寄存器信息,并执行新的任务。 (3)上下文切换完后,新的任务投入运行。 如下图所示:

Linux 进程调度之schdule主调度器

以上就是Linux 进程调度之schdule主调度器的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/71935.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月13日 13:11:09
下一篇 2025年11月13日 13:45:45

相关推荐

  • Enso (ENSO)币是什么?值得投资吗?Enso工作原理和代币经济学概述

    enso (enso)是一个去中心化的共享网络,使开发者能够为跨不同区块链、rollup 和应用链的智能合约生成可执行字节码。enso 是基于 tendermint 设计的layer-1区块链,它映射了所有智能合约交互,使开发者能够从单一来源与任何链上的智能合约进行交互。这解决了区块链生态系统中的碎…

    2025年12月9日 好文分享
    000
  • USDT会归零吗?稳定币风险与安全性深度分析

    Binance币安 欧易OKX ️ Huobi火币️ USDT会不会归零,是很多用户关心的核心问题。直接说结论:从实际运行机制和市场现状来看,USDT短期内归零的可能性极低。但这不等于它没有风险。理解它的底层逻辑和潜在隐患,比单纯猜测“会不会归零”更重要。 USDT是怎么保持稳定的? USDT(Te…

    2025年12月9日
    000
  • 什么是Akash Network(AKT)币?如何运作?AKT代币经济学及价格预测

    去中心化云计算市场正在快速扩张,akash 网络(akt)作为亚马逊网络服务(aws)的 web3 替代方案,正引领行业发展。随着区块链应用的加速,越来越多的开发者开始转向去中心化的托管和计算平台,这为 akt 代币的增长创造了理想的环境。 Binance币安 欧易OKX ️ Huobi火币️ 投资…

    2025年12月9日
    000
  • 新手避坑指南如何识别“空气币”和跑路盘

    Binance币安 欧易OKX ️ Huobi火币️ 刚进币圈的新手,面对五花八门的项目很容易踩坑。空气币和跑路盘最常见,它们包装得再好,也有迹可循。关键不是听他们说什么,而是看他们做了什么。 看白皮书和实际用途 拿到一个新项目,先别急着投钱,去看看它的白皮书。真正有价值的项目会清晰说明自己要解决什…

    2025年12月9日
    000
  • 什么是稳定币?通俗讲解USDT、USDC、DAI的区别

    Binance币安 欧易OKX ️ Huobi火币️ 稳定币就是价格稳定的加密货币,通常盯住1美元。你可以把它想象成数字世界的“代金券”——你存入1美元,就能拿到1个价值1美元的数字代币,能在区块链上转账、交易或投资,不用担心像比特币那样大起大落。 USDT:流通最广,但透明度有争议 USDT是最早…

    2025年12月9日
    000
  • Recall (RECALL)币是什么?代币经济学、空投领取以及价格预测

    随着人工智能(ai)和区块链的融合加速,recall(recall)正在成为2025年10月最受关注的项目之一。在binance alpha的支持下,recall以其独特的愿景吸引了投资者的关注——一个去中心化的ai市场,智能代理可以在其中竞争、合作并创造真正的经济价值。recall 代币的发行不仅…

    2025年12月9日
    000
  • 什么是山寨币?币圈“Altcoin”入门全解析

    Binance币安 欧易OKX ️ Huobi火币️ 简单说,山寨币就是比特币之外的所有加密货币。这个词最早是用来形容那些模仿比特币、想取而代之的“替代品”,但现在范围更广了,基本指除了比特币(有时也包括以太坊)以外的其他主流或非主流币种。它们不是要完全复制比特币,而是用区块链技术解决比特币的一些短…

    2025年12月9日
    000
  • Anthropic (ANTH.PVT)是什么?会是下一个 OpenAI 级别的突破吗? 2025年价格预测

    人工智能 (ai) 迅速成为全球科技经济的内核,很少有公司能像 anthropic (anth.pvt) 一样崛起得如此之快。这家由前 openai 研究人员于几年前创立的公司,如今已从一家专注于人工智能安全领域的小众初创公司,转型成为全球估值最高的私 营企业之一。在亚马逊、谷歌和 salesfor…

    2025年12月9日
    000
  • Linea($LINEA)是什么?怎么买?LINEA空投、未来展望及价格预测

    linea是一种基于以太坊的第 2 层扩展解决方案,旨在通过利用 zk-rollups 技术实现更快、更便宜的交易。 Binance币安 欧易OKX ️ Huobi火币️ 近日,有消息称该公司正与连接全球超过11000家金融机构的国际汇款网络SWIFT合作,探索利用区块链进行银行间支付,引发关注。 …

    2025年12月9日 好文分享
    000
  • 比特币的稀缺性:2100万枚的总量限制

    全球比特币交易平台推荐: 欧易OKX: Binance币安: 火币Huobi: Gateio芝麻开门: 在数字货币的浩瀚星空中,比特币无疑是最璀璨的一颗。它的出现,不仅革新了我们对货币的认知,更在“稀缺性”这一古老经济学概念上,赋予了全新的诠释。当你深入了解比特币时,一个核心数字会反复出现:2100…

    好文分享 2025年12月9日
    000
  • Bittensor Crypto (TAO)币是什么?工作原理、最新价格走势分析指南

    尽管市场大部分仍在消化最新一轮低迷带来的冲击,但 tao 加密货币却强势回归,重新吸引了机构和散户投资者的关注。这股热潮源于灰度颠覆性的举措、subnet 代币的创新,以及其价格飙升,远超竞争对手。 Binance币安 欧易OKX ️ Huobi火币️ 在本指南中,我们将分析推动 TAO 加密货币最…

    2025年12月9日
    000
  • BTC价格预测:技术盘整与长期看涨基本面相结合

    Binance币安 欧易OKX ️ Huobi火币️ BTC价格预测 ‍ 技术分析:BTC 显示短期盘整信号 比特币价格目前交易价为 111,140 USDT,低于 20 日移动平均线 116,583 USDT,表明短期面临下行压力。MACD 指标显示负动量位于 -3,330.50,但与信号线的差距…

    2025年12月9日
    000
  • 比特币的匿名性与监管挑战

    在数字货币的世界中,比特币(bitcoin)无疑是那个最耀眼的存在。它自诞生之日起,便以其独特的去中心化、加密技术以及所谓的“匿名性”吸引了全球的目光。然而,这种“匿名性”究竟是真实的,还是仅仅是一种错觉?它在金融监管日益收紧的今天,又面临着怎样的挑战?本文将深入探讨比特币的匿名性特质,并剖析其在全…

    好文分享 2025年12月9日
    000
  • 比特币的微交易:小额支付的潜力

    比特币的微交易,听起来似乎有些矛盾,毕竟我们习惯了比特币作为一种投资工具,或者用于大额跨境支付。然而,隐藏在这一技术深处的,却是它在小额支付领域——即“微交易”——的巨大潜力。微交易,顾名思义,指的是金额极小的支付行为,可能只有几美分,甚至更少。传统金融体系处理这类交易时,往往会面临高昂的手续费和漫…

    好文分享 2025年12月9日
    000
  • 热点追踪:2025年最受关注的山寨币板块

    Binance币安 欧易OKX ️ Huobi火币️ 2025年,山寨币市场不再是普涨行情,而是由几个具备真实需求和机构资金流入的核心板块主导。从链上数据到宏观环境,这些领域正展现出强劲的增长势头,成为当前投资的焦点。 Layer 2与模块化区块链:解决以太坊瓶颈的基础设施 随着以太坊网络活动日益频…

    2025年12月9日
    000
  • 比特币链上数据解读:洞察市场情绪

    在数字货币的浩瀚宇宙中,比特币无疑是那颗最耀眼的星辰。它不仅仅是一种颠覆性的金融资产,更是一个庞大而透明的区块链生态系统。深入理解比特币市场,绝不能仅仅停留在价格k线的表面波动,而是要潜入其底层——比特币链上数据。这些公开、可验证的数据如同一个巨大的宝藏,蕴藏着关于市场参与者行为、资金流向、矿工活动…

    好文分享 2025年12月9日
    000
  • 比特币的安全性:密码学与去中心化

    在数字货币的浩瀚宇宙中,比特币无疑是那颗最闪耀的星。它不仅仅是一种支付工具,更代表着一种革命性的技术和理念。然而,对于许多初次接触甚至已经有所了解的用户来说,比特币的安全性始终是一个核心且令人好奇的问题。它如何保证交易不可篡改?如何防止伪造?它的价值存储是否真的牢不可破?这些疑问的答案,深植于其底层…

    好文分享 2025年12月9日
    000
  • 比特币信仰者(Maximalist):为何坚守比特币?

    在数字货币的浩瀚星海中,比特币(bitcoin)无疑是最璀璨的那颗星。它的诞生,不仅催生了区块链技术,更点燃了一场关于货币、权力与自由的深刻革命。然而,在这场革命中,有一群人显得尤为坚定,他们就是被称为“比特币信仰者”(bitcoin maximalist)的群体。他们坚信比特币是唯一的、最终的、无…

    好文分享 2025年12月9日
    000
  • 解码区块链:数字货币的底层技术

    区块链,一个在当今科技领域如同黑洞般吸引目光的词汇,它不仅仅是比特币、以太坊等数字货币的“幕后英雄”,更被誉为颠覆传统金融、物联网、供应链管理乃至社会治理的革命性技术。当谈及“解码区块链:数字货币的底层技术”,我们实际上是在探寻一幅宏伟的数字画卷,它描绘了一个去中心化、透明可信且不可篡改的未来。这幅…

    好文分享 2025年12月9日
    000
  • 币圈小白必看:告别盲投,理性投资

    在充满机遇与挑战的币圈,无数投资者怀揣着财富梦想踏入这片天地。然而,对于许多“小白”来说,币圈如同一个迷宫,信息冗杂,风险暗藏。盲目跟风、听信小道消息,往往导致血本无归,不仅错失了潜在的收益,更可能带来巨大的经济损失。告别盲投,树立理性的投资观念,是每一位币圈新人迈向成功的第一步。本文将深入剖析币圈…

    好文分享 2025年12月9日
    000

发表回复

登录后才能评论
关注微信