linux操作系统整体概述

       linux操作系统主要分为三个部分:进程管理、内存管理、文件系统管理(外部设备作为设备文件属于文件系统管理)。

       首先介绍系统的启动:在计算机启动后,首先得到处理的是BIOS和EFI等系统固件,系统固件从磁盘的引导扇区加载引导加载程序,而引导加载程序负责将Linux系统的内核装入内存,并跳转到linux内核所在的地址,开始linux内核的初始化工作。建立进程0,核心程序执行,将根文件系统安装到“/”下,然后创建新进程1(内核态),进程1调用系统调用execve()执行用户态下的可执行程序init()(/sbin/init)从而转变为用户态下的第一个进程,由该进程来为每个用户创建终端子进程shell,在shell中输入命令就可以创建终端用户进程。在用户进程执行的过程中,终端进程shell会调用wait()处于阻塞状态,直到用户进程执行完毕才恢复。

        现有一个可执行文件a.out,它的功能是从/home/user1/hello.txt中读取文件内容然后输出在显示器上。欲借助该程序的运行来解析linux操作系统的运作流程。

        在shell终端中输入命令“/home/user1/a.out”后,由终端进程shell作为父进程调用fork()系统调用创建一个a.out的子进程,具体流程如下:

        1. 为子进程在内核空间中分配一个8kb的union task_union内存区,用于存放进程的PCB(task_struct结构)和内核栈(thread_union结构)。

        2. 让当前指针指向父进程的pcb,并把父进程的pcb的内容拷贝到刚刚分配的新进程的pcb中,此时父进程与子进程的pcb是相同的。

        3. 为子进程分配一个唯一的进程标识符pid。

        4. 更新子进程pcb中不能从父进程pcb中继承的其他的域,例如进程间的亲属关系的域。并且将父进程地址空间的逻辑副本复制到子进程中,这里的逻辑副本采用写时复制。

        5. 将子进程的pcb插入进程链表以确保进程间的亲属关系,再将pcb插入pidHash并把pcb的状态域设置为task_running,然后调用wake_up_process()把子进程插入到运行队列链表。

        6.让父进程和子进程平分剩余的时间片,然后在父进程中返回子进程的pid,在子进程中返回0。

        linux采用虚存映射,它并不是一开始就将映像装入到物理内存的,而是把可执行文件从磁盘链接到进程的用户空间,把对文件的访问转化为对虚存区的访问。随着程序的运行,被引用的程序部分会由操作系统装入到物理内存中,这种将映像链接到进程的用户空间的方法叫做虚存映射。当可执行映像被映射到用户空间时,内核会为其分配虚拟区间,创建一组vm_area_struct结构来描述各个虚存区间的起始点和终止点,每个vm_area_struct结构代表可执行映像的一部分,而这些结构可以通过的mm_struct结构中的mmap指针来获取。mm_struct结构称为内存描述符,用来对进程的整个用户空间进行描述,其中pgd指针指向进程的页目录基地址,而mm_struct结构又可以由pcb中的mm指针来寻找到。    

        随着a.out进程被选中执行,操作系统会通过vm_area_struct寻找该进程的代码段,从中获取指令的虚拟地址,然后通过页目录来将其转化为物理地址,由于该进程的可执行映像只是被链接到虚拟空间,并没有为其分配物理地址以及建立从虚拟空间到物理空间的映射,所以第一次转化地址的时候,页目录相应位置的P=0,代表该页还没有被装入内存,会产生缺页中断,向内核请求调页,内核在收到缺页中断之后,通过伙伴算法来分配所需的物理内存(使用_get_free_pages函数完成物理页块的分配),然后由处理机调度中的作业调度选中相应作业调入内存(此时该进程相对于作业调度而言处于执行状态,但是相对于进程调度而言则是处于就绪状态,直到进程调度程序调度该进程占据cpu才算真正的执行),再由内核完成从虚拟空间到物理空间的地址映射关系。注意,linux操作系统的物理内存分配采用动态内存分配技术,即它把实际物理页面的分配推迟到不能推迟为止,也就是说,一直推迟到进程要访问的页不在物理内存为止,由此引起一个缺页异常后才会分配实际的物理地址。

        至此,进程便可以开始执行了,但当进程执行到要读取“/home/user1/hello.txt”文件的指令时,open()函数会产生一个open()系统调用(系统调用是中断号为128的一个软中断),open()系统调用有一个对应的系统调用号,通过该系统调用号可以从系统调用表中找到与其对应的服务例程sys_open()。sys_open()函数中的getname()函数从用户空间中把文件的路径名拷贝到内核空间,并通过get_unused_fd()从当前进程的“打开文件表”(files_struct结构)中找到一个空闲的表项,该表项的下标即为文件描述符fd。然后通过file_open()找到文件名对应的索引节点的dentry结构和inode结构,并找到或创建一个由file数据结构代表的读写文件的“上下文”。最后通过fd_install()函数,将新建的file数据结构的指针安装到当前进程的files_struct结构中,其位置即已分配的下标fd。

        file_open()函数的执行流程介绍:

        1. 从活动文件表BFD中找到主目录MFD的相应表目,也就是与待打开文件相联系的有关表目复制到内存。(BFD表整个系统只有一张)

        2. 依据MFD表目项中的标识符ID找到对应的活动名字表SFD,然后从SFD中搜索对应的文件名,再依据文件名所对应的标识符ID从BFD中找到文件对应的文件说明信息(也就是inode结构与dentry结构信息)。(SFD表每个用户一张)

        至此,文件已经链接到进程的用户空间中了,要读该文件时,便可以依据文件说明信息通过通道控制方式从磁盘中对应的块中读取文件信息输入缓冲区,读取的方法由虚拟文件系统VFS提供,由具体文件系统实现,然后由内核分配物理页面存放文件信息并且更新进程的页表。接着进程申请显示器设备,然后由内核依据设备分配规则来为其分配显示器。最后在文件内容输出到显示器后,进程结束,返回到终端进程shell,由其调用wait()函数处理已变为僵尸状态的子进程。

   

/*

 *  以上内容是我通过阅读《计算机操作系统教程第4版》和《Linux操作系统原理与应用第2版》之后自己梳理的操作系统脉络,如果问题,欢迎指出!

*/    

猜你喜欢

转载自www.cnblogs.com/ybxm/p/12395419.html