Linux课程总结
通过linux这门课,主要了解到了linux系统的一般执行过程。包括引导 、加载内核,init初始化过程、到进程切换进程调度等过程。
1、函数调用框架
32位
pushl %eip ()
movl $0x12345, %eip ()
64位
pushq %rip ()
movq $0x12345, %rip ()
上述伪指令先是把当前的EIP寄存器压栈,把0x12345这个立即数放到EIP寄存器里,该寄存器是用来告诉CPU下一条指令的存储地址的。把当前的EIP寄存器的值压栈就是把下一条指令的地址保存起来,然后给EIP寄存器又赋了一个新值0x12345,也就是CPU执行的下一条指令就是从0x12345位置取得的。
ret
上述代码实际上做了一个动作,如下一条伪指令,注意,这个动作并不存在实际对应的指令,我们用“()”来特别标记一下,这个动作是由硬件一次性完成的。出于安全方面的原因,EIP寄存器不能被直接使用和修改。
popl %eip() # 32位
popq %rip(*) # 64位
2、系统调用
int $0x80 或 syscall指令
系统调用处理入口entry_INT80_32 或 entry_SYSCALL_64
保存现场
do_int80_syscall_32或do_syscall_64
系统调用内核处理函数组成的ia32_sys_call_table和sys_call_table数组
进程调度时机syscall_return_slowpath(regs)可以跟踪到schedule函数
恢复现场
系统调用返回iret或sysret
继续执行int $0x80 或 syscall指令的下一条指令。
其中关键的是schedule函数,其中调用了 swicth_to进行关键的上下文切换。通过汇编代码了解到了关键的切换过程,函数堆栈的恢复与保存,指令切换等等。
3、fork和execve
内核装载可执行程序的过程,实际上是执行一个系统调用execve,和前面分析的fork及其他的系统调用的主要过程是一样的。但是execve这个系统调用的内核处理过程和fork一样也是比较特殊的。正常的一个系统调用都是陷入内核态,再返回到用户态,然后继续执行系统调用后的下一条指令。fork和其他系统调用不同之处是它在陷入内核态之后有两次返回,第一次返回到原来的父进程的位置继续向下执行,这和其他的系统调用是一样的。在子进程中fork也返回了一次,会返回到一个特定的点——ret_from_fork,通过内核构造的堆栈环境,它可以正常系统调用返回到用户态,所以它稍微特殊一点。同样,execve也比较特殊。当前的可执行程序在执行,执行到execve系统调用时陷入内核态,在内核里面用do_execve加载可执行文件,把当前进程的可执行程序给覆盖掉。当execve系统调用返回时,返回的已经不是原来的那个可执行程序了,而是新的可执行程序。execve返回的是新的可执行程序执行的起点,静态链接的可执行文件也就是main函数的大致位置,动态链接的可执行文件还需要ld链接好动态链接库再从main函数开始执行。
execve的内核执行过程:
进入内核态修改进程的寄存器和上下文等数据,并且进入新程序的入口,将本进程完全替换成新的可执行程序,如果无法执行可执行程序,会回到原进程继续向下执行。
通过这门课,我了解到了linux内核的一些关键部分,对linux系统以及程序运行的底层过程有了更深的了解,对于中断、进程切换、调用和cpu寄存器、进程创建、可成型程序结构等有了更多的理解,提对续程序编写、调试提供了更加清晰的思路和过程。