2023-2024-1 20232824《Linux内核原理与分析》第九周作业

Ⅰ.理解进程调度时机跟踪分析进程调度与进程切换的过程

理解 Linux 系统中进程调度的时机,可以在内核代码中搜索 schedule()函数,看都是哪里调用了 schedule(),判断我们课程内容中的总结是否准确;

使用 gdb 跟踪分析一个 schedule()函数 ,验证您对 Linux 系统进程调度与进程切换过程的理解;

特别关注并仔细分析 switch_to 中的汇编代码,理解进程上下文的切换机制,以及与中断上下文切换的关系。

Linux进程调度时机主要有:

1、进程状态转换的时刻:进程终止、进程睡眠;

2、当前进程的时间片用完时(current->counter=0);

3、设备驱动程序

4、进程从中断、异常及系统调用返回到用户态时;

时机1,进程要调用sleep()或exit()等函数进行状态转换,这些函数会主动调用调度程序进行进程调度;

时机2,由于进程的时间片是由时钟中断来更新的,因此,这种情况和时机4是一样的。

时机3,当设备驱动程序执行长而重复的任务时,直接调用调度程序。在每次反复循环中,驱动程序都检查need_resched的值,如果必要,则调用调度程序schedule()主动放弃CPU。

时机4,如前所述,不管是从中断、异常还是系统调用返回,最终都调用ret_from_sys_call(),由这个函数进行调度标志的检测,如果必要,则调用调用调度程序。那么,为什么从系统调用返回时要调用调度程序呢?这当然是从效率考虑。从系统调用返回意味着要离开内核态而返回到用户态,而状态的转换要花费一定的时间,因此,在返回到用户态前,系统把在内核态该处理的事全部做完。

每个时钟中断(timer interrupt)发生时,由三个函数协同工作,共同完成进程的选择和切换,它们是:schedule()、do_timer()及ret_form_sys_call()。我们先来解释一下这三个函数:

schedule():进程调度函数,由它来完成进程的选择(调度);

do_timer():暂且称之为时钟函数,该函数在时钟中断服务程序中被调用,是时钟中断服务程序的主要组成部分,该函数被调用的频率就是时钟中断的频率即每秒钟100次(简称100赫兹或100Hz);

ret_from_sys_call():系统调用返回函数。当一个系统调用或中断完成时,该函数被调用,用于处理一些收尾工作,例如信号处理、核心任务等等。

在内核代码中搜索 schedule()函数:

结合ChatGPT了解:

Ⅱ.实验

和前面的实验过程相同,先搭建好基础环境和设置:

cd LinuxeKernel
rm menu -rf
git clone https://github.com/mengning/menu.git
cd menu
mv test_exec.c test.c
make rootfs

冻结内核:

cd LinuxKernel   
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S  //冻结内核的启动

进入新的Terminal,开启gdb调试,建立链接:

cd LinuxKernel 
gdb
(gdb)file linux-3.18.6/vmlinux
(gdb)target remote:1234

分别在schedule(进程调度的主体函数)context_switch(实现进程切换的函数)pick_next_task(负责根据调度策略和调度算法选择下一个进程)三处设置断点。

在switch_to设置断点时失败,因为在 Linux 内核中,switch_to 是一个用于进行进程切换的宏。它通常用于将控制权从一个进程切换到另一个进程。不能设置断点。

接下来c运行:

switch_to的汇编代码:

// 定义 switch_to 宏,用于在内核中进行进程切换
#define switch_to(prev, next, last) \
do { \
    asm volatile ( \
        // 保存当前进程的寄存器状态,包括 esi、edi、ebp 寄存器
        "pushl %%esi\n\t" \
        "pushl %%edi\n\t" \
        "pushl %%ebp\n\t" \
        // 将当前栈指针 esp 的值保存到 prev->thread.esp 中
        "movl %%esp,%0\n\t" \
        // 将 next 进程的栈指针 esp 的值加载到 esp 寄存器
        "movl %3,%%esp\n\t" \
        // 将标签 1f 的地址保存到 %1 中
        "movl $1f,%1\n\t" \
        // 将 next 进程的指令指针 eip 的值推送到栈上
        "pushl %4\n\t" \
        // 跳转到 __switch_to 标签,开始进行实际的上下文切换
        "jmp __switch_to\n" \
        // 标签 1,用于在切换完成后返回到这里
        "1:\t" \
        // 恢复先前保存的寄存器状态,包括 ebp、edi、esi
        "popl %%ebp\n\t" \
        "popl %%edi\n\t" \
        "popl %%esi" \
        // 输出部分,指定了宏的输出操作数
        : "=m" (prev->thread.esp), "=m" (prev->thread.eip) \
        // 输入部分,指定了宏的输入操作数
        : "m" (prev->thread.ebp), "m" (next->thread.esp), "m" (next->thread.eip) \
        // clobber 部分,指定了汇编代码可能修改的寄存器
        : "memory"); \
} while (0)

这个宏使用了内联汇编 (asm volatile) 来执行一些汇编指令,完成从当前进程到下一个进程的上下文切换。

Ⅲ.总结

Linux进程调度基于分时和优先级,包括用户态和内核态的进程。内核线程是只有内核态的特殊进程,代表中断处理和其他内核任务。整体执行过程可描述为从运行的用户态进程X切换到用户态进程Y。内核线程可以主动调度而无需中断上下文切换,通过schedule()函数和context_switch宏实现上下文切换,其中switch_to用于关键上下文切换。这一结构允许灵活而高效的进程管理和调度。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值