博学帅气的孟老师的Linux内核分析这门课程结束了,学习时间虽短,但收获很多,感谢孟老师的同时最后再做个总结。
主要学习了进程调度、中断过程和系统调用。这三块是linux内核的核心。
1)中断过程。中断过程是主动或被动发起一个中断,中断发生时硬件会自动保存CS,SS,FLAGS,SS,SP,接着进入内核态保存通用寄存器、堆栈等现场,然后根据中断向量号调用相应的中断处理程序,处理完成后恢复现场,中断返回。
2)系统调用。系统调用是特殊的中断过程,一般由程序主动调用。系统调用是当程序执行int 0x80时发生。首先通过中断进入内核态,然后根据保存在eax中向量号进行相应的分发,相应参数保存在ebx,edi,esi等寄存器中。最后,处理完成后恢复现场、中断返回。
3)进程调度。进程调度要理解进程是如何从一个进程切换到另一个进程的。主要包括保存当前的eip、esp,然后切换到下一个进程的eip和esp,中间切换要准确无误。
博文列表如下:
计算机是如何工作的。从cpu的角度看,就是输入、运算、输出(可能也没有输出)。从程序角度看,当程序运行时,需要在内存中的栈上分配一段空间,伴随着程序的运行,esp,ebp,eip等各个寄存器发生变化。
cpu将代码段中的语句,经过取指、翻译、执行后的结果,可以输出I/O、写回数据段等。接着再由eip自加一生成下一条指令的地址,继续执行。关于程序运行时栈中的变化情况,博客正文中已结合图示演示。
对于计算机系统如何工作的主要三点:
1)存储程序,计算机系统最基本的特性;
2)函数调用堆栈,高级语言得以运行的基础;
3)中断,多道程序操作系统的基点。
对于操作系统如何工作的,在操作系统原理中描述为主要对进程管理、存储管理、I/O管理、文件管理。
rest_init()就是start_kernel从进程内核一启动的时候就一直会存在,就是0号进程;0号进程创建了1号进程以及其他的一些服务的内核线程,这样整个系统就启动起来了。
系统调用与中断的关系,即系统调用的三层皮:xyz( API )、system_call( 中断向量 )和sys_xyz( 服务程序 )。系统调用是一种特殊的中断,用eax放调用号码,ebx、ecx等放参数,eax放返回值,int 0x80进入中断处理程序,此时进入内核态。
1)执行int 0x80指令后系统从用户态进入内核态,跳到system_call()函数处执行相应服务进程。在此过程中内核先保存中断环境,然后执行系统调用函数。
2)system_call()函数通过系统调用号查找系统调用表sys_cal_table来查找具体系统调用服务进程。
3)执行完系统调用后,iret之前,内核会检查是否有新的中断产生、是否需要进程切换、是否学要处理其它进程发送过来的信号等。
4)内核是处理各种系统调用的中断集合,通过中断机制实现进程上下文的切换,通过系统调用管理整个计算机软硬件资源。
5)如没有新的中断,restore保存的中断环境并返回用户态完成一个系统调用过程。
在Linux中创建新进程中,用fork得到的子进程是父进程的复制,它从父进程处复制了整个进程的地址空间,包括进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设定、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。子进程所独有的只是它的进程号、资源使用和计时器等。
1)无论用三种系统调用clone、fork、vfork中的哪种来创建一个新进程,都是通过调用do_fork来实现的
2)通过复制父进程PCB的task_struct创建一个新进程
3)子进程修改复制后的PCB,如pid、进程链表等
4)fork()系统调用产生的子进程从ret_from_fork处开始执行,p->thread.ip = (unsigned long)ret_from_fork
5)可通过返回值判断当前进程是父进程还是子进程,父进程处返回进程号,子进程自己返回0
新的可执行程序通过修改内核堆栈EIP作为新程序的起点,从new_ip开始执行后start_thread把返回到用户态的位置从Int 0x80的下一条指令变成新加载的可执行文件的入口位置。当执行到execve系统调用时,陷入内核态,用execve加载的可执行文件覆盖当前进程的可执行程序,当execve系统调用返回时,返回新的可执行程序的执行起点(main函数位置),所以execve系统调用返回后新的可执行程序能顺利执行。execve系统调用返回时,如果是静态链接,elf_entry指向可执行文件规定的头部(main函数对应的位置0x8048***);如果需要依赖动态链接库,elf_entry指向动态链接器的起点。动态链接主要是由动态链接器ld来完成的。
Linux中进程切换的一般步骤为:
1)检测当前进程的状态,挂起当前进程的IO请求以防止死锁
2)获取当前运行CPU,以及它的可运行进程队列
3)从进程队列中获取当前进程的task_struct,并通过进程调度算法从队列中选择一个合适的进程作为待调入进程
4)检测待调入进程的状态以确保其正确性
5)使用switch_to宏来进行当前进程与待调入进程的切换(期间完成新进程的资源准备工作)
6)新进程完成schedule(),结束整个进程切换过程
总结:Linux是多进程操作系统。内核是核心,通常由负责响应中断的中断服务程序、负责管理多个进程的调度程序、负责管理进程地址空间的内存管理程序、负责网络进程间通信的系统服务程序等共同组成。
收获:通过学习对Linux有了更深入的了解,对GCC、GDB等各种调试命令更加熟练;对进程地址空间、内存管理、程序的加载、进程上下文切换,中断上下文切换等有了进一步的理解。
遗憾:每周只有一次课,对于学习Linux内核是不够的。还有很多内容没有涉及且没有实际动手写代码,对于内核处理原理的运用上很模糊,这方面的知识就要自己在以后的时间学习了。