目录:
《Linux内核分析》第八周 进程的切换和系统的一般执行过程
一、计算机是如何工作的
- 存储程序计算机工作模型:冯诺依曼体系结构
- X86汇编基础:CPU的寄存器(通用寄存器、段寄存器、标志寄存器)、常见汇编指令、堆栈
- 汇编一个简单的C程序分析其汇编指令执行过程
二、操作系统是如何工作的?
- 函数调用堆栈
- 借助Linux内核部分源代码模拟存储程序计算机工作模型及时钟中断
- 在mykernel基础上构造一个简单的操作系统内核
- 三个法宝:
- 存储程序计算机:所有计算机基础性的逻辑框架
- 堆栈:高级语言的起点,函数调用需要堆栈机制
- 中断机制:多道系统的基础,是计算机效率提升的关键
三、构造一个简单的Linux系统MenuOS
- Linux内核源代码简介:sched_init()进程调度初始化函数,函数内关键的初始化——对0号进程,即idle进程进行初始化;rest_init()其他初始化函数,函数内将创建1号进程,即init进程。
- 构造一个简单的Linux系统
- 跟踪调试Linux内核的启动过程:道生一,一生二,二生三,三生万物。内核启动过程包括start_kernel之前和之后,之前全部是做初始化的汇编指令,之后开始C代码的操作系统初始化,最后执行第一个用户态进程init。一般分两阶段启动,先是利用initrd的内存文件系统,然后切换到硬盘文件系统继续启动。
四、扒开系统调用的三层皮(上)
1. 用户态、内核态和中断处理过程
- 内核态:一般现代CPU有几种指令执行级别。在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别对应着内核态
- 用户态:在相应的低级别执行状态下,代码的掌控范围有限,只能在对应级别允许的范围内活动
- 中断处理是从用户态进入内核态的主要方式,中断/int指令会在堆栈上保存一些寄存器的值:如用户态栈顶地址、当前的状态字、当时cs:eip的值(当前中断程序的入口)
2. 系统调用概述
- 系统调用是操作系统为用户态进程与硬件设备进行交互提供的一组接口
- 系统调用概述和系统调用的三层皮:xyz(API)、system_ call(中断向量)、sys_xyz(中断向量对应的中断服务程序)
3. 使用库函数API和C代码中嵌入汇编代码触发同一个系统调用
- 使用库函数API获取系统当前时间
- C代码中嵌入汇编代码的方法
- 使用C代码中嵌入汇编代码触发系统调用获取系统当前时间
五、扒开应用系统的三层皮(下)
1. 给MenuOS增加time和time-asm命令
rm menu -rf //强制删除当前menu
git clone http://github.com/mengning/menu.git //重新克隆新版本的menu
cd menu
ls
make rootfs
vi test.c //进入test.c文件
MenuConfig("getpid","Show Pid",Getpid);
MenuConfig("getpid_asm","Show Pid(asm)",GetpidAsm); //在main函数中增加MenuConfig()
int Getpid(int argc,char *argv[]);
int GetpidAsm(int argc,char *argv[]); //增加对应的Getpid和GetpidAsm两个函数
make rootfs //编译
2. 使用gdb跟踪系统调用内核函数sys_time
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
gdb
(gdb)file linux-3.18.6/vmlinux
(gdb)target remote:1234 //连接到需要调试的MenuOS
(gdb)b start_kernel //设置断点
(gdb)c //执行,可见程序在start_kernel处停下
list //可查看start_kernel的代码
(gdb)b sys_time //sys_time是13号系统调用对应的内核处理函数,在该函数处设置断点
(gdb)c
3. 系统调用在内核代码中的工作机制和初始化
- 系统调用在内核代码中的工作机制和初始化
- 简化后便于理解的system_call伪代码
- 简单浏览system_call和iret之间的主要代码
六、进程的描述和进程的创建
- 操作系统的三大管理功能:进程管理、内存管理、文件系统;
- PCB task_struct中:进程状态、进程打开的文件、进程优先级信息;
PID唯一的标识进程;
- 进程的创建
- 使用系统调用clone、fork、vfork均可创建一个新进程,但都是通过调用do_fork来实现进程的创建;
- 复制父进程PCB--task_struct来创建一个新进程,要给新进程分配一个新的内核堆栈;
- 修改复制过来的进程数据,比如pid、进程链表等等执行copy_process和copy_thread
- p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
- p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址
七、可执行程序的装载
- 预处理、编译、链接和目标文件的格式
C源代码(.c)经过编译器预处理被编译成汇编代码(.asm)
汇编代码由汇编器被编译成目标代码(.o)
将目标代码链接成可执行文件(a.out)
可执行文件由操作系统加载到内存中执行
目标文件的三种形式: 1. 可重定位文件.o,用来和其他object文件一起创建可执行文件和共享文件.2. 可执行文件,指出应该从哪里开始执行. 3. 共享文件,主要是.so文件,用来被链接编辑器和动态链接器链接
-
可执行程序、共享库和动态加载
1.创建新进程
2.新进程调用execve()系统调用执行指定的ELF文件
3.调用内核的入口函数sys_execve(),sys_execve()服务例程修改 -
当ELF被load_elf_binary()装载完成后,函数返回至do_execve()在返回至sys_execve()。ELF可执行文件的入口点取决于程序的链接方式:
1.静态链接:elf_entry就是指向可执行文件里边规定的那个头部,即main函数处。
2.动态链接:可执行文件是需要依赖其它动态链接库,elf_entry就是指向动态链接器的起点。
八、进程的切换和系统的一般执行过程
1. 进程切换的关键代码switch_to分析
- 进程进度与进程调度的时机分析
- 断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule()
- 内核线程可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度
- 用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度
- 进程上下文切换相关代码分析
- 为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,这叫做进程切换、任务切换、上下文切换
- 挂起正在CPU上执行的进程,与中断时保存现场是不同的,中断前后是在同一个进程上下文中,只是由用户态转向内核态执行
- 进程上下文包含了进程执行需要的所有信息
- schedule()函数选择一个新的进程来运行,并调用context_ switch进行上下文的切换,这个宏调用switch_ to来进行关键上下文切换
2. Linux系统的一般执行过程
Linux系统的一般执行过程分析
```
最一般的情况:正在运行的用户态进程X切换到运行用户态进程Y的过程:- 正在运行的用户态进程X
- 发生中断——save cs:eip/esp/eflags(current) to kernel stack,then load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack).
- SAVE_ALL //保存现场
- 中断处理过程中或中断返回前调用了schedule(),其中的switch_to做了关键的进程上下文切换
- 标号1之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行)
- restore_all //恢复现场
iret - pop cs:eip/ss:esp/eflags from kernel stack
8.继续运行用户态进程Y
```- Linux系统执行过程中的几个特殊情况
``` - 通过中断处理过程中的调度时机,用户态进程与内核线程之间互相切换和内核线程之间互相切换,与最一般的情况非常类似,只是内核线程运行过程中发生中断没有进程用户态和内核态的转换
- 内核线程主动调用schedule(),只有进程上下文的切换,没有发生中断上下文的切换,与最一般的情况略简略
- 创建子进程的系统调用在子进程中的执行起点及返回用户态,如fork
加载一个新的可执行程序后返回到用户态的情况,如execve
```- 内核与舞女
``` - 进程的地址空间一共有4G,其中0——3G是用户态可以访问,3G以上只有内核态可以访问
- 内核相当于出租车,可以为每一个“招手”的进程提供内核态到用户态的转换。
- 没有进程需要“承载”的时候,内核进入idle0号进程进行“空转”。当用户进程有需求时,内核发生中断,帮助用户进程完成请求,然后再返回到用户进程。就好像Taxi将用户载了一圈之后又把用户放下来。
- 3G以上的部分就是这样的“出租车”,是所有进程共享的,在内核态部分切换的时候就比较容易
内核是各种中断处理程序和内核线程的集合
```Linux系统架构和执行过程概览
- Linux操作系统架构概览
- 最简单也是最复杂的操作——执行ls操作
- 从CPU和内存的角度看Linux系统的执行
九、小结体会
从网易云课堂开课至今,可以说是受益匪浅,不仅有网课老师的教学,还有刘老师课上的讲解,系统调用的学习让我对内核的运行机制有了更深的了解与掌握,也认识到了linux与windows的众多不同,在平时的学习中有一些内容仅仅是浅尝辄止而没有来得及细嚼慢咽,掌握的不够全面。在日后的学习中我会定时查漏补缺,争取有朝一日能够完全领会内核源码的精妙之处并能吸收借鉴用于其它方面。