理解进程调度时机跟踪分析进程调度与进程切换的过程
理解进程调度时机跟踪分析进程调度与进程切换的过程
实验过程
任务一
- 理解 Linux 系统中进程调度的时机,可以在内核代码中搜索 schedule()函数,看都是哪里调用了 schedule();
使用 grep 命令: 在内核源码的根目录运行以下命令:
grep -rnw "schedule()"
分析调用时机:
schedule() 常在以下场景中被调用:
进程需要主动放弃 CPU:
进程进入等待状态(如阻塞在某个资源上)。
通常通过调用 schedule() 主动放弃 CPU 使用权。
例如:在等待锁或信号量时。
系统中断或上下文切换:
在硬件中断或软件中断处理完成后,需要切换到其他进程。
典型场景:时钟中断触发调度。
内核等待事件:
内核中的某些子系统等待某个条件(如 I/O 操作完成),会调用 schedule()。
通常伴随 wait_event() 或 wait_queue()。
预定义的调度点:
某些代码路径明确指定需要触发调度,例如 mutex_lock()、cond_resched()。
关键调用点的说明
以下是一些常见的 schedule() 调用点及其意义:
- 锁等待:mutex_lock() 或 spin_lock(): 如果资源已被锁定,当前进程需要调用 schedule() 进入等待队列,直到资源可用。
- 阻塞等待:wait_event(): 如果某个条件未满足,调用 schedule() 挂起当前进程,直到条件满足。
- 内核抢占:cond_resched(): 用于主动触发调度,确保长时间运行的任务不会占用 CPU 太久。
- 睡眠:msleep() 或 schedule_timeout(): 当前进程主动进入睡眠状态。
分析源码
例如,在 kernel/sched/core.c 中找到 schedule() 函数的实现:
asmlinkage __visible void __sched schedule(void)
{
struct task_struct *tsk = current;
sched_submit_work(tsk);
...
pick_next_task();
...
}
//该函数的主要作用是将当前任务标记为非运行状态,并选择下一个可运行任务。
任务二
- 使用 gdb 跟踪分析一个 schedule()函数 ,验证您对 Linux 系统进程调度与进程切换过程的理解;
qemu-system-x86_64 -kernel ./linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -append "console=ttyS0" -s -S
schedule()函数和__schedule()函数:
context_switch():
switch_to宏定义:
调度的发生主要有两种方式:
1:主动式调度(自愿调度)
在内核中主动直接调用进程调度函数schedule(),当进程需要等待资源而暂时停止运行时,会把状态置于挂起(睡眠),并主动请求调度,让出cpu。
2:被动式调度(抢占式调度、强制调度)
用户抢占和内核抢占
(1)用户抢占发生在:从系统调用返回用户空间和从中断处理程序返回用户空间。
(2)内核抢占:在不支持内核抢占的系统中,进程/线程一旦运行于内核空间,就可以一直执行,直到它主动放弃或时间片耗尽为止。这样一些非常紧急的进程或线程将长时间得不到运行。
进程的切换:
(1)为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,这叫做进程切换、任务切换、上下文切换;
(2)挂起正在CPU上执行的进程,与中断时保存现场是不同的,中断前后是在同一个进程上下文中,只是用用户态转向内核态执行;
(3)进程上下文包含了进程执行需要的所有信息
用户地址空间:包括程序代码,数据,用户堆栈等
控制信息:进程描述符,内核堆栈等
硬件上下文(注意中断也要保存硬件上下文知识保存的方法不同)
(4)schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换
next = pick_next_task(rq, prev);
context_switch(rq, prev, next);
switch_to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程