• [MIPS] SO simple (Monitor Residente)

 #448616  por overxfl0w13
 12 Jul 2014, 04:36
Buenas, ya comenté que me apetecía escribir un SO en MIPS y aprovechando una práctica de la uni he implementado algo sencillo.
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
 #448666  por Scorpio
 12 Jul 2014, 17:17
Se ve muy bueno, aunque poca cosa puedo entender de todo, eres una maquina hermano, sigue así.

//Regards.
 #448721  por strup
 13 Jul 2014, 00:57
Si me hablaste del SO que estavas escribiendo en mips y de ti no lo dude ,haber si en un futuro vemos a otro linus torvalds pero a lo español , seria bueno eh te lo juro xDD.
Esperemos alguien que sepa de mips se anime a meter mas cosas ya que podria tener futuro, un saludo maquina y sigue asi