Desarrollo de un Sistema Operativo para Raspberry Pi 2

95
Desarrollo de un Sistema Operativo para Raspberry Pi 2 Alejandro Cancelo Correia Tomás Golomb Durán Raúl Sánchez Montaño Grado en Ingeniería Informática, Facultad de Informática, Universidad Complutense de Madrid Trabajo Fin de Grado Junio de 2020 Director: José Luis Risco Martín

Transcript of Desarrollo de un Sistema Operativo para Raspberry Pi 2

Page 1: Desarrollo de un Sistema Operativo para Raspberry Pi 2

Desarrollo de un Sistema Operativo para Raspberry Pi 2

Alejandro Cancelo CorreiaTomás Golomb DuránRaúl Sánchez Montaño

Grado en Ingeniería Informática, Facultad deInformática, Universidad Complutense de Madrid

Trabajo Fin de Grado

Junio de 2020

Director:José Luis Risco Martín

Page 2: Desarrollo de un Sistema Operativo para Raspberry Pi 2

Índice general

Palabras clave 4

Resumen 5

1. Introducción 61.1. Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.2. Estado del arte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71.3. Metodología y plan de trabajo . . . . . . . . . . . . . . . . . . . . . . . . . . . 71.4. Estructura de la memoria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

2. Diseño 92.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92.2. Arranque del sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142.3. Kernel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152.4. Entrada / Salida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172.5. Interrupciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312.6. Memoria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362.7. Gestor de memoria dinámica . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372.8. Procesos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402.9. Interfaz gráfica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 472.10. Cerrojos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 562.11. Sistema de ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

3. Manual de uso 683.1. Reglas del Makefile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 683.2. Ejecutar el Sistema Operativo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 693.3. Comandos disponibles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 703.4. Utilizar el depurador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 763.5. Repositorio git . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

4. Conclusiones y trabajo futuro 804.1. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 804.2. Trabajo futuro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81

2

Page 3: Desarrollo de un Sistema Operativo para Raspberry Pi 2

ÍNDICE GENERAL 3

5. Contribuciones al proyecto 83

A. Introduction 88A.1. Objectives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88A.2. State of the art . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89A.3. Methodology and work plan . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89A.4. Document structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90

B. Conclusions and future work 91B.1. Future work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92

Bibliografía 94

Agradecimientos 95

Page 4: Desarrollo de un Sistema Operativo para Raspberry Pi 2

Palabras clave

Palabras clave en Español

Entrada/Salida

Memoria

Procesamiento

QEMU

Fundación Raspberry Pi

Raspberry Pi 2

Sistema Operativo

Keywords in English

Input/Output

Memory

Processing

QEMU

Raspberry Pi Foundation

Raspberry Pi 2

Operating System

4

Page 5: Desarrollo de un Sistema Operativo para Raspberry Pi 2

Resumen

Desarrollo de un Sistema Operativo para Raspberry Pi 2

Este trabajo extiende el deseo de la fundación Raspberry Pi de estimular la enseñanza de lainformática, desarrollando un sistema operativo compacto para la Raspberry Pi 2 con el objetivoprincipal de reforzar la docencia en la Facultad de Informática de la Universidad Complutense deMadrid.

Se han desarrollado los módulos esenciales de un sistema operativo convencional que se pue-den agrupar en tres bloques: memoria, procesamiento y entrada/salida. De esta forma, se buscafacilitar y acelerar el trabajo de los docentes para la renovación de prácticas y contenido de lasasignaturas troncales que son los pilares de todas las ramas de la ingeniería informática.

Asimismo, se abren las puertas a la creación de un ambiente de propósito educativo alrede-dor del Sistema Operativo dentro de la comunidad universitaria promulgándolo a través de otrostrabajos de fin de grado que mejoren y amplíen éste. Además, se puede orientar a aplicacionesuniversitarias como un tablón de anuncios digital, dispositivos autónomos o sensores que midenla calidad del aire, temperatura o aforo.

Para ello se comparte el código fuente de artOS bajo la licencia MIT en el siguiente repositorio:https://github.com/dacya/tfg1920-raspiOS.

Development of an Operating System for the Raspberry Pi 2

This project extends the Raspberry Pi foundation’s desire of encouraging the computer scienceeducation by developing an operating system for the Raspberry Pi 2 with the main objective ofstrengthen the education of the Computer Engineering Faculty.

It has been developed essential modules from a conventional Operating System, which canbe grouped into three categories: memory, processing and input/output. This way, we are loo-king into making easier and speed up the task of the renovation of projects and fundamental sub-jects’syllabus, which are the fundamental pillars of all computer science degrees.

Furthermore, this project takes the first steps towards the creation of an educational environ-ment and passing the responsibility of improving and expanding out to others bachelor’s degreeprojects. In addition, the future development can be oriented to real applications such as a digitalbulletin board, autonomous devices or humidity, air quality, temperature or capacity sensors.

To fulfill this goals, the code of artOS is shared under the MIT license in the following repo-sitory: https://github.com/dacya/tfg1920-raspiOS.

5

Page 6: Desarrollo de un Sistema Operativo para Raspberry Pi 2

Capítulo 1

Introducción

Este trabajo de fin de grado se centra en el diseño e implementación de un sistema operativopara la Raspberry Pi 2, la segunda versión de la famosa serie de placas fabricada por la fundacióninglesa Raspberry Pi [7].

El trabajo ha consistido en el desarrollo de los módulos esenciales que conforman un sistemaoperativo. Éstos se pueden agrupar en tres bloques:

La gestión de memoria física es el bloque esencial que da soporte a la mayoría de los mó-dulos. Los componentes se encargan de distribuir la memoria disponible para que puedanconvivir el resto sin confrontamientos. Por ejemplo, los módulos de esta agrupación aco-modan las necesidades de los procesos para dar lugar a la concurrencia sin colisiones dedatos.

El control de procesamiento se encarga de racionar la potencia del procesador a través deun sistema de procesos combinado con el sistema de interrupciones, pudiendo elegir entredistintos esquemas para adaptarse a las necesidades.

La entrada y salida ofrece varias alternativas para la recepción y emisión de datos, porejemplo: el banco de pines de la GPIO o la interfaz gráfica para el usuario disponible através del puerto HDMI rompiendo así la dependencia con otro Sistema Operativo auxiliar.

1.1. Objetivos

El objetivo principal del proyecto es diseñar un sistema operativo de código abierto y conpropósitos educativos para la Raspberry Pi 2, denominado artOS.

Todo el proyecto se pone a disposición de la comunidad para que se pueda usar como herra-mienta base en las asignaturas basadas en Sistemas Operativos con el fin de facilitar y agilizarel proceso de actualización y mejora del contenido y prácticas de éstas. El proyecto está estruc-turado para su fácil modificación, primando la sencillez de diseño y comprensión. Además, esimportante que se pueda lanzar en un emulador, de tal forma que se puedan realizar fácilmentemodificaciones, pruebas y depuraciones, por eso hemos elegido el emulador QEMU.

El sistema operativo desarrollado cumple con los siguientes requisitos:

6

Page 7: Desarrollo de un Sistema Operativo para Raspberry Pi 2

1.2. ESTADO DEL ARTE 7

Soporte básico para la arquitectura ARM.

Sistema mínimo de E/S, con capa de gestión de interrupciones y una interfaz básica deusuario.

Ejecución multiproceso con cerrojos para permitir concurrencia.

Gestión de memoria y sistema de ficheros con los métodos básicos.

1.2. Estado del arte

La fundación Raspberry Pi tiene el objetivo de estimular la enseñanza de la informática. Sinduda se ha conseguido considerando la existencia de este proyecto, pero, además, el dispositivo hatranscendido más allá resultando en un microprocesador de carácter general destacando por el usoextendido en la robótica gracias al banco de puertos que tienen todas las versiones a un lateral dela placa. Además, se ha desarrollado una gran comunidad alrededor de foros, blogs y páginas webpara desarrollar múltiples herramientas de código abierto.

La primera Raspberry Pi se lanzó en febrero de 2012. Tras su lanzamiento, en junio de esemismo año, se lanzó el primer SO completamente compatible con la infraestructura de la Rasp-berry Pi, cuyo nombre es Raspbian [15]. Además, el interés en SO desarrollados para Raspberry Piha ido aumentando gracias al auge de el IoT (del inglés Internet of Things), de manera que inclusoMicrosoft lanzó su propio SO [10] compatible con Raspberry Pi.

En cuanto al entorno educativo, existen algunos intentos de desarrollo libre con un propósitomás académico, como el proyecto en el que nos hemos embarcado. Uno de ellos es de la Universi-dad de Cambridge [11], el cual está pensado para realizar el SO solamente en código ensambladory para Raspberry Pi. El nuestro, sin embargo, está programado en C y ensamblador, por lo quees más legible. Además, hemos avanzado más allá en todos los sentidos ya que solo se presentanaplicaciones concretas y no un Sistema Operativo de carácter general.

Otro de los intentos, el cual nos ha servido de guía debido a que está pensado para la RaspberryPi 2, es el desarrollado por Jake Sandler [17], trabajador de Google. Explica de manera muyfluida los procedimientos, aunque tiene varios fallos de diseño en el sistema de procesos, erroresen el módulo de GPU (no funciona) o el uso de una estructura de una lista genérica en variosmódulos con fallos de implementación. Además, hemos ido más allá que la visión de este intentodesarrollando una interfaz de usuario, un sistema de ficheros y hemos mejorado la abstracción delos módulos, por ejemplo, junta el mailbox y el frambuffer en uno único.

Por último, también indicar que los dos intentos anteriores no se esfuerzan en mejorar la reusa-bilidad del código, mientras que nosotros hemos creado librerías e interfaces para facilitar el desa-rrollo futuro.

1.3. Metodología y plan de trabajo

Junto al director del trabajo, José L. Risco Martín, se decidió realizar reuniones periódicasen las que se marcaba el progreso y se planteaban las siguientes tareas a realizar. A raíz de la

Page 8: Desarrollo de un Sistema Operativo para Raspberry Pi 2

8 CAPÍTULO 1. INTRODUCCIÓN

pandemia del COVID-19 y el decreto de estado de alarma, se mantuvo el ritmo de reunionespero telemáticamente a través de la aplicación web Meet de Google. Respecto a la comunicaciónentre miembros, se han utilizado aplicaciones de mensajería y antes de la pandemia se realizaronreuniones en las salas de la biblioteca y laboratorios de la Facultad de Informática.

Como el grupo está formado por tres componentes, se ha trabajado en paralelo centrándosecada alumno en una rama de desarrollo. Para ello, se ha hecho uso de la herramienta git para elcontrol de versiones y de un repositorio en GitHub para guardar y gestionar nuestro progreso.Además, para facilitar el uso de los módulos se han documentado las funciones del código fuenteexpuestas utilizando la misma sintaxis definida por JavaDoc.

Respecto a esta memoria, cada miembro del equipo ha plasmado y explicado técnicamente sutrabajo en el capítulo 2 de diseño. El resto de capítulos se han distribuido equitativamente para sudesarrollo con varias iteraciones entre los alumnos y el director José L. Risco Martín.

1.4. Estructura de la memoria

Este documento recoge el proceso de desarrollo del sistema operativo, como compilarlo yejecutarlo y las conclusiones finales. La distribución de capítulos es la siguiente:

El capítulo 2 explica técnicamente el diseño con detalles de implementación y ejemplos deuso de los módulos que componen el sistema operativo.

El capítulo 3 muestra como compilar, ejecutar el sistema operativo y las funcionalidadesdisponibles dentro de él.

En el capítulo 4 se exponen las conclusiones finales y se indican posibles líneas de trabajofuturo.

Finalmente, en el capítulo 5 se resumen las aportaciones realizadas por cada uno de losmiembros del equipo.

Page 9: Desarrollo de un Sistema Operativo para Raspberry Pi 2

Capítulo 2

Diseño

En este capítulo se muestra en detalle el diseño y la implementación de los distintos módulosque componen un sistema operativo moderno [18] además de ejemplos de uso. Para detallar losmódulos, hemos utilizado el orden cronológico de implementación, ya que los últimos módulosimplementados utilizan en gran medida módulos anteriores.

2.1. Introducción

Al desarrollar un sistema operativo, aparecen varias dependencias con el hardware que provo-can la incompatibilidad entre dispositivos. En este proyecto se trabaja conociendo la arquitecturaARMv7 y, por ejemplo, el proceso de arranque, interrupciones y parte del sistema de procesos seimplementan a bajo nivel con el repertorio de instrucciones propio del dispositivo [9]. Luego uncambio en el conjunto de instrucciones provoca la incompatibilidad de esos módulos como se hacomprobado en Raspberry Pi 3, la nueva versión, que tiene un set de instrucciones de 64 bits.

Por este motivo, y como este trabajo final de grado esta enfocado a la creación de un sistemaoperativo para la plataforma de Raspberry Pi 2 Model B, antes de empezar con el diseño convieneconocer las características mas destacables y con las que se va a interactuar de la placa:

Un procesador basado en la arquitectura ARMv7, en concreto, el modelo Cortex-A7 a900MHz de 32 bits.

1 GB de SDRAM (del inglés Synchronous Dynamic Random Access Memory).

Puerto HDMI (del inglés High-Definition Multimedia Interface).

Ranura para Micro SD (del inglés Secure Digital).

Tarjeta gráfica VideoCore IV 3D graphics core.

Banco de puertos GPIO (del inglés General Purpose Input/Output).

2.1.1. El Toolchain

En este capítulo se explicará el conjunto de herramientas necesarias para construir, depurar yejecutar el proyecto de forma sencilla y automatizada.

9

Page 10: Desarrollo de un Sistema Operativo para Raspberry Pi 2

10 CAPÍTULO 2. DISEÑO

En este proyecto se han usado y preparado para futuros desarrolladores las siguientes partes:

Un Makefile con el que poder compilar y ejecutar el proyecto.

Un compilador y un depurador, además de herramientas varias para poder analizar los fiche-ros resultados.

Un emulador con el que poder probar el proyecto.

2.1.2. Estructura del Makefile

Los archivos Makefile son parseables gracias a la herramienta make del proyecto GNU [8],donde se puede encontrar una definición más precisa acerca de como funcionan los Makefile, yde cómo obtener la herramienta make en el caso de que no estuviera ya instalada en el sistema detrabajo.

Makefile del proyecto

En esta sección se procederá a mostrar el Makefile que se ha usado a lo largo de todo eldesarrollo y a explicar en qué consiste cada regla y las variables declaradas.

De la linea 1 a la 10 del código 2.1 se declaran una serie de variables que sirven como argu-mentos para el compilador y el enlazador (línea 1 a la 5), para saber dónde se sitúa el código fuente(línea 8), dónde está el código en ensamblador (línea 9) y en qué carpeta guardar los archivos quegenerará el compilador al acabar su ejecución (línea 10).

1 CC = compiler/bin/arm-none-eabi2 OBJCOPY = compiler/bin/arm-none-eabi-objcopy3 C_OPT = -mcpu=cortex-a7 -O0 -Wall -Wextra -fpic -ffreestanding -std=

gnu99 -nostdlib -I berryOS/include -g4 #-g preserves the program’s identifiers and symbols5 L_OPT = -ffreestanding -nostdlib -mcpu=cortex-a76

7 #-fpic position independent code8 SRC_DIR = berryOS/src9 SRC_ARCH = berryOS/arch

10 BUILD_DIR = build

Código 2.1: Inicio de nuestro Makefile

Las opciones de compilación para el código c (variable C_OPT) son las siguientes:

-mcpu sirve para indicar al compilador para qué procesador se quiere que se ejecute elcódigo C.

-O0 le indica al compilador que no optimice código de ninguna forma. Esta opción acom-pañada junto con la opción -g facilita la depuración.

Page 11: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.1. INTRODUCCIÓN 11

-Wall y -Wextra sirve para que el compilador avise de cualquier tipo de posibles erroresque encuentre durante las primeras fases de la compilación.

-fpic permite al compilador generar código que sea independiente de donde esté localizadoen memoria. Esto se traduce en que, por ejemplo, los cálculos de las direcciones de memoriaa las que saltar para una instrucción de la forma cmp sean relativos en vez de absolutos.

-ffreestanding obliga al compilador a trabajar en un entorno en el que es posible que lalibrería estándar no exista, y que el programa pueda no empezar en la función típica "main".Un entorno como este se traduce en que es posible que las funciones estándar pueden notener su funcionalidad típica.

-std simplemente fuerza a que el compilador siga los estándares definidos por gnu99.

-nostdlib hace que el compilador no enlace las librerías estándar, acción que hace por de-fecto.

-I simplemente indica dónde están los archivos .h que se quieren incluir.

-g indica que se mantengan en el propio ejecutable ciertos símbolos fuente para poder de-purar con GDB.

Las siguiente variables declaradas son arrays que contienen las rutas para cada archivo conextensión .c o .S que se quieren compilar. En caso de querer comprobar el contenido de estasvariables, se puede ejecutar la regla variable_test.

Antes de continuar, es necesario mencionar que para poder obtener el valor de una variable, sedebe escribir como $(<nombre de variable>).

1 C_FILES = $(wildcard $(SRC_DIR)/*/*/*.c)2 C_FILES += $(wildcard $(SRC_DIR)/*/*.c)3 C_FILES += $(wildcard $(SRC_DIR)/*.c)4

5 ASM_FILES = $(wildcard $(SRC_ARCH)/ARMv7/*.S) #Remember to add thecontext.S when using processes

6 ASM_FILES += $(wildcard $(SRC_DIR)/proc/*/*.S)7 ASM_FILES += $(wildcard $(SRC_DIR)/proc/*.S)8

9 OBJ_FILES = $(C_FILES:$(SRC_DIR)/%.c=$(BUILD_DIR)/%_c.o)10

11 OBJ_FILES += $(ASM_FILES:$(SRC_DIR)/%.S=$(BUILD_DIR)/%_s.o)

A continuación se explican las reglas que son los puntos de partida de la herramienta make, detal forma que pueden ser ejecutadas escribiendo en la terminal el comando "make <nombre de la

regla>".Para que una regla se ejecute deben cumplirse todas sus dependencias. Si se quiere ejecutar

la regla build del fragmento 2.2 primero debe cumplirse que el archivo linker.ld, cuya posición en

Page 12: Desarrollo de un Sistema Operativo para Raspberry Pi 2

12 CAPÍTULO 2. DISEÑO

el repertorio de carpetas viene definido por el path que contiene una de las variables previamentemostradas, y todos los archivos con extensión .o, los archivos objeto creados por el compilador ynecesarios para el enlazador.

1 build: $(SRC_ARCH)/ARMv7/linker.ld $(OBJ_FILES)2 @echo "Linking .o files..."3 $(CC)-gcc -T $(SRC_ARCH)/ARMv7/linker.ld -o $(BUILD_DIR)/myos.elf $

(L_OPT) $(OBJ_FILES)4 #$(CC)-objcopy $(BUILD_DIR)/myos.elf -O binary $(BUILD_DIR)/myos.

bin5 @echo "Done!"

Código 2.2: Regla build del Makefile

Nótese que las siguientes reglas garantizan que lo archivos .o existen una vez acabada la eje-cución, por ejemplo:

1 #target for .c files2 $(BUILD_DIR)/%_c.o: $(SRC_DIR)/%.c3 @mkdir -p $(@D)4 @echo "Compiling .c files..."5 $(CC)-gcc $(C_OPT) -MMD -c $< -o $@6 @echo ""7

8 #target for .s files9 $(BUILD_DIR)/%_s.o: $(SRC_DIR)/%.S

10 @mkdir -p $(@D)11 @echo "Compiling .S files..."12 $(CC)-gcc $(C_OPT) -MMD -c $< -o $@13 @echo ""$(SRC_DIR)/

El resto de reglas sirven para:

La regla run para iniciar todo el proceso de compilar, enlazar y ejecutar el programa final.

Realizar el mismo proceso que la regla anterior, salvo que esta vez el programa se ejecutaráy esperará a que haya un GDB para continuar el proceso. De esta forma, podemos depurarel código con la regla debug.

Las reglas build y build_hard permiten únicamente compilar y enlazar el programa. En elcaso de la última, genera la imagen del sistema operativo listo para ser cargado en la tarjetaSD de la Raspberry Pi 2.

La regla clean borra todo los archivos que se crean con las ejecuciones de las reglas build obuild_hard.

1 run: build2 @echo ""3 @echo "Running qemu..."

Page 13: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.1. INTRODUCCIÓN 13

4 qemu-system-arm -m 256 -M raspi2 -serial stdio -kernel $(BUILD_DIR)/myos.elf

5

6 debug: build7 @echo ""8 @echo "Debugging in qemu..."9 qemu-system-arm -m 256 -M raspi2 -serial stdio -kernel $(BUILD_DIR)

/myos.elf -S -gdb tcp::123410

11 build: $(SRC_ARCH)/ARMv7/linker.ld $(OBJ_FILES)12 @echo "Linking .o files..."13 $(CC)-gcc -T $(SRC_ARCH)/ARMv7/linker.ld -o $(BUILD_DIR)/myos.elf $

(L_OPT) $(OBJ_FILES)14 #$(CC)-objcopy $(BUILD_DIR)/myos.elf -O binary $(BUILD_DIR)/myos.

bin15 @echo "Done!"16

17 build_hard: build18 $(OBJCOPY) $(BUILD_DIR)/myos.elf -O binary $(BUILD_DIR)/kernel7.img19 @echo "Done!"20 clean:21 rm -rf $(BUILD_DIR)/

Código 2.3: Regla de ejemplo

2.1.3. El compilador

El compilador empleado pertenece a la toolchain de arm-none-eabi. Este conjunto de herra-mientas se enfoca en la generación de código para procesadores ARM, es open source, está enfoca-do en lo que se conoce como sistemas bare metal y compila con ARM-EABI. Se puede encontraren la pagina oficial de ARM [4], aunque también se incluye este paquete en el repositorio delproyecto en la carpeta denominada compiler/.

Dentro de la carpeta compiler/bin/ se encuentran todos los programas que se han usado para eldesarrollo. Los más frecuentes son el programa GDB con el que depurar el código y el compiladorGCC cuestión.

2.1.4. El emulador

Se decidió emplear el emulador de QEMU debido a que no todos los miembros del equipotienen en posesión una Raspberry Pi 2 con la que trabajar, y a la rapidez que ofrece para probar elcódigo.

Además, el emulador ha servido para poder continuar con el trabajo durante el confinamientoobligatorio decretado en el estado de alarma a causa de la pandemia de COVID-19. Es por elmismo motivo, el cual no se ha podido depurar el código al completo en la placa pero sí en elemulador.

Para instalar el emulador QEMU en Linux se consigue con el siguiente comando:

1 sudo apt-get install qemu

Page 14: Desarrollo de un Sistema Operativo para Raspberry Pi 2

14 CAPÍTULO 2. DISEÑO

2.2. Arranque del sistema

La Raspberry Pi 2 tiene una forma muy peculiar de realizar la etapa de bootloading, por lo queen esta sección hablaremos de forma breve acerca de ella. Antes de comenzar, debemos mencionarque la Raspberry Pi tiene el kernel del sistema operativo, junto con otros programas básicos, en latarjeta micro SD.

Para obtener todos los archivos y el firmware necesarios en la SD, seguiremos el siguienteproceso:

1. Instalaremos Raspbian en la SD siguiendo este tutorial oficial [6]. Nosostros hemos utilizadoRaspberry Pi Imager. Tras la instalación, tendremos la tarjeta dividida en dos carpetas, booty rootfs.

2. Insertamos la SD en la Raspberry Pi 2 y conectamos el HDMI para comprobar que se iniciaRaspbian, lo que indica que la instalación se ha realizado correctamente.

3. Compilaremos el proyecto utilizando make build_hard (más información sobre cómo utilizarlos comandos del Makefile en la sección 3.1). De esta manera, obtendremos en la carpetabuild el archivo kernel7.img.

4. Finalmente, borramos de la carpeta boot todos los archivos *.img, y copiamos el archivo delpaso anterior en su lugar.

El bootloading se podría dividir en cinco etapas. No entraremos en demasiado detalle acercade estas etapas, ya que lo que se hace dentro de cada una de ellas no es conocimiento 100 % abiertoal público.

Las cinco etapas son:

1. Se ejecuta el firmware de la ROM, este software cargará la siguiente etapa en la cache L2.El componente que ejecuta el programa es la GPU, la CPU permanece deshabilitada durantetodo el proceso hasta la última fase. La SDRAM también está desactivada.

2. Esta etapa consiste en ejecutar el código dentro del archivo bootcode.bin que está dentro dela micro SD y habilitar la SDRAM para poder cargar la siguiente etapa.

3. Lo que se ejecutará en esta etapa es el código del archivo loader.bin, este archivo es capaz deparsear los ficheros con formato .elf y será el que cargue el fichero start.elf para la siguienteetapa.

4. start.elf cargará el fichero llamado kernel.img y leerá los ficheros config.txt, cmdline.text ybcm2835.dtb.Si el fichero con extensión .dtb existe, se cargará en la posición de memoria 0x100 y elkernel en la posición 0x8000.

Page 15: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.3. KERNEL 15

Si la línea disable_commandline_tags=true está dentro del fichero config.txt, el kernel secarga en la posición 0x0.En cualquier otro caso, el kernel siempre se cargará en la posición 0x8000 y los ATAGS seañadirán a partir de la 0x100.

5. A partir de esta etapa es cuando el sistema operativo gana control del hardware y se empiezaa ejecutar su kernel.

Para ayudar a entender este proceso más fácilmente, observa la figura 2.1.

Figura 2.1: Diagrama del bootloading

2.3. Kernel

El kernel [5] es lo primero que se ejecutará tras el proceso de arranque y se encargará de reali-zar las acciones necesarias para ajustar ciertas partes e inicializar los distintos módulos. De esto seencargarán berryOS/arch/ARMv7/boot.S y berryOS/src/kernel.c respectivamente. Los procesosde boot.S son los siguientes:

Page 16: Desarrollo de un Sistema Operativo para Raspberry Pi 2

16 CAPÍTULO 2. DISEÑO

Pausar 3 de los 4 procesadores para conseguir un sistema monoprocesador.

Colocar ceros en toda la sección de BSS(del inglés Block Started by Symbol). Esto es nece-sario para seguir el estándar de C, en el que las variables globales sin inicializar deben valercero.

Cambiar a los distintos modos de ejecución para inicializar sus registros. Básicamente utili-zaremos dos: el modo IRQ (del inglés Interrupt Request) y el supervisor.

Inicializar la tabla de vectores de excepciones con las funciones que las tratarán. De estamanera, cuando salte una excepción, se saltará directamente a la función que la tratará.La función str_vect_table que se encuentra en berryOS/arch/ARMv7/interrupt_init.S, es laencargada de esto, situando la tabla a partir de la posición de memoria cero.

Habilitar las interrupciones.

Llamar a la función kernel_main de kernel.c

En esta misma sección se pueden realizar otras acciones, las cuales no hemos utilizado porqueno eran básicas, pero se podrían considerar en el futuro:

La MMU(del inglés Memory Management Unit) para la gestión de memoria virtual.

La inicialización de dispositivos de entrada/salida.

La inicialización de NEON, relacionado con la implementación del SIMD (del inglés SingleInstruction Multiple Data) avanzado, y VFP (del inglés Vector Float Point), la extensión paravectores de punto-flotante.

La utilización de Secure World para iniciar la Raspberry Pi en modo seguro.

El método kernel_main, inicializará cada uno de los módulos que lo necesiten, llamando a sufunción init. Estos son:

Modulo de entrada/salida: compuesto por la GPIO, la UART (del inglés Universal Asyn-chronous Receiver-Rransmitter) y la GPU (del inglés Graphics Process Unit).

Modulo de memoria: compuesto por el proceso de mapeado de la memoria y el gestor dememoria dinámica.

Modulo de procesos: incluye los procedimientos para realizar cambios entre procesos y elmecanismo para registrar planificadores.

Comandos: inicializa los mecanismos necesarios para crear y registrar comandos.

Consola: la cual contiene la interfaz con la que se comunica el usuario.

Interrupciones: compuesto por las funciones que tratan las distintas interrupciones del sis-tema.

Timer local: encargado de inicializar el timer local, de manera que salte cada un tiempodeterminado.

Page 17: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.4. ENTRADA / SALIDA 17

2.4. Entrada / Salida

Esta sección explica los módulos de entrada y salida en el orden temporal en que se han idodesarrollando, así como a los módulos dependientes disponibles en el momento y las necesidadesque se presentan, por ejemplo, si se está trabajando en la Raspberry o en el emulador QEMU.Esto se traduce en un incremento de complejidad con cada nuevo módulo, pero que respondecon una mejor experiencia de usuario y, en el proceso de desarrollo del proyecto, proporcionauna depuración y testeo más rápido y eficaz. Este esfuerzo culmina en una librería estándar deentrada/salida similar a la conocida popularmente como stdio.h.

2.4.1. GPIO

Este módulo tiene como objetivo simplificar el uso de una de las características más famosas dela serie de placas Raspberry: los pines de Entrada/Salida de Propósito General (General Purpose

Input/Output pins (GPIO) en inglés).El banco de pines GPIO se utiliza como puerto para la conexión de periféricos hardware con

la CPU. Cada pin tiene varias funcionalidades disponibles y son manipulables por medio de unalista de registros bien definidos por el fabricante1 [12]. En la figura 2.2 se muestra la disposiciónde los pines.

Figura 2.2: Imagen que ofrece https://es.pinout.xyz/ con la información de cada pin

1La lista completa se puede consultar en la sección 6.2 del manual de periféricoshttps://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf

Page 18: Desarrollo de un Sistema Operativo para Raspberry Pi 2

18 CAPÍTULO 2. DISEÑO

Mapeo de pines

1 # d e f i n e GPIO_BASE ( PHYSICAL_PBASE + 0 x00200000 )

Código 2.4: Constante para mapeo de los pines GPIO

La arquitectura de la Raspberry Pi 2 mapea la memoria dejando en el rango de 0x3F000000-0x3FFFFFFF las direcciones que usarán los periféricos de Entrada/Salida, en concreto los pinesGPIO. Estos valores varían si se usa memoria virtual, y dado que en este proyecto no ha sidoimplementada, por eso se facilita dicho rango. Como se muestra en el fragmento de código 2.4,la constante global PHYSICAL_PBASE tiene la dirección inicial 0x3F000000 y en este módulo seutiliza para calcular las direcciones de los registros de manipulación de los pines GPIO como seespecifica en la documentación.

1 //GPIO function select (registers 0 to 9)2 #define GPFSEL0 ((volatile uint32_t*)(GPIO_BASE + 0x000))3 //GPIO function select (registers 10 to 19)4 #define GPFSEL1 ((volatile uint32_t*)(GPIO_BASE + 0x004))5 //GPIO function select (registers 20 to 29)6 #define GPFSEL2 ((volatile uint32_t*)(GPIO_BASE + 0x008))7 //GPIO function select (registers 30 to 39)8 #define GPFSEL3 ((volatile uint32_t*)(GPIO_BASE + 0x00C))9 //GPIO function select (registers 40 to 49)

10 #define GPFSEL4 ((volatile uint32_t*)(GPIO_BASE + 0x010))11 //GPIO function select (registers 50 to 53)12 #define GPFSEL5 ((volatile uint32_t*)(GPIO_BASE + 0x014))

Código 2.5: Registros de manipulación GPIO de /include/io/gpio.h

El fragmento de código anterior muestra parte de la lista de los registros de manipulación delos pines. Son las direcciones de memoria de los registros de 32 bits, que se marcan como volatilepara esquivar la cache y evitar problemas de transmisión de datos.

Manipulación de los pines

La escritura y lectura de los registros ya mencionados es a nivel binario, por tanto es necesarioconocer lo operadores binarios (llamados bitwise operators en inglés) nativos de C para entenderla explicación y, consecuentemente, el código.

[ 0b-número- ] Indica que el formato de -número- es binario.Ejemplo: 0b1000 = 8

[ & ] Operador AND lógico a nivel binario.Ejemplo: 0b1 & 0b0 = 0b0

[ | ] Operador OR lógico a nivel binario.Ejemplo: 0b1 | 0b0 = 0b1

Page 19: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.4. ENTRADA / SALIDA 19

[ < < ] Operador de desplazamiento2 hacia la izquierda.Ejemplo: 0b1 < < 3 = 0b1000

[ > > ] Operador de desplazamiento hacia la derecha.Ejemplo: 0b1000 > > 3 = 0b1

Escritura en un pin

1 t y p e d e f enum {2 INPUT = 0b000 ,3 OUTPUT = 0b001 ,4 ALT0 = 0b100 ,5 ALT1 = 0b101 ,6 ALT2 = 0b110 ,7 ALT3 = 0b111 ,8 ALT4 = 0b011 ,9 ALT5 = 0 b010

10 } p i n _ a l t _ f u n c t ;11

12 void p i n _ s e t _ f u n c t i o n ( unsigned i n t pin , p i n _ a l t _ f u n c t f u n _ s e l ) {13 i f ( p i n > 53) {14 re turn ;15 }16

17 v o l a t i l e u i n t 3 2 _ t ∗ r e g _ o b j ;18

19 sw i t ch ( p i n / 10) {20 case 0 :21 r e g _ o b j = GPFSEL0 ;22 break ;23 case 1 :24 r e g _ o b j = GPFSEL1 ;25 break ;26 [ . . . ]27 r e g _ o b j = GPFSEL4 ;28 break ;29 case 5 :30 r e g _ o b j = GPFSEL5 ;31 break ;32 }33

34 ∗ r e g _ o b j &= ~(0 b111 << ( ( p i n % 10) ∗ 3) ) ;35 ∗ r e g _ o b j | = f u n _ s e l << ( ( p i n % 10) ∗ 3) ;36 }

Código 2.6: Configuración de los pines /src/io/gpio.c

En el fragmento del código 2.6 se muestra cómo configurar la funcionalidad o forma de tra-bajar de un pin. Por ejemplo, un pin se puede utilizar para datos de entrada, para salida o paracontrolar eventos (por ejemplo si un pin toma cierto valor en algún momento del ciclo de reloj).

Los registros se dividen en paquetes de bits únicos para cada pin, en el ejemplo, cada registro sedivide en paquetes de 3 bits que debe tomar alguno de los valores que se definen en el enumerado.

2Los operadores de desplazamiento también son útiles (y más eficientes) para calcular multiplicaciones por 2 (haciala izquierda) y divisiones enteras entre 2 (hacia la derecha)

Page 20: Desarrollo de un Sistema Operativo para Raspberry Pi 2

20 CAPÍTULO 2. DISEÑO

Con matemáticas no tan complejas, se puede calcular fácilmente el registro y la posición donde sedebe escribir en este.

Por lo general, el esquema para escribir en un registro ARM es el siguiente:

1. Calcular el registro donde se encuentra el pin dividiendo entre el número de pines queentran por registro. [Líneas 19-32 del código 2.6]

2. Limpiar el valor que hubiese con anterioridad en el paquete de bits. Desplazando tantos1s como el tamaño del paquete y posteriormente negarlo para dejar a 0 únicamente lasposiciones de los bits del paquete al aplicar el AND.3 [Línea 34 del código 2.6]

3. Asignar el valor aplicando al contenido del registro una OR con el nuevo dato desplazadohasta donde el paquete va. [Línea 35 del código 2.6]

En la figura 2.3 se muestra de forma visual el proceso recién comentado y el efecto que seproducen en los paquetes de bits.

Figura 2.3: Esquema visual de escritura en un registro

Lectura de un pin

1 i n t p i n _ g e t _ l e v e l ( unsigned i n t p i n ) {2 i f ( p i n > 53)3 re turn −1;4

3En ocasiones este paso se puede omitir porque ARM limpia ciertos registros por ciclo de reloj o, dicho de otraforma, una vez usados

Page 21: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.4. ENTRADA / SALIDA 21

5 v o l a t i l e u i n t 3 2 _ t ∗ r e g ;6

7 sw i t ch ( p i n / 32) {8 case 0 :9 r e g = GPLEV0 ;

10 break ;11 case 1 :12 r e g = GPLEV1 ;13 break ;14 }15

16 / / Acorde a l esquema e x p l i c a d o a c o n t i n u a c i o n17 u i n t 3 2 _ t v a l o r = ∗ r e g ;18 v a l o r = v a l o r >> ( p i n % 32) ;19 v a l o r &= 0b1 ;20

21 / / S i m p l i f i c a d o22 re turn ( ( ∗ r e g ) >> ( p i n % 32) ) & 1 ;23 }

Código 2.7: Lectura de un pin /src/io/gpio.c

La rutina del código 2.7 muestra la implementación para leer el valor actual de un pin. De-vuelve 1 para el estado high, que corresponde a un voltaje positivo a nivel hardware, y 0 para low

y que se traduce en 0 voltios.Igual que en la escritura, el siguiente esquema sirve para leer de un registro el valor actual del

pin:

1. Calcular el registro donde se encuentra el paquete de bits correspondiente al pin. [Líneas7-14 del ejemplo. 2.7]

2. Guardar el valor del registro usando el operador * de acceso a puntero. [Líneas 17 delejemplo 2.7]

3. Desplazar el valor al principio, eliminando así los datos de los pines anteriores. [Línea 18del ejemplo 2.7]

4. Limpiar el valor del resto de pines a la derecha que no interesan. [Línea 19 del ejemplo 2.7]

En la figura 2.3 se muestra de forma visual el proceso recién comentado y el efecto que seproducen en los paquetes de bits para conseguir el resultado.

Page 22: Desarrollo de un Sistema Operativo para Raspberry Pi 2

22 CAPÍTULO 2. DISEÑO

Figura 2.4: Esquema visual de escritura en un registro

Ejemplo de uso

A continuación se muestra de forma abstracta las principales operaciones desarrolladas en lalibrería sobre uno de los puerto del banco GPIO: Configurar el puerto como salida, liberar voltajepor el puerto, leer el estado de uno de los pines y parar la salida de voltaje.

1

2 # i n c l u d e < i o / gp io . h>3

4 / / Se c o n f i g u r a e l p i n 11 como s a l i d a5 p i n _ s e t _ f u n c t i o n ( 1 1 , OUTPUT) ;6

7 / / Se e n v i a v o l t a j e a l p u e r t o 118 p i n _ s e t _ o u t p u t ( 1 1 ) ;9

10 / / El p i n d e v u e l v e un 1 y por t a n t o e m i t e v o l t a j e11 i n t c u r r e n t _ s t a t e = p i n _ g e t _ l e v e l ( 1 1 ) ;12

13 / / Se c o r t a e l v o l t a j e14 p i n _ c l e a r _ o u t p u t ( 1 1 ) ;

Código 2.8: Ejemplo de uso con io/gpio.c

Como prueba de trabajo, se adjunta la foto 2.5 que tomó el equipo mientras depuraba en laRaspberry Pi 2 cedida por la Universidad Complutense de Madrid para comprobar que el sistemaoperativo realizaba el proceso de arranque correctamente.

Page 23: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.4. ENTRADA / SALIDA 23

Figura 2.5: Depurando con un LED y la Raspberry Pi 2 cedida por la UCM.

2.4.2. UART

La UART es un dispositivo incorporado en la placa que sirve de protocolo para transmitirinformación de carácter general entre dispositivos. En el caso del emulador QEMU, se utilizaexclusivamente para imprimir por la terminal. En la Raspberry Pi 2 se utiliza a través de los pinesGPIO 14 y 15 configurados correctamente y mediante un cable TTL como el de la figura 2.6. Anivel software, se resume en leer y escribir de un registro mapeado en memoria de forma similar ala explicada en la sección 2.4.1.

Figura 2.6: Cable TTL pin a USB. https://www.amazon.es/ADAFRUIT-Cable-serie-puntos-directa/dp/B00DJUHGHI

De la misma forma que en el módulo de la GPIO, se deben mapear los registros a partir dela dirección física y que cambia si se activa la memoria virtual. Se puede ver la definición de ladirección base en el siguiente código:

Page 24: Desarrollo de un Sistema Operativo para Raspberry Pi 2

24 CAPÍTULO 2. DISEÑO

1 # d e f i n e UART_BASE ( PHYSICAL_PBASE + 0 x00201000 )2

3 # d e f i n e UART0_DR ( ( v o l a t i l e u i n t 3 2 _ t ∗ ) (UART_BASE + 0 x000 ) )4 [ . . . ]

Inicialización

En el caso de estar utilizando el emulador QEMU, nos dimos cuenta de que no es necesariorealizar ningún proceso de inicialización, presumiblemente porque ya se realiza automáticamenteal ser el único puerto de comunicación entre programa/emulador y el sistema donde se ejecuta.Sin embargo, para la Raspberry Pi 2 se debe proceder como se explicará a continuación.

0. Desactivar la UART.

1 ∗ (UART0_CR) = 0 ;

1. Configurar los pines asignando a la alternativa de funcionalidad 5 que corresponde a la dela UART.

1 p i n _ s e t _ f u n c t i o n ( 1 4 , ALT5) ;2 p i n _ s e t _ f u n c t i o n ( 1 5 , ALT5) ;

2. Desactivar las resistencias pull-up/pull-down (pud). Estas resistencias sirven para evitarel efecto denominado floating pin que ocurre en pines de entrada sin conectar y que provoca ruidoeléctrico, en este caso, se desactivan porque los pines de entrada estarán enchufados al cable TTL.

1 p i n _ s w i t c h _ p u d ( 1 4 , 0b00 ) ;2 p i n _ s w i t c h _ p u d ( 1 5 , 0b00 ) ;

3. Por último se asignan varios parámetros del protocolo, como por ejemplo: el baudio, seactiva la transmisión y la recepción, el tamaño de palabra (a 8 bits = 1 Byte = 1 char), tratamientode errores y reactivar la UART.

1 / / u s i n g baud as 115200 , INTEGER = 3000000 / (16∗115200) = 1 .627 ~ 12 ∗ (UART0_IBRD) = 1 ;3

4 [ . . . ]5

6 s e l e c t o r = 0 ;7 s e l e c t o r = (7 < <4) ; / / 8 b i t s each word and FIFO e n a b l e8 ∗ (UART0_LCRH) = s e l e c t o r ;9

10 [ . . . ]11

12 s e l e c t o r = 0 ;13 s e l e c t o r | = (1 < <9) ; / / r e c e i v e e n a b l e14 s e l e c t o r | = (1 < <8) ; / / t r a n s m i t e n a b l e15 s e l e c t o r | = 1 ; / / u a r t e n a b l e16

17 ∗ (UART0_CR) = s e l e c t o r ;

Page 25: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.4. ENTRADA / SALIDA 25

Lectura

La lectura de la UART es muy simple, únicamente se trata de leer de un registro, pero se debesaber cuando. Para eso, a través de un registro de control se comprueba que se haya detectado undato en un while. Por tanto se trata de una operación bloqueante activa síncrona.

1 char u a r t _ r e c v ( ) {2 whi le ( ∗ (UART0_FR) & (1 << 4) ) ;3

4 re turn ∗ (UART0_DR) ;5 }

Código 2.9: Rutina de lectura de un char en la UART

Escritura

De la misma forma que la lectura, todo se gestiona a través de los registros pertinentes. Antesde leer se debe esperar a que la UART esté disponible, por tanto, igual que la lectura, se trata deuna operación bloqueante activa síncrona.

1 void u a r t _ p u t c ( unsigned char c ) {2 whi le ( ∗ (UART0_FR) & (1 << 5) ) ;3

4 ∗ (UART0_DR) = c ;5 }

Código 2.10: Rutina de escritura de un char en la UART

Ejemplos de uso

Las dos sencillas operaciones ya explicadas sirven como pilares para implementaciones máscomplejas:

1 void u a r t _ p u t s ( c o n s t char∗ s t r ) {2 / / Llamar a l a r u t i n a p a r a cada c h a r d e l a r r a y3 f o r ( s i z e _ t i = 0 ; s t r [ i ] != ’\0’ ; i ++)4 u a r t _ p u t c ( ( unsigned char ) s t r [ i ] ) ;5 }6

7 u a r t _ p u t s ("Hola lector!" ) ;

Código 2.11: Rutina de escritura de un string (char *) en la UART

1 void u a r t _ h e x _ p u t s ( u i n t 3 2 _ t v a l u e ) {2 char s t r _ a r g u m e n t [ 9 ] = {’0’ ,’0’ ,’0’ ,’0’ ,’0’ ,’0’ ,’0’ ,’0’ , ’\0’ } ;3 c o n v e r t _ t o _ s t r ( va lue , s t r _ a r g u m e n t , 8 ) ;4 u a r t _ p u t s ("0x" ) ;5 u a r t _ p u t s ( s t r _ a r g u m e n t ) ;6 u a r t _ p u t s ("\r\n" ) ;7 }8

9 c o n s t char∗ h e l l o = ’Hola lector!’

Page 26: Desarrollo de un Sistema Operativo para Raspberry Pi 2

26 CAPÍTULO 2. DISEÑO

10

11 u a r t _ h e x _ p u t s ( h e l l o ) ;12 /∗ S a l i d a r e p r e s e n t a t i v a : 0x00014CA0 ∗ /

Código 2.12: Rutina de escritura de una dirección de memoria en la UART

Figura 2.7: Salida por consola del arranque del SO emulado en QEMU a través de la UART

2.4.3. Mailbox, framebuffer y GPU

Esta sección explica cómo conseguir la interacción de la CPU con la GPU para poder rende-

rizar imágenes por el puerto HDMI. Se explican estos tres módulos juntos porque son necesariospara lograr el objetivo. El proceso es complejo y se dificulta aún más al estar incompleta la docu-mentación proporcionada por el fabricante4.

El fin del proceso es conseguir un framebuffer, un array de píxeles compartido entre la GPU yla CPU en memoria donde es posible la lectura y escritura. Los datos que contiene se enviarán porel puerto HDMI en cada fotograma y por tanto renderizable en cualquier pantalla conectada.

Mailbox

El mailbox es un sistema que facilita el intercambio de datos por mensajes entre la CPU ARMy los dispositivos exteriores al chip, los periféricos, entre ellos está la tarjeta gráfica. Tiene varioscanales, aunque en este proyecto únicamente se ha usado el octavo y por eso sólo se han modeladola estructuras de mensajes para éste, pero, definiendo las estructuras pertinentes, el módulo deberíaservir para cualquiera ya que se ha conseguido la abstracción necesaria para el envío y recepción.

Lista de canales mailbox:

0: Power management

1: Framebuffer4La documentación oficial proporcionada por el fabricante de los distintos canales mailbox

https://github.com/raspberrypi/firmware/wiki/Mailboxes

Page 27: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.4. ENTRADA / SALIDA 27

2: Virtual UART

3: VCHIQ

4: LEDs

5: Buttons

6: Touch screen

7: 5

8: Property tags (ARM ->VC)

9: Property tags (VC ->ARM)

Como ya se ha explicado varias veces en este capítulo de entrada/salida, la lectura y escriturase realiza por medio de registros mapeados en memoria. En este caso, los datos en los registros yano se tratan a nivel binario, sino que son estructuras con un formato definido como se muestra enla figura 2.8.

Figura 2.8: Estructura del mapeo de registros https://jsandler18.github.io/extra/mailbox.html

La figura 2.8 se refleja en código como el struct del fragmento 2.13. Posteriormente se definenlos punteros a los registros de datos que van a tener esta forma.

1 t y p e d e f s t r u c t {2 u i n t 8 _ t c h a n n e l : 4 ; / / Canal de l a mai lbox3 u i n t 3 2 _ t d a t a : 2 8 ; / / D i r e c c i o n d e l mensa je4 } m a i l box _me ssa ge_ t ;5

6 # d e f i n e MAILBOX_BASE PHYSICAL_PBASE + MAILBOX_OFFSET7 # d e f i n e MAIL0_READ ( ( ( ma i lbo x_m ess age _ t ∗ ) (0 x00 + MAILBOX_BASE) ) )8 # d e f i n e MAIL0_STATUS ( ( ( m a i l b o x _ s t a t u s _ t ∗ ) (0 x18 + MAILBOX_BASE) ) )9 # d e f i n e MAIL0_WRITE ( ( ( ma i lbo x_m ess age _ t ∗ ) (0 x20 + MAILBOX_BASE) ) )

Código 2.13: Definición de registros de la mailbox

5Como ya se ha comentado, la documentación compartida por el fabricante en esta área es insuficiente

Page 28: Desarrollo de un Sistema Operativo para Raspberry Pi 2

28 CAPÍTULO 2. DISEÑO

Recepción/lectura

1 /∗ ∗2 ∗ R e t u r n s t h e s i z e f o r a mai lbox message t a g as3 ∗ d e f i n e d i n t h e p r o t o c o l .4 ∗5 ∗ @param t a g t h e t a g t o g e t t h e s i z e o f6 ∗ @return b y t e s o f a mai lbox message t y p e7 ∗ /8 ma i lb ox_ mes sag e_ t m a i l b o x _ r e a d ( m a i l b o x _ c h a n n e l _ t c h a n n e l ) {9 m a i l b o x _ s t a t u s _ t s t a t ;

10 ma i lb ox_ mes sag e_ t r e s ;11

12 / / Make s u r e t h a t t h e message i s from t h e r i g h t c h a n n e l13 do {14 / / Make s u r e t h e r e i s ma i l t o r e c i e v e15 do {16 s t a t = ∗MAIL0_STATUS ;17 } whi le ( s t a t . empty ) ;18

19 / / Get t h e message20 r e s = ∗MAIL0_READ ;21 } whi le ( r e s . c h a n n e l != c h a n n e l ) ;22

23 re turn r e s ;24 }

Código 2.14: Rutina de recepción de mensaje por un canal mailbox

Como se ve en el fragmento del código 2.14, la lectura es similar a las vistas con anterioridad.Es una operación síncrona, bloqueante y con espera activa para comprobar que la cola tienemensajes así como la pertenencia al canal correspondiente. Devuelve el puntero a la estructura delmensaje recibido.

Emisión/escritura

1 void mai lbox_send ( ma i lb ox _me ssa ge_ t msg , m a i l b o x _ c h a n n e l _ t c h a n n e l ) {2 m a i l b o x _ s t a t u s _ t s t a t ;3 msg . c h a n n e l = c h a n n e l ;4

5 / / Make s u r e you can send ma i l6 do {7 s t a t = ∗MAIL0_STATUS ;8 } whi le ( s t a t . f u l l ) ;9

10 / / send t h e message11 ∗MAIL0_WRITE = msg ;12 }

Código 2.15: Rutina de emisión de mensaje por un canal mailbox

De la misma forma que la recepción de datos del código 2.14, se trata de una operación

síncrona, bloqueante y de espera activa. Simplemente se debe escribir el puntero del mensaje enel registro de escritura comprobando que la cola de mensajes no esté llena como se puede ver enel fragmento de código 2.15.

Page 29: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.4. ENTRADA / SALIDA 29

Obtención de framebuffer

Ahora que ya se conoce el protocolo de comunicación mailbox, el algoritmo a grandes rasgos6

sigue los siguientes pasos:

1. Enviar un mensaje con varios parámetros de configuración para el framebuffer a la GPUa través del canal MAILBOX_PROPERTY_CHANNEL.

2. Enviar un mensaje de instanciación de framebuffer a la GPU a través del canal MAIL-

BOX_PROPERTY_CHANNEL.

3. De no haberse producido ningún error, el último mensaje enviado tendrá el puntero dememoria al framebuffer, esto es posible porque las estructuras de los mensajes se pasanpor referencia.

GPU

La inicialización de este módulo consiste en obtener el puntero del framebuffer explicadoanteriormente y almacenarlo en una variable global para su uso. Cuando se haya conseguido esto,la librería está preparada para leer y escribir en cada píxel que se verá por la pantalla conectada alpuerto HDMI de la Raspberry o en una ventana del emulador QEMU.

Un píxel es un número de 24 bits7 que representa un color siguiendo el formato estándar RGBcon 8 bits para cada color. En total admite 224 = 16,777,216 colores. La definición que se hausado es la siguiente:

1 t y p e d e f s t r u c t {2 u i n t 8 _ t r ;3 u i n t 8 _ t g ;4 u i n t 8 _ t b ;5 } c o l o r _ 2 4 ;6

7 # d e f i n e BLACK ( ( c o l o r _ 2 4 ) {0 x00 , 0x00 , 0x00 } )8 # d e f i n e WHITE ( ( c o l o r _ 2 4 ) {0xFF , 0xFF , 0xFF } )9 # d e f i n e RED ( ( c o l o r _ 2 4 ) {0xFF , 0x00 , 0x00 } )

El proceso para colorear un un píxel es tan fácil como copiarlo en la posición correspondientesiempre que esté dentro de los límites del framebuffer. La rutina de escritura es la siguiente:

1 void w r i t e _ p i x e l ( u i n t 3 2 _ t x , u i n t 3 2 _ t y , c o l o r _ 2 4 ∗ p i x ) {2 i f ( x < f b i n f o . wid th && y < f b i n f o . h e i g h t ) {3 u i n t 8 _ t ∗ l o c a t i o n = f b i n f o . buf + y∗ f b i n f o . p i t c h + x∗BYTES_PER_PIXEL ;4 memcpy ( l o c a t i o n , pix , BYTES_PER_PIXEL ) ;5 }6 }

6Como el código es bastante denso en apartados de configuración no se han añadido fragmentos, está disponible en/src/io/framebuffer.c

7Puede variar según la configuración del framebuffer

Page 30: Desarrollo de un Sistema Operativo para Raspberry Pi 2

30 CAPÍTULO 2. DISEÑO

La lectura, es igual de sencillo que escribir. La operación es útil para aplicar filtros o capturarla pantalla. Para lograrlo únicamente se debe calcular el puntero y acceder a él como se describeen el siguiente fragmento:

1 c o l o r _ 2 4 ∗ r e a d _ p i x e l ( u i n t 3 2 _ t x , u i n t 3 2 _ t y , c o l o r _ 2 4 ∗ p i x ) {2 i f ( x < f b i n f o . wid th && y < f b i n f o . h e i g h t ) {3 u i n t 8 _ t ∗ l o c a t i o n = f b i n f o . buf + y∗ f b i n f o . p i t c h + x∗BYTES_PER_PIXEL ;4 re turn l o c a t i o n ;5 }6 }

Estos dos métodos sirven de base para el desarrollo de un sistema gráfico de vistas con unainterfaz gráfica simple como se explica en la sección 2.9. La figura 2.9 muestra el Log de arranquedel SO en la consola a partir de un framebuffer de 640x480 píxeles con un sistema de vistas creadocon éste módulo.

Figura 2.9: Log de arranque del SO en el sistema de vistas desarrollado

2.4.4. stdio.h

La idea de este módulo es concentrar todos los métodos de entrada y salida en una únicalibrería estándar para futuros programadores. Por el momento agrupa la UART y la consola de lainterfaz gráfica que se ha desarrollado a partir del módulo de GPU.

Incluye varias funciones de impresión, todas redirigen los datos a la UART y a la consola peroalguna tiene funciones añadidas como el salto de linea o enriquecimiento. A modo de ejemplo, lasiguiente rutina muestra la implementación para imprimir por pantalla.

1 void p r i n t ( char∗ t e x t ) {2 u a r t _ p u t s ( t e x t ) ;3 c o n s o l e _ p u t S t r ( t e x t , NULL, NULL) ;4 }

Page 31: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.5. INTERRUPCIONES 31

Respecto a la entrada de datos directa del teclado, el único método que se ha desarrollado es através de la UART. La rutina de lectura que se muestra a continuación explica el procesamiento deuna línea entera (hasta encontrar el carácter de final de línea o por tamaño máximo del buffer).

1 void readLn ( char∗ out , i n t max ) {2 i n t s i z e = 0 ;3 char c ;4 whi le ( ( c = u a r t _ r e c v ( ) ) != ’\n’ && s i z e < max ) {5 o u t [ s i z e ++] = c ;6 }7 }

2.5. Interrupciones

En cualquier tipo de arquitectura moderna se da soporte para que el procesador pueda tratarexcepciones, aquí hablaremos del soporte que da ARM. Las excepciones son eventos que obligana la CPU a dejar lo que sea que esté haciendo para tratarlos, algunos ejemplos de estos eventosson:

Las interrupciones.

Intentar hacer una división entre cero (ejemplo 5/0).

Acceder a una zona de memoria prohibida.

Intentar ejecutar una instrucción que no está definida en el repertorio de instrucciones.

Antes de continuar, debemos hacer un inciso en el diseño de ejecución de la arquitecturaque estamos usando. Esta arquitectura da soporte para ejecutar en varios modos, estos modos deejecución se diferencian en la cantidad de permisos que tienen así como en el número de registrosque pueden usar. Los permisos se enumeran como PL0, PL1 y PL2 siendo PL0 el que menosprivilegios tiene y PL2 el que más (PL0 y PL1 tienen privilegios con seguridad y sin seguridad8).Los modos de ejecución son User, System, Hypervisor9, Supervisor, Abort, Undefined, Monitor,IRQ y FIQ.

La figura 2.10 siguiente muestra, a groso modo, diferencias fundamentales entre modos, sepueden observar en la primera columna a la izquierda los registros disponibles para el desarrolla-dor. Cada columna representa qué registros poseen cada uno de los modos (si el campo está vacíosignifica que el registro es el mismo que el de la columna User). Todos los modos, salvo el System,poseen registros SPSR, estos registros sirven para guardar el estado del programa (Valor del regis-tro CPSR) tal y como se explicará más adelante, también, desde el modo Hypervisor en adelante,cada uno de los modos posee su propio registro de pila con la intención de que cada uno tenga supropia pila. Se puede observar que el modo FIQ (Fast Interrupt Request) posee registros propios

8La extensión de seguridad no forma parte de este TFG, se deja para que futuros alumnos la complementen9Contiene soporte para virtualización. Tampoco forma parte de este TFG

Page 32: Desarrollo de un Sistema Operativo para Raspberry Pi 2

32 CAPÍTULO 2. DISEÑO

Figura 2.10: Diagrama de los distintos modos de un procesador

(de r8 en adelante), esto se debe a que este tipo de interrupciones están pensadas en la velocidaddel tratamiento, por esa misma razón suele colocarse la rutina de tratamiento justo después de laVector Table.

A continuación se detallan los pasos que se deben tomar para manejar una excepción.La forma que tiene el CPU para tratar las excepciones es mediante la vector table. La vector

table normalmente empieza10 en la dirección 0x00000000. El contenido de la vector table sueleser una única instrucción de salto a la subrutina que trata la excepción correspondiente (tambiéndepende de la arquitectura, normalmente ARM hace eso), la figura 2.11 muestra qué tipo de ex-cepción va a ser la que trate cada contenido de la vector table, hay que sumar (o restar) el offset ala dirección en la que empiece el vector.

Una vez explicado esto, podemos introducir los pasos que se toman y hay que tomar cuandoocurre una excepción.

Al ocurrir una excepción, se realizan estos pasos automáticamente

CPSR (Current Program Status Register) en el registro auxiliar SPSR (Saved Program Statusregister) del modo que va a tratar la excepción.

Se actualiza el registro CPSR para que refleje que estamos en otro modo distinto, entre otrascosas.

10Se puede configurar para que empiece en la 0xFFFFFFFF, para más información sobre el proceso, leer architecturalmanual sección B1.8.1 Exception vectors and the exception base address

Page 33: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.5. INTERRUPCIONES 33

Figura 2.11: Diagrama de la Vector Table

Se guarda la dirección en la que surgió la excepción en el registro LR del modo al quecambiamos.

Se actualiza PC para que vaya a la posición de la vector table que contiene la instrucción desalto para tratar la excepción (se hace PC = DIR INICIO VECTOR TABLE +- offset).

Lo anterior mencionado es lo que hace la CPU automáticamente, ahora mencionaremos lo quese tiene que hacer manualmente:

El programador debe guardar el estado del procesador en la pila del modo que está tratandola excepción.

Tratar la excepción.

Restaurar el estado del procesador.

Introducir la dirección de vuelta y restaurar CPSR. La dirección de vuelta requiere que sele aplique un ajuste, la figura 2.12 muestra qué ajustes hay que tomar (la instrucción conel condicional s y los registros pc y r14 (lr) permite restaurar CPSR a la vez que se hace elajuste).

La razón por la que a lo largo de esta sección se han comentado todas las características queposee la arquitectura para tratar todo tipo de excepciones es que esos mismos conceptos se aplican

Page 34: Desarrollo de un Sistema Operativo para Raspberry Pi 2

34 CAPÍTULO 2. DISEÑO

Figura 2.12: Ajustes para volver de una excepción

a las interrupciones. En la siguiente sección, crearemos algo con lo que poner a prueba los temastratados.

2.5.1. Local timer

En la sección anterior se ha detallado cómo diseñar la vector table y cómo personalizar ru-tinas de tratamiento. A continuación se detalla cómo aplicar esos conocimientos para tratar unainterrupción IRQ. En particular, controlaremos el Local timer, un reloj del sistema que nos servirápara crear eventos periódicamente, cosa que aprovecharemos para la posterior gestión de procesos.

Como bien hemos mencionado, el timer que usaremos se denomina local timer aunque a vecesse puede encontrar con el nombre de generic timer. Es un conjunto de relojes que tiene cada núcleode la CPU, hay 2 relojes físicos, uno virtual y un contador. Cada reloj se utiliza en modos quetengan los permisos11 adecuados:

Uno de los relojes físicos solo se puede usar en el modo PL2.

El otro se puede usar en los modos PL1 con seguridad y sin seguridad.

El reloj virtual solo es accesible desde el modo PL1 sin seguridad.

Debemos mencionar que estos timers reciben su valor desde lo que se denomina system coun-

ter, a efectos prácticos no nos interesa aún esta parte.

Configuración

Al igual que pasa con otros componentes como la UART, para configurar el local timer tene-mos una serie de registros mapeados en memoria además de una cierta funcionalidad dada a travésde la interfaz CP1512 del CPU.

11Recordamos, permisos PL0, PL1 o PL212Para conocer más acerca de esto, leer del architectural manual [4] la sección A2.9 Co-processor support

Page 35: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.5. INTERRUPCIONES 35

Figura 2.13: Ajustes para volver de una excepción

La dirección de memoria en la que se empieza a tratar todo lo relacionado con el local timer eslo que llamaremos dirección base en 0x40000000 estando desde el rango [dirección base + 0x40,dirección base + 0x4C] los registros de control para las interrupciones y en el rango de [dirección

base + 60, dirección base + 6c] los registros para conocer de dónde viene la interrupción.

Los registros que se pueden acceder gracias a la interfaz CP15 son los mostrados en la figura2.13, con estos registros se puede controlar cuándo salta la interrupción (CVAL), ver el valor delreloj (TVAL) y habilitar las interrupciones cuando TVAL sea igual al CVAL entre otras cosas(CTL)

Emplearemos el reloj virtual13. Los pasos para inicializar el local timer son bastante sencillos:

1. Accedes al correspondiente registro CTL para habilitar el timer.

2. Se introduce el valor deseado en el registro CVAL, ejemplo, si se quiere que haya unainterrupción cada segundo, se pone en CVAL el valor de la frecuencia del system counter

(usando CNTFRQ).

3. Se Habilitan las interrupciones en el correspondiente registro situado en dirección base +

offset.

Para que el trabajo de leer y escribir en los registro CNTx_TVAL, CNTx_CTL y CNTx_CVALsea más fácil, se ofrecen unas funciones en la librería local_timer.c.

13Sin virtualización, el contador virtual tiene los mismos valores que el físico

Page 36: Desarrollo de un Sistema Operativo para Raspberry Pi 2

36 CAPÍTULO 2. DISEÑO

2.6. Memoria

Para poder utilizar la memoria de una manera ordenada, necesitamos estructurarla de maneraque no haya carreras de datos al utilizar una zona de memoria ya en uso. Para ello, primero esnecesario saber la cantidad de memoria total del sistema, la cual obtendremos utilizando los atags.

2.6.1. Atags

Los atags son el método que tiene la Raspberry Pi 2 de pasar información sobre el hardware ala imagen del SO. Estos atags se calculan en el proceso de arranque, y son pasados al kernel comouna lista, la cual empieza en la posición de memoria 0x100, además de pasarse como parámetro alkernel usando el registro r2.

Dado que decidimos centrarnos más en el emulador que en el hardware, como se en el Capítulo4.2, no se desarrolló más el archivo atags.c, el cual contiene el código para devolver la cantidad to-tal de memoria disponible. Sin embargo, en atags.h, se observan todas las estructuras que manejanlos atags [16].

Cuando ejecutamos el código en el emulador, no hay un proceso de arranque, por lo quelos atags no contienen información. Por esto, devolvemos que la cantidad de memoria total son256 MB, parámetro que decidimos nosotros a la hora de iniciar el emulador en el Makefile. Esteparámetro se seleccionó así debido a que 256 MB nos pareció suficiente memoria para la extensiónactual del proyecto. Una memoria mayor provocaría un tiempo de inicialización mayor sin sernecesario.

2.6.2. Organización de la memoria

Una vez obtenida la cantidad total de memoria, necesitamos dividirla en páginas para podermanejarla de una manera sencilla. Hemos decidido utilizar páginas de 4 KiB, ya que son lo sufi-cientemente grandes para almacenar los datos que necesitamos, pero sin perder una gran cantidadde espacio al reservar una página. De esta manera obtenemos un total de 65536 páginas.

Esta organización de memoria está inspirada en el tutorial de Jake Sandler[17]. Teniendo lamemoria dividida en partes iguales, vemos tres secciones diferenciadas en nuestro espacio dememoria, como vemos en la figura 2.14:

Sección del kernel: esta sección consta de 280 páginas, y contiene:

• El propio código del Sistema Operativo: sabemos donde termina gracias a la varia-ble __end del archivo linker.ld, lo que nos ayuda a manejar la memoria sin temor asobreescribir el código.

• Los metadatos de las páginas: formateada como un array de metadatos. Estos meta-datos constan de:

◦ Vaddr_mapped para indicar la dirección de memoria virtual a la que pertenecela página. Esto se utilizará como soporte para trabajo futuro, ya que no hemosimplementado memoria virtual, como explicaremos en el Capítulo 4.2.

Page 37: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.7. GESTOR DE MEMORIA DINÁMICA 37

◦ Flags: los cuales son allocated (indica que la página está reservada), kernel_page(indica que la página pertenece a la sección del kernel) y kernel_heap_page (in-dica que la página pertenece a la sección del heap).

Sección del heap: consta de 256 páginas (1MiB), y será utilizada en memoria dinámica(sección 2.7).

Sección "libre": contiene el resto de páginas, un total de 65000, que no estarán reservadasy serán utilizadas en otros módulos, como el de procesos o el sistema de ficheros, utilizandopara ello los métodos alloc_page() y free_page(pointer) del archivo mem.h.

Figura 2.14: Estructura de la memoria.

Una vez que tenemos las secciones en las que vamos a dividir la memoria, queda inicializarla.En esta inicialización necesitamos:

1. Obtener cuántas páginas ocupará cada una de las secciones.

2. Recorrer el array de metadatos, poniendo los flags adecuados según la sección en la que seencuentre la página.

3. Añadir los metadatos (su dirección) de las páginas libres a una lista. Esta lista facilita-rá la implementación de los métodos alloc_page() y free_page(pointer). En el caso dealloc_page(), sacaremos una dirección de la lista, modificaremos el flag allocated, calcu-laremos la dirección de la página usando la dirección de los metadatos y la devolveremostras limpiarla (poner a cero la página entera). En el caso de free_page(pointer), se hará elproceso inverso, calculando la dirección de los metadatos utilizando la dirección de la pági-na, cambiando allocated y guardando la dirección calculada en la lista.

2.7. Gestor de memoria dinámica

Con la estructura de memoria que hemos explicado hasta ahora, la mínima cantidad de me-moria que podríamos reservar es una página, es decir 4 KiB. Esta cantidad de almacenamiento esdemasiado grande si solo queremos reservar, por ejemplo, una array de char de tamaño arbitra-rio. Para solucionar esto, necesitamos un mecanismo para obtener zonas de memoria del tamañodeseado. Este mecanismo es el gestor de memoria dinámica.

Page 38: Desarrollo de un Sistema Operativo para Raspberry Pi 2

38 CAPÍTULO 2. DISEÑO

Para implementarlo, necesitamos una zona de memoria de la que poder obtener segmentosdel tamaño requerido, e información de control para saber qué partes están siendo utilizadas. Lazona de memoria que utilizaremos es la sección del Heap del punto anterior, considerando las256 páginas de esta un único gran bloque de memoria, el cual se utilizará como una doble listaenlazada. Por lo tanto, la información de control que utilizaremos por cada segmento de memoriaes la siguiente:

Next: un puntero al siguiente segmento de memoria. Será NULL si el segmento es el últimode la lista.

Prev: un puntero al segmento de memoria anterior. Será NULL si el segmento es el primerode la lista.

Is_allocated: indica si el segmento está siendo utilizado o no.

Segment_size: el tamaño del segmento, sin tener en cuenta el tamaño de la información decontrol asociado a él.

Teniendo lo anterior, ya solo nos queda inicializar la lista y crear métodos para obtener seg-mentos de memoria y liberarlos.

2.7.1. Heap_init(), kmalloc() y kfree()

Todas estas funciones están implementadas en el archivo berryOS/src/mem.c. Para inicializarel heap, lo que hacemos es crear un único bloque libre de aproximadamente 1 MiB (hay querestarle el tamaño de la información de control, 16 bytes), como se observa en la figura 2.15a.Esto es lo que realiza la función heap_init().

La función kmalloc(tamaño) se encarga de devolver un puntero a una zona de memoria deltamaño específico. Para ello, busca el segmento más cercano al tamaño requerido en la lista y lodevuelve. En caso de que este tamaño sea demasiado grande (el criterio que se utiliza es que elespacio sobrante sea mayor que dos veces el tamaño de la información de control), se divide elbloque en dos partes, uno del tamaño requerido, y otro con el espacio sobrante. Este mecanismose observa en las figuras 2.15a y 2.15b.

Finalmente, la función kfree(puntero), se encarga de liberar el puntero de memoria. Para ello,se pone el valor is_allocated a 0. Además, para desfragmentar la memoria, se ha incorporadoun mecanismo que se encarga de absorber a los segmentos colindantes no utilizados, creando unbloque más grande, hasta encontrar un bloque utilizado o el inicio o el final de la lista. Un ejemplode este mecanismo serían las figuras 2.15c y 2.15d

Page 39: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.7. GESTOR DE MEMORIA DINÁMICA 39

(a) Memoria tras heap_init()

(b) Tras realizar un kmalloc(10)

(c) Tras una serie de operaciones.

(d) Tras un kfree(pointer), siendo pointer el puntero al bloque de tamaño 50

Figura 2.15: Ejemplo de funcionamiento de heap_init(), kmalloc() y kfree()

Page 40: Desarrollo de un Sistema Operativo para Raspberry Pi 2

40 CAPÍTULO 2. DISEÑO

2.8. Procesos

Una vez que tenemos implementados el gestor de memoria principal y el timer, tenemos lonecesario para poder implementar procesos del kernel.

Para implementar los procesos, es necesario que definamos ciertos componentes principalesdel módulo. El sistema operativo será el encargado de gestionar los procesos por lo que necesitauna estructura para identificarlos, esa estructura es la PCB. La estructura de la PCB se encuentraen berryOS/include/proc/pcb.h, se denomina process_control_block_t y consta de los siguientescampos:

stack_pointer_to_saved_state: Este campo almacenará el valor del registro sp stack pointer,este registro siempre está apuntando a la cima de pila, y la pila que usamos es de tipo full

descending14.

stack_page: El inicio de la pagina que utilizaremos como pila de proceso. Nos servirá paraimplementar control sobre la cantidad de información que se introduce en la pila.

pid: el identificador del proceso.

DEFINE_LINK(pcb): Esto es una directiva de la librería berryOS/include/utils/list.h. Conesta librería podemos crear listas de datos genéricas (independientes del tipo de datos quese le introduce). La funcionalidad de la directiva es crear punteros al siguiente y al anteriornodo denominados nextpcb y prevpcb respectivamente. Esa decisión de diseño viene dadaporque crearemos una lista denominada run_queue que tendrá todas las PCBs asociadas alos procesos que esperan para poder ejecutarse.

proc_name: Donde almacenaremos el nombre del proceso.

Merece la pena hacer notar que, usamos el término "procesos en ejecución", por lo que en estemódulo también implementaremos un planificador básico, el Round-Robin, y daremos la posibili-dad de incluir el resto de planificadores académicos como el First Come First Serve, First In First

Out e incluso la posibilidad de implementar uno propio al que denominamos OTHER.

Ahora hablaremos sobre la inicialización de la parte de procesos. Esta inicialización se realizaen la función process_init() la cual hará lo siguiente:

1. Inicializamos la run_queue con la macro INITIALIZE_LIST(lista, tipo).

2. Crearemos el proceso Init. Para ello, hay que inicializar su información de control.

Reservaremos espacio para el nombre, un PID y la página de memoria en la que situa-remos su pila.

14La pila va de direcciones mayores a direcciones menores

Page 41: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.8. PROCESOS 41

Basándonos en la dirección de la página reservada, calcularemos la posición inicialdel campo stack_pointer_saved_state como el final de la página menos el tamaño de laestructura proc_saved_state_t. Esta estructura, que contiene cada uno de los registros,se utilizará para inicializar el proceso cuando entre en ejecución por primera vez.

Inicializaremos los valores de la estructura anterior. Los valores de r0-r12 los inicia-lizaremos con basura, dado que el proceso no los utilizará. El valor de pc será la di-rección de init_function(), que contiene el código de Init. El de lr será la direcciónreap(), que se encargará de borrar el proceso cuando termine su ejecución (sección2.8.4). Finalmente, en el cpsr pondremos el valor que indica que el modo de ejecuciónes supervisor.

3. Añadimos el proceso Init a la run_queue.

4. Elegiremos como scheduler principal el que tenemos asignado por defecto, basado en Round-Robin.

2.8.1. Secuencia de ejecución de llamada al scheduler

El timer que creamos como ejemplo de uso de las interrupciones en la sección 2.5.1 lo em-plearemos para crear el planificador de nuestro módulo de procesos. Este timer tendrá un cuantodefinido con una directiva denominada QUANTUM (reside dentro del archivo berryOS/include/-

local_timer.h).La secuencia es la siguiente:

1. Cuando salta el timer, se genera una interrupción de tipo IRQ, esa interrupción será tratadapor una rutina personalizada (tal y como se menciona en la sección 2.5 de interrupciones).Esa rutina reside dentro del archivo berryOS/arch/ARMv7/interrupt_init.S y se denominairq_s_handler.

2. Al tratar la interrupción comprobaremos quién la ha generado, en ese caso es el schedulerpor lo que llamaremos a su correspondiente rutina de tratamiento, esta rutina se denominaschedule y reside dentro de berryOS/src/proc/pcb.c. Lo que se hará en esta rutina es com-probar qué scheduler queremos utilizar y aplicar su algoritmo correspondiente.

3. Una vez se está aplicando la labor de planificación, se realizará el cambio de contexto paraque el siguiente proceso pueda proceder a su ejecución. El cambio de contexto diseñado eneste TFG es un proceso complejo y el cual se explicará en la sección 2.8.2.

4. Al acabar de realizar el cambio de contexto, retornamos a la rutina de tratamiento de lainterrupción del apartado 1 y damos paso a que empiece a ejecutar el proceso actual por ellugar en donde se quedó la última vez que ejecutó o que empiece por primera vez.

2.8.2. Cambio de contexto

La esencia del cambio de contexto es que podamos guardar el estado de un proceso en memoriay cargar el estado del proceso que pasa a ejecutar de la memoria, esto lo hacemos gracias al

Page 42: Desarrollo de un Sistema Operativo para Raspberry Pi 2

42 CAPÍTULO 2. DISEÑO

planificador que hemos implementado. Puesto que no tenemos a nuestra disposición la memoriavirtual, tuvimos que implementar una versión que fuese compatible sin ella. El código de cómose realiza el cambio de contexto viene dado en el archivo berryOS/src/proc/context.S, todo lo queharemos, tendrá que ser escrito en ensamblador.

Dentro del archivo mencionado podemos encontrar tres subrutinas clave: yield_to_next_process,load_process y switch_process_context. Sin entrar en mucho detalle, cada una de ellas tiene unpapel clave, la primera sirve para cargar el siguiente proceso una vez el actual ha terminado deejecutar definitivamente (proceso explicado en la sección 2.8.4), la siguiente sirve para cargar elprimer proceso de todos, Init, y la última para cambiar de un proceso en ejecución por el siguiente.

Las tres subrutinas siguen un mismo patrón, por lo que vamos a hablar primero de cómo car-gamos un hipotético proceso de memoria. Recomendamos encarecidamente que se tenga delantela subrutina switch_process_context, lo que vamos a explicar tiene lugar en la segunda mitad delcódigo de esa subrutina.

Cargar de memoria y guardar en memoria son procesos, en sí mismos, sencillos no obstantevamos a documentar esta gestión por pasos ya que es fácil perderse. Hemos decidido que cuandoun proceso se le quita de la CPU, todo su estado será guardado en su pila de ejecución, esa pilala podemos encontrar con el nombre de stack_pointer_to_saved_state dentro de su estructura dePCB. Como bien sabemos, las pilas son full descending lo que significa que la cabeza de la pilaestá apuntando al primer elemento del estado del proceso y el resto viene después, la estructura15

es la siguiente:

1 t y p e d e f s t r u c t {2 u i n t 3 2 _ t c p s r ; / / ( Saved P r o c e s s S t a t e R e g i s t e r )3 u i n t 3 2 _ t ∗ l r ; / / p o i n t e r t o r e t u r n a d d r e s s4 u i n t 3 2 _ t ∗ pc ;5 u i n t 3 2 _ t r12 ;6 u i n t 3 2 _ t r11 ;7 u i n t 3 2 _ t r10 ;8 u i n t 3 2 _ t r0 ;9 u i n t 3 2 _ t r1 ;

10 u i n t 3 2 _ t r2 ;11 u i n t 3 2 _ t r3 ;12 u i n t 3 2 _ t r4 ;13 u i n t 3 2 _ t r5 ;14 u i n t 3 2 _ t r6 ;15 u i n t 3 2 _ t r7 ;16 u i n t 3 2 _ t r8 ;17 u i n t 3 2 _ t r9 ;18 } p r o c _ s a v e d _ s t a t e _ t ;

Después de r9 están datos importantes que no queremos corromper, por lo que lo único querealizaremos es una extracción ordenada de los registros y procederemos a guardarlos en posicio-nes adecuadas de la pila de interrupciones, ahora mismo no importa en cuáles, luego se explicarácon más detalle.

15Esta estructura se puede encontrar en berryOS/include/proc/pcb.h

Page 43: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.8. PROCESOS 43

1. Guardaremos el registro cpsr en el spsr, recordamos que durante la ejecución de la subruti-na, seguimos en el estado IRQ, de esta lo que garantizamos es una recarga automática delregistro cpsr correcto

2. Después guardamos el registro lr en una variable global que hemos denominado __pro-

cess_lr y la cual emplearemos en el tramo final de la rutina irq_s_handler

3. Ahora procederemos a obtener los registro pc, r12, r11, y r10 y guardarlos, como ya hemosmencionado, en posiciones específicas de la pila de interrupciones

4. El paso final es obtener el resto de registros y realizar el mismo proceso que en el puntoanterior

Una vez hecho esto, marcaremos un flag denominado __scheduler_finished y guardaremos elvalor actual de stack_pointer_to_saved_state en una variable global llamada __process_sp para suposterior uso al final de la subrutina en ensamblador de tratamiento de interrupciones.

Lo que se hará para cargar el proceso consiste en el siguiente fragmento de código sacado dela subrutina irq_s_handler.

1 b l i r q _ c _ h a n d l e r2 pop { r0−r12 , l r }3

4 push { r0 }5 l d r r0 , = _ _ s c h e d u l e r _ f i n i s h e d6 l d r r0 , [ r0 ]7 cmp r0 , #0 x1 / / Have t h e s c h e d u l e r i n t e r r u p t been t r e a t e d ?8 bne n o r m a l _ i r q _ e x e c u t i o n9 pop { r0 } / / we r e c o v e r t h e c o r r e c t v a l u e o f r0

10 push { r0 , l r } / / we s t o r e r0 wi th l r i n t o i r q s t a c k11 l d r r0 , = _ _ p r o c e s s _ l r12 /∗ we l o a d where we w i l l r e t u r n from t h e f u n c t i o n13 where t h e p r o c e s s was i n t e r r u p t e d ∗ /14 l d r r0 , [ r0 ]15 push { r0 } / / we make s u r e l r c o n t a i n s a p o i n t e r t o t h a t r e g i o n o f memory16

17 l d r r0 , =__stack_memory18 s t r sp , [ r0 ]19 mov sp , #IRQ_STACK20

21 mrs r0 , s p s r22 msr c p s r _ c x s f , r0 / / we change c p s r t o t h e c o r r e s p o n d i n g v a l u e o f c p s r

o f t h e new p r o c e s s23 /∗ we l o a d t h e c o r r e c t v a l u e o f r0 and we r e t u r n t o where our new24 p r o c e s s were i n t e r r u p t e d ∗ /25 / / we change e x e c u t i o n mode so does t h e s t a c k and l r26 l d r r0 , = _ _ p r o c e s s _ s p / / we u p d a t e sp v a l u e27 l d r sp , [ r0 ]28 l d r r0 , =__stack_memory29 l d r r0 , [ r0 ]30 ldmfd r0 ! , { l r }31 ldmfd r0 , { r0 , pc }32

33 n o r m a l _ i r q _ e x e c u t i o n :34 pop { r0 }35 / / r f e i a sp ! / / we do t h e i n v e r s e o p e r a t i o n o f s r s d b

Page 44: Desarrollo de un Sistema Operativo para Raspberry Pi 2

44 CAPÍTULO 2. DISEÑO

36 subs pc , l r , #4

La finalidad de todo este código es obtener en la pila un resultado como el de la figura 2.16,y así poder introducir esos valores en los registros correspondientes. La variable __stack_memory

nos sirve para saber dónde empiezan esas zonas de memoria especiales de la pila irq que mencio-namos antes, para cuando el código llega a esta parte, esa variable ya ha cumplido su finalidad,por lo que la emplearemos como variable temporal para no perder la dirección de memoria queapunta a la figura 2.16.

Como podemos ver al final del código, recuperaremos la pila del proceso que va a entrargracias a __proces_sp y recuperaremos el resto de registros gracias a la variable mencionada en elpárrafo anterior.

Figura 2.16: Pila irq al final de la subrutina irq_s_handler

Ahora explicaremos el proceso contrario, cómo guardar en la pila del proceso su estado. Re-comendamos encarecidamente que se tenga delante el código de la primera mitad de la subrutinaswitch_process_context.

Antes de explicar nada, introduciremos el que será el paso fundamental de todo este proceso.

1 i r q _ s _ h a n d l e r :2 /∗3 I t i s n e c e s s a r y t o s w i t c h t o s u p e r v i s o r mode and s t o r e some r e g i s t e r s4 i n t o i t ’ s s t a c k f o r ha v in g s u p p o r t f o r n e s t e d e x c e p t i o n s5 ∗ /6 push { r0−r12 , l r }7 l d r r12 , =__stack_memory8 s t r sp , [ r12 ]9 l d r r0 , = _ _ s c h e d u l e r _ f i n i s h e d

10 mov r12 , #0 x011 s t r r12 , [ r0 ]12 b l i r q _ c _ h a n d l e r

Ese paso fundamenta está en este código, consiste en guardar en la variable global __stack_memory

Page 45: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.8. PROCESOS 45

la dirección a la que apunta el registro sp de interrupciones una vez guardado el estado del procesoque está ejecutando actualmente para poder tratar la interrupción. Como se puede notar, este pasoes fundamental ya que gran parte del trabajo para obtener el estado del proceso en ejecución sehace por defecto al tratar cualquier tipo de interrupción.

Ahora es explicaremos qué hacemos en la primera mitad de la subrutina switch_process_context.

1. Obtenemos el valor de stack_pointer_to_saved_state de el parámetro adecuado de la fun-ción.

2. Una vez obtenido ese valor, lo que necesitamos hacer es recuperar el registro sp del proceso.Recordamos que al cambiar a el modo irq, se cambian ciertos registros, entre ellos el sp,por lo que necesitaremos volver al modo de ejecución en el que está actualmente el procesopara obtener dicho registro. También necesitaremos recuperar el registro lr del proceso yaque también le ocurre lo mismo que al registro sp.

3. Ahora obtenemos en orden el valor del estado del procesador que ha sido guardado en lapila de interrupciones y al cual podemos acceder gracias a la variable __stack_memory.

4. El orden en el que se extrae y se guardan los registros es el siguiente: r0-r9 primero y luegor10, r11, r12, lr16 y cpsr. Estos registros serán guardados cumpliendo la estructura descritaen el código introducido al principio de esta sección.

Una vez explicado esto, el resto de subrutinas siguen los mismos patrones por lo que ya pode-mos pasar a hablar de cómo crear un proceso nuevo.

2.8.3. Crear un nuevo proceso

Para crear un proceso nuevo, emplearemos la subrutina siguiente subrutina

1 void c r e a t e _ k e r n e l _ t h r e a d ( k t h r e a d _ f u n c t i o n _ f t h r e a d _ f u n c , char ∗ name , i n tname_s ize ) ;

Los pasos para crear un nuevo proceso son los mismos que para crear el proceso Init, pero condos diferencias. La primera es que el nombre del proceso nos lo pasarán por parámetro, por lo quetendremos que comprobar que no supere la máxima longitud de nombres. La segunda es que enpc guardaremos el puntero a la función que nos pasan por parámetro, que contendrá el código aejecutar por el proceso.

2.8.4. Finalizar un proceso: reap()

Recordemos que los procesos se realizan en una subrutina. Debido a esto, cuando finalice lasubrutina, se sobrescribirá el valor de pc con lr. Gracias a esto, y a que al inicializar el proce-so, guardamos en lr el puntero a la función reap(), cuando finalice la ejecución del proceso, seejecutará reap().

16Este registro no es el lr del proceso, es el lr que se guardó en la pila al iniciar la rutina de tratamiento de interrupción,por consiguiente, es el lugar en el que se ha interrumpido al proceso, su pc

Page 46: Desarrollo de un Sistema Operativo para Raspberry Pi 2

46 CAPÍTULO 2. DISEÑO

Por tanto, reap() contendrá el código para eliminar un proceso finalizado. Para ello tendremosque:

1. Sacar de la run_queue el siguiente proceso a ejecutar.

2. Liberar la página que estaba usando el proceso.

3. Liberar la estructura con la información de control del proceso.

4. Cambiar al contexto del nuevo proceso con la función yield_to_next_process()

2.8.5. Interfaz para registrar nuevos scheduler

La interfaz para registrar planificadores es muy sencilla, primero necesitaremos unas estructu-ras de control y luego subrutinas que comprueben el estado de esas estructuras.

1 t y p e d e f enum {2 FIFO = 0 ,3 RR = 1 ,4 FCFS = 2 ,5 OTHER = 36 } s c h e d _ t y p e _ t ;7

8 t y p e d e f s t r u c t {9 k s c h e d u l i n g _ f u n c t i o n _ f s c h e d _ f u n c t i o n ;

10 char r e g i s t e r e d ;11 } s c h e d _ f _ c o n t r o l _ t ;12

13 t y p e d e f s t r u c t {14 k s c h e d u l i n g _ f u n c t i o n _ f b y _ d e f a u l t ;15 s c h e d _ f _ c o n t r o l _ t s c h e d u l e r s [MAX_SCHEDULERS ] ;16 s c h e d _ t y p e _ t us ing ;17 } s c h e d _ c o n t r o l _ t ;

sched_control_t contendrá los planificadores que podemos emplear (máximo 4), qué planifi-cador estamos empleando (variable using) y para cada planificador, su algoritmo asociado y si estáregistrado o no.

Para registrar un planificador lo único que tendremos que hacer es llamar a la subrutina

1 i n t r e g i s t e r _ s c h e d u l e r _ p o l i c y ( k s c h e d u l i n g _ f u n c t i o n _ f sched_func , s c h e d _ t y p e _ tp o l i c y _ t y p e ) ;

con el algoritmo que queremos que implemente y el tipo de planificador que será, esta subrutinanos devolverá si la operación se ha podido realizar (0) o si ha habido algún error (-1).

También podemos borrar un planificador usando la subrutina

1 void u n r e g i s t e r _ s c h e d u l e r _ p o l i c y ( s c h e d _ t y p e _ t p o l i c y _ t y p e ) ;

la cual siempre tendrá efecto y podemos cambiar el planificador que vamos a utilizar con la subru-tina

1 i n t c h a n g e _ s c h e d u l i n g _ p o l i c y ( s c h e d _ t y p e _ t p o l i c y _ t y p e ) ;

Page 47: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.9. INTERFAZ GRÁFICA 47

la cual nos devolverá si se ha podido cambiar (0) o si no ha sido posible el cambio (-1).

2.8.6. Funciones de impresión

Estas funciones fueron creadas con motivos de depuración, o para ser utilizadas en la creaciónde comandos.

print_processes()

Imprime por la UART el contenido de la run_queue, además del número de elementos con-tenidos. La implementación consiste básicamente en imprimir el tamaño de la lista, y de recorreresta imprimiendo el nombre del proceso y su PID.

console_print_processes()

Esta función tiene la misma función que la de arriba, con la diferencia de que la impresiónla realiza por consola en vez de por la UART. Además, también imprime el nombre y PID delproceso que se encuentra actualmente en ejecución, cuya referencia se encuentra almacenada enla variable global current_process. Para más información relacionada con la consola y la interfazde usuario, ir a la sección 2.9.

Esta función se utilizará en ps_function(), el cual es el método trigger del comando ps descritoen el capítulo 3.5.2. El proceso a seguir para registrar comandos será explicado en la sección 2.9.3.

2.9. Interfaz gráfica

Una vez que se ha implementado el módulo de GPU explicado en la sección 2.4.3 se puedeimplementar una interfaz gráfica. En ocasiones, se infravalora este apartado, al menos cuando setrata de implementarlo a bajo nivel, pero sin duda es un tema interesante que rasca varias ramasde la ingeniería, entre ellas estructuras de datos, algoritmos eficientes y una alta relación con lasmatemáticas más puras.

2.9.1. Sistema de vistas

Diseño

El sistema está basado en el diseño de vistas que maneja Android [3] con la estructura declases como la de la figura 2.17. Todo el entramado se genera a partir de dos abstracciones:

1. Una vista o view[2] es una clase abstracta java que tiene la jurisdicción de un conjuntorectangular de píxeles.

2. Un grupo de vistas o viewgroup [1] es también una clase abstracta que extiende de vistay tiene un atributo lista de hijos o children que pueden ser de tipo vista o grupo de vistas.Igual que la vistas, tienen el control de un grupo de píxeles pero que en este caso reparteentre todos sus hijos, esto se conoce como disposición o layout.

Page 48: Desarrollo de un Sistema Operativo para Raspberry Pi 2

48 CAPÍTULO 2. DISEÑO

Figura 2.17: Jerarquía de clases del sistema de vistas en Android.

Implementación

C no es un lenguaje de programación orientada a objetos, por lo que las técnicas de herencia ypolimorfismo que tiene java no están disponibles. Aún siendo un reto complejo, se ha conseguidoun sistema semejante con el uso de punteros genéricos (void *) para conseguir el polimorfismo asícomo la abstracción y el paso de funciones por parámetros, típico de la programación declarativa,para la herencia.

Vista

1 t y p e d e f s t r u c t VIEW {2 i n t wid th ;3 i n t h e i g h t ;4 i n t x ;5 i n t y ;6 i n t f o n t S i z e ;7 c o l o r _ 2 4 bgColor ;8 c o l o r _ 2 4 t e x t C o l o r ;9

10 char∗ t e x t ;11 TEXT_ALIGN t e x t A l i g n ;12 i n t t e x t L i n e s ;13 i n t t e x t O v e r f l o w ;14 i n t padd ing ;15 } VIEW;

Código 2.16: Representación de una vista o view.

Como se puede ver en el struct de una vista, tiene varias funcionalidades extra, pero la base,como ya se explicó en el diseño, se centra en la gestión de los píxeles designados. Su área estádefinida por el rectángulo formado por el valor de la altura y anchura en la posición indicada porlos atributos x e y.

1 void draw (VIEW∗ v ) {2 i n t wid th = v−>x + v−>wid th ;

Page 49: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.9. INTERFAZ GRÁFICA 49

3 i n t h e i g h t = v−>y + v−>h e i g h t ;4

5 f o r ( i n t i = v−>x ; i < wid th ; i ++) {6 f o r ( i n t j = v−>y ; j < h e i g h t ; j ++) {7 w r i t e _ p i x e l ( i , j , &v−>bgColor ) ;8 }9 }

Código 2.17: Renderización de una vista.

En el fragmento del código 2.17 se muestra cómo una vista renderiza sus píxeles asignando elcolor que tiene de fondo, para ello se hace uso del módulo de la GPU.

Grupo de vistas

1 t y p e d e f enum v t y p e {2 TYPE_VIEW ,3 TYPE_VIEW_GROUP4 } v t y p e ;5

6 t y p e d e f s t r u c t VIEW_OR_GROUP {7 void ∗ c h i l d ;8 v t y p e t y p e ;9 } VIEW_OR_GROUP;

10

11 t y p e d e f s t r u c t VIEW_GROUP {12 VIEW view ;13 VIEW_OR_GROUP_list_t c h i l d r e n ;14 void (∗ l a y o u t ) ( void ∗ ) ;15 i n t d i r t y ;16 } VIEW_GROUP;

Código 2.18: Definición de grupo de vistas.

Para los grupos de vistas se presentan los retos ya comentados, conseguir la herencia, el poli-morfismo y la abstracción.

Con respecto al polimorfismo, se usa un estructurado con un puntero genérico (void *) acom-pañado de un enumerado que indica de qué clase se trata (línea 1-9 y 13 de 2.18).

Un grupo de vistas extiende de una vista, para conseguir esto, se encapsula una vista dentro dela definición (línea 12 del código 2.18).

Para conseguir la técnica de la abstracción, es decir, varios tipos de grupos de vistas que tengandistinta forma de distribuir las vistas, se crea un atributo variable función (línea 14 de 2.18) queviene a ser un método de una clase abstracta que cabe implementar17.

1 void l a y o u t G r o u p (VIEW_GROUP∗ vg ) {2 vg−>l a y o u t ( vg ) ;3 VIEW_OR_GROUP∗ node = s ta r t_ i t e ra t e_VIEW_OR_GROUP_l i s t (&vg−> c h i l d r e n ) ;4 whi le ( has_next_VIEW_OR_GROUP_list (&vg−>c h i l d r e n , node ) ) {5 node = next_VIEW_OR_GROUP_list ( node ) ;6 i f ( node−>t y p e == TYPE_VIEW_GROUP) {7 l a y o u t G r o u p ( node−> c h i l d ) ;

17Se ha creado un módulo con las implementaciones más famosas de Android en /src/ui/layouts.c, https://github.com/dacya/tfg1920-raspiOS/blob/master/berryOS/src/ui/layouts.c

Page 50: Desarrollo de un Sistema Operativo para Raspberry Pi 2

50 CAPÍTULO 2. DISEÑO

8 }9 }

10 }

Código 2.19: Cálculo de la distribución de píxeles.

La característica de un grupo de vistas, es que gestiona el conjunto de píxeles que tiene asig-nado entre sus hijos. La forma de distribución la describe el método layout que debe asignar eltamaño y la posición del área que le toca a cada uno (línea 2 de 2.19). Una vez calculado, se debellamar a todos sus hijos a que hagan lo mismo si también son grupos de vistas, de esta forma seconsigue generar un árbol de vistas (línea 4 de 2.19).

1 void drawGroup (VIEW_GROUP∗ vg ) {2 draw(&vg−>view ) ;3 i f ( vg−> d i r t y )4 vg−>l a y o u t ( vg ) ;5 VIEW_OR_GROUP∗ node = s ta r t_ i t e ra t e_VIEW_OR_GROUP_l i s t (&vg−> c h i l d r e n ) ;6 whi le ( has_next_VIEW_OR_GROUP_list (&vg−>c h i l d r e n , node ) ) {7 node = next_VIEW_OR_GROUP_list ( node ) ;8 i f ( node−>t y p e == TYPE_VIEW_GROUP) {9 a d j u s t G r o u p R e l a t i v e ( vg , node−> c h i l d ) ;

10 drawGroup ( node−> c h i l d ) ;11 } e l s e i f ( node−>t y p e == TYPE_VIEW) {12 a d j u s t G r o u p R e l a t i v e ( vg , node−> c h i l d ) ;13 draw ( node−> c h i l d ) ;14 }15 }16 vg−> d i r t y = 0 ;17 }

Código 2.20: Dibujar un grupo de vistas.

Dibujar o renderizar un grupo de vistas se trata de una función recursiva, primero se dibujaa sí mismo y luego realiza la llamada pertinente a cada uno de sus hijos dependiendo de su tipo(fragmento 2.20).

Es interesante la importancia del orden de llamadas, en el ejemplo explicado se dibuja primeroel padre y posteriormente los hijos en orden FIFO, es decir, el padre queda en el fondo y encimaestán dibujados los hijos. Este orden podría ser distinto y por tanto obtener resultados visualesdistintos, a esto se le suele conocer como índice z o z-index.

Se ha añadido el flag dirty para gestionar cuando no se requiere redibujar al completo lajerarquía y así se gana eficiencia podando el árbol. Éste se marca en las funciones de edición delgrupo (cambios de color, layout o hijos) y una vez realizado el redibujado se desmarca.

Ejemplo de uso

1 VIEW_GROUP conso leView ;2 VIEW l i n e S e p a r a t o r ;3 VIEW t e x t I n p u t ;4 VIEW_GROUP d i s p l a y ;5

6 VIEW_GROUP s t a t u s B a r V i e w ;7 VIEW brand ;

Page 51: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.9. INTERFAZ GRÁFICA 51

8 VIEW t ime ;9 VIEW v e r s i o n ;

Código 2.21: Definición de vistas para la consola.

Al modelar una interfaz, lo primero es instanciar las vistas de forma estática ya que es im-portante que se mantengan en memoria durante todo el ciclo de vida por las llamadas de redibujo

(fragmento 2.21).

1 addView(& s t a t u s B a r V i e w , &brand ) ; / / nombre d e l SO2 addView(& s t a t u s B a r V i e w , &t ime ) ; / / r e l o j3 addView(& s t a t u s B a r V i e w , &v e r s i o n ) ; / / v e r s i o n4

5 addViewGroup(& consoleView , &d i s p l a y ) ; / / fondo negro6 addView(& consoleView , &l i n e S e p a r a t o r ) ; / / l i n e a b l a n c a s e p a r a d o r a7 addView(& consoleView , &t e x t I n p u t ) ; / / i n p u t

Código 2.22: Creación del árbol de vistas del sistema operativo.

Una vez configuradas todas las vistas, se crea el árbol añadiendo una dentro de otra como semuestra en el fragmento de código abstracto 2.22. Para cada elemento añadido se vuelve a calculary dibujar la disposición. El resultado final se puede visualizar en la figura 2.18.

Figura 2.18: Consola diseñada con el sistema de vistas.

2.9.2. Consola

La consola es un módulo que tiene un proceso propio que se crea al inicializarse (fragmento2.23), su función básica (rutina 2.24) es analizar la entrada de datos, ejecutar comandos y retrans-mitir la entrada a través de la librería stdio.h explicada en la subsección 2.4.4 de Entrada / Salida.

1 void s t a r t _ c o n s o l e ( ) {2

Page 52: Desarrollo de un Sistema Operativo para Raspberry Pi 2

52 CAPÍTULO 2. DISEÑO

3 [ . . . ]4

5 c r e a t e _ k e r n e l _ t h r e a d ( r e a d _ p r o c , "uart_console_input" , 19 ) ;6 }

Código 2.23: Lanzamiento del proceso al inicializar la consola

1 void r e a d _ p r o c ( void ) {2 char∗ comm = kmal loc ( MAX_CONSOLE_LINE_INPUT_SIZE + 2) ;3 char c ;4

5 do {6 c = r e a d C h a r ( ) ; / / b l o q u e a n t e7 sw i t ch ( c ) {8 case 127 : / / de l , b o r r a r c h a r9 [ . . . ]

10 case ’\n’ : / / f i n de l i n e a11 case ’\r’ :12 [ . . . ] / / l o g i c a de comandos13 d e f a u l t : / / i n p u t14 [ . . . ]15 p r i n t ( c ) ; / / s t d i o . h16 }17 } whi le ( 1 ) ;18 }

Código 2.24: Rutina del proceso de la consola

La parte gráfica de este módulo sigue la estructura explicada en 2.18. Para añadir una linea detexto o una cadena se implementan las funciones siguientes:

En la raíz del entramado de la interfaz se encuentra un grupo de vistas que contendrá gruposde vistas que representan lineas, a su vez, a esas lineas se le añadirán vistas con las cadenas detextos.

1 void c o n s o l e _ p u t S t r ( char∗ s t r , c o l o r _ 2 4 ∗ t e x t C o l o r , c o l o r _ 2 4 ∗ bgColor ) ;2 void c o n s o l e _ p u t L n ( char∗ s t r , c o l o r _ 2 4 ∗ t e x t C o l o r , c o l o r _ 2 4 ∗ bgColor ) ;

En el fragmento anterior, la primera rutina añade una vista a la última línea añadida, si sedetecta desbordamiento realiza una llamada a la segunda función. Respecto al método para agregaruna línea nueva, se añade un grupo de vistas nuevo, la línea, y dentro se crea una vista nueva con lacadena de texto, si se detecta desbordamiento realiza una llamada recursiva con la parte del textosobrante. Adicionalmente, las dos funciones reciben por parámetro el color del texto y del fondo.Se puede visualizar la estructura que sigue en la figura siguiente:

Page 53: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.9. INTERFAZ GRÁFICA 53

Figura 2.19: Esquema del árbol de vistas de la interfaz de la consola.

2.9.3. Comandos

Un comando está compuesto por la palabra clave, una breve descripción y el disparador conparámetros como una función main convencional en C.

1 t y p e d e f s t r u c t COMMAND {2 char∗ key ;3 char∗ h e l p T e x t ;4 void (∗ t r i g g e r ) ( i n t argc , char∗∗ a rgv ) ;5 } COMMAND

El módulo contiene una lista dinámica con todos los comandos registrados. Se pueden añadiry eliminar a través de las funciones siguientes:

1 void regcomm (COMMAND∗ command ) ;2 void unregcomm (COMMAND∗ command ) ;

Matching

Cuando la consola detecta una línea nueva, como se explica en el apartado 2.9.2, la trocea porpalabras y las envía a éste módulo de comandos para que encuentre uno con la primera palabracomo clave y las restantes se interpretan como parámetros.

La detección del comando se realiza recorriendo toda la lista comparando las palabras claves,sin duda, esta búsqueda se puede mejorar implementando un diccionario. Para cada comandocoincidente se llamará a su disparador con la lista de parámetros (código 2.25).

Page 54: Desarrollo de un Sistema Operativo para Raspberry Pi 2

54 CAPÍTULO 2. DISEÑO

1 void commatch ( char∗ match , i n t argc , char∗∗ a rgv ) {2 i n t n o t _ f o u n d = 1 ; / / f l a g3 comm_wrapper∗ commw = s t a r t _ i t e r a t e _ c o m m _ w r a p p e r _ l i s t (& commands_ l i s t ) ;4

5 whi le ( h a s _ n e x t _ c o m m _ w r a p p e r _ l i s t (& commands_ l i s t , commw) ) {6 commw = n e x t _ c o m m _ w r a p p e r _ l i s t (commw) ;7 i f ( s t r e q ( match , commw−>comm−>key ) ) { / / comparac ion de c l a v e s8 commw−>comm−> t r i g g e r ( a rgc , a rgv ) ; / / l l a m a d a a l d i s p a r a d o r9 n o t _ f o u n d = 0 ;

10 }11 }12

13 i f ( n o t _ f o u n d )14 e n r i c h e d P r i n t L n ("command not found" , &RED, NULL) ;15 }

Código 2.25: Rutina de lanzamiento de comandos

Problema de inicialización del módulo

El orden de inicialización de módulos por el kernel influye drásticamente en los comandos. Sepuede dar el caso en el que un módulo busque crear un comando y que el gestor de éstos aún noesté disponible.

Una primera aproximación sería ponerlo a la cabeza, pero al requerir de memoria dinámica noes posible por lo que se forzaría a que este módulo no pudiese tener comandos.

Por tanto, la solución alternativa ocurrida, es instanciar de forma estática un array de coman-dos y que al llamar a la rutina de inicialización el módulo copie todos los comandos en la listadinámica original. De esta forma, la función de registro de comandos debe conocer cuando estáinstanciado el módulo y realizar las operaciones necesarias en cada situación. Se puede ver partede la implementación en el siguiente fragmento de código:

1 COMMAND∗ d e l a y e d L i s t [ 2 0 ] ;2 i n t nDelayed = 0 ;3 i n t i n i t i a l i z e d = 0 ;4

5 void i n i t_commands ( ) {6 / / p r o c e s o de i n i c i a l i z a c i o n d e l modulo l i s t a7

8 [ . . . ]9

10 i n i t i a l i z e d = 1 ; / / marca r como i n i c i a l i z a d o11

12 f o r ( i n t i = 0 ; i < nDelayed ; i ++) {13 regcomm ( d e l a y e d L i s t [ i ] ) ; / / v o l v e r a r e g i s t r a r l o s comandos14 }15 }16

17 void regcomm (COMMAND∗ comm) {18 i f ( i n i t i a l i z e d ) {19 / / memoria d i n a m i c a20 [ . . . ]21 } e l s e {22 i f ( nDelayed < 20) {

Page 55: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.9. INTERFAZ GRÁFICA 55

23 d e l a y e d L i s t [ nDelayed ++] = comm ; / / a r r a y e s t a t i c o24 }25 }26 }

Ejemplo de uso

A continuación, se instancia el comando shcomm (del inglés show commands) que muestratodos los comandos registrados actualmente en el sistema. Se debe instanciar en memoria estática.

1 / / i n s t a n c i a e s t a t i c a d e l comando2 COMMAND shcomm ;3

4 / / d i s p a r a d o r d e l comando5 void shcomm_t r igge r ( i n t argc , char∗∗ a rgv ) {6 MARK_UNUSED( a r g c ) ;7 MARK_UNUSED( a rgv ) ;8

9 i n t s i z e = s i z e _ c o m m _ w r a p p e r _ l i s t (& commands_ l i s t ) ;10 comm_wrapper∗ comm = s t a r t _ i t e r a t e _ c o m m _ w r a p p e r _ l i s t (& commands_ l i s t ) ;11

12 e n r i c h e d P r i n t L n ("Available commands" , &YELLOW, NULL) ;13

14 f o r ( i n t i = 0 ; i < s i z e ; ) {15 [ . . . ]16 }17 }18

19 void i n i t_commands ( ) {20 [ . . . ]21

22 / / c o n f i g u r a c i o n y r e g i s t r o23 shcomm . key = "shcomm" ;24 shcomm . h e l p T e x t = "Lists all registered commands." ;25 shcomm . t r i g g e r = shcomm_t r igge r ;26 regcomm(&shcomm ) ;27 }

Código 2.26: Ejemplo de uso del módulo de comandos instanciando el comando shcomm.

Page 56: Desarrollo de un Sistema Operativo para Raspberry Pi 2

56 CAPÍTULO 2. DISEÑO

Figura 2.20: Salida del comando shcomm.

2.10. Cerrojos

Para implementar los locks ARMv7 [4] ofrece un sistema de marcado de direcciones de memo-ria según instrucciones de load y store atómicas además de una instrucción que permite desmarcardirectamente (ldrex, strex y clrex).

El control del estado de esas marcas viene dado por lo que dan a conocer como un local

monitor el cual pone ciertas restricciones para los accesos y los guardados en la memoria marcaday define dos estados open access y exclusive access.

La instrucción atómica strex es capaz de devolver si la operación ha tenido éxito (status 0) osi no se ha podido realizar (status 1) en un registro con el que poder comprobar el resultado de laoperación.

La figura 2.21 muestra la tabla de transiciones para una dirección que está en un cierto estadoa la que se le aplica una de las operaciones y el resultado que genera.

Page 57: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.10. CERROJOS 57

Figura 2.21: Tabla de transición del local monitor

Con esto explicado pasamos a ver el código utilizado para implementar los locks.

1 lock_mutex :2 l d r r1 , = l o c k e d3 t e s t _ l o c k :4 l d r e x r2 , [ r0 ]5 cmp r2 , r1 / / T e s t i f mutex i s l o c k e d or u n l o c k e d6 beq w a i t _ l o c k / / I f l o c k e d − w a i t f o r i t t o be r e l e a s e d , from 27 s t r e x n e r2 , r1 , [ r0 ] / / Not locked , a t t e m p t t o l o c k i t8 cmpne r2 , #1 / / Check i f S t o r e −E x c l u s i v e f a i l e d9 beq t e s t _ l o c k / / F a i l e d − r e t r y from 1

10 / / Lock a c q u i r e d11 dmb / / R e q u i r e d b e f o r e a c c e s s i n g p r o t e c t e d r e s o u r c e12 mov pc , l r13 w a i t _ l o c k :14 / / WAIT_FOR_UPDATE / / Take a p p r o p r i a t e a c t i o n w h i l e w a i t i n g f o r mutex t o

become u n l o c k e d15 wfi16 nop / / i t s h e r e b u t i t w i l l n e v e r be e x e c u t e d17 b t e s t _ l o c k / / R e t r y from 118

19

20 / / un lock_mutex21 / / D e c l a r e f o r use from C as e x t e r n vo id unlock_mutex ( vo id ∗ mutex ) / /22 . g l o b a l un lock_mutex23 unlock_mutex :24 l d r r1 , = u n l o c k e d25 dmb / / R e q u i r e d b e f o r e r e l e a s i n g p r o t e c t e d r e s o u r c e26 s t r r1 , [ r0 ] / / Unlock mutex27 / / SIGNAL_UPDATE

Page 58: Desarrollo de un Sistema Operativo para Raspberry Pi 2

58 CAPÍTULO 2. DISEÑO

28 mov pc , l r

Como podemos ver, primero realizamos una carga del valor que hay dentro del mutex conla instrucción atómica ldrex, luego comprobamos si el mutex está cogido o no, si no está cogidosimplemente intentamos marcar el mutex con la instructión atómica condicional strexne y luegocomprobamos el resultado de esa operación (vemos si nos ha devuelto un estado de 1 o 0), siresulta que hemos fallado la operación, volvemos a intentar coger el mutex, en caso contrariosimplemente volvemos de la subrutina porque el mutex ya está cogido.

2.11. Sistema de ficheros

Nuestro sistema de ficheros está basado en el nodo indexado[14], parecido a ext2 y al cualpasaremos a denominar i-nodo o i-nodos. Todos los métodos y macros descritos en esta sección seencontraran en los archivos berryOS/include/fs/fs.h y berryOS/src/fs/fs.c.

Antes de hablar del diseño, enumeramos algunas macros que hemos desarrollado para ajustarlos parámetros de nuestro sistema de ficheros, y veremos a lo largo de esta sección. Estos son:

MAXPAGESPERFILE: nos indica cuántas páginas de memoria utilizaremos como máxi-mo para almacenar la información de los archivos. Se puede modificar a conveniencia antesde ejecutar el sistema operativo.

MAXFILESIZE: se calcula multiplicando el tamaño de página por la macro anterior. Porejemplo, si utilizamos dos páginas, el tamaño máximo del archivo será de 8 KB.

MAXFILENAMESIZE: el tamaño máximo del nombre del archivo. También se puedemodificar a conveniencia.

MAXFILESPERDIR: el número de archivos que caben en un directorio. Se calcula comoel tamaño de página dividido por la estructura dir_entry_t, dando como resultado un máximode 146 archivos por directorio.

NUM_PAGES_INODE_TABLE: el número de páginas que se utilizarán para almacenarla tabla de i-nodos. Esta macro tambén se puede modificar a conveniencia.

NUM_INODES: el número total de i-nodos del sistema. Se calcula dividiendo el tama-ño de página entre el tamaño de la estructura i_node_t, y multiplicando el resultado porNUM_PAGES_INODE_TABLE. Por ejemplo, teniendo 2 páginas para la tabla de nodos,tenemos un total de 512 i-nodos.

NUM_INODES_PER_PAGE: contiene cuantos i-nodos caben en cada página de memoria.Se calcula dividiendo la anterior macro por NUM_PAGES_INODE_TABLE.

Comentado lo anterior, explicaremos los nodos indexados. La información que almacenaremosen esta estructura de datos es la siguiente, contenida en la estructura i_node_t :

Page 59: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.11. SISTEMA DE FICHEROS 59

Size: tiene distintos significados según el tipo del archivo. En el caso de archivos regulares,almacenará su tamaño en bytes. Sin embargo, en el caso de los directorios, contiene elnúmero de archivos que contiene.

Num_pages: contiene el número de páginas de memoria utilizadas por el i-nodo, con unmáximo de MAXPAGESPERFILE páginas. Esta macro no afectará a los directorios, loscuales solo utilizarán una página, como explicaremos más adelante.

Type: un bit que indica el tipo de archivo. El bit 0 indicaría fichero regular, mientras que elbit 1 indica directorio.

Free: un bit que indica si el i-nodo está libre (0) o está siendo utilizado(1).

Pages: las distintas páginas que está utilizando el archivo.

Debido a las limitaciones producidas por no tener reloj, no se puede añadir información comofechas de creación o modificación. Información que sí se podría añadir sería, por ejemplo, el modo(escritura o lectura).

2.11.1. Directorios

En relación a los directorios, se utilizará su página para almacenar la estructura dir_t, la cualcontiene:

Num_childs: contiene el número de hijos que tiene el directorio.

Child: un array de tamaño MAXFILESPERDIR de tipo dir_entry_t. Los dos primeros hi-jos corresponderán al propio directorio, y a su nodo padre. Por otro lado, la estructuradir_entry_t contiene:

• Inode_num: el número de i-nodo del archivo. A través de él podremos acceder a sui-nodo correspondiente.

• Filename: un array de char que contiene el nombre del archivo. El tamaño máximoviene dados por MAXFILENAMESIZE.

• Fn_size: el tamaño del nombre del archivo.

Los directorios utilizan solo una página de memoria, ya que 146 archivos por directorio nosparecen suficientes para la extensión actual del sistema operativo.

2.11.2. Estructura del sistema de ficheros

Nuestro sistema de ficheros está compuesto básicamente por una lista de i-nodos mapeados so-bre NUM_PAGES_INODE_TABLE páginas de memoria. Decidimos no añadir estructuras comobloques, bitmaps o superbloques ya que no nos parecieron necesarios.

Utilizamos variables globales para almacenar información clave de la situación actual del sis-tema:

Page 60: Desarrollo de un Sistema Operativo para Raspberry Pi 2

60 CAPÍTULO 2. DISEÑO

I_node_list_pages: un array de tipo i_node_t* que contiene las páginas en las que se alma-cenará la tabla de inodos.

Root_dir: una variable de tipo textsldir_t* que contiene el directorio raíz.

Current_glob: una variable de tipo textsldir_t* que contiene el directorio en el que se en-cuentra el usuario.

FreeInodes: el número de i-nodos libres de la tabla.

MinFreeInode: contiene o el último número de i-nodo que se ha reservado, o el mínimolibre en caso de que alguno haya sido liberado. Se utiliza para agilizar la búsqueda de uni-nodo libre.

Interface: una variable de tipo fs_interface que contiene la interfaz de usuario. Esto seexplicara con más detalle en la sección 2.11.9.

2.11.3. Inicialización del sistema de ficheros: fs_init()

La inicialización constará de cuatro tareas: inicializar la interfaz, registrar los comandos einicializar la tabla de i-nodos y el directorio raíz. La inicialización de la interfaz la comentare-mos en la sección 2.11.9. Para registrar los comandos, simplemente llamaremos al método regis-ter_filesystem_commands(), el cual explicaremos en la sección 2.11.8.

Para inicializar la tabla de i-nodos, necesitamos reservar una página con alloc_page() por cadauna de las posiciones del array i_node_list_pages. Con esto, habremos reservado el espacio dondese almacenarán los i-nodos, los cuales tenemos que inicializar. Para ello, recorremos cada uno delos i-nodos señalándolos como libre, poniendo el campo free del i-nodo a 0. Para conseguir esto,necesitamos una función que, dado el número del i-nodo, te devuelva su posición en memoria.Esta función es get_inode(number), y la explicaremos en la próxima sección.

Inicializado ya lo anterior, solo queda crear el directorio raíz. A este le asignaremos el númerode i-nodo 0, e instanciaremos el i-nodo correspondiente, asignándole que ya no está libre, que esde tipo directorio, que tiene 2 entradas, que utiliza sólo una página de memoria y reservaremos esapágina de memoria.

Dado que el nodo raíz es un directorio, también tendremos que instanciar su estructura dir_t,que se encuentra en la página de memoria reservada. Para ello diremos que tiene dos hijos, loscuales son “.” y “..”, y sus números de i-nodos serán el de la raíz, es decir, 0.

Finalmente, le daremos su valor inicial a current_glob, que será el directorio raíz, a free_inodes(el cual será NUM_INODES -1) y a minFreeInode, el cual será 1.

2.11.4. Funciones auxiliares

Estas funciones fueron creadas para englobar código que se repite en muchas funciones, paraextender las funcionalidad del sistema de ficheros, o bien utilizadas para depurar. Las explicaremosa continuación.

Page 61: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.11. SISTEMA DE FICHEROS 61

get_inode(number)

Como dijimos antes, devuelve el puntero asociado a un número de i-nodo, y se calcula utili-zando NUM_INODES_PER_PAGE de la siguiente manera:

1 re turn ( i \ _node \ _ t ∗ ) ( ( ( i n t ) i \ _node \ _ l i s t \ _pages [ num / NUM\ _INODES \ _PER \ _PAGE ] )+ ( num \ % NUM\ _INODES \ _PER \ _PAGE) ∗ s i z e o f ( i \ _node \ _ t ) ) ;

getFreeInode()

Devuelve el primer i-nodo libre de la tabla. Para ello se recorren todos los i-nodos desdeminFreeInode hasta encontrar uno libre.

fileExists(filename, first_child, father)

Comprueba si existe el archivo “filename” en el directorio “father”. Para ello hay que recorrerel array child de father, comparando “filename” con el nombre de la estructura dir_entry_t conte-nida en cada posición del array. En el caso de que se haya encontrado se devuelve la posición enel array, y en caso contrario, -1.

Por otro lado, “firts_child” indica desde qué posición comenzaremos a recorrer el array. Estoes necesario para incluir o no en la búsqueda los nodos “.” y “..” ya que, por ejemplo, no losqueremos incluir al buscar un archivo para escribir (no son modificables), pero sí los queremosincluir cuando queramos cambiar el directorio actual.

calculatePath(path, filename, fnsize)

Las funciones de las secciones siguientes admiten paths absolutos y relativos. Los formatos deestos paths son los siguientes:

Relativos: son nombres separados por caracteres ’/’. Un ejemplo sería: “directorio/directo-rio/archivo”

Absolutos: comienzan por “~/”, seguidos de nombres separados por ’/’. Un ejemplo sería“~/directorio/directorio”.

Por tanto calculate_path() devolverá el último directorio (la estructura dir_t) referenciado en“path”. También devolverá el nombre del último eslabón del path a través de “filename”, y sutamaño a través de “fnsize”.

Para ello, realizaremos lo siguiente:

1. Comprobaremos si el path es absoluto o relativo, comprobando el primer carácter (’~’). Sies absoluto, el directorio desde el que comenzaremos a resolver será el raíz, y si es relativo,comenzaremos desde current_glob.

Page 62: Desarrollo de un Sistema Operativo para Raspberry Pi 2

62 CAPÍTULO 2. DISEÑO

2. Recorremos “path” carácter a carácter hasta que encontremos ’\0’, guardando los caracteresen “filename”. Si encontramos un ’/’, significa que hemos completado un nombre de direc-torio, y este se encuentra en “filename”. Por tanto, comprobaremos que “filename” exista enel directorio padre.

3. Si el directorio existe, obtenemos su número de i-nodo en el array child del padre. Con elnúmero de i-nodo obtengo la estructura dir_t* del directorio, y este pasa a ser el padre.

Si no existe, devolvemos NULL indicando que la resolución ha fallado.

4. Cuando terminemos de recorrer “path”, devolveremos el dir_t* que hemos ido calculando.

exists(path)

Una extensión de fileExists() para admitir paths. Llama a calculate_path() para obtener elúltimo nombre de archivo de la cadena, y utiliza fileExists() para comprobar que este nombreexiste en el directorio devuelto por calculate_path().

getFileSize(path)

Devuelve el tamaño de un archivo. Para ello, se resuelve “path” para obtener el directorioque contiene al archivo, y con esto obtener el i-nodo del archivo con get_inode(). Finalmente,devolvemos el campo size del i-nodo.

2.11.5. Funciones de manejo

En esta sección explicaremos las funciones básicas que debe tener un sistema de ficheros,como leer o escribir, por ejemplo.

getFsInterface()

Devuelve la interfaz para que el usuario la modifique. Explicaremos cómo en la sección 2.11.9.

createFile(path)

Crea un archivo en la ruta especificada. El nombre del archivo a crear será el último eslabónde la cadena.

Comenzaremos resolviendo el path para obtener el directorio que contendrá el archivo. Des-pués, reservaremos la página donde se almacenarán sus datos y obtendremos un i-nodo con lasfunciones getFreeInode() y getInode().Tras esto, inicializaremos el i-nodo con la página reservaday diciendo que no está libre, tiene una página, tamaño 0 y tipo regular.

Tras lo anterior, solo queda añadir la información al directorio padre, añadiendo un hijo mása su array child y copiando en él el nombre del archivo, la longitud del nombre y su número dei-nodo.

Page 63: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.11. SISTEMA DE FICHEROS 63

createDir(path)

Crea un directorio en la ruta especificada. El nombre del archivo a crear será el último eslabónde la cadena.

Está función realiza los mismos pasos que la anterior, con la diferencia de que el tipo serádirectorio. Tras lo anterior queda inicializar la estructura dir_t del directorio a crear, añadiendo “.”y “..” a su array de hijos. El número de i-nodo que añadiremos a “.” será el del directorio creado,y el de “..” derá el directorio padre.

write(path, text)

Concatena al final del archivo dado por path “text” y devuelve el número de bytes escritos.Al igual que en los otros métodos, resolveremos el path y obtendremos el i-nodo del archivo.

Es necesario, antes de continuar, que el tipo del archivo sea regular.Antes de comenzar a copiar “text” en las páginas del i-nodo, es necesario calcular el offset del

archivo, así como en qué página escribiremos. Lo haremos de la siguiente manera:

1 char∗ pos = ( ( char ∗ ) i n F i l e −>pages [ i n F i l e −> s i z e / PAGE_SIZE ] ) + ( i n F i l e −> s i z e %PAGE_SIZE ) ;

2 i n t a c t u a l _ p a g e = i n F i l e −> s i z e / PAGE_SIZE ;

Con el offset calculado, ya podemos copiar carácter a carácter a partir de offset. Sin embargo,es posible que al escribir, lleguemos al final de la página. Si este es el caso tendremos que hacer losiguiente:

Si ya hemos utilizado el máximo número de páginas permitidas (dado por MAXPAGES-PERFILE), finalizamos la transferencia y devolvemos el número de bytes escritos hasta elmomento.

Si no, si la página siguiente ya ha sido reservada, por ejemplo, porque se escribió pero seha movido el offset hasta la página anterior (aunque actualmente no hay ningún mecanismopara mover el offset), el offset se coloca al inicio de la página reservada.

Finalmente, si el caso no es ninguno de lo anteriores, necesitamos reservar una página nueva,en cuyo comienzo se colocará el offset.

Finalmente, actualizaremos el tamaño del archivo y devolveremos el número de bytes escritos.

read(path, bytes)

Devuelve los primeros “bytes” de contenido del archivo referenciado por “path”. La funciónreservará el espacio necesario como un array de char, pero depende del usuario liberar la memoriauna vez finalizado el uso del puntero.

Obtendremos el i-nodo resolviendo el path y comprobaremos que el fichero es de tipo regular.También es necesario obtener el mínimo entre el tamaño del archivo y el número de bytes reque-rido, para no leer basura, además de calcular el offset, que será el inicio de la primera página deli-nodo.

Page 64: Desarrollo de un Sistema Operativo para Raspberry Pi 2

64 CAPÍTULO 2. DISEÑO

Una vez obtenido el mínimo, utilizaremos kmalloc() para obtener el puntero y copiaremoscarácter a carácter desde el offset al puntero. Al igual que con write(), necesitaremos comprobarque hemos llegado al final de la página. Si se da esta situación, el offset pasará a ser la siguientepágina del i-nodo. Finalmente, devolveremos el puntero devuelto por kmalloc().

changeDir(path)

Cambia la variable global current_glob, recordemos que referencia al directorio en el que seencuentra el usuario del SO, al directorio referenciado por path.

Tras obtener el i-nodo resolviendo el path, comprobaremos que el archivo es de tipo directorio.Tras la comprobación, simplemente asignaremos a current_glob el puntero a la primera página deli-nodo calculado.

2.11.6. Funciones de borrado

A la hora de borrar archivos necesitaremos realizar dos tareas. La primera, es limpiar cadauna de las estructuras, de manera que no se pueda referenciar un archivo borrado. La segunda esliberar los recursos que fueron utilizados para su creación como las páginas utilizadas o el i-nodoreservado.

deleteFile(father, inFile, numChild)

Borra el archivo “inFile” del directorio “father”. El argumento “numChild” es utilizado paraevitar volver a buscar el archivo en el padre.

Lo primero que haremos será liberar el i-nodo. Para ello, actualizaremos la variable minFreeI-node en caso de que el nodo liberado sea menor. Después, liberaremos las páginas que utilizaba yactualizaremos el i-nodo a libre. Finalmente, borraremos las referencias al archivo en el padre, va-ciando la posición “numChild” del array child de “father”. Con vaciando, nos referimos a igualara 0 cada uno de los campos de dir_entry, desde el nombre del archivo hasta el número del i-nodo.

deleteDirContent(inFile)

Esta función eliminará el directorio “inFile” y realizando llamadas recursivas para borrar todosu contenido.

Recorreremos el array child de “inFile”, el cual se encuentra en su página de memoria. Re-cordemos que la segunda posición de este array contiene la información del padre, por lo quedeberemos evitar borrar su información.

Al recorrer el array, si la posición actual es un archivo regular, invocaremos a la función ante-rior. Sin embargo, si es un directorio, llamaremos a esta misma función con el hijo.

Finalmente, tras eliminar cada uno de sus hijos, liberaremos los recursos de “inFile”, de lamisma manera que hacemos en deleteFile().

Page 65: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.11. SISTEMA DE FICHEROS 65

delete(path)

Esta función eliminará el archivo referenciado por “path”, independientemente de si es unarchivo regular o un directorio utilizando las dos anteriores.

Comenzamos resolviendo el path para obtener el i-nodo. Tras esto, si el archivo es regular,llamaremos a deleteFile(), y si es un directorio, invocaremos a deleteDirContent(). En el casode deleteDirContent(), borrará el contenido del directorio, pero no la referencia del i-nodo en supadre, por lo que deberemos vaciar la posición en el array child del padre.

Finalmente, si el archivo que hemos eliminado se encuentra en una posición intermedia delarray, provocará que falle sucesivos borrados e impresiones. Para solucionarlo, copiaremos la in-formación del archivo colocado en la última posición del array en el espacio generado por elarchivo borrado.

2.11.7. Funciones de impresión

Estas funciones fueron creadas con propósito de depuración, y después utilizadas para crearcomandos en la siguiente sección.

recPrintFs(inode, j)

Este método imprime recursivamente el contenido del sistema de ficheros a partir del direc-torio “inode”. El parámetro “j” es utilizado para indicar el nivel de profundidad en el que nosencontramos, de manera que podamos indentar de manera correcta la impresión.

Obtendremos el i-nodo con get_inode(), y con el i-nodo conseguiremos el directorio de lamisma manera que lo hacíamos en la función deleteDirContent(). Tras esto, recorreremos su arraychild. Es importante evitar los hijos “.” y “..” para evitar ciclar indefinidamente. Finalmente, si elhijo de la posición del array es un archivo regular, imprimiremos su nombre, y si es un directorio,llamaremos recursivamente a esta función con el hijo y con “j” más uno.

printFs()

Imprime el sistema de ficheros al completo. Básicamente es una llamada a recPrintFs(), con elnúmero de i-nodo 0 (recordemos que corresponde al nodo raíz) y el nivel 0.

listDirectory(path)

Imprime el contenido del directorio referenciado por “path”.Tras obtener el i-nodo resolviendo el path, comprobaremos que este sea un directorio. Si es

así, simplemente queda recorrer su array child imprimiendo los nombres de los hijos, excluyendo“.” y “..”.

2.11.8. Funciones de los comandos

Los comandos de esta parte han sido creados como se describe en la sección 2.9.3. El métodoregister_filesystem_commands() le asignará a cada comando su texto de ayuda, su clave y su

Page 66: Desarrollo de un Sistema Operativo para Raspberry Pi 2

66 CAPÍTULO 2. DISEÑO

disparador. Por ejemplo, para mkdir, se realiza lo siguiente:

1 COMMAND mkdir ;2

3 void m k d i r _ f u n c t i o n ( i n t argc , char∗∗ a rgv ) {4 MARK_UNUSED( a r g c ) ;5 c r e a t e D i r ( a rgv [ 0 ] ) ;6 re turn ;7 }8

9 s t a t i c vo id r e g i s t e r _ f i l e s y s t e m _ c o m m a n d s ( ) {10 mkdir . h e l p T e x t = "Create a directory" ;11 mkdir . key = "mkdir" ;12 mkdir . t r i g g e r = m k d i r _ f u n c t i o n ;13 regcomm(& mkdir ) ;14 . . .15 }

Por tanto, como se observa en el ejemplo anterior, cada disparador simplemente llamará a lafunción correspondiente del sistema de ficheros con los parámetros necesario. La correspondenciaentre comando y método del sistema de ficheros es la siguiente:

mkdir - createDir().

mkfile - createFile().

cd - changeDir().

del - delete().

lsall - printFs().

ls - listDirectory().

cat - read(): dado que cat imprime el archivo entero, el número de bytes que se utiliza comoargumento es el resultado de getFileSize().

echo - write().

2.11.9. Interfaz proporcionada para sobrescribir los métodos

Esta interfaz corresponde a la estructura fs_interface, y permite sobrescribir los siguientesmétodos:

write()

read()

delete()

createFile()

createDir()

Page 67: Desarrollo de un Sistema Operativo para Raspberry Pi 2

2.11. SISTEMA DE FICHEROS 67

exists()

printFs()

getFileSize()

changeDir()

listDirectory()

Esta estructura se inicializa en fs_init(), asignando a cada método de la interfaz su método co-rrespondiente de las secciones anteriores. Para utilizar esta interfaz, es obligatorio que los métodoscreado devuelvan el mismo tipo y tengan los mismos tipos que los métodos originales.

Por tanto, para poder sobrescribir un método, hay que crear el método que lo sobrescribirá, ymodificarlo la interfaz, la cual obtendremos con el método de get_FsInterface(). Un ejemplo deesto sería lo siguiente:

1

2 void myPr in tFs ( ) {3 e n r i c h e d P r i n t L n ("myPrintFs se ha ejecutado" , &RED, NULL) ;4 }5

6 void k e r n e l _ m a i n ( u i n t 3 2 _ t r0 , u i n t 3 2 _ t r1 , u i n t 3 2 _ t a t a g s ) {7 [ . . . ]8

9 f s _ i n t e r f a c e ∗ aux = g e t F s I n t e r f a c e ( ) ;10 aux−> p r i n t F s = myPr in tFs ;11 aux−> p r i n t F s ( ) ;12

13 [ . . . ]14 }

El resultado de la ejecución de lo anterior de puede observar en la figura 2.22.

Figura 2.22: Ejemplo de uso de la interfaz del sistema de ficheros

Page 68: Desarrollo de un Sistema Operativo para Raspberry Pi 2

Capítulo 3

Manual de uso

Dado que el desarrollo del proyecto se ha realizado en sistemas Linux, específicamente enUbuntu, este manual se realizará enfocado a este último. Para poder ejecutar y probar el sistemaoperativo, necesitaremos:

La rama master del proyecto en Github1 que contiene un Makefile que construye el pro-yecto, el compilador necesario, un depurador, el código fuente y la documentación másrelevante recolectada.

El emulador es un software distribuido por QEMU, una serie de simuladores de procesado-res, en concreto el de la Raspberry Pi 2. Se puede instalar utilizando el siguiente comando"sudo apt install qemu".

La Raspberry Pi 2 necesita una tarjeta microSD de memoria y el cable de alimentacióncorrespondiente.

3.1. Reglas del Makefile

Situándonos con una terminal en la carpeta raíz del proyecto (aquella que contiene el Makefi-le), ejecutaremos el comando "make <nombre de la regla>", donde esta regla puede ser:

build: Compila el proyecto, creando los object y la imagen del sistema operativo, y confi-gurando lo necesario para ejecutarlo con el simulador. Tanto run como debug llaman a estaregla, por lo que no es necesario hacer build antes de ejecutar estas. La imagen del sistemaoperativo es build/myos.elf.

build_hard: Realiza la misma compilación que build, pero adaptándola a la ejecución en laRaspberry Pi 2. La imagen pasará a llamarse kernel7.img. De esto se hablará en el capítulo4.2.

variable_test: Utilizado para imprimir todos los nombres de los archivos que componen elSO, mostrando los object, los assembly y los c.

1https://github.com/dacya/tfg1920-raspiOS

68

Page 69: Desarrollo de un Sistema Operativo para Raspberry Pi 2

3.2. EJECUTAR EL SISTEMA OPERATIVO 69

clean: Elimina todo lo generado en el proceso de compilación, para eliminar archivos inne-cesarios cuando se termina de utilizar, como los object.

run: Arranca el simulador con la imagen del sistema operativo, si existe. Si no, ejecuta buildpara generar la imagen. Es la regla que utilizaremos para probar el sistema operativo.

debug: Actúa igual que run, excepto que se para en la primera línea de código. Para utili-zarlo es necesario conectar el emulador con el depurador. Explicaremos cómo hacerlo y suuso en la sección 3.4 de este capítulo.

3.2. Ejecutar el Sistema Operativo

Emulador QEMU

Una vez compilado con make build (o simplemente make) como en la figura 3.1, se utilizaráel comando make run desde la carpeta raíz del proyecto, lo que ejecutará el simulador y se abrirála pantalla que simula la salida HDMI de la Raspberry Pi como en la figura 3.2.

Figura 3.1: Ejecución de make build para compilar el SO para QEMU

Page 70: Desarrollo de un Sistema Operativo para Raspberry Pi 2

70 CAPÍTULO 3. MANUAL DE USO

Figura 3.2: Ejecución de make run para lanzar SO con el QEMU

Para poder interactuar con ella, el método de entrada es a través de la UART, como se explicaen la sección 2.4 del capítulo anterior. Por tanto, es necesario tener seleccionada la terminal dondeejecutamos el comando a la hora de escribir.

Finalmente, para ver las distintas cosas que podemos hacer con el SO, utilizaremos el comandoshcomm para mostrar una lista de los comandos disponibles y su funciones.

Raspberry Pi 2

En este caso se debe usar el comando make build_hard que compilará el sistema operativo conel nombre kernel7.img en la carpeta build. Para ejecutar la imagen en la Raspberry es tan sencillocomo borrar todos los archivos imagen y por último mover el nuestro a la tarjeta microSD.

La tarjeta microSD debe tener el sistema Raspbian [13] cargado en ella para reutilizar sussistema de arranque y, así, facilitar el diseño y su puesta en marcha reemplazando la imagen delsistema.

3.3. Comandos disponibles

Los comandos han sido creados para mostrar las funcionalidades de algunos módulos de nues-tro sistema operativo. Estos son:

1. Memoria.

prheap: imprime el espacio disponible en el heap, que como vimos en la sección 2.7,se utilizará para almacenar datos de pequeño tamaño, como un array de char. [Figura3.3]

Page 71: Desarrollo de un Sistema Operativo para Raspberry Pi 2

3.3. COMANDOS DISPONIBLES 71

Figura 3.3: Salida del comando prheap.

prmem: imprime un bitmap de las primeras sesenta páginas de la memoria. [Figura3.4]

Figura 3.4: Salida del comando prmem.

2. Interfaz gráfica.

cls: vacía la pantalla. [Figura 3.5]

Page 72: Desarrollo de un Sistema Operativo para Raspberry Pi 2

72 CAPÍTULO 3. MANUAL DE USO

Figura 3.5: Efecto de cls

(a) Antes de ejecutar cls (b) Después de ejecutar cls

shcomm: imprime los comandos disponibles y sus funcionalidades. [Figura 3.6]

Figura 3.6: Salida del comando shcomm.

3. Procesos:

ps: imprime el nombre y el PID de todos los procesos actuales. [Figura 3.7]

Page 73: Desarrollo de un Sistema Operativo para Raspberry Pi 2

3.3. COMANDOS DISPONIBLES 73

Figura 3.7: Salida del comando ps.

4. Sistema de gestión de ficheros.

mkfile <ruta>: crea en la ruta un archivo ordinario, sobre el que se pueden usar loscomandos cat, echo y delete. [Figura 3.8]

Figura 3.8: Efecto del comando mkfile.

echo <texto><ruta>: concatena un texto a un archivo ordinario. Este texto no admiteespacios. [Figura 3.9]

Page 74: Desarrollo de un Sistema Operativo para Raspberry Pi 2

74 CAPÍTULO 3. MANUAL DE USO

Figura 3.9: Ejemplo de uso del comando echo.

cat <ruta>: imprime por pantalla el contenido de un archivo ordinario. [Figura 3.10]

Figura 3.10: Salida del comando cat.

mkdir <ruta>: crea un directorio en la ruta, sobre el que se puede usar los comandoscd, ls y delete.[Figura 3.11]

Figura 3.11: Efecto del comando mkdir.

ls [<ruta>]: lista los archivos contenidos en el directorio de la ruta. Si no se utiliza laruta, se lista el directorio actual. [Figura 3.12]

Page 75: Desarrollo de un Sistema Operativo para Raspberry Pi 2

3.3. COMANDOS DISPONIBLES 75

Figura 3.12: Salida del comando ls.

lsall: imprime toda la jerarquía de archivos desde root. [Figura 3.13]

Figura 3.13: Salida del comando lsall.

cd <ruta>: cambia el directorio actual al de la ruta. [Figura 3.14]

Figura 3.14: Efecto del comando cd.

del <ruta>: borra el archivo de la ruta. Si el archivo es uno ordinario, simplemente loborra. Si es un directorio, borra recursivamente su contenido. [Figura 3.15]

Page 76: Desarrollo de un Sistema Operativo para Raspberry Pi 2

76 CAPÍTULO 3. MANUAL DE USO

Figura 3.15: Efecto del comando del.

3.4. Utilizar el depurador

Para ejecutar el SO y ser capaces de emplear los beneficios de un depurador, necesitaremos dosterminales situadas en la carpeta raíz del proyecto. La primera se utilizará para utilizar el comandomake debug, lo que abrirá un puerto para conectar un depurador. La segunda la utilizaremos paraejecutar GDB (GNU Debugger).

Para ejecutar GDB, utilizaremos el comando ./compiler/bin/arm-none-eabi-gdb, lo que eje-cutará el programa y nos mostrará una interfaz de comandos. Primero, necesitamos añadir losnombres de función, los nombres de variable, etc. Para ello, importaremos el ejecutable del SOcon el comando file build/myos.elf. Tras esto, ya solo queda conectar el depurador a el emulador,para lo que utilizaremos en la misma terminal el comando target remote localhost:<port> siendo<port> el puerto en el que el emulador de QEMU espera para poder acoplarle un depurador2.Una vez finalizado todo lo anterior, ya podremos usar GDB como si depuráramos cualquier otroprograma.

Un ejemplo exitoso del proceso anterior se puede observar en la figura 3.16. La subfigura 3.16acorrespondería a la terminal en la que utilizaremos GDB, mientras que la subfigura 3.16b muestrala que contiene el proceso de QEMU.

2Esta información se muestra por pantalla al ejecutar el comando make debug

Page 77: Desarrollo de un Sistema Operativo para Raspberry Pi 2

3.5. REPOSITORIO GIT 77

Figura 3.16: Ejemplo de inicio de depuración correcto.

(a) Terminal donde se utiliza GDB (b) Terminal que contiene a QEMU

3.5. Repositorio git

El repositorio git está alojado en la plataforma GitHub: https://github.com/dacya/tfg1920-raspiOS

3.5.1. Estructura del repositorio git

Directorio raíz

El repositorio se compone de tres carpetas:

La carpeta del código del sistema operativo.

Los archivos del compilador y su documentación.

La documentación oficial distribuida por el fabricante que hemos ido recogiendo del pro-yecto.

Page 78: Desarrollo de un Sistema Operativo para Raspberry Pi 2

78 CAPÍTULO 3. MANUAL DE USO

Figura 3.17: Directorio artOS

Directorio del código fuente

Con respecto a la carpeta con todo el código fuente se estructura de la siguiente forma:

src contiene los módulos en código C.

include recoge los archivos cabecera .h de todos los módulos públicos.

arch aloja las dependencias de arquitectura en código ARM.

LICENSES contiene las licencias del código.

Figura 3.18: Directorio artOS

3.5.2. Aportaciones al repositorio

Si se trabaja dentro del repositorio se debe seguir la técnica git-flow donde el desarrolladortrabaja en una rama clonada de master o wip y posteriormente debe abrir una petición de fusión(merge request o pull request) a la rama de desarrollo wip para que sea analizada y fusionadacorrectamente. Para cada versión finalizada en wip, previamente testeada, se fusiona con master

pasando a ser la última versión estable.

Page 79: Desarrollo de un Sistema Operativo para Raspberry Pi 2

3.5. REPOSITORIO GIT 79

Para cambios de repositorios externos o forks se debe pedir una petición de fusión (merge

request o pull request) para que sea analizado por los desarrolladores y posteriormente aceptado.

IMPORTANTE: Todas las funciones expuestas por un módulo deben ir documentadassiguiendo el formato JavaDoc.

Page 80: Desarrollo de un Sistema Operativo para Raspberry Pi 2

Capítulo 4

Conclusiones y trabajo futuro

4.1. Conclusiones

En este Trabajo Final de Grado hemos desarrollado un Sistema Operativo completamentefuncional para la Raspberry Pi 2. Para facilitar el diseño y su puesta en marcha, se ha reutilizadoel complejo sistema de arranque de Raspbian 4.19. Los módulos desarrollados se describen acontinuación:

El kernel es el módulo principal de nuestro sistema operativo, es el encargado de inicializarcorrectamente y en el orden adecuado el resto de módulos y configuraciones del hardware. Estemodulo se ejecuta tras el proceso de arranque.

El módulo de gestión de E/S fue uno de los primeros módulos en definirse y permaneció activodurante todo el desarrollo del proyecto. Con este módulo somos capaces de recibir y enviar datosa través de la UART. Además, hemos creado una librería que facilita el uso del banco de pines deentrada y salida característico de la Raspberry Pi. Gran parte del desarrollo de este TFG dependede la depuración, sin este módulo no habríamos sido capaces de crear este sistema operativo.

El módulo de gestión de interrupciones y excepciones nos permite tratar cualquier tipo deevento que interfiera en la tarea del CPU. El objetivo de este módulo consiste en dar soporte paralas interrupciones, pero durante su desarrollo nos dimos cuenta de que las excepciones se tratanigual, por lo que se ha dejado la base para tratarlas. A través de este módulo gestionamos unode los relojes hardware para que interrumpa periódicamente la ejecución de cualquier proceso,funcionalidad esencial para el módulo de gestión de procesos.

El módulo de gestión de memoria proporciona la organización de toda la memoria accesiblepor el CPU y la capacidad de reservar espacios de memoria sin conflictos entre los distintos mó-dulos. Hemos tenido en cuenta el método que tiene el arranque de la Raspberry Pi 2 para obtenerla cantidad de memoria disponible a través de los ATAGS.

El módulo de gestión de procesos consiste en el seguimiento de distintos programas a los que

80

Page 81: Desarrollo de un Sistema Operativo para Raspberry Pi 2

4.2. TRABAJO FUTURO 81

asociamos una PCB (de inglés Process Control Block). Como se ha mencionado, con el módulo degestión de excepciones e interrupciones se ha diseñado un planificador que permite la concurrenciaentre todos los procesos dentro del sistema operativo con una interfaz para desarrollar fácilmentenuevas estrategias.

El módulo de gestión de sistema de ficheros crea una capa de abstracción para el manejo deinformación dentro del sistema operativo en forma de ficheros y carpetas. Este módulo está basadoen la representación del nodo indexado que implementa GNU/Linux.

El módulo de la interfaz gráfica aprovecha el canal de mensajería mailbox de la arquitecturapara comunicar la tarjeta gráfica con el procesador. Se obtiene así una sección compartida dememoria por la cual la CPU puede gestionar a nivel de píxel la información transmitida por elpuerto HDMI formando así la librería GPU. A partir de ésta hemos desarrollado un sistema devistas similar al de Android con el que se implementa una consola con comandos que trabaja enun proceso propio.

Finalmente, el módulo de sincronización ofrece cerrojos para ofrecer atomicidad en seccionescríticas, necesario para el procesamiento concurrente.

A lo largo del desarrollo de este TFG hemos aplicado los conocimientos que hemos adquiridoen asignaturas como: Sistemas Operativos, Ampliación de Sistemas Operativos, Estructura de Da-tos y Algoritmos, Ingeniería del Software, Estructura de Computadores, Desarrollo de SistemasInteractivos, etc. Además de la parte más técnica, también se ha aprendido a trabajar en equipo yse ha explotado al máximo el trabajo en paralelo con herramientas de control de versiones y conespecial atención al diseño ya que la división en módulos es esencial para el trabajo en paralelosin conflictos.

También hemos adquirido nuevas habilidades como la capacidad para filtrar información útilen la documentación del fabricante, profundización del funcionamiento de arquitecturas ARM asícómo en su programación y la capacidad de adaptación a las circunstancias.

4.2. Trabajo futuro

Creemos que el proyecto que hemos llevado a cabo cumple con las especificaciones menciona-das en la sección 1 de introducción y a partir de estas bases nos gustaría dar posibles ampliacioneso posibles proyectos como trabajo futuro.

En primer lugar, se debería dar soporte para distintos modos de ejecución, como por ejemplo,soporte para memoria virtual o seguridad.

En segundo lugar, se han sentado las bases para crear una interfaz sencilla y eficiente quefacilite el registro de rutinas en C para el tratamiento de interrupciones. Se deberían explotar estasbases.

Page 82: Desarrollo de un Sistema Operativo para Raspberry Pi 2

82 CAPÍTULO 4. CONCLUSIONES Y TRABAJO FUTURO

En tercer lugar, el sistema de gestión de ficheros se puede extender en sus capacidades parapoder dar soporte a enlaces rígidos y enlaces simbólicos, permisos para leer, ejecutar o escribir enun fichero, etc.

En cuarto, lugar el módulo de procesos se podría ampliar creando distintos planificadoresgracias a que se ha implementado una interfaz de abstracción para ello.

En quinto lugar, con respecto a los módulos relacionados con la interfaz gráfica, se podría darsoporte para la realización de cálculos directamente en la tarjeta gráfica liberando de carga a laCPU. Además, se ha dejado el sistema de vistas preparado para crear un sistema de ventanas.

Finalmente, el hardware de la Raspberry Pi ofrece muchas más posibilidades de las que hemosaprovechado en este TFG, nos gustaría remarcar que es un buen trabajo futuro permitir la imple-mentación para el manejo de puertos USB, Ethernet, sistema de audio, Bluetooth y WiFi entremuchas otras cosas. También el SO podría usarse de herramienta en otras ramas de trabajo comopor ejemplo en la robótica.

Page 83: Desarrollo de un Sistema Operativo para Raspberry Pi 2

Capítulo 5

Contribuciones al proyecto

Alejandro Cancelo Correia

Desde el principio nos dimos cuenta de que el diseño de funcionalidades base dependían mu-cho del lenguaje ensamblador por lo que mi tarea a lo largo de este TFG ha sido dedicarme acomprender la arquitectura lo máximo que pudiese en el tiempo que tuvimos y aplicar esos co-nocimientos en los distintos módulos que lo requiriesen. Para ello utilicé los documentos quepodéis encontrar en el fichero berryOS/documentation/ y largas horas vagando por foros y websespecializadas en diseños bare metal en busca de más conocimiento.

Una de mis primeras tareas fue comprender e implementar el proceso boot, aquí he de aclararuna cosa, la subrutina en ensamblador del boot la obtuvimos del tutorial que nos ofreció nuestroDirector de TFG José L. Risco Martín, mi participación en esa subrutina fue para hacerla másdescriptiva y para permitir la inicialización de un componente base de mi siguiente tarea, las inte-rrupciones, pero antes me gustaría mencionar que durante el desarrollo del boot y de la subrutinakernel_main (la cual se encarga de inicializar los demás módulos) se me ocurrió aplicar un están-dar de inicializaciones el cual seguimos a lo largo del TFG.

Mi siguiente tarea fue crear el módulo de interrupciones. Esta tarea, aunque sencilla, requeríaque aprendiese acerca de todos los tipos de excepciones que la arquitectura del Cortex-A7 podíaofrecer, por lo que no solo me dediqué a las interrupciones, también me dedique a preparar todotipo de excepciones para que alumnos futuros solo tengan que editar los cambios para que seadapten a los nuevos módulos que vayan a implementar. También desarrollé una librería con lasinterrupciones más importantes de las 72 posibles existentes en el CPU y definir una rutina detratamiento en C que se ejecutase por cada interrupción.

A continuación pasé a desarrollar un reloj que disparase interrupciones periódicamente, apro-vechando los distintos componentes hardware que ofrecía el Cortex-A7. Nuestra primera idea erausar la propia Raspberry Pi para ello, pero debido al gran trabajo que nos suponía y que quedafuera del alcance de este proyecto, decidimos que la mejor idea era enfocarnos en el emulador deQEMU, por lo que tenía que investigar el soporte que daba QEMU para los relojes dl CPU, aquíme ayudó nuestro Director de TFG José L. Risco Martín, ya que era consciente de que QEMUúnicamente daba soporte a lo que se conoce como System Timer, unos relojes que pertenecían acada núcleo y del cual hablaré más tarde.

83

Page 84: Desarrollo de un Sistema Operativo para Raspberry Pi 2

84 CAPÍTULO 5. CONTRIBUCIONES AL PROYECTO

Una vez acabada la implementación del reloj y ver que todo funcionaba a la perfección, decidíincluir una pequeña librería en C, con la que abstraer y facilitar el uso de dicho módulo.

En este punto del desarrollo, relevé a mi compañero Raúl Sánchez Montaño de sus responsa-bilidades con el módulo de procesos del kernel y me di cuenta de varias cosas. Mi compañero basósus diseños en el tutorial que ya he mencionado previamente, el problema era que el que creó eltutorial no comprendía bien qué era un proceso y qué responsabilidad tenía el kernel, sus diseñosconsistían en entender a los procesos como subrutinas, en vez de como elementos separados sinningún tipo de dependencia, bajo esa premisa la subrutina de kernel_main se entendía como unproceso, cosa que no puede estar más lejos de la realidad, por lo tuve que deshacerme de algunosde sus diseños como: el diseño de la estructura del PCB, los cambios de contexto y el planifica-dor e implementarlo de cero dando soporte a la verdadera asincronicidad que tienen los procesos.Además implementé una pequeña interfaz para registrar planificadores.

Una vez resuelto este problema, nos surge la situación de que ahora sí tenemos asincronicidad,por lo que también podríamos tener condiciones de carrera así que mi última tarea consitió en crearcerrojos que permitiesen proteger las secciones críticas del código.

En resumen, mi contribución a este TFG ha sido el contacto directo con la arquitectura delCPU creando funcionalidades en ensamblador, apoyando a esas funcionalidades con algún códigoen C o librería en C diseñado por mi y entender el flujo de ejecución de la propia arquitectura.

Tomás Golomb Durán

Mi trabajo en el proyecto respecto a la implementación se ha centrado en el bloque de módulosde entrada y salida que culmina en una liberaría estándar (stdio.h), en un sistema de vistas quecompone la interfaz de usuario y varios sistemas de comunicación a través del banco de puertosde la placa.

Empecé con la librería de control GPIO que tiene como objetivo abstraer el uso del bancode pines que incluye la placa para aplicaciones de comunicación entre dispositivos y depuraciónen la placa, pero, también, pensando en el futuro, para que otros alumnos puedan utilizarla comoherramienta para el desarrollo de aplicaciones robóticas. A raíz de este último punto, se me ocurrióque las funciones expuestas por el módulo deberían estar bien documentadas, por eso impulsé eluso del formato de documentación JavaDoc en todo el proyecto.

Posteriormente, revisé el módulo que mis compañeros desarrollaron sobre la UART reutili-zando la librería GPIO ya que este dispositivo de comunicación de carácter general transmite losdatos a través del banco de pines.

Mi siguiente objetivo fue buscar el uso de la GPU para poder desarrollar una interfaz de usuariopropia del sistema a través del puerto HDMI, ya que, hasta el momento, la salida de datos através de la UART mantiene la dependencia con otro sistema operativo de una consola bash. Paraello, tuve que buscar información a través de foros, wikis y el repositorio de Linux porque ladocumentación del fabricante estaba incompleta. Finalmente, conseguí implementar el protocoloa través del sistema de intercambio de mensajes mailbox entre la CPU y la GPU para la creaciónde un framebuffer, una estructura de datos compartida entre los dos componentes para renderizar

Page 85: Desarrollo de un Sistema Operativo para Raspberry Pi 2

85

imágenes. Esto funciona correctamente en el emulador QEMU, pero no se han conseguido lograrlos mismos resultados en el hardware que necesitan largas horas de depuración que se vieroninterrumpidas a causa de la pandemia del COVID-19.

Una vez conseguido el framebuffer, tenemos acceso a la lectura y escritura de cada píxel de unapantalla conectada al puerto HDMI. Consecuentemente, implemento un sistema de vistas basadoen el diseño del sistema operativo de código abierto Android. Se convirtió en un reto bastantecomplejo porque el lenguaje de programación C no es orientado a objetos y por tanto no existenlas técnicas de herencia, polimorfismo y clases abstractas como si aprovecha Android con java, porlo que tuve que ingeniar estas técnicas. De esta forma, surge un módulo de interfaces de usuario(IU) con el que soy capaz de implementar una consola con un componente de entrada de datos yuna barra de estado.

En este momento pienso que es necesario el diseño de una librería estándar entrada y sali-da, conocida popularmente como stdio.h, que actualmente redirige la salida de datos a través dela UART y la consola gráfica. Esta abstracción (o fachada) facilitaría a futuros desarrolladoresredireccionar el flujo de datos sin la obligación de refactorizar el proyecto.

Una vez terminada la consola, me propongo el desarrollo de un sistema de comandos con pasode parámetros lo suficientemente abstracto para que cada módulo pueda adaptarse y crear el suyopropio. Este módulo tiene especial interés técnico porque le afecta el orden en como se inicializanlos módulos por el kernel pudiendo así afectar al sistema entero. Esto se debe a que se puedenencontrar dependencias entre módulos en ambos sentidos, por lo que uno se encontrará que el otrono está inicializado.

En el apartado de miscelánea, he implementado una lista dinámica doblemente anidada conun nodo fantasma que incluye varias funcionalidades intentando que sea lo más eficiente posibley usando directivas del compilador para conseguir genericidad. Ésta se ha utilizado prácticamenteen todos los módulos: memoria, procesos, vistas, comandos, etc.

A raíz de varios problemas que se nos presentaron al trabajar en paralelo, mis compañeros measignaron el rol de director de proyecto por tener más experiencia con el uso de sistemas de controlde versiones y, en concreto, git y GitHub. Me encargaba de repartir y gestionar las tres líneas detrabajo y el análisis de las peticiones de fusión de código para solucionar los conflictos al fusionarlas ramas del repositorio git.

En relación a la redacción de la memoria, he redactado de la parte del diseño los módulos yamencionados, el capítulo introductorio, el resumen y palabras clave y he ayudado con la revisiónde varias secciones del proceso iterativo.

En resumen y para concluir, he desarrollado el apartado de entrada y salida de datos pasandopor implementaciones a bajo nivel (la UART, la GPIO y la GPU) y “escalar” un poco más con eldesarrollo de un sistema gráfico de vistas basado en Android que se comunica a través del puertoHDMI para romper la dependencia de tener que trabajar con otro sistema operativo. A raíz deestos módulos he programado una consola con un sistema de comandos con paso de parámetros

Page 86: Desarrollo de un Sistema Operativo para Raspberry Pi 2

86 CAPÍTULO 5. CONTRIBUCIONES AL PROYECTO

adaptable a cualquier módulo y he facilitado una librería estándar de entrada y salida para poderredireccionar el flujo de datos. Además de codificar, he adquirido un rol de gestión de trabajo yrepositorio para evitar los conflictos de trabajar en paralelo.

Raúl Sánchez Montaño

Tras una de las primeras reuniones, y tras recibir de parte de nuestro director de TFG, JoséL. Risco Martín, la tarjeta de memoria y la Raspberry Pi 2, decidimos que me encargaría de laspruebas en hardware. Debido a esto, una de mis primeras tareas consistió en conseguir el firmwarenecesario en la tarjeta de memoria para ejecutar nuestro SO en la Raspberry Pi. Además, tambiéninvestigué, junto a mis compañeros, como funcionaba las distintas partes del proceso de arranquede esta placa.

Tras esto, y basándome en el tutorial1 de Jake Sandler aportado por José L. Risco Martín,hice una primera aproximación al SO la cual ejecutaba con éxito la fase de boot, convirtiendola Raspberry en un sistema monoprocesador, y conseguía hacer parpadear un LED utilizando laUART.

Tras esto, y continuando con el tutorial, realicé los módulos de memoria y memoria dinámica.Para realizar la parte de memoria, tuve que informarme de cómo se pasaban datos sobre el hardwa-re al software en la Raspberry Pi, ya que necesitaba saber cuánta memoria tenía disponible. Estose hace mediante los atags, así que creé un archivo que contenía todas las estructuras relacionadascon los atags, aunque finalmente solo implementé el método que devolvía la cantidad de memoriadisponible.

Volviendo al módulo de memoria, y fijándome en el tutorial, cree el método de inicializaciónque dividía la memoria en páginas, y dividía estas en tres secciones, además de corregir varioserrores de concepto del tutorial. En este proceso, también inicialicé los metadatos necesarios parael correcto manejo de las páginas. Además, implementé métodos que permitían obtener páginasde memoria para ser utilizadas en otros módulos, y liberarlas una vez finalizado su cometido.

Tras el módulo anterior, en el mismo archivo, comencé con el módulo de memoria dinámica.Para ello, inicialicé una de las secciones del módulo anterior para obtener una zona de memoriapara extraer fragmentos del tamaño requerido. Además, implementé métodos basados en el tutorialpara obtener fragmentos y liberarlos, modificados para solucionar problemas con el tratamientode punteros. Estos métodos también realizan un proceso de desfragmentación de la memoria.

Mientras trabajaba en los módulos anteriores, realizaba pruebas en hardware para probar dis-tintos módulos que había creado mi compañero Tomás Golomb Durán. Estos módulos son laGPIO, encargada de manejar los pines de la Raspberry Pi, y el módulo encargado de manejarel HDMI. Además, hice pruebas para intentar comunicarme con la Raspberry Pi a través de laUART física, utilizando un cable USB-TTL. Pese a que tanto la UART como el HDMI funcio-naban perfectamente en el simulador, todas las pruebas en hardware fueron infructuosas, por loque unánimemente, y tras consultar a nuestro director, decidimos que a causa de la pandemia y lasdificultades de no poder depurar en grupo nos centrásemos en implementar para el simulador.

1https://jsandler18.github.io/

Page 87: Desarrollo de un Sistema Operativo para Raspberry Pi 2

87

El siguiente módulo en el que participé fue el de procesos. Basándome en el tutorial nuevamen-te, implementé las estructuras que almacenarían los datos de la PCB, además de los métodos parainicializar las estructuras y elementos necesarios. También implementé los métodos para crear unnuevo proceso y borrar un proceso acabado, además de una función de impresión para depurar elcódigo. En relación al cambio entre procesos, utilicé y manejé el código ensamblador del tutorialpara obtener una primera aproximación. Tras ejecutar el código y depurar, comprendimos que ha-bía un gran fallo de concepto en el diseño del cambio de procesos. A partir de este momento, tomóel relevo mi compañero Alejandro Cancelo Correia, ya que tenía más conocimientos del lenguajeensamblador, dado que había implementado el módulo de interrupciones, el cual contiene una grancantidad de ensamblador. Debido a la importancia de este módulo, y a las complejidades genera-das por él, todo el grupo participó en su depuración varias veces, ya que encontramos errores yfallos de concepto en varias ocasiones.

El último módulo que implementé fue el sistema de gestión de archivos. Para realizarlo, busquéinformación de las distintas estructuras que utiliza un sistema basado en i-nodos, como ext2, asícomo la manera en la que gestiona la estructura de directorios. Tras investigar lo suficiente imple-menté las estructuras necesarias y la tabla de i-nodos, así como la inicialización del directorio raízy los metadatos de los i-nodos. Tras esto, implementé los métodos básicos, crear y borrar directo-rios y archivos y leer y escribir archivos. Además, para darle más funcionalidades, añadí métodospara cambiar de directorio y listar archivos, además de crear varias funciones de impresión paradepurar el proceso.

Después de realizar lo anterior, y para equiparar un poco más el sistema a un sistema de gestiónde archivos común, añadí los procesamientos necesarios para permitir que hubiesen paths relativosy absolutos. Finalmente, y para permitir que el código fuese más usable y editable, añadí unainterfaz en la que el usuario puede implementar sus propio métodos, sobrescribiendo lo creadospor defecto.

La última parte que implementé fueron los comandos que se ejecutan desde la consola. Trasla realización de la consola y del mecanismo para crear y registrar comandos por parte de micompañero Tomás Golomb Durán, se permitía implementar comandos propios. Por ello, decidíimplementar distintos comandos en cada uno de los módulos en los que había participado, demanera que el usuario final del SO pudiese realizar acciones como manejar archivos o ver unalista de los procesos en ejecución. Para crear estos comandos, la mayoría de las funciones ya lashabía implementado, aunque también tuve que crear otras y adaptar algunas.

En resumen, mi aportación al TFG fueron: las pruebas en hardware iniciales, los módulos dememoria y memoria dinámica, el módulo de gestión de archivos y la implementación de varioscomandos para la consola.

Y para terminar, también me gustaría comentar que ha habido un tiempo dedicado al aprendi-zaje del manejo de Github y el protocolo git. Este aprendizaje no es solo a nivel individual, si notambién a nivel cooperativo, ya que manejar un proyecto tan amplio, y además, tan modularizado,ha provocado que le tuviésemos que sacar el máximo partido posible a la gestión de ramas.

Page 88: Desarrollo de un Sistema Operativo para Raspberry Pi 2

Apéndice A

Introduction

This Bachelor’s Degree Final Project is focused on the design and implementation of an Ope-rating System for the Raspberry Pi 2, the second version of the well-known series of single-boardcomputers made by the english Raspberry Pi foundation [7].

This project has consisted in the design of the essential modules that define an operating sys-tem. These modules can be grouped into three sections:

The physical memory management is the essential section that support most of the modu-les. Its components are in charge for the distribution of the available memory between therest of the modules without confrontations. For example, the section’s modules take care ofthe processesñeeds, allowing concurrent access to data without race conditions.

The processing control section is in charge of rationing the processor executing powerusing the processes management system in combination with the interrupt handling system,allowing to choose between different schemes for adapting to the eventual needs.

The input/output module offers many alternatives for data reception and transmission, forexample, the GPIO pins or the graphical user interface available through the HDMI port andso breaking the dependency with an auxiliary OS.

A.1. Objectives

The project’s main goal is to develop an open source with educational purposes OperatingSystem for the Raspberry Pi 2 called artOS.

This project can be used by the community as a basics tool in the Operating Systems basedsubjects with the main goal of simplify and speed up the process of renewing and improving thecurriculum and practical activities of these. The project is structured to allow modifications inan easy way, giving priority to the simplicity and understanding. Also, it’s important that the OScould be launched in an emulator, so we can easily make changes, tests and debug errors and that’swhy we chose QEMU.

The developed operating system achieves the following requisites:

Basic support for the ARM architecture.

88

Page 89: Desarrollo de un Sistema Operativo para Raspberry Pi 2

A.2. STATE OF THE ART 89

Minimal input/output system, with a interrupt management layer and a basic graphical userinterface.

Multi-threading execution with locks that allows concurrency.

Memory management and a file system module with basic features.

A.2. State of the art

The Raspberry Pi foundation has the objective of encouraging the education of computer scien-ce. Without a doubt, it has been reached considering this project existence, and the fact that thisdevice has transcended beyond, becoming a general purpose microprocessor that stands out due toits extensive use in robotics, due to the bank of pins that all previous and future versions have ina lateral of the board. In addition, it has been developed a great community around forums, blogs,and websites which has result in several open source tools.

The first Raspberry Pi was released in February 2012. After its release, in June that sameyear, was released the first totally compatible OS with Raspberry PI infrastructure, whose nameis Raspbian [15]. Besides, the interest in operating systems designed for Raspberry Pi has beenincreasing thanks to the rise of IoT, so that even Microsoft released its own compatible OS [10]with Raspberry Pi.

As regards the educational environment, there are some attempts of open source developmentwith academic purpose, like our project. An example is this Cambridge University project [11]which tries to implement the OS in assembly code for the Raspberry Pi first version. Nevertheless,our project is programmed in C and assembly language, which makes it more legible. Besides, wehave progressed further than the scope of this attempt.

Another related project, which we have used as a guide as it has been designed for the Rasp-berry Pi 2, developed by Jake Sandler [17] from Google. He explains in a very free-flowing waythe procedures, although he made some mistakes. These range from simple errors like a bad mana-gement of pointers to a big misunderstanding related with processes. Besides, we have developedfurther than this attempt vision, implementing a graphical user interface with commands and a filesystem.

Lastly, the two previous attempts don’t make an effort to improve the code’s usability, whilewe have created libraries and interfaces in order to ease future development.

A.3. Methodology and work plan

Along with our project director, José L. Risco Martín, we decided to conduct periodical mee-tings in which the progress was verified and the following tasks considered. As a result of COVID-19 pandemic and the state of alarm decree, we kept the meetings but telematically, using the Meet

application made by Google. In relation to the communication between members, it has beenused messaging apps and, before the pandemic, we used to gather in the Computer EngineeringFaculty’s library group rooms and laboratories.

Page 90: Desarrollo de un Sistema Operativo para Raspberry Pi 2

90 APÉNDICE A. INTRODUCTION

As the group is formed by three components, the work has been distributed in a developmentbranch for each student. To achieve this, git has been used for versions control and a GitHub

repository to save and manage our progress. Besides, to ease the use of the modules betweenmembers, the code has been commented using the same syntax defined by JavaDoc.

Regarding this document, each member has explained technically his work in the Design chap-ter 2. The rest of the chapters have been equally distributed for it’s development with some iterationbetween the students and the director José L. Risco Martín.

A.4. Document structure

This document collects the development process of the operating system, how to compile andexecute it and the final conclusions. The structure is as it follows:

1. The second chapter explains technically the design with implementation details and usageexamples of the Operating System modules.

2. The third chapter shows how to compile and run the operating system and the includedfeatures in it.

3. In the fourth chapter explains the final conclusions and the future lines of work are indicated.

4. Lastly, the fifth contains the summary of each member contributions.

Page 91: Desarrollo de un Sistema Operativo para Raspberry Pi 2

Apéndice B

Conclusions and future work

In this Bachelor’s Degree Final Project we have developed an utterly functional OperatingSystem for Raspberry Pi 2. To ease the design and to start as soon as possible with the project, wehave reused the Raspbian 4.19 boot process. The developed modules are explained below:

The kernel is the main module of our Operating System, it is in charge of initializing correctlyand in order the rest of the modules and hardware configurations. For this reason, the kernel isexecuted after the boot process.

The I/O management module was one of the first implemented sections along with this project.With this module we can send and receive data through the UART. Besides, we have created alibrary that makes easier the usage of the input/output bank of pins characteristic of RaspberryPi. A huge part of this project development depends on debugging, so, without this module, wewouldn’t have been able to create this operating system.

The interrupts and exceptions module allows us to process any event that interfere with theCPU’s normal execution. The goal of this module is to support interrupts, but as the rest of ex-ceptions are processed in the same way, we also have created what is necessary to support alltype of exceptions. With this module we are able to use a physical clock to perform periodicallyinterruptions to any of the processes executing, which is essential for the processes managementmodule.

The memory management module provides organization of the memory that can be accessibleby the CPU, and the capability to allocate memory without conflicts among modules. We useAtags to retrieve the available memory, a Raspberry Pi protocol to pass hardware system datawhen booting up the system.

The processes management module keeps the track of programs that have a PCB (ProcessControl Block) associated with them. As mentioned before, using the interrupts and exceptionsmodule we have designed a scheduler that allows concurrency between processes.

91

Page 92: Desarrollo de un Sistema Operativo para Raspberry Pi 2

92 APÉNDICE B. CONCLUSIONS AND FUTURE WORK

The file system management module creates an abstract layer to manage data inside the ope-rating system with files and directories based on the indexed node file system implemented inGNU/Linux.

The graphical user interface module takes profit of the mailbox channel from the architectureto communicate the graphics card with the processor, obtaining a shared section that can be usedby the CPU to send information at pixel level to be displayed through the HDMI port. Theseoperations are the base of the GPU library that uses the graphical user interface module. By usingthis library, we have developed a system based on the Android views system, which results in acommand console running on its own process.

Lastly, the synchronization module allows atomicity on critical sections using locks, essentialfor concurrent processing.

In this project, we have used the knowledge learnt in subjects such as: Operating Systems, Ad-vanced Operating Systems and Networks, Data Structures, Fundamentals of Algorithms, SoftwareEngineering, Computer Organization, Interactive Systems Development, etc. Besides the technicalpart of the project, we have also applied teamwork and parallel work using version control systemsand a modular design that prevents conflicts during the implementation.

We also have acquired new abilities like gathering and filtering useful information through theofficial manual documentations, a better knowledge of the ARM architecture and its programmingand look for solutions in unexpected situations.

B.1. Future work

We think that the project achieve the specifications of introduction’s 1 section, and using theseas basis we want to propose some future projects:

First of all, should be extended the support for different execution modes, as virtual memoryor security.

In second, use the implemented interrupts to create a simple, efficient interface that makeeasier to register C methods for interruption handling.

In third place, improve the file system module to support hard links and symbolic links, filepermissions, etc.

In fourth place, implement different schedulers using the processes module interface.In fifth place, related to the graphic user interface module, give executing support to the GPU

in order to reduce the CPU load. Furthermore, it can be implemented a windows system using theviews and view groups framework.

Lastly, the Raspberry Pi hardware offers a lot of possibilities that can be used, as the USBports, Ethernet, the sound system, Bluetooth, WiFi and others. Also the OS can be used as a toolin other branches like robotics.

Page 93: Desarrollo de un Sistema Operativo para Raspberry Pi 2

Bibliografía

[1] Android. Código fuente de ViewGroup.java. https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/view/

ViewGroup.java.

[2] Android. Código fuente de View.java. https://github.com/aosp-mirror/

platform_frameworks_base/blob/master/core/java/android/view/

View.java.

[3] Android. Layouts system overview. https://developer.android.com/guide/

topics/ui/declaring-layout.

[4] ARM. Official developer ARM documentation. https://developer.arm.com/.

[5] Daniel Pierre Bovet and Marco Cesati. Understanding the Linux Kernel. O’Really, 2000.

[6] Raspberry Pi foundation. Proceso para utilizar sistemas operativos en la Rasp-

berry Pi. https://www.raspberrypi.org/documentation/installation/installing-images/.

[7] Raspberry Pi foundation. Página principal de Raspberry Pi. https://www.

raspberrypi.org/.

[8] GNU. Uso de la regla make de GNU. https://www.gnu.org/software/make/.

[9] Christopher Hinds and William Hohl. ARM Assembly Language: Fundamentals and Techni-

ques. CRC Press, 2nd edition, 2014.

[10] Microsoft. Sección de IoT de Microsoft. https://developer.microsoft.com/

es-es/windows/iot/.

[11] Department of Computer Science and Technology of Cambridge. Baking Pi, curso para el

desarrollo de un Sistema Operativo para Raspberry Pi. https://www.cl.cam.ac.

uk/projects/raspberrypi/tutorials/os/.

[12] Raspberry Pi. BCM2837 ARM Peripherals. https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.

Revised.-.V2-1.pdf.

93

Page 94: Desarrollo de un Sistema Operativo para Raspberry Pi 2

94 BIBLIOGRAFÍA

[13] Raspberry Pi. Tutorial oficial de instalación de Raspbian en la tarjeta MicroSD. https://www.raspberrypi.org/downloads/.

[14] Jesús Carretero Pérez. Sistemas operativos : una visión aplicada. McGraw-Hill / Interame-ricana de España, 2 edition, 2007.

[15] Raspbian. Página principal de Raspbian. https://www.raspbian.org/.

[16] Vincent Sanders. Sección de un artículo sobre arrancar un kernel ARM de Linux que

describe los ATAGS. http://www.simtec.co.uk/products/SWLINUX/files/booting_article.html#appendix_tag_reference.

[17] Jake Sandler. Building an Operating System for the Raspberry Pi. https://

jsandler18.github.io/.

[18] Andrew S. Tanenbaum. Modern Operative Sistems. Prentice Hall, 4th edition, 2014.

Page 95: Desarrollo de un Sistema Operativo para Raspberry Pi 2

Agradecimientos

Agradecemos a nuestro director José L. Risco Martín por el tiempo cedido para orientarnos yayudarnos a lo largo del desarrollo de este trabajo. También queremos agradecer a todos aquellosque nos han animado a seguir avanzando a raíz de la fuerte situación generada por la pandemia deCOVID-19.

95