06 PA1:学习记录

PA1学习记录

计算机可以没有寄存器吗?

CHATGPT:
在现代计算机体系结构中,寄存器是一个至关重要的组成部分,因为它们存储CPU中需要处理的数据和指令。因此,在现代计算机中,寄存器是必不可少的。

寄存器是CPU内部的存储单元,它们通常比其他存储器(如内存)更快速。寄存器存储CPU正在处理的数据和指令,这些数据和指令用于执行算术、逻辑和控制操作。CPU需要快速地读取和写入这些寄存器中的数据,以便能够快速地处理指令和数据。

虽然理论上,计算机可以没有寄存器,但是在现代计算机中,它们是必不可少的。没有寄存器,CPU将需要不断地从内存中读取和写入数据,这将严重降低计算机的性能。因此,寄存器在现代计算机体系结构中扮演着至关重要的角色。

RTFSC(阅读源码)

Nemu的主要组成:monitor, CPU, memory, 设备
CPU:通过读取寄存器的值,进行运算。
memory:存储信息
monitor:监控客户计算机的运行状态
设备:外设,方便使用

nemu项目的构成:

configs : 预先提供的配置文件
include : 全局使用的头文件
Kconfig : 配置信息管理的规则
Makefile : Makefile构建脚本
scripts : Makefile构建脚本
src : .c 源文件
tools : 工具 如:生成表达式

一个程序是从哪里开始执行的

C语言是从main函数开始执行的

选择RISV 64

通过更改 nemu/tools/kconfig 文件实现更改ISA

为什么全部都是函数?

在使用函数调用的情况下,init_monitor()函数将整个监视器的初始化分解成多个小任务,每个小任务都是由一个独立的函数完成的。这样的设计带来了以下几个好处:

代码复用性:如果需要在其他地方使用这些小任务中的某一个或多个,只需要单独调用对应的函数即可,而不需要复制粘贴函数体。这样的代码重用可以提高代码的可维护性和可扩展性。

可读性:将任务拆分成多个小函数,可以使代码更加模块化,易于理解和调试。

可维护性:当需要修改某个小任务时,只需要修改对应的函数代码,而不需要修改init_monitor()函数的代码。这样的设计可以减少代码修改带来的风险,从而提高代码的可维护性。

可测试性:每个小任务可以单独进行测试,这样可以更容易地发现和定位问题。

综上所述,使用函数调用可以提高代码的可读性、可维护性、可扩展性和可测试性。虽然将函数展开后代码的正确性不受影响,但使用函数调用可以使代码更加优雅、易于理解和管理。

GDB调试

layout src 代码与程序一起调试 (会出现花屏 通过 ctrl  x  a 输入两次刷新屏幕来解决)
layout split 通过查看汇编一起调试

run:开始运行程序

break:设置断点

break function:在函数function的开头设置断点
break file.c:line:在文件file.c的第line行设置断点
continue:继续运行程序直到下一个断点

next:单步执行一行代码,不进入函数内部

step:单步执行一行代码,进入函数内部

print:打印变量的值

print var:打印变量var的值
print *ptr:打印指针ptr所指向的值
info:显示调试信息

info break:显示所有断点
info locals:显示当前函数的所有局部变量
backtrace:显示函数调用栈

finish:从当前函数返回

quit:退出GDB调试器

优美的退出

uint32_t 当为-1时 实际上为 uint32_t MAX
q退出报错:return !good 改成 return good
返回值大于0

简易调试器

在这里插入图片描述

预学习知识Ctags

ctags是一种代码索引工具,可以用来生成源代码的索引文件,支持多种编程语言,包括C、C++、Java、Python等。以下是一些常用的ctags命令:

ctags filename:生成filename文件的索引文件tags。

ctags -R dir:生成目录dir下所有文件的索引文件tags。

ctags -e filename:生成filename文件的索引文件tags,格式为Emacs。

ctags --extra=+q filename:在tags文件中添加每个标识符的类别。

ctags --exclude=pattern dir:在生成tags文件时排除符合模式pattern的文件。

ctags -a filename:将filename文件的标识符添加到现有的tags文件中。

ctags -f tagsfile filenames:将filenames文件生成一个tagsfile索引文件。

ctags --fields=+iaS filename:在tags文件中包含标识符、访问类型、类型和文件名。

ctags --langmap=c:.h filename:将.h文件看作是C文件,以便正确地索引它们。

ctags --verbose filename:在生成tags文件时显示详细信息。

这些命令只是ctags的一部分,可以在终端中输入ctags --help查看所有的命令和选项。

生成文件后可以实现快速跳转函数
推荐使用neovim
具体安装如下:
参考北大 吴同学:https://github.com/ZyWCN1998/MyDevEnvFile

字符串处理常用的函数

字符串处理:ssprintf  ssscanf  strcmp strcpy strnpy  strtok
字符串到数字的转化:atoi strtoul

单步执行

在cpu-exec.c文件中存在如下函数:

void trace_and_difftest(Decode *_this,vaddr_t dnpc) 输出命令在每一次CPU执行时都会执行
void exec once(Decode *s,vaddr t pc) 单步执行
void execute(uint64_t n) 可选择执行多少步
void cpu_exec(uint64 t n) 仿真CPU工作 在单步执行中调用该函数即可

打印寄存器

在文件isa-def.h中存在如下结构体定义,该结构体中gpr存放了32个寄存器的值,以word_t的类型,可以通过在isa_reg_display(void);中调用实现打印寄存器。

#include <common.h>
typedef struct {
word_t gpr[32]; 
vaddr t pc; }
loongarch32r CPU_state;
typedef MUXDEF(CONFIG ISA64,uint64 t,uint32 t)word t;

扫描内存

初级版本的扫描内存(后面实现表达式可以加进来),通过调用paddr.c文件中的函数

word_t paddr_read(paddr_t addr, int len) {
  if (likely(in_pmem(addr))) return pmem_read(addr, len);
  IFDEF(CONFIG_DEVICE, return mmio_read(addr, len));
  out_of_bound(addr);
  return 0;
}

表达式求值

词法分析

"0x80100000+   ($a0 +5)*4 - *(  $t1 + 8) + number"
完成表达式求值后能够进行如下的计算

正则表达式

以下是一些常见的正则表达式:

匹配一个数字:\d
匹配一个非数字字符:\D
匹配一个字母或数字:\w
匹配一个非字母或数字字符:\W
匹配一个空格字符:\s
匹配一个非空格字符:\S
匹配一个任意字符(除了换行符):.
匹配字符串的开头:^
匹配字符串的结尾:$
匹配一个或多个前面的字符:+
匹配零个或多个前面的字符:*
匹配零个或一个前面的字符:?
匹配一个特定的字符:[]
匹配一个范围内的字符:[a-z][0-9] 等
匹配一个不在特定范围内的字符:[^abc]
匹配一个单词边界:\b
匹配一个非单词边界:\B
匹配一个特定数量的前面的字符:{n}{n,m} 等
捕获匹配到的字符:()
匹配重复的字符:\1\2

表达式递归求值

通过对下列框架进行增加实现 + - * / == != &&

uint64_t eval(uint64_t p, uint64_t q) {
  if(p > q) Assert(0, "Bad expression");
  else if(p == q) {
    if(Tokens[p].type == TK_Num || Tokens[p].type == TK_RNum || Tokens[p].type == TK_HNum) return strtoul(Tokens[p].str, NULL, 10);
    else Assert(0, "This char is not Number!");} 
  else if(check_parentheses(p, q) == true) return eval(p + 1, q - 1);
  else {
  uint64_t op =  c_main(p, q);
  if(Tokens[op].type == TK_Neg) return -1*eval(op+1,q);
  else if(Tokens[op].type == TK_Point) {uint64_t num = paddr_read(eval(op + 1,q), sizeof(uint32_t)); return num;}
  uint64_t val1 = eval(p, op - 1);
  uint64_t val2 = eval(op + 1, q);
  switch (Tokens[op].type) {
    case TK_Plus : return val1 + val2; break;
    case TK_Sub : return val1 - val2; break;
    case TK_Mul : return val1 * val2; break;
    case TK_Div : Assert(val2 != 0, "The dividend cannot be 0!"); return val1 / val2; break;
    case TK_EQ : return val1 == val2; break;
    case TK_NEQ : return val1 != val2; break;
    case TK_AND : return val1 && val2; break;
    default: Assert(0, "eval(p ,q) Operation is Error!"); break;
    }
  } 
}
括号的判断

通过一个标志位从左到右,如果遇到)就-1,(就+1,如果
存在小于0,或者不等于0,该表达式非法,利用特殊判断区分()() 与 (()())

bool check_parentheses(uint64_t a, uint64_t b) {
  if(strcmp(Tokens[a].str, "(") != 0 || strcmp(Tokens[b].str, ")") != 0) return false;
  int flag = 0;
  uint64_t i;
  for(i = a; i <= b; i++){  // 
    if(strcmp(Tokens[i].str, "(") == 0) ++flag;
    if(strcmp(Tokens[i].str, ")") == 0) --flag;
    if(flag<0) return false;
    if(flag == 0 && i != b) return false;
  }
  if(flag != 0) return false;
  return true;
}

实现带有负数的算术表达式的求值 (选做)

可以看文章中监视点有提示,先通过对指定类型进行判断,然后进行特殊处理,注意表达式的优先级。
负号是个单目运算符, 分裂的时候需要注意什么?
需要只分裂一边!

如何测试你的代码

关键思路,利用gen进行字符串的读入,其他的如gen_num以及gen_rand_op都调用gen,需要设置最后一个输入的标志位

void gen(char c) {
  if (buf_end_pos <  Buf_size - 1) {
    buf[buf_end_pos] = c;
    ++buf_end_pos;
    buf[buf_end_pos] = '\0';
  } else {
    printf("gen Error");
  }
}

监视点

需要创建.h文件存结构体以及函数声明,随后进行调用主要考察链表的使用。

WP* new_wp() { 
  if(free_ == NULL) {
    Assert(0,"No_free_wp");
  }
  //取出空闲节点
  WP* new = free_;
  free_ = free_-> next;
  //将节点放在head上去
  new -> next = head;
  head = new;
  return new;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值