寇亚飞+ 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
第一周:反汇编一个C语言小程序
对寄存器的作用,以及汇编代码的基础知识有了一定得了解,C语言中程序的调用,在底层汇编是如何实现的有了一定的认识。
第二周:完成一个简单的时间片轮转多道程序内核代码
简单模拟内核代码。主要包括函数调用堆栈、函数堆栈框架、内核的初始化、中断、进程上下文切换过程的简述以及基于时间片轮转的多道程序模拟。
第三周:跟踪分析Linux内核的启动过程
计算机是通过执行指令来完成一系列操作,这其中很重要的一点就是eip寄存器,它给cpu指明了下一条要执行的指令是谁。因此,x86计算机启动的第一个动作就是从硬盘固定位置读取BIOS程序位置到eip中,然后BIOS例行程序检测完硬件并完成相应的初始化之后就会寻找可引导介质,找到后把引导程序加载到指定内存区域后,就把控制权交给了引导程序。这里一般是把硬盘的第一个扇区MBR和活动分区的引导程序加载到内存(即加载BootLoader),加载完整后把控制权交给BootLoader。引导程序BootLoader开始负责操作系统初始化,然后启动操作系统。
在start_kernel中,创建了系统的第一个进程,即0号进程。
道生一(start_kernel–>cpu_idle),一生二(kernel_init和kthreadd),二生三(即前面0、1和2三个进程),三生万物(1号进程是所有用户态进程的祖先,2号进程是所有内核线程的祖先)
第四周:使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
关于系统调用的知识。主要包括对系统调用与用户态、内核态的理解、通过编写汇编代码了解系统调用机制。
系统调用:即便是最简单的程序,在进行输入输出等操作时也会需要调用操作系统所提供的服务,也就是系统调用。Linux下的系统调用是通过中断(int 0x80)来实现的。
传递参数:在执行int 80指令时,寄存器 eax 中存放的是系统调用的功能号,而传给系统调用的参数则必须按顺序放到寄存器 ebx,ecx,edx,esi,edi 中,当系统调用完成之后,返回值可以在寄存器 eax 中获得。Linux 采用的是 C 语言的调用模式,这就意味着所有参数必须以相反的顺序进栈,即最后一个参数先入栈,而第一个参数则最后入栈。
系统调用不但把用户从底层的硬件编程中解放出来,提高了编程的效率,也极大的提高了系统的安全性。
第五周:分析system_call中断处理过程
添加一个自己编写的系统调用,加深对系统调用在内核代码中的处理过程的理解。
系统调用通过SAVE ALL保存上下文,调用内核函数,执行RESTORE_ALL并返回用户模式。
第六周:分析Linux内核创建一个新进程的过程
关于进程的描述和创建。主要包括PCB的组织形式、进程的数据结构进程描述符、fork系统调用的关键执行过程。
创建的新进程从哪里开始执行?
新进程是从ret_from_fork处开始执行的。对于fork执行处理过程来说,父子进程共享同一段代码空间,”一次调用,两次返回“,其实对于调用fork的父进程来说,如果fork出来的子进程没有得到 调度,那么父进程从fork系统调用返回,同时分析sys_fork知道,fork返回的是子进程的id。再看fork出来的子进程,由 copy_process函数可以看出,子进程的返回地址为ret_from_fork(和父进程在同一个代码点上返回),返回值直接置为0。所以当子进 程得到调度的时候,也从fork返回,返回值为0。ret_from_fork()调用schedule_tail()函数,用存放在栈中的值再装入所有寄存器,并强迫CPU返回到用户态。这样,eax寄存器就装过两个值,一个是子进程的值0,一个是父进程的值——子进程的PID。然后在fork()、vfork()或clone()返回时,新进程将开始执行。在不同的进程中返回不同的值。
第七周:Linux内核如何装载和启动一个可执行程序
Linux 系统通过用户态 execve 函数调用内核态 sys_execve 系统调用,负责将新的程序代码和数据替换到新的进程中,打开可执行文件,载入依赖的库文件,申请新的内存空间,最后执行 start_thread 函数设置 new_ip 和 new_sp,完成新进程的代码和数据替换,然后返回并执行新的进程代码。 具体的执行流程即sys_execve -> do_execve -> do_execve_common -> exec_binprm -> search_binary_handler -> load_binary ->(对于我们这里的ELF,会跳转到)load_elf_binary (也执行了elf_format)-> start_thread
第八周:理解进程调度时机跟踪分析进程调度与进程切换的过程
操作系统中进程的调度是非常常见的一种状态,无论采用何种调度册策略,它都是从进程队列中选择了一个新进程而已。因此,掌握进程调度的时机与进程上下文的切换非常重要。
Linux系统的一般执行过程
最一般情况:正在运行的用户态进程X切换到运行用户态进程Y的过程
正在运行的用户态进程X
发生中断——save cs:eip/esp/eflags(current) to kernel stack,then load cs:eip(entry of a specific ISR) and ss:esp(point to kernel stack).
SAVE_ALL //保存现场
中断处理过程中或中断返回前调用了schedule(),其中的switch_to做了关键的进程上下文切换
标号1之后开始运行用户态进程Y(这里Y曾经通过以上步骤被切换出去过因此可以从标号1继续执行)
restore_all //恢复现场
iret - pop cs:eip/ss:esp/eflags from kernel stack
继续运行用户态进程Y
特殊情况
通过中断处理过程中的调度,用户态进程与内核进程之间互相切换,与一般情形类似;
内核进程程主动调用 schedule 函数,只有进程上下文的切换,没有中断上下文切换;
创建子进程的系统调用在子进程中的执行起点及返回用户态,如:fork;
加载一个新的可执行程序后返回到用户态的情况,如:execve;
Linux是计算机专业得学生一定要掌握得一门操作系统,在全部的课程中,我对Linux内核中的核心部分有了较全面的了解。对内核的启动、系统调用的执行过程、中断、进程的创建、切换等方面有了新的认识,阅读大牛们的Linux内核源码,使我对编码有了更深入的认识。孟老师的网课,这是我上的第二门,收获颇丰,值得回味。
Linux内核博大精深,只掌握了皮毛,源码也有很多读不懂的地方。实验可能做的也比较按部就班,对Linux的学习是一个漫长的过程吧。