Linux内核学习总结

时间:2021-03-18 15:48:31

刘森林 原创作品转载请注明出处
《Linux内核分析》MOOC课程

博客目录

第一周:计算机是如何工作的
第二周:完成一个简单的时间片轮转多道程序内核代码
第三周:Linux内核启动分析过程
第四周:使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
第五周:分析system_call中断处理过程
第六周: 分析fork函数对应的系统调用处理过程
第七周:分析exec*函数对应的系统调用处理过程
第八周: Linux进程调度——schedule()函数分析

课程总结

1、计算机是如何工作的

主要介绍一些基础知识,包括汇编语言基础知识、C语言函数调用过程中的堆栈变化和参数传递过程。

  • 存储程序计算机工作模型——冯诺依曼体系结构。
  • 程序:告诉计算机操作的步骤、输入的数据、如何存放处理后的结果。
  • 汇编语言:汇编语言是机器语言的一种翻译。

2、完成一个简单的时间片轮转多道程序内核代码

简单模拟内核代码。主要包括函数调用堆栈、函数堆栈框架、内核的初始化、中断、进程上下文切换过程的简述以及基于时间片轮转的多道程序模拟。

  • 操作系统三个法宝:存储程序计算机、函数调用堆栈、中断机制。
  • 操作系统两把宝剑:中断上下文、进程上下文的切换。
  • 中断:多道程序设计,可以多个程序同时运行,当一个中断发生时,由CPU和内核代码共同实现了保存现场和恢复现场。
  • 操作系统核心功能:进程调度和中断机制,通过与硬件的配合实现多任务处理,再加上上层应用软件的支+ 持,最终变成可以使用户可以很容易操作的计算机系统。
  • 进程切换:当正在运行的进程等待其他的系统资源时,Linux内核将取得CPU的控制权,并将CPU分配给其他正在等待的进程。进程切换机制中包含esp的切换、堆栈的切换。

3、Linux内核启动分析过程

这一周内,我们学习了阅读内核源码和构造Linux系统MenuOS,主要包括源码主要结构、内核启动及调试方法和start_kernel的解读。我们总结为:道生一(start_kernel–>cpu_idle),一生二(kernel_init和kthreadd),二生三(即前面0、1和2三个进程),三生万物(1号进程是所有用户态进程的祖先,2号进程是所有内核线程的祖先)

  • 内核启动完成进入menu程序,支持三个命令quit、version、help
  • ipc和进程通信相关的目录

4、使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用

关于系统调用的知识。主要包括对系统调用与用户态、内核态的理解、通过编写汇编代码了解系统调用机制。

  • Intel x86 CPU有四种不同的执行级别0-3,Linux只使用了0级和3级分别表示内核态和用户态。
  • 0xc0000000以上的逻辑地址空间只能在内核态下访问,0x00000000-0xbfffffff的逻辑地址空间在两种状态下都能访问。
  • 中断处理是从用户态进入内核态的主要方式,而系统调用只是一种特殊的中断。
    中断处理的完整过程:
    Linux内核学习总结
  • 操作系统提供的API(应用编程接口)和系统调用的关系:并非一一对应
  • 系统调用的三层皮:xyz、system_call和sys_xyz
    Linux内核学习总结
  • 在Linux中通过int$0x80来执行系统调用——产生向量为128的编程异常
  • 进程需要通过传递一个名为系统调用编号的参数来指明需要哪个系统调用(通过eax寄存器来实现传递)——系统调用号将xyz和sys_xyz关联起来
  • system_call是Linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由eax传递的系统调用号

5、分析system_call中断处理过程

本周实验需要我们添加一个自己编写的系统调用,加深对系统调用在内核代码中的处理过程的理解。

  • 第一步,SAVE_ALL保存现场。
  • 第二步,确定中断信息,将系统调用号通过eax传入,通过sys_call_table查询到调用的系统调用,然后跳转到相应的程序进行处理。
  • 第三步,处理中断。
  • 第四步,RESTORE_ALL恢复系统调用时的现场,iret返回用户态

6、分析fork函数对应的系统调用处理过程

这周我们主要学习了关于进程的描述和创建。主要包括PCB的组织形式、进程的数据结构进程描述符、fork系统调用的关键执行过程。

  • 操作系统三大功能:进程管理(核心)、内存管理、文件系统
  • PCB task_struct中包含:进程状态、进程打开的文件、进程优先级信息
  • 进程控制块PCB——进程描述符task_stuck提供了内核所需了解的进程信息
  • 进程的创建:start_kernel …cpu_idle –>kernel_init和kthreadd –>0、1、2号进程(其中1号进程是所有用户线程的祖先,2号进程是所有内核线程的祖先)
  • fork()系统调用:在用户态创建一个子进程,在父进程和子进程中各会返回一次,在子进程中pid的返回值为0,在父进程中的返回值为子进程的pid
  • p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令

7、分析exec*函数对应的系统调用处理过程

本周我们学习了关于程序的加载过程和ELF文件格式。主要包括得到一个可执行程序过程、ELF文件格式的结构和静态链接、可执行程序的静态加载过程和动态加载过程。

  • 链接器的两个任务:符号解析、重定位
  • 目标文件的三种形式:可重定位目标文件(编译器和汇编器可生成)、可执行目标文件(链接器可生成)、共享目标文件(编译器和汇编器可生成)
  • 新的可执行程序起点——一般是地址空间为0x8048000或0x8048300
  • execve执行静态链接程序时,通过修改内核堆栈中保存的eip的值作为新进程的起点
  • 动态连接有两种形式:可执行程序装载时动态连接和运行时动态链接
  • 运行时动态装载链接至少需要用到dlopen、dlsym函数

8、Linux进程调度——schedule()函数分析

本周我们主要学习了关于进程切换和整体执行过程。主要包括Linux进程调度算法简介、进程切换代码分析、switch_to理解、进程相关的数据结构简析。

  • Linux进程调度是基于分时和优先级的
  • Linux中,内核线程是只有内核态没有用户态的特殊进程
  • 内核可以看作各种中断处理过程和内核线程的集合
  • 用户态进程无法主动调度,只能通过系统调用或其他中断陷入内核的时机进行调度
  • Linux中,内核线程可以主动调度,主动调度时不需要中断上下文的切换
  • Linux内核调用schedule()函数进行调度,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换。
  • 进程调度的时机——schedule()函数实现调度

感想

收获

  • 对于linux的基础知识有了一定的了解,有些东西是之前一直想学习的却没有机会
  • 学习了使用系统调用以及gdb调试工具等,为以后的学习打下了基础
  • 之前对于操作系统的很多疑惑,例如进程创建切换,都在这门课得到了一定的了解

遗憾

  • 依然有很多不理解的地方,没有来得及去学习,很多难点值的这门课结束之后慢慢钻研