课程学习总结报告

时间:2020-07-09
本文章向大家介绍课程学习总结报告,主要包括课程学习总结报告使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

一、linux系统概念模型

1. 概述

linux系统是一个多用户多任务的分时操作系统,函数调用是操作系统的三大法宝之一,使得编程极为灵活。由于CPU的运行速度远远大于外设,所以中断机制的使用解决了CPU等待外设的情况。系统调用是一种特殊的中断,封装了对系统的一些底层的操作,保证了系统的安全性。在中断返回时可能发生进程切换,内核线程也可以主动发起进程切换

本篇博文是对整个Linux操作系统分析课程的整体回顾,下面对上述焦点问题分别详细阐述

2. 函数调用

函数调用过程概述

  • 首先调用者的call函数将它的下一条指令地址保存在栈顶,将eip(rip)设置为被调用函数的起始地址

  • 建立被调用者函数的堆栈框架、执行被调用者函数体、拆除被调用者函数框架

  • ret指令将调用者的下一条指令地址恢复到eip(rip)

函数调用中的硬件操作

例如对于函数调用:call 0x12345

/*32位*/
pushl %eip (*) 
movl $0x12345, %eip (*) 

/*64位*/
pushq %rip (*)
movq $0x12345, %rip (*) 

函数返回:ret

 /*32位*/
 popl %eip (*)
 
 /*64位*/
 popq %rip (*)

上面的指令均为伪指令,这个动作由硬件一次性完成,这是由于EIP寄存器不能被直接修改和使用

函数调用中的软件操作

例如对于函数:

int main() {
	return f(8)+1;
}

在32位系统中,汇编为:

main:  
	pushl %ebp
	movl %esp, %ebp
	subl $4, %esp
	movl $8, (%esp)
	call f
	addl $1, %eax
	movl  %ebp,%esp
	popl  %ebp
	ret

在64位系统中,汇编为:

main:
	pushq %rbp
	movq %rsp, %rbp
	movl $8, %edi
	call f
	addl $1, %eax
	popq %rbp
	ret

我们对比32位系统和64位系统的函数调用:

  1. 共同点:

    • 都在调用之前保存了原来的栈,为新函数开辟了新的栈空间

    • 调用返回时都恢复了原来的栈空间

    • 函数的返回值都在eax(rax)寄存器中保存

  2. 区别:

    • 参数传递上存在差别,32位系统将参数存在新栈的栈底

3. 中断和异常

中断和异常的区别与联系:

中断是异步的,由硬件随机产生,在程序执行的任何时候可能出现

异常是同步的,在特殊的或出错的指令执行时由CPU控制单元 产生

我们用“中断信号”来通称这两种类型的中断

中断上下文

中断上下文不同于进程上下文,中断上下文只包含了很有限的几个寄存器,建立和终止这个上下文所需要的时间很少。中断程序只能使用被中断进程的内核栈作为自己的运行栈

中断和异常的硬件级处理

  • 当CPU正常运行时,执行一条指令后,cs和eip包含了下一条将要执行的指令的逻辑地址
  • 在执行这条指令之前,CPU会检查在运行前一条指令时是否发生了一个中断或者异常。
  • 如果发生了中断或异常:
    • 确定中断向量号i,读idtr寄存器指向的IDT表中的第i项
    • 从gdtr寄存器获得GDT的基地址,并在GDT中查找, 以读取IDT表项中的段选择符所标识的段描述符
    • 确定中断是由授权的发生源发出的,只允许从低特权级陷入高特权级,反之不可以
    • 如果是由用户态陷入内核态,用与新特权级相关的栈段和栈指针装载ss和esp寄存器,在新的栈中保存ss和esp以前的值
    • 如果发生的是故障,用引起异常的指令地址修改cs 和eip寄存器的值,以使得这条指令在异常处理结束后能被再次执行
    • 在栈中保存eflags、cs、eip;如果异常产生一个硬件出错码,则将它保存在栈中
    • 装载cs和eip寄存器,其值分别是IDT表中第i项描述符的段选择符和偏移量
  • 中断返回:
    • 用保存在栈中的值装载cs、eip和eflags寄存器
    • 检查中断时是在内核态还是用户态,如果在内核态,则终止;如果在用户态,从栈中装载ss和esp寄存器

中断和异常的软件级处理

异常处理:

  • 按照pt_regs结构定义的堆栈数据格式完成相应的入栈操作,进一步完成现场的保存

  • 把堆栈地址中的do_handler_name()函数的地址装入edi寄存器中,并在这个位置写入fs值,使栈结构进一步与pt_regs结构完全一致

  • 最后执行call *%edi指令

中断处理:

可以用下面的汇编代码来总结:

SAVE_ALL
movl %esp,%eax
call do_IRQ jmp
$ret_from_intr

4. 系统调用

系统调用是陷阱这种软中断方式主动从用户态进入内核态的

传统的系统调用:

int $0x80 指令会触发系统调用,CPU压栈⼀些关键寄存器,根据eax寄存器传递的系统调用号调用对应的内核处理函数,接着内核负责保存现场,系统调用内核函数处理完后恢复现场,后通过iret出栈哪些CPU压栈的关键寄存器。

快速系统调用:

sysenter和syscall都借助CPU内部的MSR寄存器来查找系统调用处理入口,其余操作与传统系统调用相同

系统调用的参数传递:

32位x86和64位的x86都是通过寄存器来传递。注意:在普通的函数调用中,32位x86使用压栈来传递参数,而64位x86仍然使用寄存器传递参数

5. 进程管理

进程创建

0号进程初始化是通过硬件编码,其他进程的初始化都是通过do_fork复制父进程的方式初始化

1号进程为是kernel_init,是所有用户进程的祖先;2号进程时kthreadd,是所有内核线程的祖先,1、2号进程都是复制0号进程得到的

父进程通过fork系统调用进入内核_do_fork函数,主要完成了:

  • 调用copy_process()复制父进程
  • 分配子进程的内核堆栈并对内核堆栈和thread等进程关键山下文进行初始化
  • 调用wake_up_new_task将子进程加入就绪队列等待调度执行

进程切换

进程调度时机: Linux内核通过schedule函数实现进程调度,分别为中断返回前和内核线程主动调用schedule

进程上下文:

  • 用户地址空间:包括程序代码、数据、用户堆栈等

  • 控制信息:进程描述符、内核堆栈等

  • 进程的CPU上下文,相关寄存器的值

当调用schedule函数时其中的switch_to做了关键的进程上下文切换。将当前进程X的内核堆栈切换到进程调度算法选出来的next进程的内核堆栈,并完成了进程上下文所需的EIP等寄存器状态切换

二、Linux模型的举例

我们举一个在linux命令行输入vim,并按下回车的例子:

我们在键盘输入回车键系统能有反应,说明发生了中断,该中断处理程序处理了键盘的输入信号

中断返回的时候,父进程fork系统调用进入内核_do_fork函数

调用copy_process()复制父进程

分配子进程的内核堆栈并对内核堆栈和thread等进程关键山下文进行初始化

调用wake_up_new_task将子进程加入就绪队列等待调度执行

Linux内核通过schedule函数实现进程调度

schedule函数中的switch_to做了关键的进程上下文切换,将之前进程的内核堆栈以及相关寄存器切换到vim进程上来,这样我们就看到进入了vim程序的界面

三、对课程的心得体会

本课程两位老师通过理论和实验双管齐下,对linux系统的原理进行了深刻的阐述,使得本人对Linux操作系统自有的特性有了一定的认识,不再像之前只是操作系统这个笼统的概念。

改进意见:增加一些个在实际工程中使用Linux的实验,这样可以让同学们在应用中从不同的角度理解Linux系统

原文地址:https://www.cnblogs.com/happyyouli/p/13275470.html