zoukankan      html  css  js  c++  java
  • 20135220谈愈敏Linux_总结

    Linux_总结

    具体博客链接

    一、计算机是如何工作的

    存储程序计算机工作模型

    • 冯诺依曼体系结构
      • 从硬件角度来看:CPU和内存,由总线连接,CPU中有一个名为IP的寄存器,总是指向内存的某一块:CS,代码段,执行命令时就取IP指向的一条指令,然后IP自加1,就指向下一条指令。
      • 从程序员角度来看:即存储程序计算机,内存存储数据和指令,CPU就是一个for循环,总是在执行下一条指令,CPU负责解释和执行这些指令。

    X86汇编基础

    • CPU寄存器
      • 通用寄存器+段寄存器+标志寄存器
    • X86汇编指令
      • mov指令:b,w,l,q分别代表8位,16位,32位,64位
      • 寻址方式:寄存器寻址,立即数寻址,直接寻址,间接寻址,变址寻址
      • 汇编指令:push,pop,call,ret
      • leave
    • 分析汇编指令片段

    汇编一个简单的C程序分析其汇编指令执行过程

    • 汇编一个简单的C程序
    • C程序和对应的汇编指令
    • 完整汇编程序执行过程分析

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

    函数调用堆栈

    • 计算机三大法宝
      • 存储程序计算机工作模型
      • 函数调用堆栈
      • 中断
    • 深入理解函数调用堆栈
      • 函数调用框架
      • 传递参数
      • 保存返回地址
      • 提供局部变量空间
    • 堆栈相关寄存器:esp堆栈指针ebp基址指针,记录当前函数调用基址
    • 堆栈操作:push,pop
    • cs:eip:总是指向下一条的指令地址,执行call的时候,保存下一条指令地址到栈顶,然后cs:eip指向调用函数入口地址。
    • 参数传递与局部变量

    借助Linux内核部分源代码模拟存储程序计算机工作模型及时钟中断

    • mykernel实验背后涉及的思想
    • 利用mykernel实验模拟计算机硬件平台
    • 由CPU和内核代码共同实现了保存现场和恢复现场

    在mykernel基础上构造一个简单的操作系统内核

    • C代码中嵌入汇编代码的写法
    • 一个简单的操作系统内核源代码
      • mypcb.h:头文件
      • mymain.c: 内核初始化和进程的启动
      • myinterrupt.c:进程的切换
    • 运行这个精简的操作系统内核

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

    Linux内核源代码简介

    • 操作系统两把宝剑
      • 中断上下文的切换
      • 进程上下文的切换
    • Linux内核源代码

    构造一个简单的Linux系统

    • 构造一个简单的Linux系统MenuOS
      • qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
        • qemu:启动一个虚拟机平台
        • -kernel:给一个内核,操作系统
        • -initrd:驱动所需的硬盘
        • rootfs.img:放一个可执行文件由menuOS源代码编译成的init

    跟踪调试Linux内核的启动过程

    • 使用gdb跟踪调试Linux内核的方法
      • qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
        • -S freeze CPU at startup (use ’c’ to start execution)指在CPU初始化之前(刚启动的时候)将其冻结
        • -s shorthand for -gdb tcp::1234 指在1234这个端口上创建的gdb server,若不想使用1234端口,则可以使用-gdb tcp:xxxx来取代-s选项
      • gdb
      • (gdb)file linux-3.18.6/vmlinux # 在gdb界面中targe remote之前加载符号表
      • (gdb)target remote:1234 # 建立gdb和gdbserver之间的连接,按c 让qemu上的Linux继续运行
      • (gdb)break start_kernel # 断点的设置可以在target remote之前,也可以在之后
        • 符号:全局函数名,全局变量名,汇编时没有,不需要符号只是一个地址,但调试时需要,所以有符号表:符号与在内存中地址的一一对应。
    • 简单分析一下start_kernel
      • 全局变量init_task,即手工创建的PCB,0号进程即最终的idle进程
      • trap_init涉及到中断,初始化一些中断向量
      • mm_init:内存管理模块初始化
      • sched_init:调用进程调度初始化
      • rest_init中:
        • kernel_init:创建1号进程
        • 第一个用户态进程:init_process,1号进程,找默认路径下的程序作为1号进程
        • kthreadd:内核线程,用来管理系统资源
        • 当系统没有进程需要执行时就调度idle进程,即0号进程,一直存在。

    四、系统调用

    用户态、内核态和中断

    • 用户态&内核态(CPU执行级别)
      • 内核态:高级别执行,可以使用特权指令,访问任意的物理地址。对应x86 0级
      • 用户态:低级别执行,代码范围受到限制。对应x86 3级(x86CPU有0-3四个级别)
    • 如何区分用户态&内核态?
      • 0xc0000000以上的地址只能在内核态下访问
      • 0x00000000-0xbfffffff两种状态都行
    • 中断处理是从用户态进入内核态主要的方式
      • 从用户态进入内核态:必须保存用户态的寄存器上下文
      • 中断/int指令在堆栈上保存寄存器的值:用户态/内核态栈顶地址(ss:esp)、状态字(eflags)、cs:eip值(内核态时指向中断服务程序入口)

    系统调用概述

    • 系统调用的意义
      • 用户不管硬件编程
      • 提高系统安全性
      • 用户程序可移植
    • API和系统调用
      • API:应用编程接口,是一个函数定义
      • 系统调用:通过软中断向内核发出明确请求
      • 不是每个API都对应一个特定的系统调用
    • 应用程序、封装例程、系统调用处理程序、系统调用服务例程之间的关系
    • 系统调用三层皮:
      • API(xyz)
      • 中断向量(system_call)
      • 中断服务程序(sys_xyz)

    使用库函数API和C代码中嵌入汇编代码触发同一个系统调用

    • 使用库函数API获取系统当前时间
    • 使用C代码中嵌入汇编代码触发系统调用获取系统当前时间
      • 传递了一个系统调用号 - eax
      • 传递了参数 - ebx

    给MenuOS增加time和time-asm命令

    • 更新menu代码到最新版
    • 在main函数中增加MenuConfig(一个命令一行,与上面的格式一样)
    • 增加对应的time和time-asm函数(就是上周写的两个函数)
    • make rootfs (rootfs是一个脚本,可以自动生成,编译)

    使用gdb跟踪系统调用内核函数sys_time

    • c:继续执行,停在断点处
    • n/s:单步运行,s进入函数,n不进入

    系统调用在内核代码中的处理过程

    • int 0x80触发一个系统调用 ->执行系统调用处理函数system_call ->返回到用户态
      • 返回到用户态之前有一个进程调度的时机,会跟踪到schedule
    • 中断向量:int 0x80 ->system_call,初始化时绑定
    • 系统调用号:将xyz和中断服务程序sys_xyz关联起来
    • 系统调用机制的初始化
      • main.c中start_kernel函数:trap_init()
        • set_system_trap_gate(SYSCALL_VECTOR,&system_call)
        • SYSCALL_VECTOR:系统调用的中断向量
        • &system_call:汇编代码入口
    • 理解system_call到iret之间的主要代码
      • 从系统调用入口开始:ENTRY(system_call)
      • SAVE_ALL //保存现场
      • system_call中:call *system_ call_table(,%eax,4) //调用了系统调用处理函数,有系统调用号eax中,是实际的系统调用处理程序。
      • 当前任务syscall_ exit_ work里面有work_ pending里面有work_ notifysig //处理pending信号,不用管
      • 重要的是work_ resched:call schedule //决定了进程调度的代码,调用完会跳转到restore_all
      • restore_all //恢复现场
      • INTERRUPT_ RETURN //irp_return宏,中断处理过程在这结束

    五、进程的描述和进程的创建

    进程的描述

    • 操作系统的三大管理功能:
      • 进程管理(最重要的)
      • 内存管理
      • 文件系统
    • 进程控制块PCB task_struct:
      • 进程状态
      • 进程打开的文件
      • 进程优先级信息
    • 进程描述符task_struct数据结构

    进程的创建

    • 进程的创建概览
      • 道生一(start_ kernel....cpu_idle)
      • 一生二(kernel_init和kthreadd)
      • 二生三(即前面0、1和2三个进程)
      • 三生万物(1号进程是所有用户态进程的祖先,2号进程是所有内核线程的祖先)
    • fork创建一个子进程
      • fork在父进程和子进程各返回一次
    • 创建一个新进程在内核中的执行过程
    • 新进程从哪里开始执行?
      • fork出来的子进程是从ret_from_fork开始执行的,然后跳转到syscall_exit,从系统调用中返回。

    六、可执行程序的装载

    预处理、编译、链接和目标文件的格式

    • 可执行程序是怎么得来的?
      • C代码
      • 编译器预处理
      • 编译成汇编代码
      • 汇编器编译成目标代码
      • 链接成可执行文件
      • 操作系统加载到内存执行
    • 目标文件的格式ELF
    • 静态链接的ELF可执行文件和进程的地址空间
      • 默认加载起始地址是0x8048000
      • ELF头部大小不同,程序入口点也将不同,程序入口在头部有定义:Entry point address,即是可执行文件加载到内存中开始执行的第一行代码

    可执行程序、共享库和动态链接

    • 装载可执行程序之前的工作
      • 可执行程序的执行环境
      • 命令行参数和环境变量是如何保存和传递的?
      • 参数传递过程:shell程序 -> execve系统调用 -> sys_execve 内核处理函数在初始化新程序堆栈时拷贝进去
    • 装载时动态链接和运行时动态链接应用举例
      • 准备.so文件
      • 分别以共享库和动态加载共享库的方式使用libshlibexample.so文件和libdllibexample.so文件

    可执行程序的装载

    • 可执行程序的装载相关关键问题分析
      • 特殊系统调用:fork、execve
      • sys_execve内部会解析可执行文件格式
      • execve系统调用返回到用户态从哪里开始执行?load_ elf_ binary -> start_thread
    • sys_execve的内部处理过程
    • 使用gdb跟踪sys_execve内核函数的处理过程
    • 可执行程序的装载与庄生梦蝶的故事
    • 浅析动态链接的可执行程序的装载

    七、进程的切换和系统的一般执行过程

    进程切换的关键代码switch_to分析

    • 进程调度与进程调度的时机分析
      • 中断处理过程(包括时钟中断、I/O中断、系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule();
      • 内核线程(只有内核态没有用户态的特殊进程)可以直接调用schedule()进行进程切换,也可以在中断处理过程中进行调度,也就是说内核线程作为一类的特殊的进程可以主动调度,也可以被动调度;
      • 用户态进程无法实现主动调度,只能被动调度,仅能通过陷入内核态后的某个时机点进行调度,即在中断处理过程中进行调度。
    • 进程上下文切换相关代码分析
      • 为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,这叫做进程切换、任务切换、上下文切换;
      • 挂起正在CPU上执行的进程,与中断时保存现场是不同的,中断前后是在同一个进程上下文中,只是由用户态转向内核态执行;
      • 进程上下文包含了进程执行需要的所有信息
        • 用户地址空间:包括程序代码,数据,用户堆栈等
        • 控制信息:进程描述符,内核堆栈等
        • 硬件上下文(注意中断也要保存硬件上下文只是保存的方法不同)
      • schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换
        • next = pick_ next_task(rq, prev);//进程调度算法都封装这个函数内部
        • context_switch(rq, prev, next);//进程上下文切换
        • switch_to利用了prev和next两个参数:prev指向当前进程,next指向被调度的进程

    Linux系统的一般执行过程

    • Linux系统的一般执行过程分析
      • 中断上下文的切换(中断和中断返回时CPU进行上下文切换)
      • 进程上下文的切换(进程调度过程中,从一个进程的内核堆栈切换到另一个进程的内核堆栈)
    • Linux系统执行过程中的几个特殊情况
      • 通过中断处理过程中的调度时机,用户态进程与内核线程之间互相切换和内核线程之间互相切换,与最一般的情况非常类似,只是内核线程运行过程中发生中断没有进程用户态和内核态的转换;
      • 内核线程主动调用schedule(),只有进程上下文的切换,没有发生中断上下文的切换,与最一般的情况略简略;//用户态进程不能主动调用
      • fork:创建子进程的系统调用在子进程中的执行起点(next_ ip = ret_ from_ fork)返回用户态,进程返回不是从标号1开始执行,直接跳转到ret_ from_fork执行然后返回到用户态;
      • 加载一个新的可执行程序后返回到用户态的情况,如execve,只是中断上下文在execve系统调用内部被修改了;

    Linux系统架构和执行过程概览

    • Linux操作系统架构概览
    • 最简单也是最复杂的操作--执行ls命令
    • 从CPU和内存的角度看Linux系统的执行

    学习心得

    收获

    还记得第一次看视频的时候是寒假,那时还在家,转眼我已经跟着网络课程学习了两个月,每周固定的看视频、做笔记、写博客、做实验,到现在已经有点习惯这种生活,突然没有视频可学,还有点空虚呢。依稀记得第一次视频老师您还说是除夕,听得到外面的鞭炮声,想想老师也是很辛苦的,很感谢老师能教给我们这么多知识。第一次亲身经历这种学习方式,也体验到了很多好处,比如每周的自评和互评,可以看到很多优秀的博客,加以学习,让自己变得更好,看见差的博客,会指出他的问题,也希望可以帮助到其他人。

    通过八周的学习,第一次系统全面的理解了Linux系统:

    • 首先是进程的创建,由0号进程idle创建其子进程1号进程init和2号进程kthreadd,1号进程是所有用户态进程的祖先,2号进程是所有内核线程的祖先。由fork来创建一个新的子进程
    • 系统调用其实就是一种中断,从用户态转到内核态,发生中断时关键点是中断上下文的切换(中断和中断返回时CPU进行上下文切换)。
    • 由中断返回到用户态之前有一个进程调度时机,可能就会发生进程切换,关键点是进程上下文的切换(进程调度过程中,从一个进程的内核堆栈切换到另一个进程的内核堆栈),调用了schedule(),关键切换由switch_to执行。
    • 进程是依附于程序存在的,可执行程序是通过预编译、编译、汇编、链接生成的。由execve装载一个可执行程序。

    遗憾

    虽然每周都在按部就班的学习,但还是在一些具体的问题上不求甚解,是一直跟着老师的思路在走,老师讲过的就知道,自己去看其他的就蒙圈了,没有更深的去理解,有一些游走于表面,特别是对代码啊堆栈的理解就有些模糊。还有就是总觉得老师的逻辑跟我的有点不太一样,每次跟着听都有些费劲,感觉太散了,自己要看几遍才能理解,然后总结按自己的想法去串一遍,自认效率比较低,所以花的时间就很多。

  • 相关阅读:
    Python pip配置国内源
    【VLC】VLC命令行参数
    发个在owasp上演讲web应用防火墙的ppt
    Tips of Linux C programming
    linux程序调试
    scrapy结合webkit抓取js生成的页面
    Using Internet Explorer from .NET
    http长连接200万尝试及调优
    nginx url解码引发的waf漏洞
    poj 2513
  • 原文地址:https://www.cnblogs.com/tymjava/p/5418929.html
Copyright © 2011-2022 走看看