Como toca tirar de simuladores de arquitecturas tipo PCSpim o Mars, no disponemos de todas las utilidades que nos ofrece el procesador MIPS (R2000 en este caso) y por tanto no se puede hacer algo muy sofisticado en el desarrollo de sistemas, no podemos manejar memoria virtual, ni controlar overflows ni en general gestionar muchas de las interrupciones porque no están implementadas. Además recordar que no es un sistema de tiempo compartido, se asemeja más a un sistema por lotes simples (Monitor residente) con interacción usuario-máquina a través de una shell y con un máximo de 2 procesos activos (la shell y el proceso ocioso).
Partiendo del esquema que se proporciona en la práctica se han ido completando los diferentes apartados tales como gestión de las interrupciones del reloj, sincronización por consulta de estado con el teclado y la consola, conmutación de procesos y algunas funciones para el cliente.
El código se compone del manejador que se encarga de interactuar con el hardware y el programa de usuario (shell), no voy a tocarlo más, quiero escribir la ruta de datos del mips no segmentada original del diseño de Hennessy y dedicarle más tiempo a eso, además hay algunas cosas que bailan si queréis apañarlas dejo el código y si hay alguna duda comentad :).
[MIPSOS.handler]
##############################################################
## ÁREA DE DATOS DEL MANEJADOR ##
##############################################################
.kdata
## Contexto del proceso principal
salvareg: .word 0,0,0 # aquí se guardan $at, $t1 y $t0
dirret: .word 0 # aquí se guarda la dirección de retorno
## Estado del proceso principal
LISTO = 0 # Posibles estados del proceso
ESPERANDO = 1
estado: .word LISTO # Estado del proceso
# (inicialmente, está listo)
despertador: .word 0
## Variables para el reloj
segundos: .word 0
#############################################################
## COMIENZA EL CÓDIGO DEL MANEJADOR ##
#############################################################
.ktext 0x80000080
## Para la cuestión 1 (2ª parte)
## $eti: j $eti # <- Normalmente, esta línea es un comentario
## Salvar contexto
.set noat
sw $at,0($k1) # Salvo $at
.set at
sw $t0,4($k1) # Salvo $t0. Lo utilizaremos para direcciones
sw $t1,8($k1) # Salvo $t1. Lo utilizaremos para datos
## Análisis de causa de excepción
mfc0 $k0,$13 # Copio registro de causa
andi $t0,$k0, 0x003c # Aíslo el código de causa
beq $t0,$zero,interrupcion # Interrupción hardware?
li $t1, 0x20 # Llamada syscall?
beq $t1, $t0, llamada
## Gestionar overflows artiméticos, fallo de página y otras excepciones ##
b retexc # Ignoro cualquier otra causa
#############################################################
## TRATAMIENTO DE INTERRUPCIONES ##
#############################################################
interrupcion:
## Preparo dirección de retorno (sólo si es el proceso principal)
lw $t0,estado
li $t1,LISTO
bne $t0,$t1,$L1
mfc0 $t0, $14 # EPC
sw $t0, dirret
## Análisis de interrupciones pendientes
$L1: andi $t0, $k0, 0x1000 # int2 pendiente?
bne $t0, $zero, int2
#andi $t0, $k0, 0x800 # int1 pendiente?
#bne $t0, $zero, int1
#andi $t0, $k0, 0x400 # int0 pendiente?
#bne $t0, $zero, int0
b retexc # interrupción espúrea
#-------------------------------------------------------------
## Tratamiento de la interrupción de RELOJ
## Cuestión 2 ##
int2:
la $t0,segundos
lw $t1,0($t0)
addi $t1,$t1,1
sw $t1,0($t0)
la $t0,0xFFFF0010
li $t2,1
sb $t2,0($t0)
## Comprobación de estado (t1 tiene segundos)##
la $t0,estado
lw $t1,0($t0)
li $s0,1
bne $t1,$s0,fin
la $t0,despertador
la $t1,segundos
lw $s0,0($t0) # valor despertador
lw $s1,0($t1) # valor segundos
bne $s0,$s1,fin
la $t0,estado
sw $zero,0($t0)
fin: b retexc # fin
#############################################################
## LLAMADAS AL SISTEMA ##
#############################################################
llamada:
## Preparo dirección de retorno
mfc0 $k0, $14 # EPC
addi $k0,$k0,4
sw $k0, dirret
## Selecciono la llamada
li $t1,11 # print_char
beq $t1,$v0,print_char
li $t1,12 # read_char
beq $t1,$v0,read_char
# print string
# print
li $t1,90 # get_version
beq $t1,$v0,get_version
li $t1,91 # get_time
beq $t1,$v0,get_time
li $t1,92 # wait_time
beq $t1,$v0,wait_time
b retexc # Función no implementada
#---------------------------------------------------------------
###### PRINT_CHAR (Servicio 11)
## Sincronización por consulta de estado con la consola ##
print_char:
li $t0, 0xffff0008
$L0: lb $t1, 0($t0) # leo palabra de estado de la consola
andi $t1, $t1, 1
beq $t1,$zero,$L0
sb $a0, 4($t0)
b retexc
###### READ_CHAR (Servicio 12)
## Sincronización por consulta de estado con el teclado ##
read_char:
li $t0,0xFFFF0000
$LR: lb $t1,0($t0)
andi $t1,$t1,1
beqz $t1,$LR
lb $t1,4($t0) # Leer del registro de datos del teclado, Cancelación automática
move $v0,$t1
b retexc
###### GET_VERSION (Servicio 90)
get_version:
li $v0,2
b retexc
###### GET_TIME (Servicio 91)
get_time:
la $t0,segundos
lw $v0,0($t0)
b retexc
###### WAIT_TIME (Servicio 92)
wait_time:
la $t0,estado
li $t1,1
sw $t1,0($t0)
la $t0,segundos
la $t1,despertador
lw $t2,0($t0)
add $t3,$t2,$a0
sw $t3,0($t1)
b retexc
#############################################################
## CONMUTACIÓN Y FIN DE MANEJADOR ##
#############################################################
retexc:
## Conmutación de procesos
lw $t0,estado
li $t1,LISTO
beq $t0,$t1,$L2 # Si (estado = LISTO), volver al proceso principal
la $k0,proceso_ocioso
b $L3 # en otro caso, volver a proceso ocioso
$L2: lw $k0,dirret # (en dirret está la dirección de retorno del
# proceso principal)
## Fijar contexto
$L3: lw $t1, 8($k1) # Restauro $t1
lw $t0, 4($k1) # Restauro $t0
.set noat
lw $at, 0($k1) # Restauro $at
.set at
rfe # restaurar bits KU/IE
jr $k0
##############################################################
###################################################################
## CÓDIGO DE INICIO ##
###################################################################
.text
.globl __start
__start:
## Preparo las interfaces de los periféricos
li $t0, 0xffff0000
sb $zero, 0($t0) # inhibo interrupción en el HW del teclado
li $t0, 0xffff0008
sb $zero, 0($t0) # inhibo interrupción en el HW de la consola
li $t0, 0xffff0010
li $t1,1
sb $t1, 0($t0) # habilito interrupción en el HW del reloj
## Preparo el registro de estado del coprocesador y fijo modo usuario
## Y desenmascarar el reloj ##
mfc0 $t0, $12
#ori $t0, $0, 0x0003 # Interrupciones habilitadas
ori $t0,$0,0x0403 # Interrupciones habilitadas y reloj desenmascarado #
mtc0 $t0, $12
## Salto al programa de usuario
la $k1, salvareg # $k1 tendrá la dirección de la zona para salvar reg.
jal main
## Shutdown
li $v0, 10
syscall # syscall 10 (exit)
###################################################################
## PROCESO OCIOSO DEL SISTEMA ##
###################################################################
proceso_ocioso: # proceso ocioso del sistema
b proceso_ocioso
[MShell.s]
# Identificadores de las funciones de sistema
print_char = 11 # print [args]
read_char = 12 # Not callable
get_version = 90 # version
get_time = 91 # time
wait_time = 92 # wait
print_str = 93 # Implementar
# Hash de las funciones implementadas #
print_char_hash = 441
get_version_hash = 448
get_time_hash = 431
wait_time_hash = 437
# segmento de datos
.data
retorn: .word 0
bondia: .asciiz "MIPSOS v."
la_hora: .asciiz " segundos\n"
prompt: .asciiz "test:mipsos:~# "
promptR: .asciiz "Response >> "
notCmd: .asciiz " ~ Command not identified ~"
buffer_int: .ascii " " # No tocar. Buffer de printf_integer
command: .space 300 # Longitud máxima de la orden
# Para hacer simple el parser, mínima longitud de orden 4,
#con el valor de las cmdLength primeras letras se genera un
#valor hash simple (suma de ascii) correspondiente a la orden introducida
cmdLength: .word 4
buffer_aux: .space 300
## Para reducir colisiones usar una mejor función de dispersión ##
#-------------------------------------------------#
# Segmento de código ("text")
.text
.globl main
main:
# Guarda adreça de retorn
sw $ra,retorn
# Saluda y da el número de versión
la $a0,bondia
jal print_string
li $v0,get_version
syscall
move $a0,$v0
jal printf_integer
jal print_NL
li $s0,0
# Main Loop #
bucle:
# Prompt de petición #
la $a0,prompt
jal print_string
# Dice la hora # {li $v0,get_time,syscall,move $a0,$v0, jal printf_integer,la $a0,la_hora,jal print_string}
# Espera 5 segundos {#li $a0,5,#li $v0,wait_time,#syscall}
# Read command #
jal read_string
jal print_NL
# Imprimir el prompt de respuesta #
la $a0,promptR
jal print_string
# Parse & Execute #
la $a0,command
jal parse_command # Parsea command y lanza la syscall correspondiente
jal print_NL
b bucle
# Shutdown
# lw $ra,retorn
# jr $ra
#-------------------------------------------------
print_string: # $a0: puntero a string acabado en \0
move $t0,$a0
lb $a0,0($t0)
beq $a0,$zero,$L4
$L3: li $v0,print_char
syscall
addiu $t0,$t0,1
lb $a0,0($t0)
bne $a0,$zero,$L3
$L4: jr $ra
#-------------------------------------------------
read_string:
# Read char #
li $a1,298 # Contador para bytes de la cadena
li $t0,'\n'
la $a2,command
$LS: li $v0,read_char # Leer caracter
syscall
or $a0,$zero,$v0
li $v0,print_char # Imprimir caracter para retroalimentación
syscall
move $v0,$a0
sb $v0,0($a2) # Guardar el caracter en el buffer de command
addiu $a2,$a2,1 # Avanzar puntero a command
beq $v0,$t0,$LE # Si se detecta un fin de linea fin de la orden
beqz $v0,$LE # Si es un null byte fin de la orden
beqz $a1,$LE # Si la cadena es de longitud máxima fin de la orden
addiu $a1,$a1,-1 # Decrementar contador de longitud
b $LS
$LE: sb $zero,0($a2) # Añadir null byte a la cadena
jr $ra # Retornar
#-------------------------------------------------
print_NL: # sense paràmetres: escriu NL (New Line)
li $a0,'\n'
li $v0,print_char
syscall
jr $ra
#-------------------------------------------------
printf_integer: # $a0: valor entero
move $t0,$a0 # dividendo inicial
li $t1,0 # cuenta de cifras
li $t2,10 # divisor
$L1: # bucle de cambio de base
divu $t0,$t2 # división entre 10
mfhi $t3 # tomo el resto
addiu $t3,$t3,'0' # calculo código ascii
sb $t3,buffer_int($t1) # guardo en buffer
addi $t1,$t1,1 # avanzo puntero
mflo $t0 # nou dividendo
bne $t0,$zero,$L1
$L2: # bucle de escritura
addiu $t1,$t1,-1 # retrocedo en buffer
lb $a0,buffer_int($t1) # tomo carácter
li $v0,print_char # escribo carácter
syscall # llamada
bne $t1,$zero,$L2
li $v0,print_char
jr $ra
#-------------------------------------------------
extract_n_command: # $a0: dirección de command, [$a1: parámetro número n] ( para tests únicamente funciones de aridad 1, no se usa el 2º parámetro )
li $t0,' ' # Constante ' '
li $t2,300 # Constante: máxima longitud del command (contador)"
la $t3,buffer_aux # Dirección del buffer auxiliar que provee el SO para funciones tipo print char, get char etc #
li $s0,'\n' # Constante: fin de linea
$LN: lb $t1,0($a0) # Cargar byte del command #
beq $t1,$t0,$GP # Si es un espacio, se supone que lo siguiente es un parámetro #
addiu $a0,$a0,1 # Si no se encuentra, avanzar el puntero de $a0 hasta encontrar
# uno o hasta alcanzar la longitud máxima del comando #
addiu $t2,$t2,-1 # Decrementar el contador
beqz $t2,$LN
$GP: ## Si el puntero se encuentra sobre un espacio, avanzar hasta
##el salto de linea, almacenando los valores del parametro en una variable ##
lb $t1,0($a0) # Cargar el valor del caracter actual (parte del param)
sb $t1,0($t3) # Almacenar en el buffer auxiliar
addiu $a0,$a0,1 # Avanzar puntero del string command
addiu $t3,$t3,1 # Avanzar puntero al buffer auxiliar
bne $t1,$s0,$GP #
## Cuando ya se haya alcanzado el fin de linea ##
sb $zero,0($t3) # "Cerrar" el buffer auxiliar añadiendo null byte eof
la $v0,buffer_aux # Cargar dirección inicial del buffer auxiliar
lb $v0,0($v0)
jr $ra
clear_buffer: la $a0,buffer_aux
li $t0,300
$LB: sb $zero,0($a0)
addiu $a0,$a0,1
addiu $t0,$t0,-1
bnez $t0,$LB
jr $ra
## Parser ##
parse_command: # $a0: command string address
## Salvar contexto en la pila, para subllamadas ##
addiu $sp,$sp,-4
sw $ra,0($sp)
la $t1,cmdLength
lw $t1,0($t1) # Cargar longitud máxima de orden (contador)
li $t2,0 # Hash inicial
$LP: lb $t3,0($a0)
addu $t2,$t2,$t3 # Sumar al hash
addiu $t1,$t1,-1 # Decrementar contador
addiu $a0,$a0,1 # Avanzar puntero orden
bnez $t1,$LP
## Ya se han sumado los 4 primeros caracteres ##
## Salvar en $v0, el identificador de syscall de la orden ##
$CPC: li $a0,print_char_hash
bne $t2,$a0,$CGV
#la $a0,command
#jal extract_n_command
#move $a0,$v0
syscall
j $LPE
$CGV: li $a0,get_version_hash
bne $t2,$a0,$CGT
li $v0,90
syscall
move $a0,$v0
jal printf_integer
j $LPE
$CGT: li $a0,get_time_hash
bne $t2,$a0,$CWT
li $v0,91
syscall
move $a0,$v0
jal printf_integer
la $a0,la_hora
jal print_string
j $LPE
$CWT: li $a0,wait_time_hash
bne $t2,$a0,$NOT
li $a0,5 # Espera los segundos que introduzca el usuario como parametro en el ejemplo 5 segundos #
li $v0,92
syscall
j $LPE
$NOT: la $a0,notCmd
jal print_string
$LPE: ## Limpiar buffers ##
jal clear_buffer
## Restaurar Contexto ##
lw $ra,0($sp)
addiu $sp,$sp,4
jr $ra
Un saludo :D