天天看点

Linux内核学习总结

Linux内核学习总结

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

对Linux系统的理解及学习Linux内核的心得

1、收获

在Linux内核分析这门课的学习过程中,我对Linux系统有了初步的了解。因为是第一次接触Linux,所以理解起来有点吃力,不过还是尽力去理解。在学习Linux系统内核的相关结构和设计原理的同时,也学会了在Linux系统内的相关操作,印象最深刻的是gdb调试内核代码的方法。

另外,在学生互评的环节中我也学习了其他人的博客,对知识有了很好的梳理和巩固,可以更全面的理解所学的知识。

2、遗憾

这八周的MOOC学习下来,我觉得只是浅浅的了解了Linux内核的相关知识,在脑海里形成了大致的框架。但是在实际的操作应用层面上,自己还很缺乏。在学习的过程中,大多数实验只能根据老师的指导来进行,对于内核中其他的东西还不是很了解。在这门课结束之后,还有许多东西要去学习。

最后,感谢孟老师这八周来的辛苦教学!

Linux内核分析博客链接和知识汇总

  • 学习链接总结

LINUX内核分析第一周学习总结——计算机是如何工作的

LINUX内核分析第二周学习总结——操作系统是如何工作的

LINUX内核分析第三周学习总结——构造一个简单的Linux系统MenuOS

LINUX内核分析第四周学习总结——扒开应用系统的三层皮(上)

LINUX内核分析第五周学习总结——扒开应用系统的三层皮(下)

LINUX内核分析第六周学习总结——进程的描述和进程的创建

LINUX内核分析第七周学习总结——可执行程序的装载

LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程

  • 学习知识总结
  • (一)计算机是如何工作的

    • 冯诺依曼体系结构——核心:存储程序计算机;
    • X86汇编基础

    (二)操作系统是如何工作的

    • 三个法宝——存储程序计算机、函数调用堆栈、中断机制;
    • 在my_schedule函数中,完成进程的切换。进程的切换分两种:

      1.下一个进程没有被调度过;

      2.下一个进程被调度过,可以通过下一个进程的state知道其状态。

      进程切换通过内联汇编代码实现,需要保存之前的进程的eip和堆栈,然后将新进程的eip和堆栈的值存入相对应的寄存器中。

    (三) Linux内核源代码简介

    • sched_init()进程调度初始化函数,函数内关键的初始化——对0号进程,即idle进程进行初始化;
    • rest_init()其他初始化函数,函数内将创建1号进程,即init进程;
    • 内核的启动过程: rest_init实际是start_kernel内核一启动的时候会一直存在,这个就叫0号进程;0号进程创建了1号进程kernel_init和其他服务线程。
    • qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

      # 关于-s和-S选项的说明:

      -S freeze CPU at startup (use ’c’ to start execution)

      -s shorthand for -gdb tcp::1234 若不想使用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之前,也可在后

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

    • 三层皮:API xyz,中断向量system_call,中断服务程序sys_xyz
    • 系统是通过中断的方式将用户态转换为内核态,并通过调用系统函数来实现系统功能。
    • 系统调用是一个软中断,中断号是0x80——通过int 0x80,触发系统调用。
    • 嵌入汇编格式为:

      asm ( assembler template

      : output operands /* optional /

      : input operands / optional /

      : list of clobbered registers / optional */

      );

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

    • set_system_trap_gate,设置系统陷阱门,即系统调用。
    • 使用gdb跟踪

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

      (gdb)list 查看代码.

      (gdb)s 单步调试进入函数体.

      (gdb)n 单步调试不进入函数体.

    • 给MenuOS增加time和time-asm命令、添加了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; //调度到子进程时的内核栈顶

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

    (七) 可执行程序的装载

    • Linux内核装载和启动一个可执行程序

      1.创建新进程

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

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

      (以上系统调用终止后,新进程开始执行放在可执行文件中的代码。)

    • 当ELF被load_elf_binary()装载完成后,函数返回至do_execve()在返回至sys_execve()。ELF可执行文件的入口点取决于程序的链接方式:

      1.静态链接:elf_entry就是指向可执行文件里边规定的那个头部,即main函数处。

      2.动态链接:可执行文件是需要依赖其它动态链接库,elf_entry就是指向动态链接器的起点。

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

    • schedule()函数负责调度;
    • linux系统的一般执行过程: X正在运行--->发生中断,可能陷入内核,CPU自动保存加载--->SAVE_ALL保存现场--->调用schedule,switch_to进程上下文切换--->标号1之后运行Y(之前有进行准备动作)--->restore_all恢复现场--->iret- pop cs:eip/ss:esp/eflags from kernel stack--->继续运行用户态进程Y
    • 特殊情况

      通过中断处理过程中的调度时机,用户态进程与内核线程之间互相切换和内核线程之间互相切换,与最一般的情况非常类似,只是内核线程运行过程中发生中断没有进程用户态和内核态的转换;

      内核线程主动调用schedule(),只有进程上下文的切换,没有发生中断上下文的切换,与最一般的情况略简略;

      创建子进程的系统调用在子进程中的执行起点及返回用户态,如fork;

      加载一个新的可执行程序后返回到用户态的情况,如execve;