结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程
一、实验要求:
结合中断上下文切换和进程上下文切换分析Linux内核一般执行过程
- 以fork和execve系统调用为例分析中断上下文的切换
- 分析execve系统调用中断上下文的特殊之处
- 分析fork子进程启动执行时进程上下文的特殊之处
- 以系统调用作为特殊的中断,结合中断上下文切换和进程上下文切换分析Linux系统的一般执行过程
二、实验过程:
fork函数:
一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
fork仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,
fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
引用一位网友的话来解释fpid的值为什么在父子进程中不同。“其实就相当于链表,进程形成了链表,父进程的fpid(p 意味point)指向子进程的进程id,
因为子进程没有子进程,所以其fpid为0.
fork特殊之处:
fork在陷⼊内核态之后有两次返回,第⼀次返回到原来的⽗进程的位置继续执⾏,但是在⼦进程中fork也返回了⼀次,会返回到⼀个特定的点——ret_from_fork,所以它可以正常系统调⽤返回到⽤户态。
execve函数:
函数执行成功时没有返回值,执行失败时的返回值为-1.
execve()用来执行参数filename字符串所代表的文件路径,第二个参数是利用数组指针来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。
execve系统调用的过程总结如下:
1. execve系统调用陷入内核,并传入命令行参数和shell上下文环境
2. execve陷入内核的第一个函数:do_execve,该函数封装命令行参数和shell上下文
3. do_execve调用do_execveat_common,后者进一步调用__do_execve_file,打开ELF文件并把所有的信息一股脑的装入linux_binprm结构体
4. __do_execve_file中调用search_binary_handler,寻找解析ELF文件的函数
5. search_binary_handler找到ELF文件解析函数load_elf_binary
6. load_elf_binary解析ELF文件,把ELF文件装入内存,修改进程的用户态堆栈(主要是把命令行参数和shell上下文加入到用户态堆栈),修改进程的数据段代码段
7. load_elf_binary调用start_thread修改进程内核堆栈(特别是内核堆栈的ip指针)
8.进程从execve返回到用户态后ip指向ELF文件的main函数地址,用户态堆栈中包含了命令行参数和shell上下文环境
execve特殊之处:
当execve在调用时陷入内核态,就执行do_execve文件,覆盖当前的可执行程序,所以返回的是新的可执行程序的起点。main函数位置是静态链接的可执行文件,动态链接的可执行文件需要连接动态链接库后在开始执行。
Linux系统的一般执行过程:
一般的情况:正在运行的用户态进程X切换到运行用户态进程Y的过程
1、正在运行的用户态进程X
2、发生中断——(CPU自动完成)
save cs:eip/esp/eflags(current) to kernel stack
load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack).
3、SAVE_ALL //保存现场
4、中断处理过程中或中断返回前调用了schedule(),其中的switch_to做了关键的进程上下文切换
5、标号1之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行)
6、restore_all //恢复现场
7、iret - pop cs:eip/ss:esp/eflags from kernel stack返回执行的是Y进程曾经发生中断时用户态的下一条指令,恢复现场,恢复不是X进程的现场,而是曾经保存的Y进程现场)
8、继续运行用户态进程Y
几种特殊情况
1、通过中断处理过程中的调度时机,用户态进程与内核线程之间互相切换和内核线程之间互相切换,与最一般的情况非常类似,只是内核线程运行过程中发生中断没有进程用户态和内核态的转换;(CS段没有发生变化)
2、用户态进程不能主动调度,主动地调用schedule(),内核线程主动调用schedule(),只有进程上下文的切换,没有发生中断上下文的切换,与最一般的情况略简略;
3、创建子进程的系统调用在子进程中的执行起点及返回用户态,如fork,返回了两次,在父进程返回一次,在子进程也返回;如果next进程是一个新创建的子进程,没有被执行过,则next进程的执行起点是ret_from_fork;
4、加载一个新的可执行程序后返回到用户态的情况,如execve;
参考:
1. https://blog.csdn.net/kxjrzyk/article/details/81603049
2. https://www.cnblogs.com/jxhd1/p/6706701.html
3. 《庖丁解牛linux内核分析》