孙业毅 原创作品 转载请注明出处
《Linux内核分析》MOOC课程:http://mooc.study.163.com/course/USTC-1000029000
【Linux内核学习总结】
@2015.05
幸福来得很突然,这门课就快结束了……
喜欢孟老师的课,是从这个课程《软件工程(C编码实践篇)》开始的,那个是我在网易云课堂第一个坚持学完的课程。
自己一直在Windows下做开发工作,出于个人兴趣,去年就想系统地学习下Linux的相关知识。从《软件工程(C编码实践篇)》课程开始,就跟着孟老师逐步熟悉Linux下的一些开发实践;那时候就感觉孟老师的讲课,深入浅出,循序渐进,实践性很强,只要跟着坚持学下去,总是收获不少;然后又看到孟老师开设的这门课《Linux内核分析》,就毫不犹豫的选择跟上,并开启了“奋斗模式”。
是时候,总结下这段时间的坚持了,也给同样对Linux内核有兴趣的你一个指南。
在这门课的学习过程中,按照老师的要求,每次课后都写一篇博文,这是一个很好的学习方式。每当写这些文章的时候,总是要多看几遍视频,再查查相关的资料,才能勉强凑成一个完整的文档;同时也把自己学到的东西更好的分享出去,吸引更多的人过来学习Linux内核,一起讨论。现把这一系列博文罗列如下,欢迎大家批阅指正。
1. 图解分析汇编代码以理解计算机是如何工作的
冯诺依曼体系结构的计算机,又叫存储程序计算机,从硬件的角度来看,其工作模型是CPU依次读取内存中的指令来完成工作。但它具体是如何完成程序员编写的非线性执行的程序呢?本次课的实验,以一段汇编代码为例,详细介绍了CPU计算模块、寄存器和内存是如何配合工作的!
2. 基于mykernel的一个简单的时间片轮转多道程序内核代码分析
mykernel是由老师建立的一个用于开放您自己的操作系统的内核平台,它基于Linux Kernel 3.9.4 source code。通过本讲的学习和实验,我们知道操作系统的核心功能就是:进程调度和中断机制,通过与硬件的配合实现多任务处理,再加上上层应用软件的支持,最终变成可以使用户可以很容易操作的计算机系统。
3. 使用gdb跟踪Linux内核启动过程
start_kernel()是内核的汇编与C语言的交接点,在该函数以前,内核的代码都是用汇编写的,完成一些最基本的初始化与环境设置工作。start_kernel就像是c代码中的main函数。不管你关注Linux的内核模块,总是离不开start_kernel函数的,因为大部分模块的初始化工作都是在start_kernel中完成的。按照这节课的实验步骤,我们可以跟踪Linux内核的启动过程。
4. 使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
即便是最简单的程序,也难免要用到诸如输入、输出以及退出等操作,而要进行这些操作则需要调用操作系统所提供的服务,也就是系统调用。除非你的程序只完成加减乘除等数学运算,否则将很难避免使用系统调用。在 Linux 平台下有两种方式来使用系统调用:利用封装后的 C 库(libc)或者通过汇编直接调用。这篇文章从示例出发,介绍了系统调用的概念,以及如何使用系统调用。
5. 分析system_call中断处里过程
通过gdb我们可以给系统调用内核处里程序如sys_write, sys_time设置断点,并让程序停在断点处,进行断点跟踪系统调用处里过程。由于system_call是完全用汇编写就一个的函数,虽然我们也可以在system_call处设置断点,但却无法让系统停在system_call处,所以也无法通过单步跟踪学习其处里流程。但system_call是所有系统调用的入口,也是程序由用户态转入内核态执行时无法越过的一个函数,其重要性不言而喻,所以我们跟随老师简化的汇编代码以及源代码学习其主要的流程。
6. 初学Linux进程的描述和进程的创建
为了管理进程,内核必须对每个进程进行清晰的描述,进程描述符提供了内核所需了解的进程信息。进程描述符task_struct的源码链接:http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h#1235。在Linux应用程序的开发中,可以通过fork、vfork和clone等API来创建一个子进程,它们在Linux内核中对应的系统调用分别为sys_fork、sys_vfork和sys_clone函数,而这些函数最终都会调用do_fork完成子进程的创建。do_fork主要是复制了父进程的task_struct,然后修改必要的信息,从而得到子进程的task_struct。
7. 初学《Linux内核如何装载和启动一个可执行程序》
Linux系统可以通过execve API启动一个新进程,该API又呼叫sys_execve系统调用,负责将新的程序代码和数据替换到新的进程中,打开可执行 文件,载入依赖的库文件,申请新的内存空间,最后执行 start_thread(regs, elf_entry, bprm->p) ,设置 new_ip, new_sp ,完成新进程的代码和数据替换,然后返回,接下来就是执行新的进程代码了。
8. 初学Linux中进程调度与进程切换过程
Linux系统的一般执行过程,最一般的情况是:正在运行的用户态进程X切换到运行用户态进程Y的过程要经过以下步骤
1). 正在运行的用户态进程X
2). 发生中断: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).
3). SAVE_ALL //保存现场,这里是已经进入内核中断处里过程
4). 中断处理过程中或中断返回前调用了schedule(),其中的switch_to做了关键的进程上下文切换
5). 标号1之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行)
6). restore_all //恢复现场
7). iret - pop cs:eip/ss:esp/eflags from kernel stack
8). 继续运行用户态进程Y
总结
通过这门课的学习,加深了我对操作系统理论的理解,知道了Linux系统是如何工作的,如何通过代码阅读、调试去跟踪验证Linux系统的运行机制。
Linux作为一个极其成功的操作系统,其内核纷繁复杂、博大精深,无疑是很难学习的,虽然在课程中孟老师化繁为简、抽丝剥茧,我也很努力地学完了本课程所有的视频,跟着老师的指导完成了全部的练习和测验,但也只感觉我是刚刚站在这一知识宝库的大门前,大门刚刚露了一个缝隙,要学习的东西真是太多太多了!
个人觉得,在这门课程中,重要的是不是学习到了多少内核代码(虽然它也很重要);重要的是学习方法,即从何处着手学习Linux内核,课程中给了我们很多这方面的提示,例如:如何调试内核,如何看懂内核中的汇编代码,如何分析系统调用,等等。
总之,作为入门,这门课程起到了很好的引导作用;师傅领进门,修行靠自身。所以于我来说,这门课虽结束了,但Linux内核的学习才刚刚开始……