2017-2018-1 20155219 《信息安全系统设计基础》第7周学习总结

教材学习内容总结

Y86指令集体系结构

内容:定义一个指令集体系结构,包括定义各种状态元素、指令集和它们的编码、一组编程规范和异常事件处理。

是一种程序员可见的状态

共有8个程序寄存器:%eax,%ecx,%edx,%ebx,%esi,%edi,%esp和%ebp。

  • 条件码:ZF(零)、SF(符号)、OF(有符号溢出)

  • 程序计数器(PC):存放当前正在执行的指令的地址

  • 存储器:很大的字节数组,保存着程序和数据。Y86系统用虚拟地址来引用存储器的位置,硬件和操作系统软件联合起来将虚拟地址翻译成实际或者物理地址。
    状态码(stat):表明程序执行的总体状态。(异常处理)

  • 常用指令如下
    image

  • halt:终止指令的执行

  • nop:占位指令,他不做任何事,后续为了实现流水线,它有一定的作用

  • xxmovl:一系列的数据传送指令,其中r代表寄存器,m代表存储器,i代表立即数.比如rrmov指令,则代表将一个寄存器的值,赋给另外一个寄存器

  • opl:操作指令,比如加法,减法等等

  • jxx:条件跳转指令,,根据后面的条件进行跳转

  • cmovxx:条件传送指令,后面的xx代表的是条件.需要注意的是,条件传送只发生在两个寄存器之间,不会将数据传送到存储器.

  • call与ret:方法的调用和返回指令.一个将返回地址入栈,并跳到目标地址.一个将返回地址入PC,并跳到返回地址.

  • push和pop:入栈和出栈操作

  • 两个存储器传送指令中的存储器引用方式是简单地基址和偏移量形式,即:
    Imm(Eb) M[Imm+R[Eb]](基址+偏移量)寻址

  • 注意:!不允许从一个存储器地址直接传送到另一个存储器地址,也不允许将立即数传送到存储器。

Y86异常

状态码:描述程序执行的总体状态。

值       名字      含义
1       AOK     正常操作
2       HLT     处理器执行halt指令(指令停止)
3       ADR     遇到非法地址
4       INS     遇到非法指令

在Y86中,任何AOK以外的代码都会使处理器停止执行指令,而没有异常处理程序。

  • Y86代码与IA32代码的主要区别:

(1)Y86可能需要多条指令来执行一条IA32指令所完成的功能。
(2)Y86没有伸缩寻址模式。

2、以“.”开头的词是汇编命令,他们告诉汇编器调整地址。创建Y86代码的唯一工具是汇编器。

  • 指令集模拟器YIS
    模拟Y86机器代码程序的执行,而不用试图去模拟任何具体处理器实现的行为。

逻辑设计和硬件控制语言HCL

  • 要实现一个数字系统需要三个主要的组成部分:

(1)计算对位进行操作的函数的组合逻辑

(2)存储位的存储器元素

(3)控制存储器元素更新的时钟信号

  • 逻辑门

逻辑门只对单个位的数进行操作,而不是整个字。一旦一个门的输入变化,在短时间内,输出就会跟着变化。

Y86顺序实现

将处理组织成阶段

  • 取指:从存储器读取指令字节,地址为程序计数器(PC)的值。指令指示符字节两个四位部分,称为icode(指令代码)和ifun(指令功能)。vaIP(下一条指令的地址)=PC+已取出指令的长度。
  • 译码:从寄存器文件读入最多两个操作数,得到valA和/或valB。
    执行:算数逻辑单元(ALU)根据ifun的值执行指令指明的操作,计算存储器引用的有效地址,或者增加或减少栈指针。得到的值称为valE。也可根据条件码执行跳转。
  • 访存:将数据写入存储器,或者从存储器读出数据。读出的值为valM。
    写回:最多可以写两个结果到寄存器文件。
  • 更新PC:将PC设置成下一条指令的地址。

逻辑设计和硬件控制语言HCL

实现一个数字系统需要的三个组成部分:计算对位进行操作的函数的组合逻辑、存储位的存储器元素,以及控制存储器元素更新的时钟信号。

  • 组合电路和HCL布尔表达式

1、将逻辑门组合成一个网,构建计算块(组合电路)的限制。

  • 注意:

两个以上的逻辑门的输出不能连接在一起,否则可能使线上信号矛盾,导致一个不合法的电压或电路故障。

这个网必须是无环的,否则会导致网络计算有歧义。

2、组合逻辑电路和c语言中逻辑表达式的区别:

组合电路的输出会持续地响应输入变化,c语言表达式只有在执行过程中被遇到才求值。

C的逻辑表达式允许参数是任意整数,0是FALSE,其他任何值0的都是TRUE,逻辑门只对位值0和1操作。

C的逻辑表达式可能被部分求值(第一个参数就能确定结果的就不会对第二个求值)。

  • 集合关系

通用格式:iexpr in {iexpr1,iexpr2,...,iexprk}

被测试的值iexpr和带匹配的值iexpr1~iexprk都是整数表达式。

  • 存储器和时钟

1、时序电路:有状态,且在这个状态上进行计算的系统。

  • 两类存储器设备:

时钟寄存器(寄存器):储存单个位或字,用时钟信号控制寄存器加载输入值。(保存程序计数器PC,条件代码CC和程序状态Stat)

随机访问储存器(储存器):储存多个字,用地址选择该读/写哪个字。(存储程序数据)

2、处理器还包括另外一个只读存储器,用来读指令。但在大多数实际系统中,这两个存储器被合并为一个具有双端口的存储器:一个用来读指令,一个用来读或写数据。

SEQ 顺序处理器

每个时钟周期上,SEQ执行一条完整指令所需的所有步骤。

  • 将处理组织成阶段

其中六个基本阶段:

取指 从存储器读取指令字节,地址为程序计数器PC的值
译码 从寄存器读入最多两个操作数,得到valA或valB
执行 算术/逻辑单元要么执行指令指明的操作,计算存储器引用的有效地址,要么增加或减少栈指针。得到的值为valE。
访存 将数据写入存储器,或从存储器中读出数据,读出的值为valM。
写回 最多可以写两个结果到寄存器文件
更新PC 将PC设为下一条指令的地址

  • SEQ抽象视图画图惯例
浅灰色方块表示硬件单元
控制逻辑块是用灰色圆角矩形表示的
线路的名字在白色椭圆中说明
宽度为字长的数据连接用中等粗度的线表示
宽度为字节或更窄的数据连接用细线表示
单个位的连接用虚线
  • SEQ的实现包括组合逻辑(不需要任何时序或控制)和两种存储器设备:

时钟寄存器 程序计数器和条件码寄存器
随机访问存储器 寄存器文件、指令存储器和数据存储器

其中指令存储器 只用来读指令(可以将这个单元看成是组合逻辑)
条件码寄存器 只在执行整数运算指令时装载
数据存储器 只在执行rmmovl、pushl或call时写入
寄存器文件 两个写端口允许每个时钟周期更新两个程序寄存器。(特殊寄存器ID 0xF表明此端口不应执行写操作)

SEQ阶段的实现

1、取指阶段:以PC为第一个字节的地址,一次读6个字节

icode 控制逻辑块计算指令
ifun 功能码
三个一位的信号(根据icode值计算)

instr_valid 发现不合法的指令
need_regids 包含寄存器指示符字节码
need_valC 包括常数字码
后五个字节是寄存器指示符字节和常数字的组合编码。

2、译码和写回阶段

都需要访问寄存器文件,根据四个端口的情况,判断应该读哪个寄存器产生信号valA、valB。

寄存器文件,支持同时进行两个读和两个写,每个端口有一个地址连接(寄存器ID)和一个数据连接(32根线路),既可以作为寄存器文件的输出字,又可以作为他的输入字。

3、执行阶段:

包括算数/逻辑单元(ALU),输出为valE信号:ALU通常作为加法器使用
包括条件码寄存器:每次运行产生零、符号、溢出、产生信号set_cc
4、访存阶段:读或者写程序数据

两个数据块产生存储器地址和存储器输入证据的值,两个产生控制信号表明应该是读还是写。

当执行读操作时,数据存储器产生valM。

根据icode,imem_error,instr_valid,dmem_error,从指令执行的结果计算状态码Stat。

5、更新PC阶段

产生程序计数器的新值,依据指令的类型和是否要选择分支,新的PC可能是valC、valM或者valP。

教材学习中的问题和解决过程

  • 问题1:教材上习题4.4中答案给出的汇编代码有一段
pushq %rbx
......
poop %rbx

其中为什么要把%rbx寄存器入栈?

  • 问题1解决方案:因为之前肯定有别的运算使%rbx有别的值,故为了保存这个值才将其入栈,我认为这与本函数无关 ,只是为了在这个函数使用后复原%rbx的值,以方便之后的继续运算,否则之前的数据会丢失。

  • 问题2:HCL描述到底是什么

  • 问题2解决方案:HCL(hardware control language,硬件控制语言),编程语言,用来模拟计算机逻辑 .HCL具有一些硬件硬件描述语言的特性,允许用户描述布尔函数和字级选择操作.另一方面,它缺乏很多在真正的HDL(Hardware Description Language,硬件描述语言)中能找到的特性.例如,声明寄存器和其他存储元素的方法,循环和条件构造,模块定义和实例化的能力,以及位提取和插入操作.

  • 问题3:过程如何抽象?

  • 问题3解决方案:在C语言中,过程抽象的结果是模块。模块包含接口和实现。
    接口要指明该模块要做什么,内容主要包含标识符、类型定义和函数。
    在C语言中,接口一般通过头文件的形式呈现。
    作为函数调用者角色的程序员,只要了解模块的接口就可以使用模块了。

其中:

  • 接口指明模块要做什么,
    其中所用的标识符、类型、函数等,
    *.h结尾,
    是函数调用者。
  • 实现则指明模块如何完成接口,
    可以完成一个接口多个实现的可能,
    *.c结尾,
    是函数实现者
什么是函数签名

函数签名包含了一个函数的信息,包括函数名、它的参数类型、它所在的类和名称空间及其他信息。函数签名用于识别不同的函数,就像签名用于识别不同的人一样,函数的名字只是函数签名的一部分。

注意不包含返回值

  • 写出多个签名的重点是改变参数的数量和类型。
  • 使用结构体的好处是不管原来有几个参数、参数类型是什么,都可以封装在一个结构体中,这样使用结构体参数的函数的参数个数总是可以只有一个。

于是我们设计出来一个万能函数,借助结构体可以把所有的函数化成万能函数的等价形式。
那么万能函数指针 uf 就可以指向所有函数了。

void *  func( void * parameter)

typedef void* (*uf)(void * para)

并发

我们接触到两类并发:
程序级并发是通过进程实现的,更细粒度的并发是函数级并发,函数级并发通过线程来实现。

  • fflush(stdout)的作用

在printf()后使用fflush(stdout)的作用是立刻将要输出的内容输出。
当使用printf()函数后,系统将内容存入输出缓冲区,等到时间片轮转到系统的输出程序时,将其输出。
使用fflush(out)后,立刻清空输出缓冲区,并把缓冲区内容输出。

在hello_single.c文件中输出为隔一秒输出一个hello或world\n。是依次输出。如果没有fflush(stdout)则会隔几秒后一下子输出好几个hello。

pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。

  • thread 线程以 不同的方法终止,通过 pthread_join 得到的终止状态是不同的,总结如下: • 如果 thread 线程通过 return 返回,value_ptr 所指向的单元里存放的 是 thread 线程函数的返回值。 • 如果 thread 线程被别的线程调用 pthread_cancel 异常终止掉, value_ptr 所指向的单元里存放的是常数 PTHREAD_CANCELED。 • 如果 thread 线程是自己调用 pthread_exit 终止的,value_ptr 所指向 的单元存放的是传给 pthread_exit 的参数。

代码调试中的问题和解决过程

  • 问题1:如何使用y86模拟器汇编并查看代码?
  • 问题1解决方案:首先我想在自己的虚拟机上安装Y86模拟器。按照如下步骤步骤做下来
    在终端输入
sudo apt-get install bison flex
sudo apt-get install tcl8.5-dev tk8.5-dev tcl8.5 tk8.5

在以下连接中下载sim压缩包
解压后
修改mkaefile文件 用文档方式打开

GUIMODE=-DHAS_GUI//将#删去
TKLIBS=-L/usr/lib/ -ltk8.5 -ltcl8.5//在尾部加上8.5
TKINC=-I/usr/include/tcl8.5//把开头i改为小写

遇到如下错误:

进入sim目录:cd sim
在sim目录下终端输入:make clean
在sim目录下终端输入:make
如图

故在实验楼中重新做次实验。
进入测试代码,教材p252页代码
asuml.ys
,可以通过make asuml.yo
进行汇编。之后得到结果如图:image

  • 问题2:对如下代码进行编译运行
#include  <stdio.h>
#include  <pthread.h>
#define	NUM	5
void	*print_msg(void *);
int main()
{
	pthread_t t[100];		/* two threads */
	char arg[30];
	int i;
	for(i=0; i<100; i++){
		sprintf(arg, "hello%d\n", i);
		pthread_create(&t[i], NULL, print_msg, (void *)arg);
	}

	for(i=0; i<100; i++)
		pthread_join(t[i], NULL);

	return 0;
}

void *print_msg(void *m)
{
	char *cp = (char *) m;
	int i;
	for(i=0 ; i<NUM ; i++){
		printf("%s", m);
		fflush(stdout);
		sleep(1);
	}
	return NULL;
}

得到如下图的结果:

与我预想结果不相符,为什么会一直输出的全部都是hello99?

  • 问题2解决方案:还没想明白

代码托管

其他(感悟、思考等,可选)

本周我学习了Y86指令集、HCL硬件描述语言和Y86的顺序实现过程。Y86指令与IA32指令很相似,而且更少一些,两个相比较更便于记忆。HCL硬件描述语言的逻辑门的画法和之前学习过的数字电路差不多,时序电路工作原理也一致。

学习进度条

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
目标 5000行 30篇 400小时
第一周 200/200 2/2 20/20 了解计算机系统、静态链接与动态链接
第三周 300/500 2/4 18/38 深入学习计算机算术运算的特性
第四周 500/1000 3/7 22/60 掌握程序崩溃处理、Linux系统编程等知识,利用所学知识优化myod,并实现head和tail命令
第五周 300/1300 2/9 10/70 掌握“进程”的概念,并学习应用相关函数;了解程序如何在机器上表示
第六周 500/1869 4/11 14/84 学习了异常控制流
第七周 380/2290 2/13 14/98 学习了Y86模拟器及相关知识

尝试一下记录「计划学习时间」和「实际学习时间」,到期末看看能不能改进自己的计划能力。这个工作学习中很重要,也很有用。
耗时估计的公式
:Y=X+X/N ,Y=X-X/N,训练次数多了,X、Y就接近了。

参考:软件工程软件的估计为什么这么难软件工程 估计方法

  • 计划学习时间:10小时

  • 实际学习时间:14小时

  • 改进情况:

(有空多看看现代软件工程 课件
软件工程师能力自我评价表
)

参考资料