操作系统

操作系统

操作系统运行程序

hello world程序的执行过程

  1. 用户通知操作系统执行hello world应用程序

  2. 操作系统找到hello world程序的相关信息, 程序的相关信息(ELF文件信息, 不包括.text, .data, .bss, stack segment, heap segment, 但是ELF信息包含了他们的位置)包含了许多的信息, 比如是否是可执行文件, 还可以通过该信息找到hello world程序在磁盘中的地址(其实程序的相关信息就是有文件的格式决定的)

  3. 通过父进程fork()出一个子进程, 每一个进程由一个task_struct结构体描述, 在task_struct结构体中有pid, ppid, gid, uid, mm, info, file_max等属性, 其中mm是虚拟内存的关键点, mm指针指向了mm_struct结构体, 该结构体有一个mmap指针, 看到map就可以想到与映射有关, mmap指向vm_area_struct结构体, vm_area_struct就是我们虚拟内存实现的核心结构了, vm_area_struct有vm_end指针, vm_start指针, vm_next指针等, 多个vm_area_struct通过vm_next连接成为链表, 其中vm_end和vm_start为他们初始化一些值, 这些值就是通过ELF得到的.text, .data等的起始位置和结束位置, 一个vm_area_struct结构体对应一个段(.data或者.text或者.bss等), 注意: 此时task_struct是分配了实际的物理地址, 但是vm_area_struct的vm_end和vm_start指针围城的区域的物理地址时没有分配的, 还有vm_area_struct的物理地址也是分配的, 所以我们可以知道虚拟地址仅仅是为在磁盘上的文件准备的, 动态的来说就是为进程准备的, 而vm_area_struct对OS来讲不是进程

  4. 在3中提到的有一个mm_struct结构体, 在该结构体中有一个属性用来维护虚拟地址到物理地址的映射, 就是我们的vm_area_struct结构体中vm_end和vm_start指针的值的区间到物理地址时怎么样的, 开个玩笑的话, 他们的值可以是我们自定义的任意的值, 只要能将他们正确的映射到物理内存地址既可以了, 但是实际上不能随便的设置

  5. 到目前为止磁盘上的文件上的任何数据都没有复制到内存中, 仅仅是CPU读取了ELF的信息知道了各个段的位置并他们初始化了vm_area_struct等结构体的默认值

  6. 将该hello world程序的task_struct放到对应优先级的就绪队列中等待CPU调用, 如果等到了running, 则CPU执行该task

  7. 接下来CPU将文件对应的段复制到"内存"中, 该"内存"是虚拟内存, CPU以为是物理内存, 所以说虚拟内存的实现是操作系统而不是硬件支持的, 所以需要OS进行编码(在mm_struct中的映射表), CPU访问内存时, 在OS中有一段代码, CPU发现对应的物理内存并没有分配数据, 所以就会引发exception, 缺页异常, 这样OS进行一系列操作(分配一页物理内存)分配物理地址即可, 就这样将文件加载到内存中并执行程序

  8. 遇到printf函数, 进入到函数定义的部分, 肯定有一个系统调用xxx, 系统调用xxx的实现是直接通过汇编实现的, 在一般的函数中使用的call调用函数, 而在系统调用中使用int 0x80等执行, CPU遇到int就会发生异常(系统调用就是异常的一种), 而执行call指令则不会, 这就是区别, CPU中的中断寄存器保存中断信息, CPU在执行了一个指令时不能处理中断, 但是执行了一个指令之后, 会检查中断寄存器中是否有值, 如果有值的话, 就证明收到了异常请求, CPU就会将PSW(Program State Word Register)寄存器的DPL位修改成内核态(这里就实现了从用户态到内核态的转换, 其实所有的状态转变都是通过修改PSW中对应的位的, 内核态和用户态必须由CPU的支持)

  9. 父进程保留现场, 将寄存器中的数据写到task_struct结构体中

  10. 在保护模式下(Hello world程序就是在该模式下运行的), 处理中断, 异常都是通过IDTR, IDT, GDT, GDTR等实现的, 下面就是讲一讲

  11. 首先, CPU读取IDTR寄存器(保存了IDT的地址)中的值获取IDT的地址, 通过0x80(十进制就是128, 这个是syscall()的值, 在Linux源码中有定义一组syscall的id号, 代表不同的系统调用, read, close, write)这个值找到IDT结构中的一条记录(段选择符), 在IDT中有一个叫做段选择符(名字可不是乱取的, 他的作用真的是用来选择一个东西的), 该段选择符的结构是index GDT|LDT PL, 三个字段, 都是使用数字表示的, index就是段选择符名字的由来, 此时CPU通过GDTR(保存了GDT的地址)中的值获取GDT的地址, 在DGT中的每一条记录都称之为段描述符, 通过index获取对应的段描述符, 段描述符包含了基地址, offset等字段, 通过这两个字段的值计算得出系统调用函数int 0x80对应的处理程序的入口地址(这里再一次声明一下: 该地址到底是虚拟地址还是物理地址呢, 上面第3点提到了, 虚拟地址就是为进程准备的, 存放进程的.data, .text, .bss, .code(包含了指令)段的, 所以这里的地址是物理地址, 内核可以说是系统调用的组合, 内核不是进程, 内核启动完毕之后会有一个systemd进程, 这个systemd进程使用的是虚拟地址), 执行该程序

  12. 系统调用结束之后, CPU实行return_from_syscall函数处理一些后事, 比如修改PSW进行模式转换, 从内核态到用户态, 继续执行下面的程序

  13. 执行完毕之后返回父进程(现场恢复)

  14. 图片一张, 这个是实模式下的, 保护模式比较复杂

  15. 保护模式

posted @ 2018-09-23 10:29  gogogo11  阅读(173)  评论(0编辑  收藏  举报