博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
c语言倒计时不影响进程_linux学习15,执行程序就像读文章,换本书就相当于切换了进程...
阅读量:6469 次
发布时间:2019-06-23

本文共 3483 字,大约阅读时间需要 11 分钟。

前面两节讨论了 linux 中进程的睡眠与唤醒机制,也介绍了 linux 内核的 cfs 进程调度方法,知道了哪些进程会被挂起,哪些进程会被投入运行。

不过,我们还不知道 linux 内核在进程调度时,是如何切换进程的的。例如,原本进程 A 正在睡眠,进程 B正在运行,现在要将 A 投入运行,将 B 设置为睡眠状态,这一过程是如何实现的呢?本节将讨论这个问题。

f1051b64cc99712a4693b3559ef29b60.png

抢占和上下文切换

我们将进程运行时的资源(栈信息、寄存器信息等)称作“上下文”,这么一来,任务抢占就可看作是 linux 内核在切换上下文,上下文切换完毕时,内核也就完成了从一个可执行进程切换到另一个可执行进程。

“上下文”这个名字其实挺贴切的,内核在执行进程时,可以看做是内核在阅读一篇文章,如果有进程需要调度,就相当于内核换了一篇文章读。

那么,linux 内核是如何实现上下文切换的呢?这其实可以从进程调度获得入口,因为上下文切换一定发生在进程调度时,查看 schedule() 函数的 C语言代码:

4141 asmlinkage void __sched schedule(void)- 4142 {| 4143 struct task_struct *prev, *next;| 4144 unsigned long *switch_count;| 4145 struct rq *rq;| 4146 int cpu;| 4147 | 4148 need_resched:| 4149 preempt_disable();| 4150 cpu = smp_processor_id();| 4151 rq = cpu_rq(cpu);| 4152 rcu_qsctr_inc(cpu);| 4153 prev = rq->curr;| 4154 switch_count = &prev->nivcsw;| 4155 | 4156 release_kernel_lock(prev); ....|- 4190 if (likely(prev != next)) {|| 4191 sched_info_switch(prev, next);|| 4192 || 4193 rq->nr_switches++;|| 4194 rq->curr = next;|| 4195 ++*switch_count;|| 4196 || 4197 context_switch(rq, prev, next); /* unlocks the rq */...
8abeb9f1cc1bf412e74b47957b08cd20.png

发现 context_switch() 函数,显然它就是负责上下文切换的函数。它的 C语言代码如下:

2447 static inline void 2448 context_switch(struct rq *rq, struct task_struct *prev, 2449 struct task_struct *next)- 2450 {| 2451 struct mm_struct *mm, *oldmm;| 2452 | 2453 prepare_task_switch(rq, prev, next);| 2454 mm = next->mm;| 2455 oldmm = prev->active_mm;...|- 2463 if (unlikely(!mm)) {|| 2464 next->active_mm = oldmm;|| 2465 atomic_inc(&oldmm->mm_count);|| 2466 enter_lazy_tlb(oldmm, next);|| 2467 } else| 2468 switch_mm(oldmm, mm, next);...| 2484 /* Here we just switch the register state and the stack. */| 2485 switch_to(prev, next, prev);...| 2493 finish_task_switch(this_rq(), prev);| 2494 }
ad55bd739e5d50bad2ce0c41a18477aa.png

容易看出,核心就是 switch_mm() 函数和 switch_to() 函数。switch_mm() 函数的 C语言代码如下,请看:

35 static inline void switch_mm(struct mm_struct *prev, 36 struct mm_struct *next, 37 struct task_struct *tsk)- 38 {| 39 int cpu = smp_processor_id();| 40 |- 41 if (likely(prev != next)) {...|| 51 load_cr3(next->pgd);|| 56 if (unlikely(prev->context.ldt != next->context.ldt))|| 57 load_LDT_nolock(&next->context);|| 58 }...| 73 }
56c3e5cfcc5a412cc3ffc48360c8072f.png

switch_mm() 函数切换了页表,它的主要作用就是把虚拟内存(前面的文章曾经介绍过,linux 中的进程都是运行在虚拟系统中的)从上一个进程映射到新进程中。

switch_to() 函数负责切换新进程的栈和寄存器,因为涉及到寄存器的操作,C语言无法方便的完成,所以 linux 内核是使用汇编代码(而且比C语言代码效率更高)实现该函数的,请看:

9050d538a128cc68962066ffc572a34c.png

从汇编代码也能看出,switch_to() 函数在切换新进程之前,将上一个进程的信息都压栈保存了,所以以后再切换回来的时候,进程能够接着上一次的状态继续运行。

进一步调度

现在已经清楚 linux 内核是如何切换进程的了。之前我们说过 linux 的进程是有优先级的概念的,高优先级的进程总是优先运行。假设某次调度后,进程 A 即将被投入运行,但是这时优先级比进程 A 更高的进程 B 也处于可运行状态了,linux 内核如何处理这种情况呢?

事实上,内核提供了 need_resched 标志位来表明是否需要重新执行一次调度。

2a238f47887de2f017039525fe43c632.png

该标志位可以通过以下三个C语言 inline 函数修改和查询:

b05e46fef28266d592f880949d1d6dfc.png

进一步跟踪,发现 need_resched 标志位其实记录在进程的 thread_info 结构体的 flag 成员里,该结构体之前有文章专门介绍过。

1db3314bf1ec1aac15845d5603422f73.png

现在就清楚了,当优先级较高的进程进入可执行状态的时候,linux 内核会设置 need_resched 标志位。而内核在进程调度后返回用户空间时,会检查 need_resched 标志,如果已被设置,则内核会重新执行一次调度。

那么,linux 内核什么时候重新调度才是安全的呢?只要进程没有持有锁,内核就可以抢占它。所以每个进程的 thread_info 结构体有一个 preempt_count 计数器,它的初始值为 0,每使用一次锁就加 1,释放一次锁就减 1,当该值为 0 的时候,linux 内核就能够抢占它。

linux 的实时调度策略

再啰嗦一下 linux 的两种实时调度策略:SCHED_FIFO 和 SCHED_RR。不特殊指定调度策略的进程一般都是 SCHED_NORMAL 策略。

SCHED_FIFO 调度策略不使用时间片,它使用先进先出的调度算法,处于可运行状态的 SCHED_FIFO 进程会比任何 SCHED_NORMAL 几的进程都先得到调度。一旦一个 SCHED_FIFO 级的进程处于可执行状态,就会一直运行,除非执行完毕或者它自己主动让出 cpu,否则就只有优先级更高的 SCHED_FIFO 和 SCHED_RR 级进程才能抢占它。

054458f526cc6dbf3e69fd7a49e94eeb.png

SCHED_RR 调度策略与 SCHED_FIFO 调度策略总体相同,只不过 SCHED_RR 调度策略也使用时间片,SCHED_RR级进程消耗完自己的时间片时,由同优先级的其他实时进程抢占。

SCHED_FIFO 和 SCHED_RR 调度策略,高优先级的进程总是立刻抢占低优先级的进程。低优先级进程不会抢占 SCHED_RR 进程,即使它的时间片已经使用完毕。

欢迎在评论区一起讨论,质疑。文章都是手打原创(本文部分参考linux内核原理和设计),每天最浅显的介绍C语言、linux等嵌入式开发,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦。

转载地址:http://usdko.baihongyu.com/

你可能感兴趣的文章
如何在linux系统下配置共享文件夹,如何在windows和Linux系统之间共享文件夹.doc
查看>>
linux操作系统加固软件,系统安全:教你Linux操作系统的安全加固
查看>>
linux中yum源安装dhcp,24.Linux系统下动态网络源部署方法(dhcpd)
查看>>
C#技术------垃圾回收机制(GC)
查看>>
漫谈并发编程(三):共享受限资源
查看>>
【转】github如何删除一个仓库
查看>>
状态模式
查看>>
VC++获得微秒级时间的方法与技巧探讨(转)
查看>>
HDOJ-1010 Tempter of the Bone
查看>>
JavaNIO基础02-缓存区基础
查看>>
日本开设无人机专业,打造无人机“人才市场”
查看>>
190行代码实现mvvm模式
查看>>
PXE部署实例
查看>>
cobbler初探------实现自动安装centos6.4
查看>>
Android Studio 2.0 preview3 BUG
查看>>
兼容几乎所有浏览器的透明背景效果
查看>>
Go语言4
查看>>
jeesite 框架搭建与配置
查看>>
Adb移植(一)简单分析
查看>>
Linux VNC server的安装及简单配置使用
查看>>