标签:ddl 速度 核函数 linux操作 函数调用 返回 main 包含 sys
linux系统是一个多用户多任务的分时操作系统,函数调用是操作系统的三大法宝之一,使得编程极为灵活。由于CPU的运行速度远远大于外设,所以中断机制的使用解决了CPU等待外设的情况。系统调用是一种特殊的中断,封装了对系统的一些底层的操作,保证了系统的安全性。在中断返回时可能发生进程切换,内核线程也可以主动发起进程切换
本篇博文是对整个Linux操作系统分析课程的整体回顾,下面对上述焦点问题分别详细阐述
首先调用者的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位系统的函数调用:
共同点:
都在调用之前保存了原来的栈,为新函数开辟了新的栈空间
调用返回时都恢复了原来的栈空间
函数的返回值都在eax(rax)寄存器中保存
区别:
中断是异步的,由硬件随机产生,在程序执行的任何时候可能出现
异常是同步的,在特殊的或出错的指令执行时由CPU控制单元 产生
我们用“中断信号”来通称这两种类型的中断
中断上下文不同于进程上下文,中断上下文只包含了很有限的几个寄存器,建立和终止这个上下文所需要的时间很少。中断程序只能使用被中断进程的内核栈作为自己的运行栈
异常处理:
按照pt_regs结构定义的堆栈数据格式完成相应的入栈操作,进一步完成现场的保存
把堆栈地址中的do_handler_name()函数的地址装入edi寄存器中,并在这个位置写入fs值,使栈结构进一步与pt_regs结构完全一致
最后执行call *%edi指令
中断处理:
可以用下面的汇编代码来总结:
SAVE_ALL
movl %esp,%eax
call do_IRQ jmp
$ret_from_intr
系统调用是陷阱这种软中断方式主动从用户态进入内核态的
传统的系统调用:
int $0x80 指令会触发系统调用,CPU压栈?些关键寄存器,根据eax寄存器传递的系统调用号调用对应的内核处理函数,接着内核负责保存现场,系统调用内核函数处理完后恢复现场,后通过iret出栈哪些CPU压栈的关键寄存器。
快速系统调用:
sysenter和syscall都借助CPU内部的MSR寄存器来查找系统调用处理入口,其余操作与传统系统调用相同
系统调用的参数传递:
32位x86和64位的x86都是通过寄存器来传递。注意:在普通的函数调用中,32位x86使用压栈来传递参数,而64位x86仍然使用寄存器传递参数
0号进程初始化是通过硬件编码,其他进程的初始化都是通过do_fork复制父进程的方式初始化
1号进程为是kernel_init,是所有用户进程的祖先;2号进程时kthreadd,是所有内核线程的祖先,1、2号进程都是复制0号进程得到的
父进程通过fork系统调用进入内核_do_fork函数,主要完成了:
进程调度时机: Linux内核通过schedule函数实现进程调度,分别为中断返回前和内核线程主动调用schedule
进程上下文:
用户地址空间:包括程序代码、数据、用户堆栈等
控制信息:进程描述符、内核堆栈等
进程的CPU上下文,相关寄存器的值
当调用schedule函数时其中的switch_to做了关键的进程上下文切换。将当前进程X的内核堆栈切换到进程调度算法选出来的next进程的内核堆栈,并完成了进程上下文所需的EIP等寄存器状态切换
我们举一个在linux命令行输入vim,并按下回车的例子:
我们在键盘输入回车键系统能有反应,说明发生了中断,该中断处理程序处理了键盘的输入信号
中断返回的时候,父进程fork系统调用进入内核_do_fork函数
调用copy_process()复制父进程
分配子进程的内核堆栈并对内核堆栈和thread等进程关键山下文进行初始化
调用wake_up_new_task将子进程加入就绪队列等待调度执行
Linux内核通过schedule函数实现进程调度
schedule函数中的switch_to做了关键的进程上下文切换,将之前进程的内核堆栈以及相关寄存器切换到vim进程上来,这样我们就看到进入了vim程序的界面
本课程两位老师通过理论和实验双管齐下,对linux系统的原理进行了深刻的阐述,使得本人对Linux操作系统自有的特性有了一定的认识,不再像之前只是操作系统这个笼统的概念。
改进意见:增加一些个在实际工程中使用Linux的实验,这样可以让同学们在应用中从不同的角度理解Linux系统
标签:ddl 速度 核函数 linux操作 函数调用 返回 main 包含 sys
原文地址:https://www.cnblogs.com/happyyouli/p/13275470.html