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

一、实验内容

  • 理解 Linux 系统中进程调度的时机,可以在内核代码中搜索 schedule()函数,看都是哪里调用了 schedule(),判断我们课程内容中的总结是否准确;
  • 使用 gdb 跟踪分析一个 schedule()函数 ,验证您对 Linux 系统进程调度与进程切换过程的理解;推荐在实验楼 Linux 虚拟机环境下完成实验。
  • 特别关注并仔细分析 switch_to 中的汇编代码,理解进程上下文的切换机制,以及与中断上下文切换的关系;

二 、实验过程

  1. 和之前的实验过程相同,先以Stopped的方式启动内核,运行代码如下:
cd LinuxeKernel  
rm menu -rf  
git clone https://github.com/mengning/menu.git  
cd menu  
mv test_exec.c test.c  
make rootfs	//运行内核
cd .. 
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S  //冻结内核

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 启动另一个终端,并输入如下代码:
cd LinuxKernel 
gdb 
(gdb)file linux-3.18.6/vmlinux
(gdb)target remote:1234  //将GDB连接到正在运行的远程程序
b schedule
b context_switch
b switch_to
b pick_next_task

在这里插入图片描述

  1. 使用c命令进行调试,跟踪分析schedule函数

在这里插入图片描述

三、内容理解

  1. 理解 Linux 系统中进程调度的时机,看都是哪里调用了 schedule()
  • 主动调用:当进程调用某些会导致阻塞的系统调用(如 sleep()、wait() 等)或明确调用 sched_yield() 时,内核会通过 schedule() 尝试选择其他可运行的进程。
  • 被动让出:(1)进程时间片耗尽:当当前进程的时间片用尽时,调度器会被触发,检查是否需要切换到其他优先级更高的进程。(2)优先级变更:如果有一个高优先级的进程被唤醒,可能触发抢占调度,调用 schedule()。
  • 进程阻塞:当进程因等待资源(如 I/O 或锁)而进入阻塞状态时,会通过调度器让出 CPU。
  • 高优先级进程唤醒:如果一个高优先级进程被唤醒,可能触发抢占式调度,调用 schedule()。
  • 中断处理后:在中断(如定时器中断)处理结束后,可能触发调度器检查是否需要切换到其他进程。
  1. 关于switch_to函数
    由于switch_to不是函数,无法设置断点,我们进入内核源码查看context_switch()函数内部,源码如下:
asm volatile(
    "pushfl\n\t"                   /* 保存当前进程的 EFLAGS 寄存器内容到栈中,保留当前的标志状态 */
    "pushl %%ebp\n\t"              /* 将当前进程的基址指针 (ebp) 压入栈,保存当前栈帧的基址 */
    "movl %%esp,%[prev_sp]\n\t"    /* 将当前栈指针 (esp) 保存到 prev->thread.sp,记录当前进程的栈顶 */
    "movl %[next_sp],%%esp\n\t"    /* 将目标进程的栈指针 (next->thread.sp) 赋值给 esp,切换到目标进程的栈 */
    "movl $1f,%[prev_ip]\n\t"      /* 将下一条指令的地址 (1: 标签对应的位置) 保存到 prev->thread.ip */
    "pushl %[next_ip]\n\t"         /* 将目标进程的下一条指令地址 (next->thread.ip) 压入栈,准备执行目标进程 */
    "jmp __switch_to\n"            /* 跳转到内核的 __switch_to 函数,完成进一步的上下文切换 */
    "1:\t"                         /* 标签 1,标识当前代码的位置,供返回时使用 */
    "popl %%ebp\n\t"               /* 恢复目标进程的基址指针 (ebp),还原目标进程的栈帧基址 */
    "popfl\n"                      /* 恢复目标进程的 EFLAGS 状态,完成标志寄存器的切换 */

    /* output parameters: 定义输出的变量及其对应的存储位置 */
    : [prev_sp] "=m" (prev->thread.sp),  /* 将当前进程的栈指针保存到 prev->thread.sp */
      [prev_ip] "=m" (prev->thread.ip),  /* 将当前进程的下一条指令地址保存到 prev->thread.ip */
      "=a" (last),                       /* 将最后一个运行的任务地址存储到 eax 寄存器 */

      /* clobbered output registers: 声明会被覆盖的寄存器 */
      "=b" (ebx), "=c" (ecx), "=d" (edx), /* ebx, ecx, edx 被汇编代码修改 */
      "=S" (esi), "=D" (edi)              /* esi, edi 被汇编代码修改 */

    /* input parameters: 定义输入的变量及其来源 */
    : [next_sp] "m" (next->thread.sp),   /* 提供目标进程的栈指针 (next->thread.sp) */
      [next_ip] "m" (next->thread.ip),   /* 提供目标进程的下一条指令地址 (next->thread.ip) */
      [prev]    "a" (prev),              /* eax 寄存器中保存当前进程的任务描述符地址 */
      [next]    "d" (next)               /* edx 寄存器中保存目标进程的任务描述符地址 */

    /* clobbered segment registers: 声明可能被修改的内存 */
    : "memory"
);

四、实验总结

在操作系统中,进程切换和中断处理是核心功能。schedule()函数负责调度,它通过调用pick_next_task()函数来执行进程调度算法,从而选择下一个即将运行的进程。随后,schedule()会利用context_switch()函数来完成进程的上下文切换,确保进程能够顺利交接控制权。
context_switch()函数的实现中,switch_to()宏扮演着至关重要的角色。它负责保存当前进程的上下文信息至其栈中,并从目标进程的栈中恢复上下文,这涉及到处理器寄存器、堆栈指针和程序计数器等关键状态的保存与恢复。
switch_to()的实现细节会根据不同的硬件架构和编译选项进行调整。它通常需要汇编语言来精确控制上下文切换的具体操作,包括寄存器状态的保存和恢复。通过传入的参数,switch_to()将控制权转移到目标进程的堆栈,并从该堆栈中恢复进程的上下文,使得新选择的进程能够继续执行。
综上,schedule()pick_next_task()context_switch()以及switch_to()共同协作,实现了进程的调度和上下文的保存与恢复。这些组件在操作系统内核中发挥着至关重要的作用,它们确保了系统能够高效地管理CPU资源,并实现进程间的平滑切换。

“20242817李臻 原创作品转载请注明出处 《Linux内核分析》《Linux内核分析》MOOC课程Linux内核分析MOOC课程”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值