首页 > 技术文章 > 《Linux内核分析》期末总结

gyt0520 2016-04-26 11:55 原文

                     《Linux内核分析》期末总结

                                                    20135109 高艺桐

      《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 

 

一、博客地址汇总

第一周学习笔记计算机是如何工作的

第二周学习笔记深入理解计算机

第三周学习笔记构造一个简单的Linux内核的MenuOS

第四周学习笔记扒开系统调用的三层皮(上)

第五周学习笔记扒开系统调用的三成皮(下)

第六周学习笔记进程的描述和进程的创建

第七周学习笔记可执行程序的装载

第八周学习笔记进程的切换和系统的一般执行过程

二、知识点总结

(一)计算机是如何工作的

  • 冯诺依曼体系结构——核心:存储程序计算机
  • x86汇编基础:CPU的寄存器(通用寄存器、段寄存器、标志寄存器)、常见汇编指令、堆栈

(二)深入理解计算机

  • 存储程序计算机:所有计算机基础性的逻辑框架
  • 堆栈:高级语言的起点,函数调用需要堆栈机制
  • 中断机制:多道系统的基础,是计算机效率提升的关键
  • 在my_schedule函数中,完成进程的切换。其中进程的切换分两种:1.下一个进程没有被调度过;2.下一个进程被调度过,可以通过下一个进程的state知道其状态

(三)构造一个简单的Linux内核的MenuOS

  • Linux内核源代码简介:

    arch/x86目录下的代码是我们最重点关注的

    fs/文件系统

    init/内核启动相关的代码

    start_kernel函数相当于C语言的main函数

    kernel/Linux内核的核心代码

    mm/内存管理代码

  • 简单分析一下Start_Kernel:

    init_task 即手工创建的PCB,0号进程即最终的Idle进程

    trap_init 初始化中断,设置中断门,系统陷阱门

    init_process Linux系统的第一个用户态进程,根目录下的init程序(作为1号进程)由kernel_init创建

    rest_init 0号进程,一直存在的进程,创建1号进程

  • qemu命令是模拟内核启动虚拟机,启动Linux内核需要三个参数(kernel、initrd、root所在的分区和目录),执行的第一个文件是init。

  • 启动过程为:启动内核->启动init->启动进程

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

  • 系统调用的三层皮:API xyz,中断向量system_call,中断服务程序sys_xyz
  • 中段处理的过程:首先保存cs:eip的值;保存当前堆栈段、栈顶、标志寄存器;加载系统调用或者系统调用的中段服务历程入口。之后执行内核代码、完成中断服务、发生进程调度

  • 系统调用号:使用eax寄存器进行传递;系统调用参数传递:每个参数长度不超过寄存器长度,参数长度不超过6个,超过6个把某一个寄存器设为指针作为内存进行传递
  • 使用库函数API和C代码中嵌入汇编代码触发同一个系统调用

  • (1)使用库函数API获取系统当前时间
    (2)C代码中嵌入汇编代码的方法
    (3)使用C代码中嵌入汇编代码触发系统调用获取系统当前时间

(五) 扒开系统调用的三成皮(下)

  •  系统调用的处理过程简化代码重要语句分析:
    (1)syscall_exit_work判断当前任务是不是需要处理
    (2)work_pending需要处理信号(work_notifysig)
    (3)work_resches需要重新调度
    (4)调度的过程中可能发生中断上下文的切换和进程上下文的切换
    (5)内核:很多种中断处理上下文的集合
  • 简单浏览system_call到iret之间重要语句分析:
  • (1)save_all保存现场
    (2)sys_call_table调用系统调用对应的处理函数
    (3)work_pending需要处理信号(work_notifysig)
    (4)call schedule(决定进程调度的代码都在schedule中)
    (5)restore all恢复现场
    (6)INTERRMPT RETURN系统调用结束
  • 使用gdb跟踪:

        (1)make rootfs:自动编译,生成根文件系统,自动启动

        (2)(gdb)list 查看代码

        (3)(gdb)s 单步调试进入函数体(4)(gdb)n 单步调试不进入函数体

(六)进程的描述和进程的创建

  • 操作系统的三大管理功能:进程管理、内存管理、文件系统
  • fork()用户态进行创建子进程,fork系统调用在父进程和子进程各返回一次
  • 子进程从哪里开始执行的:ret_from_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; //调度到子进程时的内核栈顶

       (5)p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址

  • start_ kernel创建了cpu_ idle,也就是0号进程。而0号进程又创建了两个线程,一个是kernel_ init,也就是1号进程,这个进程最终启动了用户态;另一个是kthreadd。0号进程是固定的代码,1号进程是通过复制0号进程PCB之后在此基础上做修改得到的
  • iret与int 0x80指令对应,一个是弹出寄存器值,一个是压入寄存器的值
  • 如果将系统调用类比于fork();那么就相当于系统调用创建了一个子进程,然后子进程返回之后将在内核态运行,而返回到父进程后仍然在用户态运行

(七)可执行程序的装载

  • 可执行程序是怎么得来的:C代码经过编译器的预处理编译、编译成汇编代码、编译器将其编译成目标代码、链接成可执行文件
  • Linux内核装载和启动一个可执行程序

       (1)创建新进程

       (2)新进程调用execve()系统调用执行指定的ELF文件

       (3)调用内核的入口函数sys_execve(),sys_execve()服务例程修改当前进程的执行上下文

  • ELF格式中主要有3种可执行文件:可重定位文件.o,可执行文件,共享目标文件

  • ELF可执行文件会被默认映射到0x8048000这个地址

  • 命令行参数和环境变量是如何进入新程序的堆栈的

    Shell程序-->execve-->sys_execve,然后在初始化新程序堆栈时拷贝进去。

    先函数调用参数传递,再系统调用参数传递

  • 当前程序执行到execve系统调用时陷入内核态,在内核中用execve加载可执行文件,把当前进程的可执行文件覆盖掉,execve系统调用返回到新的可执行程序的起点

  • 动态链接库的装载过程是一个图的遍历过程,ELF格式中的.interp和.dynamic需要依赖动态链接器来解析,entry返回到用户态时不是返回到可执行程序规定的起点,返回到动态链接器的程序入口

(八)进程的切换和系统的一般执行过程

  • 进程调度的时机:

  • (1)中断处理过程(包括时钟中断、I/O中断、系统调用和异常),直接调用schedule(),或者通过need_resched标记调度的schedule()
    (2)用户态进程只能被动调度,即在中断处理过程中进行调度
    (3)内核线程可以直接调用schedule(),也可以在中断处理过程中进行调度,可以主动调度也可以被动调度
  •  进程上下文切换:挂起正在cpu上运行的进程,与中断时保存现场是不同的,中断前后是在同一个上下文中,只是由用户态转向内核态,但是进程切换是两个进程间的切换

        (1)需要切换的信息:用户地址空间、控制信息、硬件上下文(中断也要保存上下文)等

        (2)schedule()中的context_switch中的switch_to切换寄存器和堆栈的状态、eip的位置

        (3)thread.sp内核堆栈的栈底;thread.ip当前进程的eip

        (4)next进程的第一条指令:next_ip一般是$1f,对于新创建的子进程是ret_from_fork

  • switch_to做了关键的进程上下文的切换
  • restore_all恢复现场
  • 0-3G用户态,3gG以上内核态访问
  • Linux系统执行过程中的几个特殊情况

    (1)内核线程通过中断处理过程中(用户态进程与内核线程间的切换以及内核进程和内核进程间的切换)没有权限的变化,cs段没有发生变化

    (2)内核线程主动调度schedule(),只有进程上下文的切换,没有发生中断上下文的切换

    (3)创建子进程的系统调用,子进程返回时的执行起点为ret_form_fork(next_ip=ret_form_fork)

三、心得体会

  通过八周的Linux内核分析课程的学习,我对Linux内核是如何工作的学习到了很多知识。每个星期按时的听课件做习题写博客成为了我逐渐养成的习惯。起初我对老师讲的一些专业的词汇还很迷茫,到现在我可以用自己的话解释那些专业术语;从动态的演示堆栈的变化到现在我可以熟练的在脑中形成一个变化图;从对代码的陌生到可以熟练的做出实验部分,包括克隆、追踪、设置断点等基本的操作;从对一些比较抽象概念的懵懂到听老师通过比喻变得更加清晰化,知道了他们彼此之间的联系与区别。

  学习对Linux内核的分析的学习,我更加的了解了计算机的工作的原理,对进程的切换、系统的调用都有很深入的理解。通过老师设置的测试+互评+自评的学习模式,我更好的学习到了别人的长处,也在这八周内不断的完善自己。

  最后感谢孟宁老师的细心讲解,让我对Linux内核的工作原理有了深入的学习。

 

推荐阅读