操作系统角度分析helloworld程序的执行

操作系统角度分析helloworld程序的执行

#include <stdio.h>
int main(int argc, char *argv[]){
    
    
    puts("hello,world");
    return 0;
}

一、用户通过命令行或双击打开的方式告知操作系统执行helloworld程序

二、操作系统分析程序

程序是经过编译链接过程的可执行文件。操作系统找到helloworld程序的相关信息,检查其类型是否是可执行文件;并通过程序首部信息,确定代码和数据在可执行文件中的位置并计算出对应的磁盘块地址。

三、可执行文件的装载:创建一个新的进程,并将helloworld可执行文件映射到该进程结构,表示由该进程执行helloworld程序

1、文件装载的步骤如下:首先在用户层,bash进程会调用fork()系统调用创建一个新的进程,然后新的进程调用execve()系统调用执行制定的ELF文件,原先的bash进程继续返回等待刚才启动的新进程结束。在进入execve()系统调用后,Linux内核就开始进行真正的装载工作。在内核中,execve()系统调用相应的入口是sys_execve(),sys_execve()进行一些参数的检查复制之后,调用do_execve()。do_execve()会首先查找被执行的文件,如果文件找到,则读取文件的前128个字节(主要是判断文件类型)。当do_execve()读取了这128个字节的文件头部后,然后调用search_binary_handle()去搜索和匹配合适的可执行文件,search_binary_handle()会通过判断文件头部的魔数确定文件的格式,并调用相应的装载处理过程,比如,如果是elf文件,则调用load_elf_binary();返回elf文件的入口地址。

在这里插入图片描述
2、fork用于创建一个进程,所创建的进程复制父进程的数据段/BSS段/堆/栈等所有用户空间信息;fork的父子进程共享代码段;在内核中操作系统重新为其申请了一个PCB,并使用父进程的PCB进行初始化;父子进程以只读方式共享页框(将页表项的保护位设置为只读),当其中之一要修改页框时,内核才通过缺页异常处理程序分配一个新的页框,并将页框标记为可写。创建过程:
(1)给新进程分配一个标识符
(2)在内核中分配一个PCB,将其挂在内存的PCB进程表上
(3)复制它的父进程的环境(PCB中大部分的内容)
(4)为其分配资源(程序、数据、栈等)
(5)复制父进程地址空间里的内容(代码共享,数据写时拷贝)
(6)将进程置成就绪状态,并将其放入就绪队列,等待CPU调度。

PCB一般包括:
   1): 进程描述信息:进程标识符(process ID),唯一,通常是一个整数;进程名,通常基于可执行文件名,不唯一。 用户标识符(user ID)。进程组关系。
   2):进程控制信息:当前状态、优先级(priority)、代码执行入口地址、程序的磁盘地址、运行统计信息(执行时间、页面调度)、进程间同步和通信、进程的队列指针、进程的消息队列指针。
   3):所拥有的资源和使用情况:虚拟地址空间的状况、打开文件列表。
   4):CPU现场信息:寄存器值(通用寄存器、程序计数器PC、程序状态字PSW、栈指针);指向该进程页表的指针。

3、虚拟存储技术是指:当进程运行时,先将其一部分装入内存,另一部分暂留在磁盘,当要执行的指令或访问的数据不在内存时,由操作系统自动完成将它们从磁盘调入内存的工作。虚拟地址空间即为分配给进程的虚拟内存。虚拟地址是在虚拟内存中指令或数据的位置,该位置可以被访问,仿佛它是内存的一部分。实际上只有程序运行时用到了才去内存中寻找虚拟地址对应的页帧,找不到才可能进行分配,这就是内存的惰性(延时)分配机制。

​ 在linux操作系统中,每当生成一个进程时,它就具有了一个虚拟地址空间,通常在32位系统中,它的大小为4GB,按3:1的比例分配给用户空间和内核空间。虚拟地址空间的简单工作原理就是将虚拟地址通过页表映射到实际物理地址(所以在c语言中malloc分配内存时,即使是malloc一个int大小的空间,但是实际上是分配了一个页的内存,一般是4096个字节,4kB)
在这里插入图片描述

4、ELF文件装载的最终目的有两个:

  1. 确定各个区域的边界:text区的起始和终止位置,data区的起始和终止位置,bss区的起始和终止位置,heap和stack的起始位置(它们的终止位置是动态变化的)。
  2. 把text区和data区的内容做mmap映射:ELF文件的内容不会被真地拷贝到内存,只有当真正需要的时候,内核才会通过page fault的形式把文件内存复制到内存中去。

在这里插入图片描述

四、cpu上下文切换,操作系统为helloworld程序设置CPU上下文环境,并跳到程序起始指令。

1、每个任务在运行前,系统都需要事先设置好CPU寄存器和程序计数器。CPU寄存器是CPU内置的容量小速度极快的内存,程序计数器是用来存储CPU正在执行的指令位置、或者即将执行的下一条指令位置。他们称为CPU的上下文。

2、下进程A:保存进程A的上下文环境(程序计数器、程序状态字、其他寄存器……),用新状态和其他相关信息更新进程A的PCB。
3、把进程A移至合适的cpu调度队列(就绪、阻塞……)
4、将进程B的状态设置为运行态,从进程B的PCB中恢复上下文(程序计数器 、程序状态字、其他寄存器……)
5、切换全局页目录以加载一个新的地址空间。

五、执行helloworld程序的第一条指令,发生缺页异常。

1、程序计数器(Program Counter,PC)用来指出下一条指令在主存储器中的地址。在程序执行之前,首先必须将程序的首地址,即程序第一条指令所在主存单元的地址送入PC,因此PC的内容即是从主存提取的第一条指令的地址。当执行指令时,CPU能自动递增PC的内容,使其始终保存将要执行的下一条指令的主存地址,为取下一条指令做好准备。当遇到转移指令时,下一条指令的地址将由转移指令的地址码字段来指定,而不是像通常的那样通过顺序递增PC的内容来取得。因此,程序计数器的结构应当是具有寄存信息和计数两种功能的结构。常见的CPU的CALL指令(“调用”指令)的功能,就是以下两点:
​ (1)将下一条指令的所在地址(即当时程序计数器PC的内容)入栈,
​ (2)并将子程序的起始地址送入PC(于是CPU的下一条指令就会转去执行子程序)。

2、CPU执行指令时,需将用户程序中的逻辑地址转换为运行时可由机器之间寻址的物理地址。顺序执行程序时,控制器首先按程序计数器所指出的指令地址从内存中取出一条指令,然后分析和执行该指令,同时将PC的值加1指向下一条要执行的指令。

3、每个进程都会有自己的段页表Page Table,页表存储了进程中虚拟地址到物理地址的映射关系,所以就相当于一张地图。进行地址变换时,先将段号与段表长度进行比较,若未越界,则与段表始址相加,得到段表中的页表始址。将页表始址与页号相加,得到页表中的物理块号。最后利用物理块号和页内地址获得物理地址。在段页式的系统中,获得一条数据或者指令需要访问内存三次。第一次访问内存中的段表,得到页表始址。第二次访问相应段的页表项,得到物理地址。第三次才是访问物理内存中的数据或者指令

在这里插入图片描述

4、MMU收到CPU的虚拟地址之后根据PCB中的页表指针开始查询页表,确定是否存在映射以及读写权限是否正常。假如目标内存页在物理内存中没有对应的页帧或者存在但无对应权限CPU 就无法获取数据,这种情况下CPU就会报告一个缺页错误。由于CPU没有数据就无法进行计算,CPU罢工了用户进程也就出现了缺页中断,进程会从用户态切换到内核态,并将缺页中断交给内核的 Page Fault Handler 处理。

在这里插入图片描述

六、操作系统分配一页物理内存,并将代码从磁盘读入内存,然后继续执行helloworld程序。

操作系统执行缺页异常处理程序:获得磁盘地址,启动磁盘,将该页调入内存。如果内存中有空闲页框,则分配一个页框,将新调入页装入,并修改页表中相应页表项的有效位及相应的页框号。若内存中没有空闲页框,则要置换内存中某一页框(lru算法);若该页框内容被修改过,则要将其写回磁盘。

七、helloworld程序执行puts函数(系统调用),在显示器上写一字符串。

八、操作系统找到要将字符串送往的显示设备,通常设备是由一个进程控制的,所以,操作系统将要写的字符串送给该进程。

九、操作系统:控制设备的进程告诉设备的窗口系统它要显示字符串,窗口系统确定这是一个合法的操作,然后将字符串转换成像素,将像素写入设备的存储映像区。视频硬件将像素转换成显示器可接收的一组控制/数据信号,显示器解释信号,激发液晶屏。

猜你喜欢

转载自blog.csdn.net/u014618114/article/details/107526375