如何在MIPS汇编语言中以秒为单位获取当前时间?

时间:2017-10-05 20:17:21

标签: assembly mips mars-simulator

我试图以小时:分钟:第二格式获取MIPS中的当前时间。我知道使用系统调用30会将自1970年1月1日以来的总毫秒数放入寄存器$ a1和$ a0,但我不知道如何将寄存器加在一起并除以1000得到总秒数。我相信其余的应该很容易。

谢谢!

1 个答案:

答案 0 :(得分:3)

除以64位数字

由于在MARS上模拟的MIPS不支持64÷32⇒64个分区 1 ,我们需要实现我们自己的多字分区。
本书 Hacker delight 作为chapter,主要基于Knuth的计算机编程艺术

这个想法原则上非常简单:将64位数字视为两位数字,每个数字为32位(因此该数字的基数为2 32 ),并执行小学数字分部。

让我们用一个基数为例来解决这个想法:考虑53/2 我们可以通过将5除以2来计算结果,得到结果 m 1 为2且余数 r 1 1. m 1 是结果的第一位数(最重要的一位)。
然后我们在53中“删除”3得到数字13,这是 r 1 * 10 + 3。 我们再做13/2来得到 m 0 = 6而 r 0 = 1。 结果是26(余数为1)。

我们采用相同的方法,唯一的区别是我们处理的数字范围从0到2 32 - 1.
因此,64位数字(如时间系统调用返回的数字)被视为两位数字:最重要的一个(最右边)在$a0中,另一个在$a1中。
例如,数字0x15EF18933B1被视为

 Digit 1  Digit 0
 0x15E    0xF18933B1

我们正在寻找的算法只是一个循环,我们将最后一个余数“加”到当前数字,我们除以除数得到结果的当前数字和剩余部分用于下一步。
请注意,“添加”是指“添加重量”,余下的 r i 未添加到当前数字 n i ,它由缩放由基础然后添加(这正是我们之前做的,我们做了1 * 10 + 3,而不是1 + 3)。 我不打算深入解释这个算法,因为它可以在网上广泛使用。

需要注意的一件非常重要的事情是,我们实际上并没有到达任何地方 由于当前余数的缩放,我们仍然需要执行64÷32⇒64格(对于十进制情况,13/2不比53/2容易!)。
不同之处在于我们知道该数字最多为两位数。

要解开这个循环论证,我们需要缩小到32÷16⇒32格 MIPS支持32÷32⇒32,通过将我们的除数限制在最多65535,我们得到了我们想要的结果 因此该算法使用16位的半字,然后将64位数视为4位数。

代码是

#Input
#   a0:a1 = N = DCBA
#   a2    = K (16-bit)

#Output
#   a0:a1 = quotient
#   hi    = reminder
div64x16:
 subu $sp, $sp, 16

 sw $a0, ($sp)
 sw $a1, 4($sp)

 add $t0, $sp, 8     # Pointer to digits (N)
 add $t3, $sp, 16    # Pointer to result (M)
 xor $t1, $t1, $t1   # Remainder

loop: 
  subu $t3, $t3, 2
  subu $t0, $t0, 2

  sll $t1, $t1, 16   # t1 = R * 65536
  lhu $t2, ($t0)     # t2 = N[i]
  addu $t2, $t2, $t1 # t2 = N[i] + R * 65536

  div $t2, $a2

  mflo $t1           # t1 = (N[i] + R * 65536) / K
  sh $t1, ($t3)      # M[i] = (N[i] + R * 65536) / K

  mfhi $t1           # t1 =  (N[i] + R * 65536) % K

bne $t0, $sp, loop

 mthi $t1

 lw $a0, 8($sp) 
 lw $a1, 12($sp)

 addu $sp, $sp, 16
 jr $ra 

输入参数位于a0:a1(64位被除数,a0中的低位字)和a2(除数)。
结果位于a0:a1(商)和hi(余数)。

注意除数有限制:它的大小必须为16位。
这大大简化了除法算法,但在计算时间时需要一些解决方法。

计算时间

鉴于来自Unix纪元(1970年1月1日)的ms获得一天中的时间除以1000 * 3600 * 24并取余数。
但是,1000 * 3600 * 24不适合16位。我们可以用三个部门来做,但是我们需要将剩余部分组合起来。

有一种更简单,更直观的方法 首先,我们摆脱了ms。我们在一天中的时间内不需要ms准确度,因此我们可以完全丢弃其余部分。

li $v0, 30
syscall

#a0:a1 = ms since epoch

li $a2, 1000 
jal div64x16  

#a0:a1 = seconds since epoch

现在我们需要除以3600 * 24 = 86400,但我们不能 一个很好的技巧是除以3600 * 12 = 43200(适合16位),这给了我们半天的数量(我们称之为 hh )和余数给出了我们在半天之内的第二个数字(称之为 hs ) 由于半天有43200秒,时间最多可以是11:59:59 我们不知道2:0:0是下午2点还是凌晨2点,我们需要检查 hh 是奇数还是知道,如果 hh 是偶然那么我们是在一天的前半天和时间是正确的,否则我们在下半天,我们将<43>(半天的秒数)添加到 hs
这会在中将半天内的秒数转换为秒数。

#a0:a1 = seconds since epoch

li $a2, 43200
jal div64x16

#a0:a1 = half-days since epoch
#hi = seconds in half-day

mfhi $s0              #Seconds in the half-day

andi $a0, $a0, 1      #a1 = 1 if odd half-day number (otherwise 0)
ror $a0, $a0, 1       #a1 < 0 if odd half-day number (otherwise 0)
sra $a0, $a0, 31      #a1 = 0xffffffff if odd half-day number (otherwise 0)
andi $a0, $a0, 43200  #a1 = 43200  if odd half-day number (otherwise 0)

add $s0, $s0, $a0     #s0 = seconds in the day

一旦拥有当天的秒数(32位数字),其余部分就很容易了。

li $t0, 3600
div $s0, $t0         
mflo $s0              #s0 = Hour

mfhi $t1 
li $t0, 60 
div $t1, $t0 
mflo $s1              #s1 = Minute
mfhi $s2              #s2 = Second

#Print the time
li $v0, 1
move $a0, $s0
syscall 

li $v0, 4
la $a0, sep
syscall 

li $v0, 1
move $a0, $s1
syscall 

li $v0, 4
la $a0, sep
syscall 

li $v0, 1
move $a0, $s2
syscall  


#Exit
li $v0, 10
syscall 

注意时间系统调用使用Java new Date().getTime()值,这是GMT时区中的当前时间。除非你住在那个时区,否则时间会有所不同。

1 此表示法表示64位被除数,32位除数和64位结果。