《C语言编程魔法书:基于C11标准》——第2章 学习C语言的预备知识 2.1 计算机体系结构简介

简介:

本节书摘来自华章计算机《C语言编程魔法书:基于C11标准》一书中的第2章,第2.1节,作者 陈轶,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

第2章

学习C语言的预备知识

我们在第1章已经大致介绍了C语言的概念以及编译、连接流程。我们知道C语言是高级语言中比较偏硬件底层的编程语言,因此对于用C语言的编程人员而言,了解一些关于处理器架构方面的知识是很有必要的,对于嵌入式系统开发的程序员而言更是如此了。

另外,C语言中有很多按位计算以及逻辑计算,所以对于初学者来说,如果对整数编码方式等计算机基础知识不熟悉,那么对这些操作的理解也会变得十分困难。因此,本章将主要给C语言初学者、同时也是计算机编程初学者,提供计算机编程中会涉及的基本知识,这样,在本书后面讲解到一系列相关概念时,初学者也不会感到陌生。

2.1 计算机体系结构简介

图2-1为一个简单的计算机体系结构图。


image

一个简单的计算机系统包含了中央处理器(CPU)以及存储器和其他外部设备。而在CPU内部则由计算单元、通用目的寄存器、程序序列器、数据地址生成器等部件构成。下面我们将从外到内分别简单地介绍这些组件。

2.1.1 贮存器

贮存器(Storage)尽管在图2-1中没有表示出来,但我们对它一定不会陌生,比如我们在PC上使用的硬盘(Hard Disk)就是一种贮存器。贮存器是一种存储器,不过它可用于持久保存数据而不丢失。因此我们通常把具有可持久保存的存储器统称为贮存器。现在PC上用得比较现代化的贮存器就是SSD(Solid-State Disk)了,俗称固态硬盘。当然,贮存器就其存储介质来说属于ROM(Read-Only Memory),即只读存储器。这类存储器的特点是数据能持久保留,比如我们PC上的文件,即便在关闭计算机之后也一直会保存在你的硬盘上,而且PC上的软件往往也是以可执行文件的形式保存在硬盘上的。但是它的读写速度非常缓慢,尤其是老式的SATA磁盘,写操作则更慢。因为通常对ROM的数据修改都要通过先读取某段数据所在的扇区,然后对该数据进行修改,再擦除所涉及的扇区,最后把修改好的数据所包含的扇区再写回去。而对于ROM来说,其扇区是有写入次数限制的,所以写入次数越多,损耗就越大。当我们发现一个硬盘访问很慢的时候,通常就是其扇区(或磁道)已经破损严重了,这是在不断纠错并交换良好的扇区所引发的延迟。在嵌入式系统中,我们用的ROM一般是EPROM、EEPROM、Flash ROM等。这些硬件的详细资料各位可以从网上轻易获得,这里不再赘述。

2.1.2 存储器

存储器(Memory)一般是指我们通常所说的内存或主存(Main Memory)。其存储介质属于RAM(Random Access Memory),即随机访问存储器。它的特点是访问速度快,可对单个字节进行读写,这与ROM需要擦除整个扇区再对整个扇区写入的方式有所不同,因此更高效、灵活。但是RAM的数据无法持久化,掉电之后就会消失。此外,RAM的成本也比ROM高昂得多,我们对比一下16GB的内存条与256GB SSD的价格就能知道。然而正因为RAM的访问速度快,并且离CPU更近,所以在许多系统中都是将程序代码与数据先读取到RAM中之后再让CPU去执行处理的。当然,在一些嵌入式系统中也有让CPU直接执行ROM中的代码并访问读ROM中常量数据的情况,因为这类系统中总线频率以及CPU频率都相对较低,并且ROM也是与CPU以SoC(System-On-Chip,系统级芯片)的方式整合在一块芯片上的,所以访问成本要低很多。而有些环境对ROM的读取速度甚至比读取RAM还更快些。

注意: 在本书中所出现的“存储器”均表示内存,即RAM。而将可持久保存数据的存储器都一律称为“贮存器”。了解了这些概念后,我们在国外网站购买Mac或PC时,看到相关的术语就不会手足无措了。这里提供Apple美国官网的Mac配置信息网页,各位可以参考:www.apple.com/macbook-pro/specs/。

2.1.3 寄存器

寄存器是在CPU核心中的、用于暂存数据的存储单元。一般处理器内部对数据的算术逻辑计算往往都需要通过寄存器(Register),而不是直接对外部存储器进行操作。因此,如果我们要计算一个加法或乘法计算,需要先把相关数据从外部存储器读到处理器自己的通用目的寄存器中,然后对寄存器做计算操作,再将计算结果也放入寄存器,最后将结果寄存器中的数据再写入外部存储器。寄存器的访问速度非常快,它是这三种存储介质中速度最快的,但是数量也是最少的。像在传统的32位x86处理器体系结构下,程序员一般能直接用的通用目的寄存器只有EAX、EBX、ECX、EDX、ESI、EDI、EBP这7个。还有一个ESP用于操作堆栈,往往无法用来处理通用计算。

2.1.4 计算单元

计算单元一般由算术逻辑单元(ALU)、乘法器、移位器构成。当然,像一般高级点的处理器还包含除法器,以及用于做浮点数计算的浮点处理单元(FPU)。它们一般都直接对寄存器进行操作。而涉及数据读写的指令会由专门的加载、存储处理单元进行操作。

2.1.5 程序执行流程

处理器在执行一段程序时,通常先从外部存储器取得指令,然后对指令进行译码处理,转换为相关的一系列操作。这些操作可能是对寄存器的算术逻辑运算,也可能是对存储器的读写操作,然后执行相关计算。最后把计算结果写回寄存器或写回到存储器。不过处理器在执行一系列指令的时候并不是每条指令都必须先经过上面所描述的整个过程才能执行下一条,而是采用流水线的方式执行,如图2-2所示。

图2-2体现了一个简单的处理器执行完一条指令的完整过程。我们这里假设从第一个取指令阶段到最后的写回阶段,这5个阶段均花费1个周期,倘若不是采用流水线的方式,而是每完成一条指令的执行再执行下一条指令,那么每条指令的处理都需要5个周期。而一旦采用流水线方式处理,那么我们可以看到,在第一条指令执行到译码阶段时,处理器可以对第二条指令做取指令操作;当第一条指令执行到执行阶段时,第二条指令执行到了译码阶段,此时第三条指令开始做取指令阶段,然后以此类推。这样,当整条流水线填充满之后,即执行到了第5条指令,那么对于后续指令而言,处理每一条指令的时间均只需要一个周期。

image

这里需要注意的是,并不是每条指令都需要访存操作,只有当需要对外部存储器做读写操作时才会动用访存执行单元。然而大部分指令都需要写回寄存器操作,即便像一条用于比较大小的指令,或一条系统中断指令,它们也会影响状态寄存器。当然,很多处理器会有空操作(NOP)指令,它仅仅占用一个时钟周期,而不会对除了指令指针寄存器以外的任何寄存器产生影响。

相关文章
|
2月前
|
存储 编译器 C语言
【C语言】数据类型全解析:编程效率提升的秘诀
在C语言中,合理选择和使用数据类型是编程的关键。通过深入理解基本数据类型和派生数据类型,掌握类型限定符和扩展技巧,可以编写出高效、稳定、可维护的代码。无论是在普通应用还是嵌入式系统中,数据类型的合理使用都能显著提升程序的性能和可靠性。
72 8
|
3月前
|
C语言
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性。本文探讨了C语言中的错误类型(如语法错误、运行时错误)、基本处理方法(如返回值、全局变量、自定义异常处理)、常见策略(如检查返回值、设置标志位、记录错误信息)及错误处理函数(如perror、strerror)。强调了不忽略错误、保持处理一致性及避免过度处理的重要性,并通过文件操作和网络编程实例展示了错误处理的应用。
96 4
|
4月前
|
NoSQL C语言 索引
十二个C语言新手编程时常犯的错误及解决方式
C语言初学者常遇错误包括语法错误、未初始化变量、数组越界、指针错误、函数声明与定义不匹配、忘记包含头文件、格式化字符串错误、忘记返回值、内存泄漏、逻辑错误、字符串未正确终止及递归无退出条件。解决方法涉及仔细检查代码、初始化变量、确保索引有效、正确使用指针与格式化字符串、包含必要头文件、使用调试工具跟踪逻辑、避免内存泄漏及确保递归有基准情况。利用调试器、编写注释及查阅资料也有助于提高编程效率。避免这些错误可使代码更稳定、高效。
711 12
|
5月前
|
存储 算法 Linux
C语言 多进程编程(一)进程创建
本文详细介绍了Linux系统中的进程管理。首先,文章解释了进程的概念及其特点,强调了进程作为操作系统中独立可调度实体的重要性。文章还深入讲解了Linux下的进程管理,包括如何获取进程ID、进程地址空间、虚拟地址与物理地址的区别,以及进程状态管理和优先级设置等内容。此外,还介绍了常用进程管理命令如`ps`、`top`、`pstree`和`kill`的使用方法。最后,文章讨论了进程的创建、退出和等待机制,并展示了如何通过`fork()`、`exec`家族函数以及`wait()`和`waitpid()`函数来管理和控制进程。此外,还介绍了守护进程的创建方法。
C语言 多进程编程(一)进程创建
|
4月前
|
C语言
教你快速理解学习C语言的循环与分支
教你快速理解学习C语言的循环与分支
25 0
|
4月前
|
C语言
初识C语言:与计算机的交流之输入与输出(scanf和printf)
初识C语言:与计算机的交流之输入与输出(scanf和printf)
322 0
|
4月前
|
存储 程序员 编译器
初识C语言,计算机语言的基石
初识C语言,计算机语言的基石
|
5月前
|
Linux C语言
C语言 多进程编程(三)信号处理方式和自定义处理函数
本文详细介绍了Linux系统中进程间通信的关键机制——信号。首先解释了信号作为一种异步通知机制的特点及其主要来源,接着列举了常见的信号类型及其定义。文章进一步探讨了信号的处理流程和Linux中处理信号的方式,包括忽略信号、捕捉信号以及执行默认操作。此外,通过具体示例演示了如何创建子进程并通过信号进行控制。最后,讲解了如何通过`signal`函数自定义信号处理函数,并提供了完整的示例代码,展示了父子进程之间通过信号进行通信的过程。
|
5月前
|
Linux C语言
C语言 多进程编程(四)定时器信号和子进程退出信号
本文详细介绍了Linux系统中的定时器信号及其相关函数。首先,文章解释了`SIGALRM`信号的作用及应用场景,包括计时器、超时重试和定时任务等。接着介绍了`alarm()`函数,展示了如何设置定时器以及其局限性。随后探讨了`setitimer()`函数,比较了它与`alarm()`的不同之处,包括定时器类型、精度和支持的定时器数量等方面。最后,文章讲解了子进程退出时如何利用`SIGCHLD`信号,提供了示例代码展示如何处理子进程退出信号,避免僵尸进程问题。
|
5月前
|
消息中间件 Unix Linux
C语言 多进程编程(五)消息队列
本文介绍了Linux系统中多进程通信之消息队列的使用方法。首先通过`ftok()`函数生成消息队列的唯一ID,然后使用`msgget()`创建消息队列,并通过`msgctl()`进行操作,如删除队列。接着,通过`msgsnd()`函数发送消息到消息队列,使用`msgrcv()`函数从队列中接收消息。文章提供了详细的函数原型、参数说明及示例代码,帮助读者理解和应用消息队列进行进程间通信。

热门文章

最新文章