首先,编程语言是不能运行的,它是可运行的计算机程序的一种表达方式,而编译器或者解释器把编程语言转换为计算机可执行的程序。
而操作系统也是一种可执行的计算机程序,它通常先在计算机上运行起来,作为一套中间层存在。我们都知道,中间层是为了提供抽象转换而存在的,而这套叫做操作系统的中间层,定义了一套规则,使得后续的可执行程序能够更容易的被运行起来,而且能更容易的利用各种硬件(显示,键盘等等),能够调用一些库来消除不同的程序间重复的运行逻辑。
总结下,编程语言是一种可执行程序的表达方式,编译器把编程语言的表达方式转换为可执行的程序,而编译器做转换的过程,可以操作系统相关(也就是转换出的程序运行在特定操作系统上),也可以操作系统无关(直接在裸机运行)。而操作系统,本身是一个程序,也可以是编程语言来表达的(编译后通常操作系统无关),也可以不是(直接写机器码),而操作系统这个特殊的程序,提供了库,硬件驱动,运行时环境,框架等,是为了让运行在其上的程序写得更容易。
以c语言中的printf举例,如printf的底层就是调用了系统调用write函数,write系统调用函数作为操作系统API,为用户层提供写文件最基本的实现。这里系统调用write函数(属于c语言)的底层实现就是系统调用指令(汇编语言)的封装。我们知道调用“系统调用”有两种方式。
( 1) 将系统调用指令封装为 c库函数,通过库函数进行系统调用,操作简单。
(2)不依赖任何库函数,直接通过汇编指令 int与操作系统通信。
所以通过printf函数的实际实现其实是汇编指令的这个例子,我们可以认为c语言的底层实现就是汇编指令,因为c语言在编译过程中也都会全被翻译成汇编。最后翻译好的汇编会全部被翻译成机器指令,每条汇编指令都有一条对应的机器码,逐行翻译就行了。然后在执行时,这些程序会从硬盘中加载进内存。
接下来就是计算机执行机器指令了。计算机执行一个机器指令经历了这5个阶段:
既然内存是用来存放当前正在使用的(即执行中)的数据和程序,那么它是怎么工作的呢?我们平常所提到的计算机的内存指的是动态内存(即DRAM),动态内存中所谓的“动态”,指的是当我们将数据写入DRAM后,经过一段时间,数据会丢失,因此需要一个额外设电路进行内存刷新操作。具体的工作过程是这样的:一个DRAM的存储单元存储的是0还是1取决于电容是否有电荷,有电荷代表1,无电荷代表0。但时间一长,代表1的电容会放电,代表0的电容会吸收电荷,这就是数据丢失的原因。刷新操作定期对电容进行检查,若电量大于满电量的1/2,则认为其代表1,并把电容充满电;若电量小于1/2,则认为其代表0,并把电容放电,藉此来保持数据的连续性。
驱动程序是什么:驱动程序与硬件相关,编写驱动程序要非常了解硬件,同时给应用层提供API函数接口,应用层可以调用这些接口去访问硬件而不必了解硬件。
如果有上层应用软件来控制硬件时,还可以这样理解:驱动程序就是衔接操作系统与外部的硬件的C代码。因为上层的应用软件最后都是会调用操作系统api去做事情的。那操作系统怎么和硬件做交互?答案就是通过驱动程序实现的。
通俗地解释:比如说你有一个应用是控制每天早上六点钟开灯.应用程序只负责在适当的时间做适当的事(到六点了,要开灯了,触发按键信号,这个信号由操作系统调度通过驱动程序相应的API接口下发至硬件);具体它不用知道为什么按下键就能开灯,因为按下键后开灯的事情就是驱动程序完成了,驱动程序再去控制硬件管脚发出高低电平信号去驱动灯的开关.
单片机驱动程序文件组成:以led点灯为例,包括三个文件led.h(包括宏定义,变量声明,函数声明),led.c(模块的具体代码实现),main.c(工程的主函数,调用模块函数并适当组合就可以完成工程的项目要求功能)三个文件。用keil编译
refer:
操作系统学习(二)–进程描述和执行
单片机驱动程序编写
OpenGL与显卡驱动
操作系统——系统调用
用户态和内核态的简单理解
底层原理——用户态和内核态的区别,和上面一篇结合起来看,非常重要!:比如print()实际上就是执行了一个输出的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
调用"系统调用函数write"的两种实现
计算机机器语言是如何被执行的