Linux及安全期中总结
黄晓妍 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
第一周 计算机是如何工作的
- 冯诺依曼结构 将程序指令存储器和数据存储器合并在一起的存储器结构。
- 计算机是怎么工作的?存储器负责存储指令,cpu负责解释指令。它们通过总线连接。 Cpu是如何解释指令:ABI:程序和 cpu接口界面,接下来主要学习汇编语言。
- 汇编语言基础 为什么学习:汇编语言是最接近机器语言的编程语言,能帮助我理解cpu是如何解释指令的。
- 寄存器:通用寄存器(编程中可以直接使用的寄存器)。段寄存器
- 常见指令: movl七种寻址方式 pop,push,ret,call
- 堆栈
堆栈详细变化过程举例。
具体见博客第二周 操作系统是如何工作的
重点理解myinterruput.c里进程切换的代码。(切换进程指的是下一个切换上执行的进程)
分成两种情况,第一种是切换到的进程是执行过的进程,第二种是切换到的进程是新进程。首先第一种,先将当前进程的ebp压栈,然后将当前进程的栈顶指针esp保存到链表结构当前进程prev->thread.sp中,再将链表中切换的进程的next->thread.sp 复制给栈顶指针esp.将链表中当前进程prev->thread.ip存入函数的入口的值,再将切换进程的next->thread.ip压栈。然后第二种,先建好链表,再执行上述过程。
第三周 构造一个简单的Linux系统MenuOS
Linux启动的理解:
在内核引导结束并启动init之后,系统就转入用户态的运行,在这之后创建的一切进程,都是在用户态进行。 就是init进程虽然是从内核开始的,即在前面所讲的init/main.c中的init()函数在启动后就已经是一个核心线程,但在转到执行init程序(如 /sbin/init)之后,内核中的init()就变成了/sbin/init程序,状态也转变成了用户态,也就是说核心线程变成了一个普通的进程。
第四周 扒开系统调用的三层皮
系统调用“三层皮”
API 中断向量 中断服务程序 当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。 Linux中是通过执行int $0x80来执行系统调用,这条汇编指令产生向量为128的编程异常;即中断向量0x80与System_call绑定起来。 系统调用号将函数xyz()和中断服务程序sys_xyz关联起来。系统调用的过程:先将系统调用号放在%eax里,相当于API和中断服务程序连接的桥梁,然后执行中断指令int$0x80,在堆栈上保存用户态栈顶地址,当时的状态字,当时的cs:eip值;内核态栈顶地址,当时的状态字,中断处理程序入口。然后系统调用的函数开始在内核执行,执行的结果(返回值)保存在%eax里,再由%eax传入gid,然后弹栈恢复现场。
第五周 扒开系统调用的三层皮 下
system_call对应的汇编代码的工作过程
sys_call只是汇编代码的声明,不是函数,所以gdb无法跟踪sys_call的位置,但是sys_call里包含的系统调用的时机非常重要:
中断向量int0x80如何与sys_call绑定的呢?
\init\main.c里的start_kernel里有trap_init()函数
\arch\x86\kernel\traps.c中有一个函数,将SYSCALL_WECTOR(系统调用中断向量)和system_call汇编代码的入口绑定。完成初始化。
系统调用处理过程
用户态的函数根据API接口,申请一个系统调用,触发int0x80中断向量指令,中断int指令(保护现场)保存用户态的栈顶地址,当时的状态字,当时的cs:eip值和内核态的栈顶指针,当时的状态字,和中断处理程序的入口(sys_call),sys_call_table根据系统调用号查找对应的函数,如果当前进程需要调度或者有其他的信号需要处理,则分别调度和处理,如果不需要,运行函数,恢复现场(restore_all), 最后iret结束。
第六周 进程的描述和进程创建
Cpu_idle启动两个线程:(0号进程是所有线程的祖先)
- Kernel_init用户态的进程启动,所有用户态进程的祖先(1号进程是所有进程的祖先)
- Kthreadd所有线程的祖先
在shell命令行创建进程的本质一样:先复制一份进程描述符,0号进程是手工写进代码的,1号进程复制0号的pcb,然后根据1号进程的需要把它的pid等等信息修改掉,再加载一个init可执行程序。
- 理解创建一个新进程如何创建和修改task_struct数据结构
一般通过系统调用来创建新的进程。fork(),vfork(),clone()都是通过调用do_fork来创建新进程的。要通过复制父进程的信息pcb(task_struct),然后给新 的子进程分配内核堆栈,再通过copy_process来修改子进程的task_struct.
特别关注新进程是从哪里开始执行的?为什么从哪里能顺利执行下去?即执行起点与内核堆栈如何保证一致。
从ret_from_thread开始执行。子进程被创建以后是在内核运行的,因为从这里开始复制父进程的task_struct,分配内核堆栈,创建进程也是一种系统调用,在内核堆栈中,执行int0x80,保存现场,来保证执行起点和内核堆栈的一致性。第七周 可执行程序的装载
新的可执行程序是从哪里开始执行的?对于静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时会有什么不同?
新的可执行程序是从程序的头部,也就是main函数开始执行的,被装载到内核的地址空间是0x8048x00(x是整数)。
为什么execve系统调用返回后新的可执行程序能顺利执行?
要得到返回之后能顺利执行的原因,让我们来先简单地分析一下整个过程。父进程在用户态中创建一个新的子进程,在内核中给子进程分配空间,子进程从ret_from_fork开始,先修改eax=0,再将五个值压栈,在执行execve时,需要将内核堆栈中以前压栈的五个之中的两个sp,ip做修改,修改的地址映射新可执行程序的入口,这样一来,新的可执行程序就可以顺利执行了。对于静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时会有什么不同?
对于静态链接和动态链接在execve系统调用返回时有很大的不同,静态链接相对于动态链接来说要简单许多,它们的不同之处主要在修改的sp,ip的值上。静态链接直接修改为新的可执行程序的入口即可(即0x8048x000,也就是main函数处);对于动态链接,让我们先来分析一下动态链接的过程。动态链接需要先加载动态链接器ld,然后由动态连接器加载程序中需要加载的动态库,以及其他的一些资源(这是一个遍历动态库依赖关系图的过程),然后才能开始执行新的可执行程序。所以对于动态链接,execve系统调用返回到动态链接加载器ld.第八周 进程的切换和系统的一般执行过程
从swtich_to理解进程上下文切换和中断上下文切换:
中断上下文切换是从用户态陷入内核,保存的是用户态进程的堆栈和状态,以及内核的堆栈和状态,在内核执行完毕以后,弹栈用户态进程的eip,flag等等,返回到用户态进程,继续执行。 进程上下文切换都发生在内核态,当一个进程时间片使用完,或者有优先级更高的(实时或者普通)进程进入进程就绪队列中,发生抢占时,先调用schedule,由pick_next_task选择下一个进程,swtich_to需要将当下的进程的堆栈和状态压栈保存,然后弹栈下一个进程的堆栈和状态,运行下一个进程。收获和遗憾
从还在寒假中到看第一个视频,那时候视频里还有年夜里的鞭炮声,一直到后来每周周一准时下载视频观看,linux内核分析到此算是一个小结。目前为止,学习Linux最困难的部分是还是入门的时候。当然因为以前的学习基础,这次学习孟宁老师的课不算太吃力。堆栈分析在学习汇编的时候吃得算透,所以学习起来还是比较轻松。收获最大的地方是老师为我揭开了linux内核的神秘面纱。在学习中,我看到了内核代码,分析里一些比较重要的内核中断,创建新进程,进程切换,加载进程的比较重要的关键代码。甚至还自己编写了系统调用函数,让我和内核有了初步的接触。学习真的是一个有趣的过程,学得越认真,基础越扎实,往后效率就越高。特别是遇到以前学得特别起劲的汇编时,想着,计算机的课程的门门相通,这里没有学懂,这笔账就会一直着,以后遇到这个部分又会吃力。所以学习还是要对得起自己。从来没想过要抄谁的作业,抄谁的考试,我很感谢一直抱着这样想法的自己。虽然现在还是有很多不足,很多不明白的地方,不欺骗自己地接着学下去。最大的遗憾是没有能和视频同步阅读完