夏虫的博客

却道天凉好个秋

一、多级存储层次

用多种存储器构成存储层次结构是提高存储系统整体性能的必要方法。

1.1 为什么需要多级存储层次

存储器的三个主要指标是容量速度单位价格。我们希望设计“容量大、速度快、价格低”的存储系统,例如,大规模应用程序要求存储系统容量大、价格低;然而,存储系统又应该在CPU执行程序时以足够快的素的向CPU提供指令和数据。然而,目前还没有一种存储器技术能满足这三个要求,现有的存储器技术只能满足其中一或两个要求。

解决这些矛盾的方法,就是采用多种存储器技术,构成多级存储层次。

一、Cache技术

现代计算机都在CPU和主存之间设置一个高速、小容量的缓冲处理器,称为Cache。Cache的存在填补了CPU和主存在速度上的巨大差距,对于提高整个计算机系统的性能有重要意义,是现代计算系统必不可少的部件,然而,Cache对程序员是透明的

此外,Cache技术这个此被广泛用于指代利用缓冲技术来实现局部数据再利用的技术。其能够缓解两个部件之间访问数据速度差距较大的问题,在硬盘、网页中都能看见缓冲技术的身影,本文专指CPU与主存之间的Cache。

阅读全文 »

本文是官网教程gem5: Creating SimObjects in the memory system的学习笔记。

本节教程是创建一个位于CPU和内存总线之间的阻塞式简单内存对象,主要实现了基本请求的传递。在下一节教程中,会在本节创建的简单内存对象之上增加部分逻辑,使其成为一个非常简单的阻塞单处理器缓存。下图是整个系统的示意图,其中,我们创建的内存对象有两个CPU侧的从端口(slave port)和一个内存总线侧的主端口(master port)。它将实现将请求从CPU传递到内存总线,并将响应从内存总线传递到CPU。

阅读全文 »

问题导入

图片全景拼接

观察者在同一个点,不是简单的平移旋转,需要运用投射关系(近大远小)

图像全局变换

研究对象为二维图像

1. 线性变换

缩放 Scale

旋转 Rotation

剪切 Shear

镜像 mirror

S = 2 * 2矩阵
$$
S = \begin{bmatrix}
a & b\
c & d
\end{bmatrix}
$$

2. 仿射变换

仿射变换 = 线性变换+平移

S = 3 * 3矩阵
$$
S = \begin{bmatrix} a & b & c\ d & e & f\ 0 & 0 & 1\end{bmatrix}
$$
6个未知数

3. 投影变换

S = 3 * 3矩阵
$$
S = \begin{bmatrix}
a & b & c\
d & e & f\
g & h & 1
\end{bmatrix}
$$
8个未知数,即8个自由度,表示能力最强

Gem5学习——创建一个简单的SimObject

一、名词解释

  • $\textcolor[RGB]{200,50,50}{SimObject}$

    在gem5中,SimObject是一个基础类,用于创建和管理模拟对象

  • $\textcolor[RGB]{200,50,50}{SimObjects}$

    SimObjects是gem5中用于管理模拟对象的组件。通常,gem5中的模拟对象都会继承自SimpleObject类,并且通过SimObjects组件来进行管理。

  • $\textcolor[RGB]{200,50,50}{SimpleObject}$

    SimpleObject是SimObject类的一个派生类,它在SimObject类的基础上增加了一些额外的功能,通常,在gem5中创建新的模拟对象时,都会从SimpleObject类进行派生,以便获得这些基本的功能。

二、创建一个简单的SimObject

即官网教程创建helloobjectgem5: Creating a very simple SimObject

$\textcolor[RGB]{100,200,50}{Gem5使用了Python和C++两种编程语言混合编程}$

gem5一方面通过python语言配置模拟器运行的参数和控制模拟过程的脚本,优势在于便于快速阅读和编写配置脚本;另一方面通过C++实现gem5中的各种模型(SimObjects),优势在于C++是一种高性能的编程语言,能够保证其模拟性能。混合编程充分发挥了两种语言的优势。

理解gem5的输出

本文是对官网教程gem5: Understanding gem5 statistics and output的整理,主要介绍了gem5的输出文件

模拟输出默认存放在m5out文件夹中,也可通过-d dirname命令行选项控制输出文件夹,但注意该选项只能在gem5.opt和配置脚本之间。例如

1
build/X86/gem5.opt -d cache_out configs/learning_gem5/part1/two_level.py

模拟系统会自动在当前文件夹下创建一个cache_out文件夹,并存放所用输出数据

输出如下

1. config.ini

模拟系统的全部配置参数

2. config.json

模拟系统的全部配置参数,json文件格式

3. stats.txt

模拟过程的数据统计。

SimObject的每个实例化都有自己的统计信息。模拟结束时,或发出特殊统计转储命令时,所有SimObjects的统计信息的当前状态都会转储到一个文件中。

统计转储以—————-开始模拟统计—————-开始。如果在gem5执行期间存在多个统计转储,则单个文件中可能存在多个此类转储。这对于长时间运行的应用程序或从检查点恢复时很常见。

每个统计信息都有一个名称(第一列)、一个值(第二列)和一个描述(最后一列以#开头),后跟统计信息的单位。

(1)有关执行的一般统计信息:

首先,统计信息文件包含有关执行的一般统计信息:

比如总模拟时间simSeconds,CPU提交的指令数simInsts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
---------- Begin Simulation Statistics ----------
simSeconds 0.000057 # Number of seconds simulated (Second)
simTicks 57467000 # Number of ticks simulated (Tick)
finalTick 57467000 # Number of ticks from beginning of simulation (restored from checkpoints and never reset) (Tick)
simFreq 1000000000000 # The number of ticks per simulated second ((Tick/Second))
hostSeconds 0.03 # Real time elapsed on the host (Second)
hostTickRate 2295882330 # The number of ticks simulated per host second (ticks/s) ((Tick/Second))
hostMemory 665792 # Number of bytes of host memory used (Byte)
simInsts 6225 # Number of instructions simulated (Count)
simOps 11204 # Number of ops (including micro ops) simulated (Count)
hostInstRate 247382 # Simulator instruction rate (inst/s) ((Count/Second))
hostOpRate 445086 # Simulator op (including micro ops) rate (op/s) ((Count/Second))

---------- Begin Simulation Statistics ----------
simSeconds 0.000490 # Number of seconds simulated (Second)
simTicks 490394000 # Number of ticks simulated (Tick)
finalTick 490394000 # Number of ticks from beginning of simulation (restored from checkpoints and never reset) (Tick)
simFreq 1000000000000 # The number of ticks per simulated second ((Tick/Second))
hostSeconds 0.03 # Real time elapsed on the host (Second)
hostTickRate 15979964060 # The number of ticks simulated per host second (ticks/s) ((Tick/Second))
hostMemory 657488 # Number of bytes of host memory used (Byte)
simInsts 6225 # Number of instructions simulated (Count)
simOps 11204 # Number of ops (including micro ops) simulated (Count)
hostInstRate 202054 # Simulator instruction rate (inst/s) ((Count/Second))
hostOpRate 363571 # Simulator op (including micro ops) rate (op/s) ((Count/Second))

(2)SimObjects的统计信息

接下来,将打印SimObjects的统计信息,比如CPU、内存控制器等等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
system.clk_domain.clock                          1000                       # Clock period in ticks (Tick)
system.clk_domain.voltage_domain.voltage 1 # Voltage in Volts (Volt)
system.cpu.numCycles 58236 # Number of cpu cycles simulated (Cycle)
system.cpu.numWorkItemsStarted 0 # Number of work items this cpu started (Count)
system.cpu.numWorkItemsCompleted 0 # Number of work items this cpu completed (Count)
system.cpu.dcache.demandHits::cpu.data 1951 # number of demand (read+write) hits (Count)
system.cpu.dcache.demandHits::total 1951 # number of demand (read+write) hits (Count)
system.cpu.dcache.overallHits::cpu.data 1951 # number of overall hits (Count)
system.cpu.dcache.overallHits::total 1951 # number of overall hits (Count)
system.cpu.dcache.demandMisses::cpu.data 136 # number of demand (read+write) misses (Count)
system.cpu.dcache.demandMisses::total 136 # number of demand (read+write) misses (Count)
...
...
...
system.mem_ctrl.avgPriority_cpu.inst::samples 228.00 # Average QoS priority value for accepted requests (Count)
system.mem_ctrl.avgPriority_cpu.data::samples 136.00 # Average QoS priority value for accepted requests (Count)
system.mem_ctrl.priorityMinLatency 0.000000018750 # per QoS priority minimum request to response latency (Second)
system.mem_ctrl.priorityMaxLatency 0.000000489500 # per QoS priority maximum request to response latency (Second)
system.mem_ctrl.numReadWriteTurnArounds 0 # Number of turnarounds from READ to WRITE (Count)
system.mem_ctrl.numWriteReadTurnArounds 0 # Number of turnarounds from WRITE to READ (Count)
system.mem_ctrl.numStayReadState 742 # Number of times bus staying in READ state (Count)
system.mem_ctrl.numStayWriteState 0 # Number of times bus staying in WRITE state (Count)
system.mem_ctrl.readReqs 364 # Number of read requests accepted (Count)
system.mem_ctrl.writeReqs 0 # Number of write requests accepted (Count)
system.mem_ctrl.readBursts 364 # Number of controller read bursts, including those serviced by the write queue (Count)
system.mem_ctrl.writeBursts 0 # Number of controller write bursts, including those merged in the write queue (Count)

安装python的Pydot包之后,当运行gem5进行模拟仿真之后,即可在m5out目录下找到config.pdf之类的配置图,如图1所示。

还可以通过dot命令将config.dot文件转换成自己需要的格式

pip install pydot
cd m5out
dot -Tpng -o config.png config.dot

RISCV1024核模拟报错

12月11日

1. fs.py代码阅读

gem5通过python内置的optparse模块添加命令行选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 实例化命令行模块
parser = optparse.OptionParser
# 通过调用configs/common/Options.py模块中的方法添加相应命令行选项
Options.addCommonOptions(parser) # 添加常用参数。如CPU类型、CPU时钟频率等
Options.addFSOptions(parser) # 添加FS模拟参数。如使用的内核、镜像文件等

# 如果命令行参数含有'--ruby',则增加ruby需要的命令行选项
if '--ruby' in sys.argv:
Ruby.define_options(parser)

# 解析参数
# parse_args()返回两个值:
# options, 这是一个对象(optpars.Values),保存有命令行参数值。只要知道命令行参数名,如file,就可以访问其对应的值:options.file。
# args,一个由positional arguments组成的列表。
(options, args) = parser.parse_args()

if args:
print("Error: script doesn't take any positional arguments")
sys.exit(1)

args 是一个包含所有命令行参数值的命名空间对象。每个命令行参数都成为这个对象的一个属性,可以通过 args.参数名 的形式访问。
在 gem5 的上下文中,这意味着 args 包含了模拟器运行所需的所有配置信息,如是否启用 Ruby 模拟、文件系统的配置等。

以上代码定义了argparse解析器并添加了命令行参数。然后它解析了这些参数,并基于这些参数来设置CPU类和内存类。之后,它构建测试系统,可能还会构建驱动系统,最后创建一个root根对象来启动模拟。

在gem5中,root是整个模拟环境的最顶层容器,它通常包含了模拟的计算机系统(如处理器、内存、总线等)以及与这些组件连接的所有设备和系统的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
np = options.num_cpus

test_sys = build_test_system(np)
if len(bm) == 2:
drive_sys = build_drive_system(np)
root = makeDualRoot(True, test_sys, drive_sys, options.etherdump)
elif len(bm) == 1 and options.dist:
# This system is part of a dist-gem5 simulation
root = makeDistRoot(test_sys,
options.dist_rank,
options.dist_size,
options.dist_server_name,
options.dist_server_port,
options.dist_sync_repeat,
options.dist_sync_start,
options.ethernet_linkspeed,
options.ethernet_linkdelay,
options.etherdump);
elif len(bm) == 1:
root = Root(full_system=True, system=test_sys)
else:
print("Error I don't know how to create more than 2 systems.")
sys.exit(1)

在这个脚本中,root对象是在不同条件下被创建的:
如果模拟的是一个双系统(dual系统),那么root会通过调用makeDualRoot()函数被创建,这意味着会有两个系统(test_sys和drive_sys)并行运行在模拟中。
如果启用了分布式模拟(dist模式),则通过调用makeDistRoot()函数创建root,此时root代表的系统会作为分布式模拟中的一个节点。
如果模拟的是单个系统,那么root会简单地通过Root(full_system=True, system=test_sys)创建,其中test_sys是通过build_test_system()函数构建的测试系统。

https://blog.csdn.net/qq_34898487/article/details/134318122

scons是一个Python写的自动化构建工具,使用SConstruct文件进行构建。

官网:https://scons.org/

Wiki:https://github.com/SCons/scons/wiki

manual page: SCons 4.6.0

SConstruct文件,类似于Make系统中的Makefile文件,这是SCons读取并控制编译构建的输入文件。 SConstruct和Makefile的最大不同是:SConstruct文件是python脚本,这会大大简化构建的过程。

Scons函数与构建顺序无关

SConstruct有一点和一般python脚本不同,SCons函数的书写调用顺序,并不影响SCons真实的构建顺序,这点和Makefile有点相似。换句话说,当你调用Program构建时(或者其他构建方法),SCons并非在此刻构建可执行文件,相反,这仅仅是告诉SCons你想要获得一个可执行文件的构建结果。举例来说,需要构建hello.c的源文件,SCons也仅仅是获取了构建可执行文件hello和源码hello.c之间的关系。

一、Scons构建方法

1. Program构建二进制文件

如果想构建hello.c程序,新建一个

1
Program('hello.c')

在SCons命令行中执行scons命令进行构建,会有scons会执行以下编译命令,默认得到hello二进制可执行文件

1
2
3
4
5
6
7
> scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
cc -o hello.o -c hello.c
cc -o hello hello.o
scons: done building targets.

1.1 指定二进制文件名称

如果想要指定二进制文件的文件名,则需要在源文件的前面,指明输出名称即可

1
Program('new_hello', 'hello.c')

1.2 简化构建输出

如果想要简化构建时命令行的输出,只看实际执行的编译命令,则在构建时使用-Q选项

1
2
3
> scons -Q
cc -o hello.o -c hello.c
cc -o new_hello hello.o

1.3 编译多个源文件

编译多个源文件,可以采用列表的方式指定多个源文件名

1
2
3
4
5
6
7
8
9
# build hello.c only
Program("My_hello", "hello.c")

# build hello.c name.c
Program("My_hello", ["hello.c", "name.c"])

# build all the .c file in the current dir
Program("My_hello", Glob('*.c'))

可以用scons内置的Split函数自动分割字符串

1
2
3
4
5
6
7
# Split function help divide the file string
Program('program', Split('main.c file1.c file2.c'))

src_file = Split("""" main.c
file1.c
file2.c""")
Program('program', src_files)

1.4 关键字参数

SCons同样支持输入或输出文件的关键字定义。输出文件的关键字是target,输入源文件的关键字是source, 因为关键字已经指明了该参数的含义,因此关键字的顺序不做要求

1
2
src_files = Split('main.c file1.c file2.c')
Program(source=src_files, target='program')

1.5 构建多个工程

用一个SConstruct文件,构建多个工程,最简单的方式是调用Program多次构建:

1
2
Program('foo.c')
Program('bar', ['bar1.c', 'bar2.c'])

1.6 多工程编译共享中间文件

同一份源码文件对应多个工程,最直接的方式是每个工程都加入该源码:

1
2
Program(Split('foo.c common1.c common2.c'))
Program('bar', Split('bar1.c bar2.c common1.c common2.c'))

当采用这种方式构建时,SCons会意识到common1.c和common2.c的复用,因此会将其仅仅编译一份中间文件,尽管生成的目标文件分别链接到各自的中间文件:

1
2
3
4
5
6
7
8
% scons -Q
cc -o bar1.o -c bar1.c
cc -o bar2.o -c bar2.c
cc -o common1.o -c common1.c
cc -o common2.o -c common2.c
cc -o bar bar1.o bar2.o common1.o common2.o
cc -o foo.o -c foo.c
cc -o foo foo.o common1.o common2.o

1.7 清理构建的工程

采用SCons我们不需要增加特殊的指令在构建后执行清除操作,相反,你可以简单使用-c或者–clean选项,此时SCons会自动删除构建的文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
cc -o hello.o -c hello.c
cc -o hello hello.o
scons: done building targets.

> scons -c
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Cleaning targets ...
Removed hello.o
Removed hello
scons: done cleaning targets.

2. Object构建中间文件

Object用于构建中间文件:

1
Object('hello.c')

执行scons命令,去构建整个工程,在LINUX工程中这将会仅仅构建hello.o。

1
2
3
4
5
6
> scons
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
cc -o hello.o -c hello.c
scons: done building targets.

3. Libary编译库文件

与Program格式相同

3.1 全部通过源文件来编译库

1
Library('foo', ['f1.c', 'f2.c', 'f3.c'])

SCons会自动根据系统来创建合适的库文件前缀和后缀,因此在Linux系统上,上述示例将会构建以下内容:

SCons会自动给库文件加入前缀和后缀。

1
2
3
4
5
6
> scons -Q
cc -o f1.o -c f1.c
cc -o f2.o -c f2.c
cc -o f3.o -c f3.c
ar rc libfoo.a f1.o f2.o f3.o
ranlib libfoo.a

如果不特别指定目标库文件名称,SCons将会从源文件列表中选择第一个作为库文件名称。

3.2 通过中间文件源文件和中间文件来编译库

上述示例介绍了通过源文件列表构建库文件,SCons同样也支持通过中间文件构建,或者源文件和中间文件混在一起构建也可以。

1
2
Library('foo', ['f1.c', 'f2.o', 'f3.c', 'f4.o'])
Library('foo', ['f1.c', 'f2.o', 'f3.c', 'f4.o'])

同时SCons也会意识到,只有源文件才需要进一步构建成中间文件:

1
2
3
4
5
> scons -Q
cc -o f1.o -c f1.c
cc -o f3.o -c f3.c
ar rc libfoo.a f1.o f2.o f3.o f4.o
ranlib libfoo.a

4. StaticLibrary编译静态库

对于LibraryStaticLibrary,他们完全等价,没有任何区别。

1
StaticLibrary('foo', ['f1.c', 'f2.c', 'f3.c'])

5. SharedLibrary构建动态库文件

1
SharedLibrary('foo', ['f1.c', 'f2.c', 'f3.c'])

Linux系统上输出为:

1
2
3
4
5
> scons -Q
cc -o f1.os -c f1.c
cc -o f2.os -c f2.c
cc -o f3.os -c f3.c
cc -o libfoo.so -shared f1.os f2.os f3.os

二、链接库文件

1. LIBS关键字指明库文件

通过指明LIBS变量关键字,来指定需要链接的库文件

1
2
Library('foo', ['f1.c', 'f2.c', 'f3.c'])
Program('prog.c', LIBS=['foo', 'bar'], LIBPATH='.')

不需要特别声明库文件的前缀(如lib),或者后缀(如.a或.lib),SCons会自动根据系统来查找相关前缀或后缀。

1
2
3
4
5
6
7
8
> scons -Q
cc -o f1.o -c f1.c
cc -o f2.o -c f2.c
cc -o f3.o -c f3.c
ar rc libfoo.a f1.o f2.o f3.o
ranlib libfoo.a
cc -o prog.o -c prog.c
cc -o prog prog.o -L. -lfoo -lbar

2.LIBPATH关键字指明库文件的查找路径

通过指明LIBPATH变量关键字,来指定库文件的查找路径

默认情况下,连接器只会在系统路径中查找库文件,SCons可以通过用户指定的LIBPATH变量,来查找用户定义路径:

1
2
Program('prog.c', LIBS = 'm',
LIBPATH = ['/usr/lib', 'usr/local/lib'])

Linux 中输出如下

1
2
3
> scons -Q
cc -o prog.o -c prog.c
cc -o prog prog.o -L/usr/lib -L/usr/local/lib -lm

这里推荐使用python的列表(list),因为python是跨平台的,这要迁移起来比较方便。当然您也可以将搜索路径放到一个字符串中,采用系统指定的分割符分开,如POSIX系统中采用冒号,Windows系统中采用分号:

1
2
3
4
# POSIX
LIBPATH = '/usr/lib:/usr/local/lib'
# Windows
LIBPATH = 'C:\\lib;D:\\lib'

python在Windows路径中要求采用反斜杠转义符

三、节点对象

在SCons内部,所有的文件和路径都被看作是节点(Nodes),这种方式让SConscript脚本更加便于迁移阅读

1. 跨平台时出现的问题

1
2
3
Object('hello.c', CCFLAGS='-DHELLO')
Object('goodbye.c', CCFLAGS='-DGOODBYE')
Program(['hello.o', 'goodbye.o'])

以上代码在linux上是可以正常编译的,但在windows上不可以,因为在Windows平台上,生成的中间文件是hello.obj和goodbye.obj,而非hello.o和goodby.o。

较好的处理方式是将Object的构建输出存入变量中,这样我们可以不断在列表后追加新的内容,将其作为Program的输入:

1
2
3
hello_list = Object('hello.c', CCFLAGS='-DHELLO')
goodbye_list = Object('goodbye.c', CCFLAGS='-DGOODBYE')
Program(hello_list + goodbye_list)

这样SConstruct脚本的跨平台性可以得到保证,其在Linux平台输出如下:

1
2
3
4
> scons -Q
cc -o goodbye.o -c -DGOODBYE goodbye.c
cc -o hello.o -c -DHELLO hello.c
cc -o hello hello.o goodbye.o

Windows平台输出如下:

1
2
3
4
5
C:\>scons -Q
cl /Fogoodbye.obj /c goodbye.c -DGOODBYE
cl /Fohello.obj /c hello.c -DHELLO
link /nologo /OUT:hello.exe hello.obj goodbye.obj
embedManifestExeCheck(target, source, env)

2. 构建方法返回目标节点列表

所有的构建方法返回一个节点对象列表,这些节点可以被用作其他构建方法的参数。

1
2
3
4
object_list = Object('hello.c')
program_list = Program(object_list)
print(type(object_list))
print(object_list)

linux平台输出如下

1
2
3
4
5
> scons -Q
<class 'SCons.Node.NodeList'>
['hello.o']
The object file is: hello.o
The program file is: hello

3. File创建文件节点 & Dir创建路径节点

SCons明确了文件节点和路径节点的不同,SCons支持FileDir两个函数,用于返回文件和路径节点:

1
2
3
4
5
hello_c = File('hello.c')
Program(hello_c)

classes = Dir('classes')
Java(classes, 'src')

通常情况下,您不需要手动调用File或Dir,因为调用构建方法时,会自动将输入作为文件或目录的名称,并将其转换为一个Node对象。

有时对象可能是文件也可能是一个路径,此时SCons提供了一个Entry函数,用于返回一个文件节点或者路径节点。

1
xyzzy = Entry('xyzzy')

4. 打印节点对象名称

大多数情况下我们需要调用节点去打印其内部的文件名称,但是请注意此时的对象是节点列表,而非文件对象,因此打印的时候需要增加下标访问

1
2
3
4
object_list = Object('hello.c')
program_list = Program(object_list)
print("The object file is: %s"%object_list[0])
print("The program file is: %s"%program_list[0])

在Linux系统输出如下:

1
2
3
4
5
> scons -Q
The object files is: hello.o
The program file is: hello
cc - o hello.o -c hello.c
cc -o hello hello.o

在上面的示例中,object_list[0]从列表中提取了一个实际的Node对象,Python的print语句将该对象转换为要打印的字符串。

5. 节点对象转换为字符串

如上节介绍所示,我们可以直接打印节点的文件信息,但是如果您想得到一个节点字符串,而非列表,则可以通过python内置的str函数实现。举例而言,如果您希望使用Python的os.path.exists来确认文件是否存在,您可以采用如下方式:

1
2
3
4
5
import os.path
program_list = Program('hello.c')
program_name = str(program_list[0])
if not os.path.exists(program_name):
print("%s doses not exist!"%program_name)

此时,在Linux系统中将会得到如下输出:

1
2
3
4
> scons -Q
hello does not exist!
cc -o hello.o -c hello.c
cc - o hello hello.o

四、判断是否需要重新编译

SCons很智能,只会编译需要编译的内容。比如我刚执行完scons,再次执行,则会提示scons: . is up to date.。 那么他是如何做到的呢?也不复杂,依赖一个Decider的方法,以及一个.sconsign.dblite文件。

默认情况下,如果文件的md5值改变了,才会重新编译。每次编译,SCons都会把md5存起来,再次执行时,如果md5没变,则不需要rebuild。

五、解析命令行选项

1. AddOption

作用:添加一项命令行选项

用法类似python中optparse库解析命令行参数的方法add_option

https://blog.csdn.net/lwnylslwnyls/article/details/8199454

1
2
3
4
5
6
7
8
9
10
AddOption(
'--prefix',
dest='prefix',
nargs=1,
type='string',
action='store',
metavar='DIR',
help='installation prefix',
)
env = Environment(PREFIX=GetOption('prefix'))

添加以上命令行选项后,执行scons -h后可以看到添加的选项说明

1
2
Local Options:
--prefix=DIR installation prefix

2. GetOption

作用:读取相应的命令行选项

通过AddOption得到的命令行参数,可以通过GetOption(‘参数名’)来获得,如上例所示

3. SetOption

作用:设置相应的命令行选项。

通过命令行选项设置的值将优先于使用SetOption设置的值

SetOption允许在脚本中设置项目默认值,并通过命令行临时覆盖它。SetOption调用也可以放在site_init.py文件中。

4. Help

在一个命令不知道怎么使用时,使用 –help 来获取当前命令的选项帮助是最好的方法。SCons 也是提供了 help 选项的编写,可以帮助用户简单的创建 –help 的语句。

1
2
3
4
5
Help("""
Type: 'scons program' to build the production program,
'scons debug' to build the debug version.
""")

scons -h输出如下

1
2
3
4
5
6
7
8
> scons -h
scons: Reading SConscript files ...
scons: done reading SConscript files.

Type: 'scons program' to build the production program,
'scons debug' to build the debug version.

Use scons -H for help about command-line options.

六、site_scons目录

在scons解析SConscript文件之前,scons将首先在各种系统目录含有SConstruct文件的目录scons 的命令行选项--site-dir指定的目录这三种目录下寻找site_scons目录,并将找到的site_scons目录放到python模块的搜索路径中,即sys.path。这样就使得SConscript文件可以引入site_scons目录下的模块并使用。

此外,有的site_scons目录还可能含有 site_init.py文件和site_tools目录

1.site_init.py

site_init.py文件将会先于SConscript文件被执行

2. site_tools

site_tools目录的路径会被预先添加到默认工作路径

七、SCons内置函数

1 GetCurrentDir()

获取当前路径。

2 Glob()

获取当前目录下的所有 C 文件。修改参数的值为其他后缀就可以匹配当前目录下的所有某类型的文件。

3 Split(str)

将字符串 str 分割成一个列表 list。

八、环境

环境是可以影响程序执行方式的值的集合,SCons区分了三种不同类型的环境,这些环境都可能会影响SCons本身的行为、它执行的编译器以及其他工具。

1. 外部环境 External Environment

外部环境是用户运行SCons时用户环境中的一组变量。这些变量不是SCons构建的自动组成部分,但在需要时可以进行检查。

外部环境即为Python 中os模块的environ字典,它提供了用户在执行SCons时有效的外部环境变量设置。

1
2
import os
print("Shell is", os.environ['SHELL'])

os.environ 是一个字典,是环境变量的字典,环境变量是程序和操作系统之间的通信方式

常见 key 字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
windows:
os.environ['HOMEPATH']:当前用户主目录。
os.environ['TEMP']:临时目录路径。
os.environ["PATHEXT"]:可执行文件。
os.environ['SYSTEMROOT']:系统主目录。
os.environ['LOGONSERVER']:机器名。
os.environ['PROMPT']:设置提示符。

linux:
os.environ['USER']:当前使用用户。
os.environ['LC_COLLATE']:路径扩展的结果排序时的字母顺序。
os.environ['SHELL']:使用shell的类型。
os.environ['LAN']:使用的语言。
os.environ['SSH_AUTH_SOCK']:ssh的执行路径。

2. 构造环境 Construction Environment

构造环境是在SConscript文件中创建的一个不同对象,它包含的值会影响SCons如何决定使用什么操作来构建目标,甚至定义应该从哪些源构建哪些目标。SCons最强大的功能之一是能够创建多个构造环境,包括从现有构造环境克隆新的自定义构造环境的能力。

构造环境是一个对象,它有许多相关的构造变量,每个变量都有一个名称和一个值,就像字典一样。

2.1 设置环境构造变量

1
2
3
4
5
6
# 由Environment()方法创建一个构造环境对象
env = Environment()
# 默认情况下,SCons使用一组适用于当前平台的构建器方法和构建变量初始化每个新的构造环境
# 初始化构造环境时,可以设置环境构造变量的值,以控制程序的生成方式。
env = Environment(CC='gcc', CCFLAGS='-O2')
env.Program('foo.c')

用户已明确指定使用GNU C编译器gcc,并且在编译对象文件时应使用-O2(优化级别2)标志。换句话说,$CC和$CCFLAGS的显式初始化会覆盖新创建的构造环境中的默认值。此时使用scons输出如下

1
2
3
> scons -Q
gcc -o foo.o -c -O2 foo.c
gcc -o foo foo.o

2.2 获取环境构造变量的值

Python打印调用将为我们输出CC和LATEX的值

ps:使用.get(xxx, None)方法进行获取意味着,如果未设置变量,我们将返回None,而不是失败

1
2
3
env = Environment()
print("CC is: %s" % env['CC'])
print("LATEX is: %s" % env.get('LATEX', None))

构造环境实际上是一个具有关联方法和属性的对象。如果您只想直接访问构造变量的字典,则可以使用env的Dictionary方法获取该字典。

1
2
3
4
env = Environment(FOO='foo', BAR='bar')
cvars = env.Dictionary()
for key in ['OBJSUFFIX', 'LIBSUFFIX', 'PROGSUFFIX']:
print("key = %s, value = %s" % (key, cvars[key]))

使用dump()方法格式化输出构造环境的信息

1
2
env = Environment()
print(env.Dump())

subst方法

直接通过字典索引的方式输出环境构造变量的值时,有些环境构造变量不会展开,使得阅读存在困难

1
2
3
4
5
6
env = Environment(CCFLAGS='-DFOO')
print("CCCOM is: %s" % env['CCCOM'])

> scons -Q
CCCOM is: $CC $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES
scons: `.' is up to date.

而使用subst方法输出,则会展开全部环境构造变量

1
2
3
4
5
6
env = Environment(CCFLAGS='-DFOO')
print("CCCOM is: %s" % env.subst('$CCCOM'))

> scons -Q
CCCOM is: gcc -DFOO -c -o
scons: `.' is up to date.

可见,CC被展开为gcc,CCFLAGS被展开为-DFOO

2.3 默认构造环境

如果没有指定构造环境,Program和Library等构造方法都是使用默认构造环境

1
2
3
4
5
6
# 使用默认构造环境
Program('foo', 'foo.c')

# 使用指定构造环境
opt = Environment(CCFLAGS='-O2')
opt.Program('foo', 'foo.c')

而控制默认构造和环境的方法为DefaultEnvironment

DefaultEnvironment函数返回初始化的默认构造环境对象,然后可以像任何其他构造环境一样对其进行操作,例如以下示例指定了默认构造环境使用的编译器为’/usr/local/bin/gcc’

1
2
3
4
5
6
# 方式一
DefaultEnvironment(CC='/usr/local/bin/gcc')
# 方式二
def_env = DefaultEnvironment()
def_env['CC'] = '/usr/local/bin/gcc'
# 两种方式等价

2.4 创建多个构造环境

SCons的一大优势是可以创造多个构造环境。

不同的源文件可能需要在命令行上启用不同的选项,或者不同的可执行程序需要与不同的库链接。SCons允许您创建和配置控制软件构建方式的多个构建环境,从而满足这些不同的构建需求。

每个构建环境根据构建某个软件或其他文件的不同方式进行定制。例如,如果我们需要用 -O2标志构建一个程序,用 -g (debug)标志构建另一个程序,我们可以这样做:

1
2
3
4
5
6
7
8
9
10
opt = Environment(CCFLAGS='-O2')
dbg = Environment(CCFLAGS='-g')
opt.Program('foo', 'foo.c')
dbg.Program('bar', 'bar.c')

> scons -Q
cc -o bar.o -c -g bar.c
cc -o bar bar.o
cc -o foo.o -c -O2 foo.c
cc -o foo foo.o

甚至可以构建同一个源文件的不同版本程序

1
2
3
4
5
6
7
8
opt = Environment(CCFLAGS='-O2')
dbg = Environment(CCFLAGS='-g')
opt.Program('foo', 'foo.c')
dbg.Program('foo', 'foo.c')

> scons -Q
scons: *** Two environments with different actions were specified for the same target: foo.o
File "/home/my/project/SConstruct", line 6, in <module>

但是因为产生了相同名字的不同中间文件而报错,为了避免这个问题,我们必须明确指定每个环境使用object生成器将foo.c编译为一个单独命名的对象文件,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
opt = Environment(CCFLAGS='-O2')
dbg = Environment(CCFLAGS='-g')
o = opt.Object('foo-opt', 'foo.c')
opt.Program(o)
d = dbg.Object('foo-dbg', 'foo.c')
dbg.Program(d)

> scons -Q
cc -o foo-dbg.o -c -g foo.c
cc -o foo-dbg foo-dbg.o
cc -o foo-opt.o -c -O2 foo.c
cc -o foo-opt foo-opt.o

2.5 复制构造环境

有时,您希望多个构造环境共享一个或多个变量的相同值。在创建每个构造环境时,不必总是重复所有公共变量,而是可以使用env构造环境的Clone方法来创建构造环境的副本。

与创建构造环境的Environment调用一样,Clone方法接受构造变量赋值,该赋值将覆盖复制的构造环境中的值。

例如,假设我们想使用gcc创建一个程序的三个版本,一个是优化的,一个调试的,还有一个两者都没有。我们可以通过创建一个“基本”构造环境来实现这一点,该环境将CC设置为gcc,然后创建两个副本,一个设置CCFLAGS用于优化,另一个设置CCFLAGS用于调试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
env = Environment(CC='gcc')
opt = env.Clone(CCFLAGS='-O2')
dbg = env.Clone(CCFLAGS='-g')

env.Program('foo', 'foo.c')
o = opt.Object('foo-opt', 'foo.c')
opt.Program(o)
d = dbg.Object('foo-dbg', 'foo.c')
dbg.Program(d)


> scons -Q
gcc -o foo.o -c foo.c
gcc -o foo foo.o
gcc -o foo-dbg.o -c -g foo.c
gcc -o foo-dbg foo-dbg.o
gcc -o foo-opt.o -c -O2 foo.c
gcc -o foo-opt foo-opt.o

2.6 更改环境构造变量

通过env的Replace方法可以更改env构造环境中环境构造变量的值,如果这个环境构造变量不存在,则新增。

1
2
3
4
5
6
7
env = Environment(CCFLAGS='-DDEFINE1')
env.Replace(CCFLAGS='-DDEFINE2')
env.Program('foo.c')

> scons -Q
cc -o foo.o -c -DDEFINE2 foo.c
cc -o foo foo.o

在构建环境实际用于构建目标之前,变量不会展开,而且SCons函数和方法调用与顺序无关,所以最后一个替换“获胜”并用于构建所有目标,而不管对Replace()的调用与对构建器方法的调用之间的顺序如何

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
env = Environment(CCFLAGS='-DDEFINE1')
print("CCFLAGS = %s" % env['CCFLAGS'])
env.Program('foo.c')

env.Replace(CCFLAGS='-DDEFINE2')
print("CCFLAGS = %s" % env['CCFLAGS'])
env.Program('bar.c')

> scons
scons: Reading SConscript files ...
CCFLAGS = -DDEFINE1
CCFLAGS = -DDEFINE2
scons: done reading SConscript files.
scons: Building targets ...
cc -o bar.o -c -DDEFINE2 bar.c
cc -o bar bar.o
cc -o foo.o -c -DDEFINE2 foo.c
cc -o foo foo.o
scons: done building targets.

3. 执行环境 Execution Environment

执行环境是SCons在执行外部命令(如编译器或链接器)以构建一个或多个目标时设置的值。

参考资料:

VictorWANG1992 - 简书 (jianshu.com)

SCons简单入门(一)-CSDN博客

SCons 4.5.2官方文档

scons_jeek_we的博客-CSDN博客

【千锋RT-Thread】第31章 SCons 构建工具 - 知乎 (zhihu.com)

1 指令系统结构的分类

CPU中用来存放操作数的存储单元主要有三种:堆栈累加器通用寄存器组。据此,指令系统(Instruction Set Architecture)可分为三种:

  • $\color[RGB]{40,120,181}{堆栈型结构}$:堆栈栈顶和次栈顶中的数据,运算后写入栈顶。两个操作数都是隐式的。优点:指令字比较短,程序占用的空间小;缺点:不能随机地访问堆栈,难以生成有效的代码,而且对栈顶的访问是个瓶颈。
  • $\color[RGB]{40,120,181}{累加器型结构}$:一个操作数是隐式的,即累加器;另一个是显示给出的。优点:指令字比较短,程序占用的空间小;缺点:只有一个中间结果暂存在累加器中,需要频繁访问存储器。

早期计算机大多采用堆栈型结构或累加器型结构

  • $\color[RGB]{40,120,181}{通用寄存器型结构}$:所有操作数都是显示给出的。优点:1.寄存器的访问比存储器快得多 2. 编译器能够更加容易、有效地分配寄存器 。根据操作数显式给出的来源不同,又可以分为两类:
    • $\color[RGB]{248,172,140}{寄存器-存储器型结构(RM结构)}:$一个操作数来自寄存器,另一个来自存储器。
    • $\color[RGB]{248,172,140}{寄存器-寄存器型结构(RR结构)}:$两个操作数都来自寄存器。

**显式给出:**用指令字中的操作数字段给出

**隐式给出:**用事先约定好的单元

阅读全文 »
0%