第9节 Linux内核学习总结【Linux内核分析】

原创作品转载请注明出处 +《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

前言:课程概述

本课程从理解计算机硬件的核心工作机制(存储程序计算机和函数调用堆栈)和用户态程序如何通过系统调用陷入内核(中断异常)入手,通过上下两个方向双向夹击的策略,并利用实际可运行程序的反汇编代码从实践的角度理解操作系统内核,然后开始分析Linux内核源代码,从系统调用陷入内核,进程调度与进程切换,最后返回到用户态进程,通过仔细分析梳理这一过程,并推广到硬件中断、缺页异常等内核执行路径,最终能从本质上把握Linux内核的实质,乃至在头脑中演绎Linux系统的运行过程。

一、Linux系统的理解

1.Linux架构

首先,一张典型的Linux操作系统架构图镇楼

这里写图片描述

上图来自孟老师视频中的截图

2.自己的理解

1) Linux操作系统工作过程中最主要的事情就是进程的切换,在进程切换的过程中会发生中断处理,这时会有内核堆栈的切换,汇编代码是理解的关键。其中中断和中断返回过程会有一个CPU上下文的切换;在进程调度的过程中有一个进程上下文的切换,即从一个进程的内核堆栈切换到另一个进程的内核堆栈。

在理解进程切换的工作过程时,可以利用gdb命令对内核代码设置断点进行调试跟踪,其中switch_to、sys_clone、do_fork等等都是很重要的函数。

2) 在运行某个进程的时候,系统会先调用gets从用户态进入内核态,把各种信息压栈之后,进入系统调用等待键盘的输入,此时它处于阻塞态。在等待的过程中CPU可能会先调度到执行其他的进程,当有键盘输入后,就会发生I/O中断,然后就可以调度回一开始的进程。当前进程执行完之后可能会进入idle,也可能会执行其他进程。

在进程等待的过程中,若键盘输入了ls,CPU就会开始执行中断处理程序,进程管理就会切换到此进程,当gets系统调用获得数据后就会返回到用户态,继续执行下面的指令。

二、课程进行知识点总结

第一节、计算机是如何工作的

计算机的基本原理是存储程序和程序控制。预先要把指挥计算机如何进行操作的指令序列(称为程序)和原始数据通过输入设备输送到计算机内存贮器中。每一条指令中明确规定了计算机从哪个地址取数,进行什么操作,然后送到什么地址去等步骤。

计算机在运行时,CPU就是一直不停的工作,只要有指令产生,就依次执行,直至遇到停止指令。内存中存放着各种指令和数据,总线将两者相连接。

第二节、操作系统是如何工作的

本节首先总结了一下linux操作系统的工作方式,并且了解到了计算机工作的三大法宝和两把宝剑,而且了解了堆栈的工作方式和如何在C语言中嵌套汇编语言的方法。

懂得了如何在Linux系统中查看相应的代码,然后进行了一段时间片轮转的操作系统内核代码的分析,跟着学习视频一步步的进行学习,虽然这样的分析挺难的,但是还是成功的完成了。

通过本讲的学习和实验,我们知道操作系统的核心功能就是:进程调度和中断机制,通过与硬件的配合实现多任务处理,再加上上层应用软件的支持,最终变成可以使用户可以很容易操作的计算机系统。

附上三大法宝&两把宝剑

三个法宝

  1. 存储程序计算机
  2. 函数调用堆栈
  3. 中断机制。

操作系统两把宝剑
- 中断上下文的切换(保存现场和恢复现场)
- 进程上下文的切换

第三节、构造一个简单的Linux系统MenuOS

本节首先是带着我们看了一下Linux内核源代码的总体结构,从同学那里发现一张图很赞,就拿过来了。

这里写图片描述

图片来源:跟踪分析Linux内核的启动过程,侵删

重点分析Linux内核的启动过程,主要介绍了Linux下3个特殊的生成,idle进程(PID = 0), init进程(PID = 1)和kthreadd(PID = 2)。通过使用gdb对内核代码进行跟踪,使我们对内核的启动有一个感性的认识。

系统进入start_kernel这个函数之前已经进行了一些最低限度的初始化,再往前研究就涉及很多硬件相关及编程语言了。内核即进入了C语言部分,它完成了内核的大部分初始化工作。实际上,可以将start_kernel函数看做内核的main函数。

系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构模型。整个linux系统的所有进程也是一个树形结构。

在本次实验中我初步体会了Linux系统的启动过程,虽然目前大多数都是跟着老师在做,但是看到自己能真实的跟踪和分析代码,算是走出了内核分析的第一步。

第四节、扒开系统调用的三层皮(上)

既然题目使扒开系统调用的三层皮,那就引用老师的图来说明具体是哪三层皮

这里写图片描述

本节实验要求是使用库函数API和嵌入汇编代码两种方式来实现一个系统调用。系统调用是用户态与内核态的桥梁,而具体的措施就是中断。通过本实验,主要是熟悉了系统调用的本质,以及系统调用和中断的关联。应用程序在用户态调用API函数,该函数将对应的系统调用号及参数保存,触发软中断,然后陷入内核态,system_call根据系统调用号调用对应的内核函数,内核函数执行完毕后将结果存放的eax中并返回给程序,程序返回的用户态。

第五节、扒开系统调用的三层皮(下)

本节主要是分析system_call具体的实现过程,主要的分如下几步:

  1. 执行int 0x80指令后系统从用户态进入内核态,跳到system_call()函数处执行相应服务进程。在此过程中内核先保存中断环境,然后执行系统调用函数。
  2. system_call()函数通过系统调用号查找系统调用表sys_cal_table来查找具体系统调用服务进程。
  3. 执行完系统调用后,iret之前,内核会检查是否有新的中断产生、是否需要进程切换、是否学要处理其它进程发送过来的信号等。
  4. 内核是处理各种系统调用的中断集合,通过中断机制实现进程上下文的切换,通过系统调用管理整个计算机软硬件资源。
  5. 如没有新的中断,restore保存的中断环境并返回用户态完成一个系统调用过程。

做了个图看得更直观些

这里写图片描述

第六节、分析Linux内核创建一个新进程的过程

从最开始用线上的虚拟机,到后面用了本地的虚拟机,这次实验的时候终于解决了本地化问题,在本机上做实验,再也不用面对系统的是不是抽风了,开森。

本章的具体内容其实是对前面章节的深化,操作系统内核三大功能: 进程管理(核心)、内存管理、文件系统。

创建一个新进程在内核中的执行过程
1) 使用系统调用sys_clone(或fork,vfork)系统调用创建一个新进程,而且都是通过调用do_fork来实现进程的创建;
2) Linux通过复制父进程PCB的task_struct来创建一个新进程,要给新进程分配一个新的内核堆栈;
3) 要修改复制过来的进程数据,比如pid、进程链表等等执行copy_process和copy_thread
4) p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
5) p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址

第七节、 Linux内核如何装载和启动一个可执行程序

本节主要是对exec相关的函数进行研究,首先是引用课程截图,展示可执行文件的生成过程:

这里写图片描述

图片来自孟老师视频截图

2. do_ execve

do_ execve调用do_ execve_ common,do_ execve_ common主要依靠exec_ binprm,其中重要的函数:search_binary_handler(bprm)。

新的可执行程序是从new_ip开始执行,start_thread实际上是把返回到用户态的位置从Int 0x80的下一条指令,变成了规定的新加载的可执行文件的入口位置,即修改内核堆栈的EIP的值作为新程序的起点。

当一个进程准备执行到execve系统调用前,该进程会首先fork出一个子进程,然后由子进程去执行execve系统调用,陷入内核态,用execve加载的可执行文件会覆盖当前子进程的可执行程序,相当于进程找了个替罪羔羊,自己逍遥法外。当execve系统调用返回时,返回新的可执行程序的执行起点,execve系统调用返回后新的可执行程序能顺利执行。

对于静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时,如果是静态链接,elf_entry指向可执行文件规定的头部(main函数对应的位置0x8048***);如果需要依赖动态链接库,elf_entry指向动态链接器的起点。动态链接主要是由动态链接器ld来完成的。

第八节、进程的切换和系统的一般执行过程

本次课程学习了操作系统如何进行进程的切换以及系统的一般执行过程。其中还是脱离不出中断的使用,进程切换最主要的时机就在于中断的过程,对关键函数switch_to的跟踪分析以及函数解读清晰地展示了堆栈如何变化的。

首先是描述了进程调度的时机:

1) 中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule();
2) 内核线程(只有内核态没有用户态)可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度;
3) 用户态进程无法实现主动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。

注意:用户态进程只能被动调度,内核线程是只有内核态没有用户态的特殊进程。

后面着重分析了switch_to从A进程切换到B进程的步骤,需要说明的一点是,switch_to是宏定义,这里不是正常的函数调用,没有switch_to的返回地址,ebp的位置是调用switch_to的函数的堆栈的基址。

顺便推荐这篇文章,对于理解x86体系结构下Linux-2.6.26的进程调度和切换非常有帮助:进程调度和切换

三、学习心得体会

通过半个学期的学习,要说我认为重要的不是学习到了多少内核代码(其实也很重要),毕竟仅仅是看了视频,做了几个实验,如果这就能把内核代码都搞懂,那不是学生是天才,就是老师是天才了;我觉得最重要的收获是学习Linux的方法,即从何处着手学习Linux内核,例如:如何调试内核、如何看懂内核中的汇编代码,如何分析系统调用等。这也是我学习之后最大的收获。另外就是一些有助于分析内核的工具,包括qemu、gdb等等,总之,虽然网课结束了但学习还远远没有结束。课程的实践性很强,在这里,其实老师更多的是一种启发式的学习,很多东西还是需要自己去领悟和实践

这里面我还学到了一个很有用的学习新知识的方法,就是先分析假设后寻找证据证明自己的猜想,这个过程其实是很棒的一个探究学习的体验。

课程方面,这是一门久经考验的课程,人气很高,在学完整个一个学期后,感觉学习过程很连贯,包括实验、测验、博客,设计相对来说比较合理。不过感觉后面部分,特别是后两章,视频中讲述的不是很详细,看完后需要查询很多资料才能理解和掌握操作系统操作进程的过程。比如进程切换那部分,虽然有个例子在那里,但是最好还能有更详细一些的讲解,否则,很多同学可能在那个地方对“一次调用两次返回”理解不深刻,无法体会到进程切换的奥秘。

附录:每周博客作业链接汇总

第1节 反汇编一个简单的C程序【Linux内核分析】

第2节 一个简单的时间片轮转多道程序内核代码【Linux内核分析】

第3节 跟踪分析Linux内核的启动过程【Linux内核分析】

第4节 使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用【Linux内核分析】

第5节 分析system_call中断处理过程【Linux内核分析】

第6节 分析Linux内核创建一个新进程的过程【Linux内核分析】

第7节 Linux内核如何装载和启动一个可执行程序【Linux内核分析】

第8节 理解进程调度时机跟踪分析进程调度与进程切换的过程【Linux内核分析】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值