0% encontró este documento útil (0 votos)
29 vistas45 páginas

Llamadas al Sistema en RISC-V: Guía Práctica

Resolución de problemas en risc v

Cargado por

daro
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
29 vistas45 páginas

Llamadas al Sistema en RISC-V: Guía Práctica

Resolución de problemas en risc v

Cargado por

daro
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

Laboratorio Arquitectura del Computador

Ing. MSc. Melanie Valcarcel Yucra


[email protected]

Arequipa – Perú
2024
• Introducción
• La interfaz binaria de aplicaciones (ABI) de RISC-V
• Nombre ABI de los registros
• Registros en el simulador RARs
• Puntos de entrada y salida del programa principal
• Ejemplo: cálculo de una expresión
• Llamadas al sistema
• Llamando al sistema: ecall
• Puntos de entrada y salida del programa principal
• Terminando un programa: Llamada al sistema EXIT
• PrintInt: Imprimiento un número entero en la consola
• ReadInt: Leyendo un número entero de la consola
• Activando el diálogo de entrada
• PrintChar: Imprimiendo un carácter
• Usando el RARs en la línea de comandos
• Recopilación de instrucciones hasta el momento
• Práctica
Laboratorio 6: Llamadas al sistema

• Objetivos de la sesión:
Aprender cómo realizar llamadas al sistema
Conocer los nombres ABI de los registros
Saber usar los registros temporales tx y los de argumento ax
Manejo de las llamadas al sistema PrintInt, ReadInt y PrintChar
Introducción

 La Entrada/salida se realiza mediante el mecanismo de dispositivos mapeados en


memoria. Sin embargo, los sistemas operativos incorporan servicios para gestionar la
entrada/salidas y facilitar su acceso desde nuestros programas. Aprenderemos
cómo invocar estos servicios. Es lo que denominamos llamadas al sistema.
La Interfaz binaria de aplicaciones (ABI) de
RISC-V
 Nuestros programas no se ejecutan de forma aislada, sino que interactúan con
el sistema operativo y las bibliotecas del sistema, que son programas diseñados por
otras personas
 Para que dos programas (módulos) puedan interactuar entre sí, se definen
una ABI: Interfaz binaria de aplicaciones. En esta interfaz se determina cómo
deben intercambiar información estas aplicaciones para que sean compatibles
Nombre ABI de los registros

 Una de las cosas que se especifica en la ABI es el nombre de los registros, y el uso que
se les da. En el RISC-V tenemos 32 registros, denotados como x0-x31. A nivel de
procesador, todos los registros (salvo el x0), son iguales: los podemos utilizar en
cualquier instrucción. No hay diferencia a nivel hardware entre ellos
 Sin embargo, a nivel de programación, a cada registro se le asigna un uso diferente y
por tanto, un nombre distinto. Esta nueva denominación la llamamos nombres ABI. En
esta tabla se resumen los nombres ABI asignados a los registros.
A lo largo del curso iremos viendo para qué sirve cada registro. Empezaremos usando estos registros:
• t0-t6: Registros temporales. Los usaremos para cualquier cálculo intermedio o temporal.
• a0-a7: Registros de argumentos. Se usan para almacenar los argumentos en las llamadas a otros módulos, y
pasar así información entre ambos módulos. Son registros temporales. Los podemos usar para cualquier
cálculo intermedio.
 A partir de ahora, SIEMPRE USAREMOS LOS NOMBRES ABI
Registros en el simulador RARs

En la columna de la izquierda, dentro de la ventana de registros del simulador RARs, están puestos los nombres
ABI de los registros. En la columna central se encuentra el número de registro real: x0,x2,x3... omitiendo la x.
Ejemplo: Cálculo de una expresión

Como ejemplo vamos a calcular la expresión (a - b) + (c + 5) para los valores iniciales de a=1, b=2 y c=3, pero
usando los registros con sus nombres ABI
Usamos los registros temporales: t0 - t6. Podemos usar cualquier de ellos para almacenar los valores de a, b y
c. Por ejemplo: t0=a, t1=b y t2=c. El resultado final lo almacenamos en t6
El programa para evaluar la expresión es:
 Lo ensamblamos y lo ejecutamos. Nos fijamos en la primera instrucción:

 Sabemos que li es una pseudo-instrucción, y que el ensambaldor la transforma en addi. Pero, además el
registro t0 se convierte a su equivalente x5, por lo que la instrucción real en ensamblador es:
También observamos que en los
registros t0, t1 y t2 contienen los
valores iniciales: 1, 2 y 3. Y que
efectivamente en t6 se
encuentra el resultado final:
t6 = 7
Llamadas al sistema
 Hemos visto cómo nuestros programas se comunican con los periféricos mediante entrada/salida mapeada.
Esto es típico de los sistemas empotrados. Sin embargo, la mayoría de las veces existe un sistema
operativo debajo que se encarga de la gestión de toda la entrada/salida, no teniendo que implementarla
nosotros
 El mecanismo que tienen nuestros programas para utilizar servicios proporcionados por el sistema
operativo se denomina llamadas al sistema
 Esta idea está esquematizada en la siguiente figura. Una vez que nuestro programa se empieza a ejecutar,
en cualquier momento se puede realizar una llamada al sistema para acceder a uno de los servicios que
ofrece, como por ejemplo imprimir un número en la consola, o leerlo de ella
En ese momento, el control lo
toma el sistema operativo,
que realiza la acción pedida.
Una vez finalizada, devuelve
el control a
nuestro programa, que se
sigue ejecutando hasta que
se termina
El sistema operativo, en nuestro
simulador, se encuentra en
las direcciones bajas, mientras que
nuestro programa está en
el segmento de código. El esquema
anterior lo podemos representar
también de esta forma:

Arranca nuestro programa(1), que se encuentra en el segmento de código (empieza en 0x00400000). En un momento
determinado se realiza una llamada al sistema(2), por lo que el control se pasa al sistema operativo(3) (cuyo código está en
la región debajo del segmento de código). Al finalizar devuelve el control otra vez a nuestro programa (4). Nuestro
programa puede realizar tantas llamadas al sistema como sea conveniente, aunque en la figura, por simplicidad, sólo se ha
esquematizado una. Por último, nuestro programa termina (5)
Llamando al sistema: ecall
Para realizar una llamada al sistema utilizamos la instrucción ecall. Desde el menú de ayuda (Tecla F1)
tenemos acceso a todas las llamadas al sistema disponibles en el simulador RARs. Están en la pestaña syscalls.
 Cada servicio proporcionado por el sistema operativo tiene asignado un número, que
debemos introducir en el registro a7. Además, si el servicio necesita de unos
parámetros de entrada, se los pasaremos a través de los registros a0, a1 y a2. Los
valores devueltos por el servicio estarán en a0 y a1. Toda esta información está
disponible en la ayuda. La primera columna indica el Nombre del servicio y la segunda
su código.
 Así, por ejemplo, el primer servicio mostrado en la ayuda es la llamada al
sistema prinInt, cuyo código es 1. Nos permite imprimir un número entero en la
consola. Como parámetro de entrada hay que introducir el número a imprimir en
el registro a0. El servicio no devuelve ningún valor. En las siguientes secciones
veremos ejemplos de uso.
Puntos de entrada y salida del programa principal
 Vamos a entender qué es lo que ocurre en un sistema RISC-V desde que lo encendemos. Al darle
alimentación, el contador de programa se inicializa con la dirección de la primera instrucción del sistema
operativo. Es decir, que es el sistema operativo el que toma el control
 Típicamente realiza la inicialización y se queda esperando. En un momento determinado, le decimos
que arranque nuestro programa, por lo que le pasa el control, y comienza su ejecución. Nuestro programa
tiene una dirección, llamada punto de entrada, que es donde se encuentra la primera instrucción. De
forma genérica, lo esquematizamos así:
 Sabemos que en el RARs el punto de entrada está en la dirección 0x00400000. Una vez que el sistema operativo nos pasa
el control, nuestro programa realiza los cálculos que sean y termina. Esta operación de "terminación" significa que hay
que devolver el control otra vez al sistema operativo. Para ello hay que invocar la llamada al sistema EXIT. En esta figura
se muestra el mismo esquema anterior, pero particularizado para el mapa de memoria del RARS.

El lugar donde se invoca el servicio exit se


denomina punto de salida
Lo siguiente es una regla de buenas
prácticas de programación:
El programa principal deberá tener un único
punto de entrada y un único punto de
salida.
Terminando un programa: llamada al
sistema EXIT
 Vamos a probar nuestra primera llamada al sistema, que será el servicio de EXIT. Todos
nuestros programas, a partir de ahora, acabarán invocando este servicio. La única es
excepción será en el sistema RISC-V real en la FPGA, ya que en él no tenemos sistema
operativo. Por lo que terminaremos con un bucle infinito (aunque en el laboratorio
sólo trabajaremos con el simulador)
 Si miramos el servicio de EXIT en la ayuda, vemos que tiene el código 10, y que no hay
que pasarle ningún argumento (ni de entrada ni de salida):
 En este programa estamos invocando el servicio Exit. Es un programa que no hace nada: en
cuanto recibe el control del sistema operativo, lo devuelve otra vez (termina).

¿Podríamos mejorar este programa? ¿Ves algo que te llame la atención?


¡¡Si!!! ¡Hay un número mágico! El 10, que se corresponde con el código del servicio
de exit. Siguiendo las buenas prácticas de programación, conviene
usar constantes para estos números mágicos. Esta es la nueva versión del programa:
PrintInt: Imprimiendo un número entero en la consola

 Parapracticar con las llamadas al sistema, vamos a invocar


el servicio de impresión de un número decimal en la
consola: PrintInt. Sacaremos un número constante, por
ejemplo, el 200
 Primero miramos los parámetros que tiene el servicio
PrintInt, así como su código de servicio:
El código es el 1 (registro a7), y como entrada hay que pasarle le valor a imprimir en el registro a0. Este es el
programa que muestra el número 200 en la consola. Utilizamos constantes para los dos servicios
utilizados: PRINT_INT y EXIT.
Lo ensamblamos y lo ejecutamos. Vemos que aparece el numero 200 en la consola:
ReadInt: Leyendo un número entero de la consola
 Vamos a probar ahora el servicio ReadInt, que nos permite leer un número entero desde la
consola. Primero miramos la documentación, para ver el código y los argumentos:

Su código es 5. No hay que pasarle ningún


argumento de entrada. El número
introducido por el usuario se devuelve en
el registro a0.
Para probarlo vamos a hacer un programa que
almacene en el registro t0 el valor introducido por el
usuario. Igual que antes, definimos la
constante READ_INT con el valor del código:
Lo ensamblamos y lo ejecutamos. Al
arrancar el programa se
queda esperando a que
introduzcamos un número en
la consola. Vemos que en la consola
aparece el cursor:
Introducimos el número 200 (por
ejemplo) y apretamos ENTER. El
programa termina. En la derecha vemos
que el registro t0 tiene el valor que
hemos introducido.
Activando el diálogo de entrada
 Para hacer más sencilla la entrada de datos, se pueden activar las ventanas de diálogo para nos aparezca
una ventana nueva cada vez que necesitemos que el usuario introduzca datos de entrada, en vez de
hacerlo por la consola.
 Esta opción se activa en el menú Settings/Pop up dialog for input syscalls (5,6,7,8,12).
Ahora, al ejecutar el programa nos
aparecerá la ventana de
diálogo pidiéndolos el número entero:
PrintChar: Imprimiendo un carácter
Podemos imprimir cualquier carácter ASCII mediante el servicio PrintChar
El código de servicio es el 11. En el registro a0 se
introduce el valor ascii del carácter a imprimir.
Las constantes ASCII se pueden introducir bien con su
número, decimal o hexadecimal, o bien directamete
como el carácter entre comillas simples: 'a', '\n', 'Z',
etc...
En este ejemplo se imprime el carácter 'A', pasándole
su código ASCII hexadecimal (0x41) y luego un salto de
línea usando como notación las comillas simples: '\n'.
Como son dos caracteres, se invoca dos veces
seguidas al servicio PrintChar
Lo ensamblamos y lo ejecutamos. En la
consola veremos que aparece la A, y dos
líneas más abajo la de finalización del
programa (porque hay un salto de línea)
Recopilación de instrucciones hasta el momento
• Instrucciones básicas: Son las que se transforman a código máquina y que ejecuta el procesador
 Pseudo-instrucciones: No existen realmente como instrucciones.
El ensamblador las transforma en instrucciones básicas. Una pseudo-instrucción puede dar
lugar a 1 ó varias instrucciones básicas.
 Directivas: Dar información al programa ensamblador. No generan código máquina.

También podría gustarte