zoukankan      html  css  js  c++  java
  • Linux内核学习总结

    陈巧然 原创作品

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



    博客目录

    1.第一周:计算机如何工作 

    2.第二周:操作系统是如何工作的

    3.第三周:构造一个简单的Linux内核MenuOS

    4.第四周:扒开系统调用的三层皮(上) 

    5.第五周:扒开操作系统的三层皮(下) 

    6.第六周:进程的描述与创建 

    7.第七周:可执行程序的装载 

    8.第八周:系统的一般执行过程 


     

    一、计算机是如何工作的

    1.冯 诺依曼体系结构是指具有存储程序的计算机系统

    • 硬件:CPU与内存通过主线连接;CPU上有一块寄存器叫做IP,该寄存器相当于指针,总是指向内存的代码段;CPU总是执行IP指向的指令然后IP自加一

    • 软件(计算机如何识别指令):通过ABI(程序与计算机的接口界面

    2.常见的寻址方式

    • 寄存器寻址(操作的都是寄存器):将eax的值直接赋值给edx;
    • 立即数寻址:将十六进制的数值123(这个立即数)直接放到edx中(和内存也没有关系);
    • 直接寻址:将0x123这个地址所指向的内存数据放到寄存器edx中;
    • 间接寻址:将ebx的值作为一个内存地址,这个地址所存储的数据放到edx中;
    • 变址寻址:与间接寻址类似,是把ebx的值加上4作为地址

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

    1.计算机是如何工作的?(总结)——三个法宝

    • 存储程序计算机工作模型,计算机系统最最基础性的逻辑结构;

    • 函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语言的时候堆栈机制对于计算机来说并不那么重要,但有了高级语言及函数,堆栈成为了计算机的基础功能;函数参数传递机制和局部变量存储

    • 中断,多道程序操作系统的基点,没有中断机制程序只能从头一直运行结束才有可能开始运行其他程序。

    2.操作系统两把利剑

    • 进程切换
    • 中断上下文

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

    1.创建及调试步骤:

    • 下载安装完后,启动并使它一开始就停止:

      qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
    • 然后启动gdb调试,设置断点,单步运行等,这个在后面的学习过程中都会经常使用

    2.分析这个内核的启动过程

    • x86 CPU启动的第一个动作CS:EIP=FFFF:0000H(换算为物理地址为000FFFF0H,因为16位CPU有20根地址线),即BIOS程序的位置;

    • BIOS例行程序检测完硬件并完成相应的初始化之后就会寻找可引导介质,找到后把引导程序加载到指定内存区域后,就把控制权交给了引导程序;

    • 引导程序BootLoader开始负责操作系统初始化,然后启动操作系统。启动操作系统时一般会指定kernel、initrd和root所在的分区和目录;

    • 内核启动过程包括start_kernel之前和之后,之前全部是做初始化的汇编指令,之后开始C代码的操作系统初始化,最后执行第一个用户态进程init(这里和讲解中以及实际的代码main.c是一致的);

    • 一般分两阶段启动,先是利用initrd的内存文件系统,然后切换到硬盘文件系统继续启动。initrd文件的功能主要有两个:

    (1)提供开机必需的但kernel文件(即vmlinuz)没有提供的驱动模块(modules)

    (2)负责加载硬盘上的根文件系统并执行其中的/sbin/init程序进而将开机过程持续下去

    3.init进程的解析——“道生一,一生二”

    • start_kernel创建了cpu_idle,即0号进程
    • 0号进程又创建了两个线程kernel_init和kthreadd
    • kernel_init即1号进程,这个进程最终启动了用户态

    四、用户态&内核态与系统调用的关系

    1.用户态、内核态区别

    • 在高级别的状态下,代码可以执行特权指令,访问任意的物理地址;一般在Linux中,0xc0000000以上的地址(指的是逻辑地址)空间只能在内核态下访问;
    • 在相应的低级别执行状态下,代码的掌控范围会受到限制;

    2.系统调用的意义

    • 把用户从底层的硬件编程中解放出来
    • 极大的提高了系统的安全性
    • 使用户程序具有可移植性

    3.中断处理程序

    • 中断指令会在寄存器上保存一些寄存器的值放入内核堆栈,比如:用户态栈顶地址,标志寄存器,cs:eip。同时,将相关联的中端服务历程的入口加载到cs:eip,把当前的堆栈段esp也加载到CPU里面
    • 中断发生之后第一件事就是保存现场;中断处理结束前的最后一件事情就是恢复现场

    五、系统调用在内核代码中的工作机制和初始化

    1.系统调用机制的初始化

    trap_gate函数中,涉及到了系统调用的中断向量和system_call的汇编代码入口;一旦执行int 0x80,CPU直接跳转到system_call

    2.简化后便于理解的system_call伪代码

    • system_call的位置就在ENTRY(system_call)处;(其他中断的处理过程与此类似)
    • syscall_table是系统调用表
    • after_call之后,保存返回值
    • 要exit的时候,会有一个syscall_exit_work,否则直接返回用户态

    3.简单浏览system_call到iret之间的主要代码

    • SAVE_ALL:保存现场
    • syscall_call:调用了系统调用处理函数
    • restore all:恢复现场(因为系统调用处理函数也算是一种特殊的“中断”)
    • syscall_exit_work:如3.中所述
    • INTERRUPT RETURN:也就是iret,系统调用到此结束

    六、可执行程序的装载

    1.装载可执行文件之前的工作

    • 要先fork一个进程,不然会覆盖shell
    • execlp加载一个程序
    • shell程序-->execve-->sys_execve,然后在初始化新程序堆栈的时候拷贝进去
    • 先传递函数调用参数,再传递系统调用参数

    2.sys_execve的内部处理过程

    • do_execve
    • do_open_exec(filename)打开要加载的文件
    • 命令行参数,结构体变量copy到bprm结构体中
    • exce_binprm(bprm),关键代码是寻找能解析当前文件的处理模块
    • register_binfmt($elf_format)注册这个格式到链表里,然后寻找能处理的模块
    • ELF可执行文件默认映射到0x8048000这个地址
    • 需要动态链接的可执行文件先加载连接器ld;否则直接把elf文件entry地址赋值给entry即可。
    • start_thread(regs,elf_entry,bprm->p)会将CPU控制权交给ld来加载依赖库并完成动态链接;对于静态链接的文件elf_entry是新程序执行的起点

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

    1.进程调度的时机

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

    2.正在运行的用户态进程X切换到运行用户态进程Y的过程

    • 正在运行的用户态进程X
    • 发生中断——

    (1)save cs:eip/esp/eflags(current) to kernel stack;

    (2)then load cs:eip(系统调用的起点,entry of a specific ISR) and ss:esp(point to kernel stack)

    • 进入内核代码,SAVE_ALL //保存现场
    • 中断处理过程中或中断返回前调用了schedule()(可能不发生,诺发生,则其中的switch_to做了关键的进程上下文切换)
    • 标号1之后开始运行上一步中选中的用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行)
    • restore_all //恢复现场
    • iret - pop cs:eip/ss:esp/eflags from kernel stack
    • 继续运行用户态进程Y

    3.以ls命令为例,学习Linux系统执行指令的过程

    • 在控制台输入ls命令
    • Shell程序分析输入参数,确定这是ls
    • 调用系统调用fork生成一个shell本身的拷贝
    • 调用exec系统调用将ls可执行文件装入内存
    • 从系统调用返回
    • Shell和ls都得到执行

    4.从CPU和内存角度看Linux系统执行

    • 执行gets()函数;
    • 执行系统调用,陷入内核;
    • 等待输入,CPU会调度其他进程执行,同时wait一个I/O中断;
    • 敲击ls,发I/O中断给CPU,中断处理程序进行现场保存、压栈等等;
    • 中断处理程序发现X进程在等待这个I/O(此时X已经变成阻塞态),处理程序将X设置为WAKE_UP;
    • 进程管理可能会把进程X设置为next进程,这样gets系统调用获得数据,再返回用户态堆栈
    • 从内存角度看,所有的物理地址都会被映射到3G以上的地址空间:因为这部分对所有进程来说都是共享的

    八、总结

    • 收获:这种教学方式很新奇,从一开始对每周的任务对的抵触,到一学期过去习惯了这种每周都花固定时间来学习的生活。大学以来学习能力下降了很多,积极性主动性什么的更是几乎消磨殆尽,而这种“使自主”学习方式,让我每天都想着还要学习还要学习,这种态度的转变我觉得是最大的收获。在实际学习中,老师提供了很多新式的平台方式,实验楼自主实验、博客借鉴、小组论坛讨论、合作完成家庭作业等,除去学校网速限制以外我从中受益很多。以前学习遇到问题可能直接就放弃了,等老师课上讲或者问同学,甚至直接跳过。而现在,总是会先自己想想如何解决,然后发到论坛中,和老师同学分享自己的思路,不同的思维碰撞之后得到正确的解答,这样对问题的记忆极其深刻永远不会忘。好的学习方法对学习效果有太大的提升。
    • 遗憾:我觉得我最大的问题还是自制力差,容易分心,遇到问题容易烦躁然后思维就转移了不专注了。现在跟着老师的新方式学习有些改观,加上自己的克制比如去学习不带手机,可以说比之前好很多。我觉得连续学习两小时中第二个小时要比第一个小时效率高很多,在以后的学习中要继续克制继续克制,多利用第二个小时。其次是遇到问题时没有耐心,经常轻易就放弃了思考,在这学期学习中已经有所改进,课程结束后更要坚持。
  • 相关阅读:
    链表和数组的区别在哪里 【微软面试100题 第七十八题】
    关于链表问题的面试题目 【微软面试100题 第七十七题】
    复杂链表的复制 【微软面试100题 第七十六题】
    二叉树两个结点的最低公共父结点 【微软面试100题 第七十五题】
    数组中超过出现次数一半的数字 【微软面试100题 第七十四题】
    对称字符串的最大长度 【微软面试100题 第七十三题】
    Singleton模式类 【微软面试100题 第七十二题】
    数值的整数次方 【微软面试100题 第七十一题】
    旋转数组中的最小元素 【微软面试100题 第六十九题】
    把数组排成最小的数 【微软面试100题 第六十八题】
  • 原文地址:https://www.cnblogs.com/20135310cqr/p/5450464.html
Copyright © 2011-2022 走看看