总结
通过半个学期的学习(主要有三条线:MOOC课、书本和课堂),获得了很多知识。
首先,通过网课老师形象生动的讲述和描述一些专业词汇,使我更加深刻的记住并掌握了这些内容:动态的展示堆栈的变化,更容易理解一段汇编代码;分析操作系统的工作,记住了三大法宝,这引领我们学习后续课程;扒开系统调用三层皮,从理论知识入手,了解三层皮:API xyz,中断向量system_call,中断服务程序sys_xyz,再从操作入手,使用gdb跟踪;中断是学习中很重要的一点,进程切换、系统调用都离不开中断。总之,在这八周的学习中,从打开电脑进程初始化到启动进程到进程切换,完整的梳理了我们平时使用计算机中各个应用程序之间如何协同操作,让我更加了解计算机的工作原理。
其次,在跟随孟宁老师学习过程中,也在阅读《Linux内核分析》这本书,这本书内容很详细,能够在孟老师授课基础上深入理解学习具体的内容,使更加理解掌握知识。
最后,每节课刘老师都会根据上周学习的网课进行总结梳理、答疑解惑,在课堂上跟着刘老师再梳理一遍孟老师的讲课内容会让同学们对知识的理解有侧重点,对听网课中不明白的地方能进一步学习。
一、听着课
(一)计算机是如何工作的
-
冯诺依曼体系结构——核心:存储程序计算机;
-
X86汇编基础
1.eip特点:
a.自加1条指令。 从该指令执行完自动指向下一条指令;
b.只可被一下指令修改:call,ret,jmp,条件跳转;
2.cpu在实际取指令是根据 cs:eip 来定位一条指令;
3.堆栈是向下增长的;
4.Eax 寄存器:函数返回值默认用它存储,返回给上一级函数。
(二)操作系统是如何工作的
-
三个法宝——存储程序计算机、函数调用堆栈、中断机制;
-
esp——堆栈指针;ebp——基址指针(记录当前函数调用基址);
-
在my_schedule函数中,完成进程的切换。进程的切换分两种:
1.下一个进程没有被调度过;
2.下一个进程被调度过,可以通过下一个进程的state知道其状态。
进程切换通过内联汇编代码实现,需要保存之前的进程的eip和堆栈,然后将新进程的eip和堆栈的值存入相对应的寄存器中。
(三) Linux内核源代码简介
-
sched_init()进程调度初始化函数,函数内关键的初始化——对0号进程,即idle进程进行初始化;
-
rest_init()其他初始化函数,函数内将创建1号进程,即init进程;
-
内核的启动过程:
rest_init实际是start_kernel内核一启动的时候会一直存在,这个就叫0号进程;0号进程创建了1号进程kernel_init和其他服务线程。
(四) 扒开系统调用的三层皮(上)
-
三层皮:API xyz,中断向量system_call,中断服务程序sys_xyz
-
系统是通过中断的方式将用户态转换为内核态,并通过调用系统函数来实现系统功能。
-
当系统中断出现时,CPU保护现场和上下文切换来保护目前用户态所运行的状态,并通过返回系统调用函数的值来让用户判断是否已经有效地调用,结果如何。
-
系统调用是一个软中断,中断号是0x80——通过int 0x80,触发系统调用。
(五) 扒开系统调用的三层皮(下)
-
set_system_trap_gate,设置系统陷阱门,即系统调用。
-
使用gdb跟踪
make rootfs:自动编译,生成根文件系统,自动启动.
(gdb)list 查看代码.
(gdb)s 单步调试进入函数体.
(gdb)n 单步调试不进入函数体.
- 给MenuOS增加time和time-asm命令、添加了fork(详见第五周学习笔记 )
(六) 进程的描述和进程的创建
-
操作系统的三大管理功能:进程管理、内存管理、文件系统;
-
PCB task_struct中:进程状态、进程打开的文件、进程优先级信息;
-
PID唯一的标识进程;
-
创建一个新进程在内核中的执行过程
1.使用系统调用clone、fork、vfork均可创建一个新进程,但都是通过调用do_fork来实现进程的创建;
2.复制父进程PCB--task_struct来创建一个新进程,要给新进程分配一个新的内核堆栈;
3.修改复制过来的进程数据,比如pid、进程链表等等执行copy_process和copy_thread
4.p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址
(七) 可执行程序的装载
- Linux内核装载和启动一个可执行程序
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就是指向动态链接器的起点。
(八) 进程的切换和系统的一般执行过程
-
schedule()函数负责调度;
-
linux系统的一般执行过程:
X正在运行--->发生中断,可能陷入内核,CPU自动保存加载--->SAVE_ALL保存现场--->调用schedule,switch_to进程上下文切换--->标号1之后运行Y(之前有进行准备动作)--->restore_all恢复现场--->iret- pop cs:eip/ss:esp/eflags from kernel stack--->继续运行用户态进程Y
- 特殊情况
通过中断处理过程中的调度时机,用户态进程与内核线程之间互相切换和内核线程之间互相切换,与最一般的情况非常类似,只是内核线程运行过程中发生中断没有进程用户态和内核态的转换;
内核线程主动调用schedule(),只有进程上下文的切换,没有发生中断上下文的切换,与最一般的情况略简略;
创建子进程的系统调用在子进程中的执行起点及返回用户态,如fork;
加载一个新的可执行程序后返回到用户态的情况,如execve;
二、看着书
(一) 第一章 Linux内核简介
-
操作系统——整个最基本功能系统中负责完成最基本功能和系统管理的部分;
-
内核——管理者/操作系统核心;
-
linux——用户空间和内核空间组成;
-
Linux是模块化的、多线程的、内核本身可调度的操作系统;
-
应用程序通过系统调用与内核通信:应用程序——>库函数——>系统调用界面——>内核——>任务;
-
CPU的活动
1.运行于用户空间,执行用户进程。
2.运行于内核空间,处于进程上下文,代表某个特定的进程执行。
3.运行于内核空间,处于中断上下文,与任何进程无关,处理某个特定的中断。
(二) 第二章 从内核出发
- 获取内核源码 Git:下载和管理Linux内核源代码
获取最新提交到版本树的一个副本
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
下载代码后,更新自己的分支到最新分支
$ git pull
-
编译内核 配置内核——make与config
-
内核开发的特点
无libc库抑或标准头文件
没有内存保护机制
不轻易在内核中使用浮点数
容积小而固定的栈
同步和并发
可移植性
(三) 第五章 系统调用
-
系统调用:用户程序在需要的时候,通过系统调用来使用硬件设备。
-
中间层:作用三个1.为用户空间提供一种硬件的抽象接口;2.保证系统稳定和安全;3.除异常和陷入,是内核唯一的合法入口。
-
用系统调用号指明到底执行哪个系统调用。
-
由int $0x80指令触发128号软中断。
-
内核在执行系统调用时处于进程上下文。
(四) 第十八章 调试
- 内核中的bug:
引用空指针会产生一个oops;垃圾数据会导致系统崩溃。
定时限制和竞争条件都允许多个线程在内核中同时运行产生的结果。
- oops
会向终端输出的内容:
1.错误消息;
2.寄存器中保存的信息;
3.可供跟踪的回溯线索。
-
通过打印来调试:printk()函数任何时候地方都可以调用它。
-
内核调试配置选项:内核开发菜单项中,依赖CONFIG_DEBUG_KERNEL。
-
系统请求键:sysctl来标记该特性的开或关(需要启用时:echo 1 > /proc/sys/kernel/sysrq)
-
内核调试器:gdb vmlinux /proc/kcore
-
使用Git进行二分搜索
(五) 第三章 进程管理
-
fork创造的子进程复制了父进程资源,包括内存及进程描述符的内容,资源的复制而不是指针的复制。
-
vfork的行为更像一个线程(指没有自已独立的内存空间),更明显的是vfork的调用将挂起当前进程(即父进程)。
-
clone根据flag的不同可以实现不同的功能。
-
只要退出,最终都调用了do_exit。
(六) 第四章 进程调度
-
调度:调度是一个平衡的过程。一方面,它要保证各个运行的进程能够最大限度的使用CP;另一方面,保证各个进程能公平的使用CPU;
-
调度功能:决定哪个进程运行以及进程运行多长时间;
-
调度实现原理:与进程的优先级有关;
-
Linux上调度实现的方法:O(1)的调度算法;
-
进程优先级:调度程序总是选择时间片未用尽且优先级高的进程运行;
-
时间片:nice值作为权重将调整进程所使用的处理器时间使用比;
-
CFS的做法是允许每个进程运行一段时间、循环轮转、选择运行最少的进程作为下一个运行进程,而不再采用分配给每个进程时间片的做法了,在所有可运行进程总数基础上计算出一个进程应该运行多久;
-
进程选择:红黑树rbtree——自平衡二叉搜索树;
-
实时调度策略:两种策略
SCHED_FIFO和 SCHED_RR
SCHED_FIFO 实现了一种简单的、先入先出的调度算怯
SCHED_RR 是带有时闹片的 SCHED_FIFO,一种实时轮流调度算挂。