码迷,mamicode.com
首页 > 其他好文 > 详细

操作系统

时间:2018-09-23 13:37:28      阅读:195      评论:0      收藏:0      [点我收藏+]

标签:一段   文件   关键点   uid   指针   有一个   info   ack   返回   

操作系统

操作系统运行程序

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. 保护模式

技术分享图片

技术分享图片

操作系统

标签:一段   文件   关键点   uid   指针   有一个   info   ack   返回   

原文地址:https://www.cnblogs.com/megachen/p/9692109.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!