理解进程调度时机跟踪分析进程调度与进程切换的过程
理解 Linux 系统中进程调度的时机
询问chatGTP
启动MenuOS
首先进入LinuxKernel 文件夹,删除menu目录,然后git clone克隆一个新版本的menu,进入menu之后,运行make rootfs脚本自动编译生成根目录系统。
cd LinuxKernel
rm -rf menu
git config --global http.sslverify false
git clone https://github.com/mengning/menu.git
cd menu
make rootfs
调试MenuOS
通过增加-s -S启动参数打开调试模式。
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
gdb
file linux-3.18.6/vmlinux
target remote:1234
b schedule
c
gdb调试
按C往下执行,跟踪到schedule(),调用了__schedule()函数。
进入__schedule()函数内部如图所示
__schedule()函数内部有一句判断是否需要进行上下文切换的语句。context_switch()是进程上下文切换调用的函数。
switch_to的汇编代码部分
#define switch_to(prev, next, last)
do {
/*
* Context-switching clobbers all registers, so we clobber
* them explicitly, via unused output variables.
* (EAX and EBP is not listed because EBP is saved/restored
* explicitly for wchan access and EAX is the return value of
* __switch_to())
*/
unsigned long ebx, ecx, edx, esi, edi;
asm volatile("pushfl\n\t" /* 保存当前进程flags */
"pushl %%ebp\n\t" /* 当前进程堆栈基址压栈*/
"movl %%esp,%[prev_sp]\n\t" /*保存ESP,将当前堆栈栈顶保存起来*/
"movl %[next_sp],%%esp\n\t" /*更新ESP,将下一栈顶保存到ESP中*/
//完成内核堆栈的切换
"movl $1f,%[prev_ip]\n\t" /*保存当前进程EIP*/
"pushl %[next_ip]\n\t" /*将next进程起点压入堆栈,即next进程的栈顶为起点*/
//完成EIP的切换
__switch_canary
//next_ip一般是$1f,对于新创建的子进程时ret_from_fork
"jmp __switch_to\n" /*prev进程中,设置next进程堆栈*/
//jmp不同于call是通过寄存器传递参数
"1:\t" //next进程开始执行
"popl %%ebp\n\t"
"popfl\n"
/*输出变量定义*/
: [prev_sp] "=m" (prev->thread.sp), //[prev_sp]定义内核堆栈栈顶
[prev_ip] "=m" (prev->thread.ip), //[prev_ip]当前进程EIP
"=a" (last),
/* 要破坏的寄存器: */
"=b" (ebx), "=c" (ecx), "=d" (edx),
"=S" (esi), "=D" (edi)
__switch_canary_oparam
/* 输入变量: */
: [next_sp] "m" (next->thread.sp), //[next_sp]下一个内核堆栈栈顶
[next_ip] "m" (next->thread.ip),
//[next_ip]下一个进程执行起点,,一般是$1f,对于新创建的子进程是ret_from_fork
/* regparm parameters for __switch_to(): */
[prev] "a" (prev),
[next] "d" (next)
__switch_canary_iparam
: /* 重新加载段寄存器 */
"memory");
} while (0)
总结
理解Linux系统的一般执行过程,可以分为以下几个步骤:
1. 启动:Linux系统在启动时,会按照顺序执行以下步骤:
- BIOS自检和硬件初始化
- 加载操作系统内核镜像到内存
- 内核自我加载和初始化
- 切换到内核态,执行内核代码
2. 内核初始化:内核在启动后,会进行一系列的初始化操作,包括:
- 初始化内存管理单元(MMU)
- 初始化进程管理单元(PMU)
- 初始化文件系统
- 初始化网络栈
3. 创建初始进程:内核会创建一个初始进程(通常是root用户),作为系统的第一进程(PID 1)。
4. 启动用户空间:内核将控制权切换到用户空间,执行初始进程。此时,操作系统开始接收和处理用户输入的命令。
5. 命令执行:用户空间中的进程(如shell)读取用户输入的命令,然后将其转换为系统调用,传递给内核。内核接收命令后,调用相应的系统调用处理程序执行命令。
6. 进程管理:内核负责调度和管理进程,包括进程的创建、终止、调度等。此外,内核还负责处理进程间的同步和互斥操作。
7. 文件系统操作:内核提供文件操作接口,允许进程读取和写入文件。文件操作通过文件系统驱动实现,如EXT2、FAT32等。
8. 网络操作:内核提供网络操作接口,允许进程进行网络通信。网络操作通过网络协议栈实现,如TCP/IP、IPX/SPX等。
9. 系统调用:内核提供一系列系统调用,供用户空间进程使用。这些系统调用涵盖了内存管理、文件操作、网络通信等各个方面。
10. 进程间通信:内核支持进程间通过管道、信号、共享内存等方式进行通信。
11. 退出:当用户空间进程执行完毕或遇到异常时,内核会将其终止,并回收资源。然后,内核重新切换到初始进程,继续执行。