CPU加电后,第一条指令是多少? 这条指令在内存中,还是BIOS中,为啥?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CPU加电后,第一条指令是多少? 这条指令在内存中,还是BIOS中,为啥?相关的知识,希望对你有一定的参考价值。
参考技术A CPU一上电代码执行的地址是0FFFFFFF0h,指向的是BIOS Flash,因为内存是RAM不加电前不能保存数据,需要将 BIOS内(ROM)的数据映射到内存才能启动。课程学习总结报告
从存储程序计算机到冯诺依曼体系结构
存储程序计算机的主要思想是将程序存放在计算机存储器中,然后按存储器中的存储程序的首地址执行程序的第一条指令,以后就按照该程序中编写好的指令执行,直至程序执行结束。
冯诺依曼体系结构的主要特点是,CPU和内存是计算机的两个最主要组成部分,内存中保存着数据和程序指令,CPU从内存中取指令执行,其中有些指令让CPU做运算,有些指令让CPU读写内存中的数据。
其中运算器、存储器、控制器、输入设备和输出设备5大基本类型部件组成了计算机硬件。
程序计数器又成为指令指针,即为IP、EIP、RIP寄存器,负责存储将要执行的下一条指令在存储器中的地址。
CPU、内存和I/O设备通过总线连接。内存中存放指令和数据。
冯诺依曼体系结构的核心是存储程序计算机。
计算机存储系统的层次结构
计算机的总线结构
有些设备像内存芯片一样连接到处理器接口上,正因为处理器接口上可以挂多个设备和内存芯片所以才叫总线。
总线内部又分为地址总线、数据总线和控制总线。
计算机的三大法宝,操作系统的两把宝剑
计算机的三个法宝
- 存储程序计算机
- 函数调用堆栈机制
- 中断
函数调用堆栈框架
堆栈可以用来传递函数的参数,还提供局部变量的空间。
操作系统有两把宝剑:
- 中断上下文
- 进程上下文
中断上下文用来保存现场和恢复现场。
系统调用
Linux操作系统采用0和3两个特权级别,分别对应内核态和用户态。用户态和内核态很显著的区分方法就是CS:EIP的指向范围。在内核态时,它可以是任意地址。在用户态时只能访问0x00000000~0xbfffffff的地址空间。
Linux下系统调用通过int 0x80中断完成,中断保存了用户态CS:EIP的值,以及当前的堆栈段寄存器的栈顶。
操作系统为用户态进程与硬件设备进行交互提供了一组接口——系统调用。
- 把用户从底层的硬件编程中解放出来
- 极大的提高了系统的安全性
- 使用户程序具有可移植性
API和系统调用是不同的
- API只是一个函数定义
- 系统调用通过软中断向内核发出一个明确的请求
- 一般每个系统调用对应一个封装例程,库再用这些封装例程定义出给用户的API
- 不是每个API都对应一个特定的系统调用
- API可能直接提供用户态的服务
- 一个单独的API可能调用几个系统调用
- 不同的API可能调用了同一个系统调用
xyz,system_call,sys_xyz——API,中断向量,服务程序
当用户态进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数
在Linux中是通过执行int $0x80来执行系统调用的,这条汇编指令产生向量为128的编程异常。
内核实现了很多不同的系统调用,进程必须指明需要哪个系统调用,这需要传递一个名为系统调用号的参数——使用EAX寄存器
system_call是Linux中所有系统调用的入口点,每个系统调用至少有一个参数,即由EAX传递的系统调用号。
除系统调用号之外,参数不能超过六个——EBX,ECX,EDX,ESI,EDI,EBP
系统调用号将xyz和sys_xyz关联起来。
操作系统内核实现操作系统的三大管理功能
进程管理、内存管理和文件系统,对应三个重要的抽象概念进程、虚拟内存和文件。
进程管理
进程描述符结构示意图如下:
进程状态转换图如下:
进程的创建:以fork函数为例
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char * argv[]) { int pid; /* fork another process */ pid = fork(); if (pid < 0) { /* error occurred */ fprintf(stderr,"Fork Failed!"); exit(-1); } else if (pid == 0) //pid == 0和下面的else都会被执行到(一个是在父进程中即pid ==0的情况,一个是在子进程中,即pid不等于0) { /* child process */ printf("This is Child Process! "); } else { /* parent process */ printf("This is Parent Process! "); /* parent will wait for the child to complete*/ wait(NULL); printf("Child Complete! "); } }
- dup_thread复制父进程的PCB
- copy_process修改复制的PCB以适应子进程的特点,也就是子进程的初始化
- 分配一个新的内核堆栈(用于存放子进程数据)
- 内核堆栈的一部分也要从父进程中拷贝
- 根据拷贝的内核堆栈情况设置eip,esp寄存器的值
进程的切换
Linux内核调用schedule()函数进行进程调度,并调用context_switch进行上下文切换,调用switch_to来进行进程关键上下文切换。
系统执行时可执行程序的装载
可执行程序的执行环境
-
一般我们执行一个程序的Shell环境,我们的实验直接使用execve系统调用。
-
Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身
- 例如,int main(int argc, char *argv[])
- 又如, int main(int argc, char *argv[], char *envp[])//envp是shell的执行环境
-
Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数
- int execve(const char * filename,char * const argv[ ],char * const envp[ ])
命令行参数和环境串都放在用户态堆栈中
- fork子进程的时候完全复制了父进程;调用exec的时候,要加载的可执行程序把原来的进程环境覆盖掉,用户态堆栈也被清空
- 命令行参数和环境变量进入新程序的堆栈:把环境变量和命令行参数压栈(如上图),也就相当于main函数启动
- shell程序-->execve-->sys_execve,然后在初始化新程序堆栈的时候拷贝进去
- 先传递函数调用参数,再传递系统调用参数
动态链接分为可执行程序装载时动态链接和运行时动态链接(大部分使用前者);
总结
通过这门课程,理解了计算机硬件的核心工作机制和用户态程序如何通过系统调用陷入内核,从实践的角度理解了操作系统内核。疫情期间很遗憾不能进行线下的教学,感谢老师在此期间的辛苦付出。
以上是关于CPU加电后,第一条指令是多少? 这条指令在内存中,还是BIOS中,为啥?的主要内容,如果未能解决你的问题,请参考以下文章