Linux内核学习总结
首先非常感谢网易云课堂这个平台,让我能够在课下学习,课上加强,体会翻转课堂的乐趣。孟宁老师的课程循序渐进,虽然偶尔我学习地不是很透彻,但能够在后续的课程中进一步巩固学习,更加深刻。上个学期学习了Linux基本的指令与操作方法,这学期的课程中终于运用上了,感觉到收获知识的满足感。课程的步骤是先建立起内核的大体框架,让我们理解各个子系统的设计理念和构建思想,这些理念让我有一个清晰的脉络,然后,给我们一项一项讲解、实践具体的实现方法、函数,我们就了解到这些函数,针对的是哪些设计思想,实现了什么样的功能,达成了什么样的目的,运用起来也就更加得心应手。
一、博客列表
第一周 计算机是如何工作的
- 冯·诺依曼结构模型:冯·诺依曼结构也称普林斯顿结构,是一种将程序指令存储器和数据存储器合并在一起的存储器结构。程序指令存储地址和数据存储地址指向同一个存储器的不同物理位置,因此程序指令和数据的宽度相同。
-
几个重要的汇编指令:push、pop、call、ret
1、pushl %eax 把eax压栈到堆栈栈底 即首先把esp减4 esp表示堆栈栈顶 ebp表示堆栈基址 2、popl %eax 把eax从堆栈栈顶取32位,放在寄存器eax中 即首先把栈顶esp的数值放在eax中,再把栈顶加4 3、call 0x12345 调用该地址 即将当前的eip(当前CPU执行命令的指针)压栈,赋给eip一个新值(CPU下一条执行的指令) 4、ret 即将call时保存的eip还原到eip寄存器,return call之前的那条指令 eip(*)这个*指程序员不能直接修改eip
第二周 操作系统是如何工作的
- 计算机“三宝”:存储程序计算机、函数调用堆栈和中断机制。
-
•cs : eip:总是指向下一条的指令地址 • 顺序执行:总是指向地址连续的下一条指令 • 跳转/分支:执行这样的指令的时候,cs : eip的值会 根据程序需要被修改 • call:将当前cs : eip的值压入栈顶,cs : eip指向被 调用函数的入口地址 • ret:从栈顶弹出原来保存在这里的cs : eip的值,放 入cs : eip中
第三周 构造一个简单的Linux系统MenuOS
-
操作系统两把宝剑:
1、中断上下文的切换:保存现场和恢复现场
2、进程上下文的切换
-
使用gdb跟踪调试内核
输入以下命令
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -S -s
-S表示:在CPU初始化之前,冻结CPU
-s表示在:1234端口上创建一个tcp接口
第四周 扒开系统调用的“三层皮”
-
如何区分用户态、内核态
CPU每条指令的读取都是通过cs:eip(代码段选择寄存器:偏移量寄存器)这两个寄存器,由硬件完成判断。
内核态时,cs与eip的值可以访问任意地址
用户态时,cs与eip只可以访问0x00000000—0xbfffffff的地址空间
-
系统调用的三层皮
一层皮:API
二层皮:中断向量对应的中断服务程序
三层皮:系统调用对应的很多不同种类的服务程序
第五周 扒开系统调用的“三层皮”(下)
三)系统调用在内核代码中的处理过程
-
系统调用在内核代码中的工作机制和初始化
整个系统调用过程中,时间很重要。
以system_call为例,int 0x80指令与systemcall是通过中断向量联系起来的,而API和对应的sys是通过系统调用号联系起来的
用户态时,系统调用xyz()使用int 0x80,它对应调用system_call
右边的处理过程(汇编代码)非常重要,通过系统调用号匹配起来
分析system_call中断处理过程
执行rm menu -rf,强制删除原有的menu文件夹,使用git命令更新menu代码至最新版。
在test.c中添加C函数、汇编函数
make rootfs,输入help,可以看到qemu中增加了之前添加的命令
进入gdb调试
设置断点
c运行
单步执行
第六周 进程的描述和进程的创建
-
操作系统三大功能
- 进程管理
- 内存管理
- 文件系统
0号进程是手工写入它的进程描述符数据,1号进程的创建是复制了0号进程的PCB,根据1号进程的需要,修改PID,加载一个init可执行程序。
-
创建一个新进程在内核中的执行过程
fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建。
Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架:
- 复制一个PCB——task_struct
- 要给新进程分配一个新的内核堆栈
- 要修改复制过来的进程数据,比如pid、进程链表等等都要改改,见copy_process内部。
- 从用户态的代码看fork();函数返回了两次,即在父子进程中各返回一次,父进程从系统调用中返回比较容易理解,子进程从系统调用中返回。那它在系统调用处理过程中的哪里开始执行的呢?这就涉及子进程的内核堆栈数据状态和task_struct中thread记录的sp和ip的一致性问题,这是在哪里设定的?copy_thread in copy_process
第七周 可执行程序的装载
-
可执行程序是怎么来的
C代码—>预处理—>汇编代码—>目标代码—>可执行文件
- ELF中三种目标文件
- 一个可重定位(relocatable)文件保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件。(主要是.o文件)
- 一个可执行(executable)文件保存着一个用来执行的程序;该文件指出了exec(BA_OS)如何来创建程序进程映象。
- 一个共享object文件保存着代码和合适的数据,用来被下面的两个链接器链接。第一个是连接编辑器[请参看ld(SD_CMD)],可以和其他的可重定位和共享object文件来创建其他的object。第二个是动态链接器,联合一个可执行文件和其他的共享object文件来创建一个进程映象。(主要是.so文件)
-
execve和fork都是特殊的系统调用
- 正常的系统调用:陷入到内核态,返回到用户态,执行系统调用的下一条指令。
- fork:进入到内核态,两次返回:第一次返回到父进程的位置,继续执行。第二次,在子进程中从ret_from_fork开始执行然后返回用户态。
- execve:当前的可执行程序执行到execve时,陷入到内核态,用execve加载的可执行文件将当前的可执行程序覆盖掉,当execve系统调用返回时,返回的不是原来的系统调用,而是新的可执行程序的执行起点,即main函数的位置。
第八周 进程的切换和系统的一般执行过程
-
进程分类
- 第一种分类
- I/O-bound:等待I/O
- CPU-bound:大量占用CPU进行计算
- 第二种分类
- 交互式进程(shell)
- 实时进程
- 批处理进程
- 第一种分类
- 调度策略:是一组规则,它们决定什么时候以怎样的方式选择一个新进程运行
Linux的调度基于分时和优先级。
-
- Linux的进程根据优先级排队
- 根据特定的算法计算出进程的优先级,用一个值表示
- 这个值表示把进程如何适当的分配给CPU
- Linux进程中的优先级是动态的
- 调度程序会根据进程的行为周期性地调整进程的优先级
- 例如:
- 较长时间未被分配到cpu的进程,通常↑
- 已经在cpu上运行了较长时间的进程,通常↓
- Linux的进程根据优先级排队
-
- 内核中的调度算法相关代码使用了类似OOD中的策略模式
- 分析schedule函数
- schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换
-
-
-
next = pick_next_task(rq, prev);//进程调度算法都封装这个函数内部
-
context_switch(rq, prev, next);//进程上下文切换
-
switch_to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程
-
-
二、课本同步自学笔记
《Linux内核设计与实现》
第一章 Linux内核简介 http://www.cnblogs.com/bonjourvivi/p/5281471.html
第二章 从内核出发 http://www.cnblogs.com/bonjourvivi/p/5281825.html
第三章 进程管理 http://www.cnblogs.com/bonjourvivi/p/5340542.html
第四章 进程调度 http://www.cnblogs.com/bonjourvivi/p/5380584.html
第五章 系统调用 http://www.cnblogs.com/bonjourvivi/p/5297783.html
第十八章 调试 http://www.cnblogs.com/bonjourvivi/p/5330939.html
《深入理解计算机系统》
第七章 http://www.cnblogs.com/bonjourvivi/p/5355861.html
三、收获与不足
最大的收获:
这是我采用翻转课堂学习方式的第三个学期,在逐渐了适应了这种学习方式的同时,这门课程也一个向导,伴随着之前学习的Linux知识的基础,把我带入Linux内核的世界。这门课不仅教会了我知识,还教会了我学习地方法。从结构入手,就像一个去除了枝枝叶叶的大树的主干,一目了然。继而分析一个个函数,调试跟踪,分析问题的时候更有针对性,更便于找到原因。
最大的遗憾:
由于我的编程功底比较弱造成了一些知识上理解的困难,例如对汇编嵌入C语言代码的写法理解不到位,还有调试的时候只是做到了很浅显的步骤,并没有深入。归根到底还是我之前学习的部分知识有欠缺,所以导致现在学习中会有吃力的现象。我会努力补上之前的短板,更好的学习深入的课程。
20135308原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000