1.程序的构成
程序是由什么构成的?简单说来,这里有一个公式:程序 = 数据 + 指令。这是程序员再熟悉不过的了,数据自不用多说,指令就是告诉程序,怎样操作目标数据,这个“操作”不仅仅是在数据之间进行简单的运算,而是包含了操作这些数据的一系列行为的集合,比如做两个数字的加法运算,除了本身的加法这一指令之外,我们还需告诉CPU从哪里读取目标数据,以及完成加法操作后将数据放到何处等。
计算机的底层是二进制数据,因此不管是指令还是数据,最终都会以二进制的形式保存。对于CPU,怎么区分我们传入的二进制代码是数据还是指令呢?这就需要不同的线路将数据和指令进行分离。
- 控制(指令)总线:用来向CPU传输指令
- 数据总线:用来向CPU传输数据
2.CPU的简单理解
一个CPU的核心部件包括三个:运算器,控制器和寄存器。
运算器是执行运算操作的地方,它会接收数据的输入,在内部计算完成之后将数据输出。
光有运算器还是不够的,由于我们的指令和数据都是保存在内存中,运算器需要知道在内存的哪个地方读取数据,哪个地方读取指令,以及在计算完成之后将数据放到哪里等。这些信息就是由控制器提供的。
寄存器是做什么的?寄存器也叫暂存器,顾名思义,就是暂时保存数据的地方。寄存器位于CPU的内部,用以保存等待处理或者处理过的数据。为什么需要寄存器?比如我们做一个四则运算,每次运算之后的结果应该保存在哪里?内存吗?如果将每步运算结果保存在内存中,无疑是加大了CPU的开销,因为要在每步计算完成之后将其放入内存中,然后再读取出来,再输入到运算器进行计算……这很麻烦,开销很大,并且浪费了CPU宝贵的性能。寄存器的作用就是将数据暂时存放在CPU的内部,那么CPU在访问这些数据的时候,效率将会得到极大的提高(就近原则)。
3.中断机制(Interrupt)
计算机有很多I/O设备,比如键盘,鼠标,,光驱,磁盘,硬盘等。如果我们敲击了键盘,CPU怎么知道我们操作的是键盘而不是鼠标或者光驱呢?我们可以将敲击键盘这个行为当做一个事件,这个事件是偶发的,CPU如果想侦测到这个事件,有两个方式可以选择:
- 轮询机制(Poll):CPU比较勤快,每隔一段时间就去看看,有没有发生这个事件,如果发生了,就执行某个操作,如果没有发生时间,则不做任何处理。
- 中断机制(Interrupt):CPU比较懒,并不想总是去查看这些事件有没有发生,而是找另一个人帮忙看着,如果发生了事件,就通知CPU,CPU再去做相应处理。
和CPU一样,我们都非常勤(lan)快(duo),喜欢轻松又有保障的活。对于轮询机制,有一个问题:事件是有随机性的,有可能CPU轮询了一天,用户并没有做出任何操作,那不是亏大发了吗。所以我们肯定会选择中断机制啦,事实上CPU采用的也正是中断机制。
4.中断控制器
上面说了CPU的中断机制。那么问题来了,CPU又怎么知道是鼠标还是键盘上产生了事件?
这时候中断控制器就登场了。中断控制器和CPU的针脚相连,如果把中断控制器比做蜘蛛,它将通过周围遍布的蛛网和计算机上的I/O设备连接,如果某个I/O设备上产生了一个事件,就会发生电信号的变化,这个变化通过蛛网传给中断控制器,控制器也就知道是哪一个设备发生了响应,进而将讯息传递给CPU。
5.南桥和北桥
对于计算机中的数据处理,有高速低速之分,或者高频和低频之分。南桥和北桥就是分别用来传输这两种类型的数据,北桥在物理上距离CPU较进,用来处理高速高频的数据。大多数的I/O设备都在南桥上,这些设备经过南桥汇总后,通过线路连接传到北桥,再由北桥传给CPU。
举例:一个网页同时给一百万个用户访问,网页文件存放在服务器的硬盘中,服务器的每次响应都需要从硬盘中读取这个网页,由于硬盘连接在南桥,读取速度和频率较慢,这会产生严重的服务器延迟(比如一个硬盘最高一次只能进行10万次的数据输出,那么剩下的九十万用户就得等着),这是非常搞糟的体验。因此,现在的服务器很多都采用了固态硬盘,并将硬盘直接连接在北桥上,以获取更高的读取性能。
6.主频和缓存
主频可以理解为CPU的计算能力,主频越大,CPU在单位时间的计算能力越强。CPU的频率可以达到1GHZ,而内存的读取速度远远低于CPU的频率。给予木桶原理,即使CPU的速度再高,内存的读取效率跟不上,计算机的整体性能也难以提高。为此我们可以造更快的内存,但是这种方式造价极高。于是出现了缓存技术,缓存技术是在速度和造价之间的择中原则。
速度越快的部件,造价越高。因此最快的核心部分,我们只要一小块,然后在其外围放性能稍低(相对于核心)造价也稍低的设备,这就是缓存。CPU有一级缓存,二级缓存...缓存在CPU和内存之间起到了承上启下的作用。
缓存的工作,需要遵循程序的局部性原理,程序的局部性原理分为时间局部性和空间局部性。
- 时间局部性:刚刚访问的数据可能还会被访问,那么将该数据需要被缓存。
- 空间局部性:如果访问一个数据,那么离这个数据非常近的数据,也应该访问的比较快。因此在访问一个数据的时候,把其周围的数据也一并载入进来,这样就提高了其周围数据的访问速度。
程序的局部性原理,不仅应用在CPU的工作上,也用在码农的编码过程中,运用时间局部性和空间局部性,可以显著提高程序的运行效率。
举例:
- Javascript中访问内层变量比访问全局变量快得多,因此我们在编码的过程中尽量少的依赖全局变量,可以提高运行的效率。(克制使用全局变量的好处不仅这一处,这里简单的举例说明)。
- for循环中缓存变量,操作数据库的时候缓存字段等。
7.机器码,汇编,高级语言
我们知道所有程序必须要编译机器码(二进制)才能被计算机识别和执行,我们也知道汇编是对机器码的抽象,让我们不用直面枯燥难懂的机器码,而使用易于人类的语言进行编程。汇编语言也叫作微码,是CPU厂商为我们暴露出来,通过微码我们可以进行针对CPU的编程。需要注意的是,不同的CPU架构不同,对于同一操作,其暴露出的微码也不尽相同。因此针对不同的CPU,我们需要分别对他们进行编程。尽管有些许蛋疼,但总比使用机器码编程好太多了。
高级语言是对汇编语言更高的抽象,结合一些额外的机制,来弥合CPU之间的不同。然后暴露出公共的API程序员调用,尽管对于这份API,其在不同的平台下可能会进行不同的实现,但是对于程序员,我们只需调用这个API,就可以实现平台的兼容,而我们自己再也不用考虑硬件的差异性了。
8.CPU的架构
下面是一些常见的架构:
- ARM:手持硬件设备(安卓,IOS)
- x86:32位平台
- x64:64位,来自AMD
- 安腾:来自惠普,Intel收购
- Alpha:惠普
- UltraSparc:SUN公司
- Power:IBM
- M68000:摩托罗拉(M68K)
- PowerPC :IBM
9.CPU怎么执行一个程序
单核的CPU,在某个时刻只能进行一次运算,为什么我们的程序看上去是并行执行呢?
这里需要引入切割机制,切割机制可以分为CPU切割和内存切割。
CPU切割:把CPU按照时间片(Slice)切割。比如一个Slice是5ms,那么每个程序只能运行5ms,下一个5ms就该换一个程序运行了(当然也可能仍然运行这个程序,具体取决于CPU的分配机制)。同时,CPU还应该有一个机制来记录程序的运行状态,以便程序在下一次运行的时候可以从先前的状态继续执行。
内存切割:引入分段机制,想象两个程序同时运行,而且都从内存的某一个编号开始占用(比如0x00000),那么后面的程序就会破坏掉前面程序的数据。现在引入了内存分段的机制,将内存分为一块块的区域,这些区域内部都从0x00000开始,那么程序就不会相互干扰了。
10.操作系统
上例中,怎么保证每个程序在每个时间片中程序都是严格执行呢?如果程序赖着不走怎么办?
这就需要引入操作系统了,操作系统就是用来对程序进行调度,以及协调其他程序进行工作,是位于硬件和应用软件之间的中间层。有了操作系统,任何程序都不能直接和硬件打交道,而是要经过操作系统这一中间层进行处理。
同一份程序,在不同的操作系统运行的效果不同的解释:程序要执行某一个功能,将请求交给操作系统,操作系统通过调用相关的库来执行,不同的操作系统实现实现某种库的方式可能不一样,那么程序的效果也就不一样了。
11.Shell
以Windows为例,为什么双击桌面上的图标就可以运行相应的程序,或则在命令行中输入calc就可以打开计算器呢?
上面的两种操作:双击图标和在命令行输入命令回车,本质上都是指令。我们的指令需要操作系统传到内核(kernel)中,然后实现程序的运行。接收指令的这个东东,就是人机交互接口(Shell)。Shell包括两种:
- GUI图形Shell
- CLI命令行Shell
不管是GUI还是CLI,目的都是向内核传送指令,实现某项操作,对于内核来说,通过什么方式传送指令并不重要。
操作系统内核的功能包括:
- 进程管理(协调)
- 内存管理
- 文件系统
- 网路功能
- 硬件驱动
- 安全机制等
12.Linux发行版
终于讲到Linux了,照例在此之前应该讲讲Linux的起源,Unix,Bell实验室,Apple和Windows等等,但是历史性的东西并不是这篇博文的重点,这里就暂且跳过。
Linux是操作系统,也就是软件,软件要想运行,就需要编译成相应的二进制代码才能运行。
上面我们也谈到过,不同的CPU架构,完成某项操作的实现方式可能不同,因此在编译操作系统的时候不应该出现交叉编译的情况(比如在AMD上编译在Intel上运行)。
也就是说,想要在某个平台上使用操作系统,需要针对这个平台进行编译。这无疑增加了学习和使用的难度,作为初学者而言,这是噩梦。于是出现了一些组织,他们来对Linux的源码和外围的一些软件进行编译集成,然后发行出来,这就是Linux的发行版。这样的公司或组织有RedHat,Debian等
13.软件包管理器
早期的软件都是随着内核一起打包发布的,这样有个明显的缺点:不利于软件的管理,如软件的安装,卸载等。于是出现了软件包管理器,用来方便软件的管理,安装,卸载等。比较著名的有dpt(debian),rpm(redhat)等。
14.Linux的基本原则
- 组装:组合功能目的单一的小程序,完成复杂的任务
- 一切皆文件
- 尽量避免捕获用户接口(交互),比如输入ls,马上列出目录下的文件,不需要其他操作。
- 配置文件保存为纯文本格式——一个简单的文本编辑器就可以完成所有的功能。
15.命令的构成
基本格式为:命令 = 命题主体 + 选项 + 参数。
命令执行:输入命令后,由shell将指令发送给kernel,kernel即对该指令进行判断,决定是否执行。
命令可以跟上选项,用来修正命令的执行方式。格式为:
- 短选项:-单一字符,如 -a,-l,-h
- 长选项:--单词,如 --help
短选项多个选项可以组合:-a -h 可以组合成 -ah。长选项通常不能组合。
命令的参数:命令的表示命令的作用对象。如:ls -ah /etc 为列出 /etc 目录下的文件,如果不加参数,默认为当前目录。
16.一些琐碎知识
Linux(我这里使用的是RedHat)下一般有6个终端可以使用,可以按键盘组合键 ctrl + alt + f1~f6切换。
$ startx & -> 启用图形界面。
Linux图形界面的类型:Gnome(C),KDE(C++),XFace(轻量级图形界面)。
Linux命令行界面类型:bash csh zsh ksh....
任何跟shell相关的启动的程序,只要shell关了,这个程序也就关了(这不废话吗)。
su:切换用户(switch user 缩写)。
su root [-l] :半切换,完全切换。
passwd:修改密码,连输两次即可。
对于root,可以随便修改,对于普通用户,需要满足密码复杂性规则。