diff --git a/blog/config.toml b/blog/config.toml
index 870e69a6..c1fbc62b 100644
--- a/blog/config.toml
+++ b/blog/config.toml
@@ -34,7 +34,7 @@ skip_anchor_prefixes = [
subtitle = "Philipp Oppermann's blog"
author = { name = "Philipp Oppermann" }
default_language = "en"
-languages = ["en", "zh-CN", "zh-TW", "fr", "ja", "fa", "ru", "ko", "ar"]
+languages = ["en", "zh-CN", "zh-TW", "fr", "ja", "fa", "ru", "ko", "ar", "es"]
[translations]
lang_name = "English (original)"
@@ -254,3 +254,28 @@ support_me = """
comment_note = """
Do you have a problem, want to share feedback, or discuss further ideas? Feel free to leave a comment here! Please stick to English and follow Rust's code of conduct. This comment thread directly maps to a discussion on GitHub, so you can also comment there if you prefer.
"""
+
+# Spanish
+[languages.es]
+title = "Writing an OS in Rust"
+description = "This blog series creates a small operating system in the Rust programming language. Each post is a small tutorial and includes all needed code."
+[languages.es.translations]
+lang_name = "Spanish"
+toc = "Tabla de Contenidos"
+all_posts = "« Todos los Posts"
+comments = "Comentarios"
+comments_notice = "Por favor deja tus comentarios en inglés si es posible."
+readmore = "leer más »"
+not_translated = "(Este post aún no está traducido.)"
+translated_content = "Contenido Traducido:"
+translated_content_notice = "Esta es una traducción comunitaria del post _original.title_. Puede estar incompleta, desactualizada o contener errores. ¡Por favor reporta cualquier problema!"
+translated_by = "Traducción por"
+translation_contributors = "Con contribuciones de"
+word_separator = "y"
+support_me = """
+
Apóyame
+
Crear y mantener este blog y las bibliotecas asociadas es mucho trabajo, pero realmente disfruto haciéndolo. Al apoyarme, me permites invertir más tiempo en nuevo contenido, nuevas características y mantenimiento continuo. La mejor manera de apoyarme es patrocinarme en GitHub. ¡Gracias!
+"""
+comment_note = """
+¿Tienes algún problema, quieres compartir comentarios o discutir más ideas? ¡No dudes en dejar un comentario aquí! Por favor, utiliza inglés y sigue el código de conducta de Rust. Este hilo de comentarios se vincula directamente con una discusión en GitHub, así que también puedes comentar allí si lo prefieres.
+"""
diff --git a/blog/content/_index.es.md b/blog/content/_index.es.md
new file mode 100644
index 00000000..4dfc7ca6
--- /dev/null
+++ b/blog/content/_index.es.md
@@ -0,0 +1,13 @@
++++
+template = "edition-2/index.html"
++++
+
+
Escribiendo un sistema operativo en Rust
+
+
+
+Esta serie de blogs crea un pequeño sistema operativo en el [lenguaje de programación Rust](https://www.rust-lang.org/). Cada publicación es un pequeño tutorial e incluye todo el código necesario, para que puedas seguir los pasos si lo deseas. El código fuente también está disponible en el [repositorio correspondiente de Github](https://github.com/phil-opp/blog_os).
+
+Última publicación:
+
+
diff --git a/blog/content/edition-2/posts/01-freestanding-rust-binary/index.es.md b/blog/content/edition-2/posts/01-freestanding-rust-binary/index.es.md
new file mode 100644
index 00000000..a4a502d9
--- /dev/null
+++ b/blog/content/edition-2/posts/01-freestanding-rust-binary/index.es.md
@@ -0,0 +1,522 @@
++++
+title = "Un Binario Rust Autónomo"
+weight = 1
+path = "es/freestanding-rust-binary"
+date = 2018-02-10
+
+[extra]
+chapter = "Bare Bones"
+
+# GitHub usernames of the people that translated this post
+translators = ["dobleuber"]
++++
+
+El primer paso para crear nuestro propio kernel de sistema operativo es crear un ejecutable en Rust que no enlace con la biblioteca estándar. Esto hace posible ejecutar código Rust directamente en el [bare metal] sin un sistema operativo subyacente.
+
+[bare metal]: https://en.wikipedia.org/wiki/Bare_machine
+
+
+
+Este blog se desarrolla abiertamente en [GitHub]. Si tienes algún problema o pregunta, por favor abre un issue allí. También puedes dejar comentarios [al final]. El código fuente completo para esta publicación se encuentra en la rama [`post-01`][post branch].
+
+[GitHub]: https://github.com/phil-opp/blog_os
+[al final]: #comments
+
+[post branch]: https://github.com/phil-opp/blog_os/tree/post-01
+
+
+
+## Introducción
+Para escribir un kernel de sistema operativo, necesitamos código que no dependa de características del sistema operativo. Esto significa que no podemos usar hilos, archivos, memoria dinámica, redes, números aleatorios, salida estándar ni ninguna otra característica que requiera abstracciones de sistema operativo o hardware específico. Esto tiene sentido, ya que estamos intentando escribir nuestro propio sistema operativo y nuestros propios controladores.
+
+Esto implica que no podemos usar la mayor parte de la [biblioteca estándar de Rust], pero hay muchas características de Rust que sí _podemos_ usar. Por ejemplo, podemos utilizar [iteradores], [closures], [pattern matching], [option] y [result], [formateo de cadenas] y, por supuesto, el [sistema de ownership]. Estas características hacen posible escribir un kernel de una manera muy expresiva y de alto nivel, sin preocuparnos por el [comportamiento indefinido] o la [seguridad de la memoria].
+
+[option]: https://doc.rust-lang.org/core/option/
+[result]: https://doc.rust-lang.org/core/result/
+[biblioteca estándar de Rust]: https://doc.rust-lang.org/std/
+[iteradores]: https://doc.rust-lang.org/book/ch13-02-iterators.html
+[closures]: https://doc.rust-lang.org/book/ch13-01-closures.html
+[pattern matching]: https://doc.rust-lang.org/book/ch06-00-enums.html
+[formateo de cadenas]: https://doc.rust-lang.org/core/macro.write.html
+[sistema de ownership]: https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html
+[comportamiento indefinido]: https://www.nayuki.io/page/undefined-behavior-in-c-and-cplusplus-programs
+[seguridad de la memoria]: https://tonyarcieri.com/it-s-time-for-a-memory-safety-intervention
+
+Para crear un kernel de sistema operativo en Rust, necesitamos crear un ejecutable que pueda ejecutarse sin un sistema operativo subyacente. Dicho ejecutable se llama frecuentemente un ejecutable “autónomo” o de “bare metal”.
+
+Esta publicación describe los pasos necesarios para crear un binario autónomo en Rust y explica por qué son necesarios. Si solo te interesa un ejemplo mínimo, puedes **[saltar al resumen](#resumen)**.
+
+## Deshabilitando la Biblioteca Estándar
+Por defecto, todos los crates de Rust enlazan con la [biblioteca estándar], que depende del sistema operativo para características como hilos, archivos o redes. También depende de la biblioteca estándar de C, `libc`, que interactúa estrechamente con los servicios del sistema operativo. Como nuestro plan es escribir un sistema operativo, no podemos usar ninguna biblioteca que dependa del sistema operativo. Por lo tanto, tenemos que deshabilitar la inclusión automática de la biblioteca estándar mediante el atributo [`no_std`].
+
+[biblioteca estándar]: https://doc.rust-lang.org/std/
+[`no_std`]: https://doc.rust-lang.org/1.30.0/book/first-edition/using-rust-without-the-standard-library.html
+
+Comenzamos creando un nuevo proyecto de aplicación en Cargo. La forma más fácil de hacerlo es a través de la línea de comandos:
+
+
+```
+cargo new blog_os --bin --edition 2018
+```
+
+Nombré el proyecto `blog_os`, pero, por supuesto, puedes elegir tu propio nombre. La bandera `--bin` especifica que queremos crear un binario ejecutable (en contraste con una biblioteca), y la bandera `--edition 2018` indica que queremos usar la [edición 2018] de Rust para nuestro crate. Al ejecutar el comando, Cargo crea la siguiente estructura de directorios para nosotros:
+
+[2018 edition]: https://doc.rust-lang.org/nightly/edition-guide/rust-2018/index.html
+
+```
+blog_os
+├── Cargo.toml
+└── src
+ └── main.rs
+```
+
+El archivo `Cargo.toml` contiene la configuración del crate, como el nombre del crate, el autor, el número de [versión semántica] y las dependencias. El archivo `src/main.rs` contiene el módulo raíz de nuestro crate y nuestra función `main`. Puedes compilar tu crate utilizando `cargo build` y luego ejecutar el binario compilado `blog_os` ubicado en la subcarpeta `target/debug`.
+
+[semantic version]: https://semver.org/
+
+### El Atributo `no_std`
+
+Actualmente, nuestro crate enlaza implícitamente con la biblioteca estándar. Intentemos deshabilitar esto añadiendo el [`atributo no_std`]:
+
+```rust
+// main.rs
+
+#![no_std]
+
+fn main() {
+ println!("Hello, world!");
+}
+```
+
+Cuando intentamos compilarlo ahora (ejecutando `cargo build`), ocurre el siguiente error:
+
+```
+error: cannot find macro `println!` in this scope
+ --> src/main.rs:4:5
+ |
+4 | println!("Hello, world!");
+ | ^^^^^^^
+```
+
+La razón de este error es que la [macro `println`] forma parte de la biblioteca estándar, la cual ya no estamos incluyendo. Por lo tanto, ya no podemos imprimir cosas. Esto tiene sentido, ya que `println` escribe en la [salida estándar], que es un descriptor de archivo especial proporcionado por el sistema operativo.
+
+[macro `println`]: https://doc.rust-lang.org/std/macro.println.html
+[salida estándar]: https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29
+
+Así que eliminemos la impresión e intentemos de nuevo con una función `main` vacía:
+
+```rust
+// main.rs
+
+#![no_std]
+
+fn main() {}
+```
+
+```
+> cargo build
+error: `#[panic_handler]` function required, but not found
+error: language item required, but not found: `eh_personality`
+```
+
+Ahora el compilador indica que falta una función `#[panic_handler]` y un _elemento de lenguaje_ (_language item_).
+
+## Implementación de Panic
+
+El atributo `panic_handler` define la función que el compilador invoca cuando ocurre un [panic]. La biblioteca estándar proporciona su propia función de panico, pero en un entorno `no_std` debemos definirla nosotros mismos:
+
+[panic]: https://doc.rust-lang.org/stable/book/ch09-01-unrecoverable-errors-with-panic.html
+
+```rust
+// en main.rs
+
+use core::panic::PanicInfo;
+
+/// Esta función se llama cuando ocurre un pánico.
+#[panic_handler]
+fn panic(_info: &PanicInfo) -> ! {
+ loop {}
+}
+```
+
+El [parámetro `PanicInfo`][PanicInfo] contiene el archivo y la línea donde ocurrió el panic, así como el mensaje opcional del panic. La función no debería retornar nunca, por lo que se marca como una [función divergente][diverging function] devolviendo el [tipo “never”][“never” type] `!`. Por ahora, no hay mucho que podamos hacer en esta función, así que simplemente entramos en un bucle infinito.
+
+[PanicInfo]: https://doc.rust-lang.org/nightly/core/panic/struct.PanicInfo.html
+[diverging function]: https://doc.rust-lang.org/1.30.0/book/first-edition/functions.html#diverging-functions
+[“never” type]: https://doc.rust-lang.org/nightly/std/primitive.never.html
+
+## El Elemento de Lenguaje `eh_personality`
+
+Los elementos de lenguaje son funciones y tipos especiales que el compilador requiere internamente. Por ejemplo, el trait [`Copy`] es un elemento de lenguaje que indica al compilador qué tipos tienen [_semántica de copia_][`Copy`]. Si observamos su [implementación][copy code], veremos que tiene el atributo especial `#[lang = "copy"]`, que lo define como un elemento de lenguaje.
+
+[`Copy`]: https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html
+[copy code]: https://github.com/rust-lang/rust/blob/485397e49a02a3b7ff77c17e4a3f16c653925cb3/src/libcore/marker.rs#L296-L299
+
+Aunque es posible proporcionar implementaciones personalizadas de elementos de lenguaje, esto debería hacerse solo como último recurso. La razón es que los elementos de lenguaje son detalles de implementación altamente inestables y ni siquiera están verificados por tipos (el compilador no comprueba si una función tiene los tipos de argumento correctos). Afortunadamente, hay una forma más estable de solucionar el error relacionado con el elemento de lenguaje mencionado.
+
+El [elemento de lenguaje `eh_personality`][`eh_personality` language item] marca una función utilizada para implementar el [desenrollado de pila][stack unwinding]. Por defecto, Rust utiliza unwinding para ejecutar los destructores de todas las variables de pila activas en caso de un [pánico][panic]. Esto asegura que toda la memoria utilizada sea liberada y permite que el hilo principal capture el pánico y continúe ejecutándose. Sin embargo, el unwinding es un proceso complicado y requiere algunas bibliotecas específicas del sistema operativo (por ejemplo, [libunwind] en Linux o [manejadores estructurados de excepciones][structured exception handling] en Windows), por lo que no queremos usarlo en nuestro sistema operativo.
+
+[`eh_personality` language item]: https://github.com/rust-lang/rust/blob/edb368491551a77d77a48446d4ee88b35490c565/src/libpanic_unwind/gcc.rs#L11-L45
+[stack unwinding]: https://www.bogotobogo.com/cplusplus/stackunwinding.php
+[libunwind]: https://www.nongnu.org/libunwind/
+[structured exception handling]: https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling
+[panic]: https://doc.rust-lang.org/book/ch09-01-unrecoverable-errors-with-panic.html
+
+### Deshabilitando el Unwinding
+
+Existen otros casos de uso en los que el no es deseable, por lo que Rust proporciona una opción para [abortar en caso de pánico][abort on panic]. Esto desactiva la generación de información de símbolos de unwinding y, por lo tanto, reduce considerablemente el tamaño del binario. Hay múltiples lugares donde podemos deshabilitar el unwinding. La forma más sencilla es agregar las siguientes líneas a nuestro archivo `Cargo.toml`:
+
+```toml
+[profile.dev]
+panic = "abort"
+
+[profile.release]
+panic = "abort"
+```
+
+Esto establece la estrategia de pánico en `abort` tanto para el perfil `dev` (utilizado en `cargo build`) como para el perfil `release` (utilizado en `cargo build --release`). Ahora, el elemento de lenguaje `eh_personality` ya no debería ser necesario.
+
+[abort on panic]: https://github.com/rust-lang/rust/pull/32900
+
+Ahora hemos solucionado ambos errores anteriores. Sin embargo, si intentamos compilarlo ahora, ocurre otro error:
+
+```
+> cargo build
+error: requires `start` lang_item
+```
+
+Nuestro programa carece del elemento de lenguaje `start`, que define el punto de entrada.
+
+## El Atributo `start`
+
+Podría pensarse que la función `main` es la primera que se ejecuta al correr un programa. Sin embargo, la mayoría de los lenguajes tienen un [sistema de tiempo de ejecución][runtime system], encargado de tareas como la recolección de basura (por ejemplo, en Java) o los hilos de software (por ejemplo, goroutines en Go). Este sistema de tiempo de ejecución necesita ejecutarse antes de `main`, ya que debe inicializarse.
+
+[runtime system]: https://en.wikipedia.org/wiki/Runtime_system
+
+En un binario típico de Rust que enlaza con la biblioteca estándar, la ejecución comienza en una biblioteca de tiempo de ejecución de C llamada `crt0` ("C runtime zero"), que configura el entorno para una aplicación en C. Esto incluye la creación de una pila y la colocación de los argumentos en los registros adecuados. Luego, el tiempo de ejecución de C invoca el [punto de entrada del tiempo de ejecución de Rust][rt::lang_start], que está marcado por el elemento de lenguaje `start`. Rust tiene un tiempo de ejecución muy minimalista, que se encarga de tareas menores como configurar los guardias de desbordamiento de pila o imprimir un backtrace en caso de pánico. Finalmente, el tiempo de ejecución llama a la función `main`.
+
+[rt::lang_start]: https://github.com/rust-lang/rust/blob/bb4d1491466d8239a7a5fd68bd605e3276e97afb/src/libstd/rt.rs#L32-L73
+
+Nuestro ejecutable autónomo no tiene acceso al tiempo de ejecución de Rust ni a `crt0`, por lo que necesitamos definir nuestro propio punto de entrada. Implementar el elemento de lenguaje `start` no ayudaría, ya que aún requeriría `crt0`. En su lugar, debemos sobrescribir directamente el punto de entrada de `crt0`.
+
+### Sobrescribiendo el Punto de Entrada
+
+Para indicar al compilador de Rust que no queremos usar la cadena normal de puntos de entrada, agregamos el atributo `#![no_main]`:
+
+```rust
+#![no_std]
+#![no_main]
+
+use core::panic::PanicInfo;
+
+/// Esta función se llama cuando ocurre un pánico.
+#[panic_handler]
+fn panic(_info: &PanicInfo) -> ! {
+ loop {}
+}
+```
+
+Podrás notar que eliminamos la función `main`. La razón es que una función `main` no tiene sentido sin un sistema de tiempo de ejecución subyacente que la invoque. En su lugar, estamos sobrescribiendo el punto de entrada del sistema operativo con nuestra propia función `_start`:
+
+```rust
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ loop {}
+}
+```
+
+Al usar el atributo `#[no_mangle]`, deshabilitamos el [name mangling] para asegurarnos de que el compilador de Rust realmente genere una función con el nombre `_start`. Sin este atributo, el compilador generaría un símbolo críptico como `_ZN3blog_os4_start7hb173fedf945531caE` para dar un nombre único a cada función. Este atributo es necesario porque necesitamos informar al enlazador el nombre de la función de punto de entrada en el siguiente paso.
+
+También debemos marcar la función como `extern "C"` para indicar al compilador que debe usar la [convención de llamadas en C][C calling convention] para esta función (en lugar de la convención de llamadas de Rust, que no está especificada). El motivo para nombrar la función `_start` es que este es el nombre predeterminado del punto de entrada en la mayoría de los sistemas.
+
+[name mangling]: https://en.wikipedia.org/wiki/Name_mangling
+[C calling convention]: https://en.wikipedia.org/wiki/Calling_convention
+
+El tipo de retorno `!` significa que la función es divergente, es decir, no está permitido que retorne nunca. Esto es necesario porque el punto de entrada no es llamado por ninguna función, sino que es invocado directamente por el sistema operativo o el bootloader. En lugar de retornar, el punto de entrada debería, por ejemplo, invocar la [llamada al sistema `exit`][`exit` system call] del sistema operativo. En nuestro caso, apagar la máquina podría ser una acción razonable, ya que no queda nada por hacer si un binario autónomo regresa. Por ahora, cumplimos con este requisito entrando en un bucle infinito.
+
+[`exit` system call]: https://en.wikipedia.org/wiki/Exit_(system_call)
+
+Cuando ejecutamos `cargo build` ahora, obtenemos un feo error del _linker_ (enlazador).
+
+## Errores del Enlazador
+
+El enlazador es un programa que combina el código generado en un ejecutable. Dado que el formato del ejecutable varía entre Linux, Windows y macOS, cada sistema tiene su propio enlazador que lanza errores diferentes. Sin embargo, la causa fundamental de los errores es la misma: la configuración predeterminada del enlazador asume que nuestro programa depende del tiempo de ejecución de C, lo cual no es cierto.
+
+Para solucionar los errores, necesitamos informar al enlazador que no debe incluir el tiempo de ejecución de C. Esto puede hacerse pasando un conjunto específico de argumentos al enlazador o construyendo para un destino de bare metal.
+
+### Construyendo para un Destino de Bare Metal
+
+Por defecto, Rust intenta construir un ejecutable que pueda ejecutarse en el entorno actual de tu sistema. Por ejemplo, si estás usando Windows en `x86_64`, Rust intenta construir un ejecutable `.exe` para Windows que utilice instrucciones `x86_64`. Este entorno se llama tu sistema "host".
+
+Para describir diferentes entornos, Rust utiliza una cadena llamada [_target triple_]. Puedes ver el _target triple_ de tu sistema host ejecutando:
+
+```
+rustc 1.35.0-nightly (474e7a648 2019-04-07)
+binary: rustc
+commit-hash: 474e7a6486758ea6fc761893b1a49cd9076fb0ab
+commit-date: 2019-04-07
+host: x86_64-unknown-linux-gnu
+release: 1.35.0-nightly
+LLVM version: 8.0
+```
+
+El resultado anterior es de un sistema Linux `x86_64`. Vemos que la tripleta del `host` es `x86_64-unknown-linux-gnu`, lo que incluye la arquitectura de la CPU (`x86_64`), el proveedor (`unknown`), el sistema operativo (`linux`) y el [ABI] (`gnu`).
+
+[ABI]: https://en.wikipedia.org/wiki/Application_binary_interface
+
+Al compilar para la tripleta del host, el compilador de Rust y el enlazador asumen que hay un sistema operativo subyacente como Linux o Windows que utiliza el tiempo de ejecución de C de forma predeterminada, lo que provoca los errores del enlazador. Para evitar estos errores, podemos compilar para un entorno diferente que no tenga un sistema operativo subyacente.
+
+Un ejemplo de este tipo de entorno bare metal es la tripleta de destino `thumbv7em-none-eabihf`, que describe un sistema [embebido][embedded] basado en [ARM]. Los detalles no son importantes, lo que importa es que la tripleta de destino no tiene un sistema operativo subyacente, lo cual se indica por el `none` en la tripleta de destino. Para poder compilar para este destino, necesitamos agregarlo usando `rustup`:
+
+```
+rustup target add thumbv7em-none-eabihf
+```
+
+Esto descarga una copia de las bibliotecas estándar (y core) para el sistema. Ahora podemos compilar nuestro ejecutable autónomo para este destino:
+
+```
+cargo build --target thumbv7em-none-eabihf
+```
+
+Al pasar un argumento `--target`, realizamos un [compilado cruzado][cross compile] de nuestro ejecutable para un sistema bare metal. Dado que el sistema de destino no tiene un sistema operativo, el enlazador no intenta enlazar con el tiempo de ejecución de C, y nuestra compilación se completa sin errores del enlazador.
+
+[cross compile]: https://en.wikipedia.org/wiki/Cross_compiler
+
+Este es el enfoque que utilizaremos para construir nuestro kernel de sistema operativo. En lugar de `thumbv7em-none-eabihf`, utilizaremos un [destino personalizado][custom target] que describa un entorno bare metal `x86_64`. Los detalles se explicarán en la siguiente publicación.
+
+[custom target]: https://doc.rust-lang.org/rustc/targets/custom.html
+
+### Argumentos del Enlazador
+
+En lugar de compilar para un sistema bare metal, también es posible resolver los errores del enlazador pasando un conjunto específico de argumentos al enlazador. Este no es el enfoque que usaremos para nuestro kernel, por lo tanto, esta sección es opcional y se proporciona solo para completar. Haz clic en _"Argumentos del Enlazador"_ a continuación para mostrar el contenido opcional.
+
+
+
+Argumentos del Enlazador
+
+En esta sección discutimos los errores del enlazador que ocurren en Linux, Windows y macOS, y explicamos cómo resolverlos pasando argumentos adicionales al enlazador. Ten en cuenta que el formato del ejecutable y el enlazador varían entre sistemas operativos, por lo que se requiere un conjunto diferente de argumentos para cada sistema.
+
+#### Linux
+
+En Linux ocurre el siguiente error del enlazador (resumido):
+
+```
+error: linking with `cc` failed: exit code: 1
+ |
+ = note: "cc" […]
+ = note: /usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
+ (.text+0x12): undefined reference to `__libc_csu_fini'
+ /usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
+ (.text+0x19): undefined reference to `__libc_csu_init'
+ /usr/lib/gcc/../x86_64-linux-gnu/Scrt1.o: In function `_start':
+ (.text+0x25): undefined reference to `__libc_start_main'
+ collect2: error: ld returned 1 exit status
+```
+
+El problema es que el enlazador incluye por defecto la rutina de inicio del tiempo de ejecución de C, que también se llama `_start`. Esta rutina requiere algunos símbolos de la biblioteca estándar de C `libc` que no incluimos debido al atributo `no_std`, por lo que el enlazador no puede resolver estas referencias. Para solucionar esto, podemos indicar al enlazador que no enlace la rutina de inicio de C pasando la bandera `-nostartfiles`.
+
+Una forma de pasar atributos al enlazador a través de Cargo es usar el comando `cargo rustc`. Este comando se comporta exactamente como `cargo build`, pero permite pasar opciones a `rustc`, el compilador subyacente de Rust. `rustc` tiene la bandera `-C link-arg`, que pasa un argumento al enlazador. Combinados, nuestro nuevo comando de compilación se ve así:
+
+```
+cargo rustc -- -C link-arg=-nostartfiles
+```
+
+¡Ahora nuestro crate se compila como un ejecutable autónomo en Linux!
+
+No fue necesario especificar explícitamente el nombre de nuestra función de punto de entrada, ya que el enlazador busca una función con el nombre `_start` por defecto.
+
+#### Windows
+
+En Windows, ocurre un error del enlazador diferente (resumido):
+
+```
+error: linking with `link.exe` failed: exit code: 1561
+ |
+ = note: "C:\\Program Files (x86)\\…\\link.exe" […]
+ = note: LINK : fatal error LNK1561: entry point must be defined
+```
+
+El error "entry point must be defined" significa que el enlazador no puede encontrar el punto de entrada. En Windows, el nombre predeterminado del punto de entrada [depende del subsistema utilizado][windows-subsystems]. Para el subsistema `CONSOLE`, el enlazador busca una función llamada `mainCRTStartup`, y para el subsistema `WINDOWS`, busca una función llamada `WinMainCRTStartup`. Para anular este comportamiento predeterminado y decirle al enlazador que busque nuestra función `_start`, podemos pasar un argumento `/ENTRY` al enlazador:
+
+[windows-subsystems]: https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol
+
+```
+cargo rustc -- -C link-arg=/ENTRY:_start
+```
+
+Por el formato diferente del argumento, podemos ver claramente que el enlazador de Windows es un programa completamente distinto al enlazador de Linux.
+
+Ahora ocurre un error diferente del enlazador:
+
+```
+error: linking with `link.exe` failed: exit code: 1221
+ |
+ = note: "C:\\Program Files (x86)\\…\\link.exe" […]
+ = note: LINK : fatal error LNK1221: a subsystem can't be inferred and must be
+ defined
+```
+
+Este error ocurre porque los ejecutables de Windows pueden usar diferentes [subsistemas][windows-subsystems]. En programas normales, se infieren dependiendo del nombre del punto de entrada: si el punto de entrada se llama `main`, se usa el subsistema `CONSOLE`, y si el punto de entrada se llama `WinMain`, se usa el subsistema `WINDOWS`. Dado que nuestra función `_start` tiene un nombre diferente, necesitamos especificar el subsistema explícitamente:
+
+```
+cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
+```
+
+Aquí usamos el subsistema `CONSOLE`, pero el subsistema `WINDOWS` también funcionaría. En lugar de pasar `-C link-arg` varias veces, podemos usar `-C link-args`, que acepta una lista de argumentos separados por espacios.
+
+Con este comando, nuestro ejecutable debería compilarse exitosamente en Windows.
+
+#### macOS
+
+En macOS, ocurre el siguiente error del enlazador (resumido):
+
+```
+error: linking with `cc` failed: exit code: 1
+ |
+ = note: "cc" […]
+ = note: ld: entry point (_main) undefined. for architecture x86_64
+ clang: error: linker command failed with exit code 1 […]
+```
+
+Este mensaje de error nos indica que el enlazador no puede encontrar una función de punto de entrada con el nombre predeterminado `main` (por alguna razón, en macOS todas las funciones tienen un prefijo `_`). Para establecer el punto de entrada en nuestra función `_start`, pasamos el argumento del enlazador `-e`:
+
+```
+cargo rustc -- -C link-args="-e __start"
+```
+
+La bandera `-e` especifica el nombre de la función de punto de entrada. Dado que en macOS todas las funciones tienen un prefijo adicional `_`, necesitamos establecer el punto de entrada en `__start` en lugar de `_start`.
+
+Ahora ocurre el siguiente error del enlazador:
+
+```
+error: linking with `cc` failed: exit code: 1
+ |
+ = note: "cc" […]
+ = note: ld: dynamic main executables must link with libSystem.dylib
+ for architecture x86_64
+ clang: error: linker command failed with exit code 1 […]
+```
+
+macOS [no admite oficialmente binarios enlazados estáticamente] y requiere que los programas enlacen la biblioteca `libSystem` por defecto. Para anular esto y enlazar un binario estático, se pasa la bandera `-static` al enlazador:
+
+[no admite oficialmente binarios enlazados estáticamente]: https://developer.apple.com/library/archive/qa/qa1118/_index.html
+
+
+```
+cargo rustc -- -C link-args="-e __start -static"
+```
+
+Esto aún no es suficiente, ya que ocurre un tercer error del enlazador:
+
+```
+error: linking with `cc` failed: exit code: 1
+ |
+ = note: "cc" […]
+ = note: ld: library not found for -lcrt0.o
+ clang: error: linker command failed with exit code 1 […]
+```
+
+Este error ocurre porque los programas en macOS enlazan con `crt0` (“C runtime zero”) por defecto. Esto es similar al error que tuvimos en Linux y también se puede resolver añadiendo el argumento del enlazador `-nostartfiles`:
+
+```
+cargo rustc -- -C link-args="-e __start -static -nostartfiles"
+```
+
+Ahora nuestro programa debería compilarse exitosamente en macOS.
+
+#### Unificando los Comandos de Construcción
+
+Actualmente, tenemos diferentes comandos de construcción dependiendo de la plataforma host, lo cual no es ideal. Para evitar esto, podemos crear un archivo llamado `.cargo/config.toml` que contenga los argumentos específicos de cada plataforma:
+
+```toml
+# en .cargo/config.toml
+
+[target.'cfg(target_os = "linux")']
+rustflags = ["-C", "link-arg=-nostartfiles"]
+
+[target.'cfg(target_os = "windows")']
+rustflags = ["-C", "link-args=/ENTRY:_start /SUBSYSTEM:console"]
+
+[target.'cfg(target_os = "macos")']
+rustflags = ["-C", "link-args=-e __start -static -nostartfiles"]
+```
+
+La clave `rustflags` contiene argumentos que se añaden automáticamente a cada invocación de `rustc`. Para más información sobre el archivo `.cargo/config.toml`, consulta la [documentación oficial](https://doc.rust-lang.org/cargo/reference/config.html).
+
+Ahora nuestro programa debería poder construirse en las tres plataformas con un simple `cargo build`.
+
+#### ¿Deberías Hacer Esto?
+
+Aunque es posible construir un ejecutable autónomo para Linux, Windows y macOS, probablemente no sea una buena idea. La razón es que nuestro ejecutable aún espera varias cosas, por ejemplo, que una pila esté inicializada cuando se llama a la función `_start`. Sin el tiempo de ejecución de C, algunos de estos requisitos podrían no cumplirse, lo que podría hacer que nuestro programa falle, por ejemplo, con un error de segmentación.
+
+Si deseas crear un binario mínimo que se ejecute sobre un sistema operativo existente, incluir `libc` y configurar el atributo `#[start]` como se describe [aquí](https://doc.rust-lang.org/1.16.0/book/no-stdlib.html) probablemente sea una mejor idea.
+
+
+
+## Resumen {#resumen}
+
+Un binario mínimo autónomo en Rust se ve así:
+
+`src/main.rs`:
+
+```rust
+#![no_std] // no enlazar con la biblioteca estándar de Rust
+#![no_main] // deshabilitar todos los puntos de entrada a nivel de Rust
+
+use core::panic::PanicInfo;
+
+#[no_mangle] // no modificar el nombre de esta función
+pub extern "C" fn _start() -> ! {
+ // esta función es el punto de entrada, ya que el enlazador busca una función
+ // llamada `_start` por defecto
+ loop {}
+}
+
+/// Esta función se llama cuando ocurre un pánico.
+#[panic_handler]
+fn panic(_info: &PanicInfo) -> ! {
+ loop {}
+}
+```
+
+`Cargo.toml`:
+
+```toml
+[package]
+name = "crate_name"
+version = "0.1.0"
+authors = ["Author Name "]
+
+# el perfil usado para `cargo build`
+[profile.dev]
+panic = "abort" # deshabilitar el desenrollado de la pila en caso de pánico
+
+# el perfil usado para `cargo build --release`
+[profile.release]
+panic = "abort" # deshabilitar el desenrollado de la pila en caso de pánico
+```
+
+Para construir este binario, necesitamos compilar para un destino bare metal, como `thumbv7em-none-eabihf`:
+
+```
+cargo build --target thumbv7em-none-eabihf
+```
+
+Alternativamente, podemos compilarlo para el sistema host pasando argumentos adicionales al enlazador:
+
+```bash
+# Linux
+cargo rustc -- -C link-arg=-nostartfiles
+# Windows
+cargo rustc -- -C link-args="/ENTRY:_start /SUBSYSTEM:console"
+# macOS
+cargo rustc -- -C link-args="-e __start -static -nostartfiles"
+```
+
+Ten en cuenta que este es solo un ejemplo mínimo de un binario autónomo en Rust. Este binario espera varias cosas, por ejemplo, que una pila esté inicializada cuando se llama a la función `_start`. **Por lo tanto, para cualquier uso real de un binario como este, se requieren más pasos**.
+
+## ¿Qué sigue?
+
+La [próxima publicación][next post] explica los pasos necesarios para convertir nuestro binario autónomo en un kernel de sistema operativo mínimo. Esto incluye crear un destino personalizado, combinar nuestro ejecutable con un bootloader y aprender cómo imprimir algo en la pantalla.
+
+[next post]: @/edition-2/posts/02-minimal-rust-kernel/index.md
diff --git a/blog/content/edition-2/posts/01-freestanding-rust-binary/index.md b/blog/content/edition-2/posts/01-freestanding-rust-binary/index.md
index 1550ec59..5a24f99a 100644
--- a/blog/content/edition-2/posts/01-freestanding-rust-binary/index.md
+++ b/blog/content/edition-2/posts/01-freestanding-rust-binary/index.md
@@ -6,6 +6,9 @@ date = 2018-02-10
[extra]
chapter = "Bare Bones"
+
+# GitHub usernames of the people that translated this post
+translators = ["dobleuber"]
+++
The first step in creating our own operating system kernel is to create a Rust executable that does not link the standard library. This makes it possible to run Rust code on the [bare metal] without an underlying operating system.
diff --git a/blog/content/edition-2/posts/02-minimal-rust-kernel/index.es.md b/blog/content/edition-2/posts/02-minimal-rust-kernel/index.es.md
new file mode 100644
index 00000000..139c2622
--- /dev/null
+++ b/blog/content/edition-2/posts/02-minimal-rust-kernel/index.es.md
@@ -0,0 +1,500 @@
++++
+title = "Un Kernel Mínimo en Rust"
+weight = 2
+path = "es/minimal-rust-kernel"
+date = 2018-02-10
+
+[extra]
+chapter = "Bare Bones"
+
+# GitHub usernames of the people that translated this post
+translators = ["dobleuber"]
++++
+
+En esta publicación, crearemos un kernel mínimo de 64 bits en Rust para la arquitectura x86. Partiremos del [un binario Rust autónomo] de la publicación anterior para crear una imagen de disco arrancable que imprima algo en la pantalla.
+
+[un binario Rust autónomo]: @/edition-2/posts/01-freestanding-rust-binary/index.md
+
+
+
+Este blog se desarrolla abiertamente en [GitHub]. Si tienes problemas o preguntas, por favor abre un issue ahí. También puedes dejar comentarios [al final]. El código fuente completo para esta publicación se encuentra en la rama [`post-02`][post branch].
+
+[GitHub]: https://github.com/phil-opp/blog_os
+[al final]: #comments
+
+[post branch]: https://github.com/phil-opp/blog_os/tree/post-02
+
+
+
+## El Proceso de Arranque {#el-proceso-de-arranque}
+Cuando enciendes una computadora, comienza a ejecutar código de firmware almacenado en la [ROM] de la placa madre. Este código realiza una [prueba automática de encendido], detecta la memoria RAM disponible y preinicializa la CPU y el hardware. Después, busca un disco arrancable y comienza a cargar el kernel del sistema operativo.
+
+[ROM]: https://en.wikipedia.org/wiki/Read-only_memory
+[prueba automática de encendido]: https://en.wikipedia.org/wiki/Power-on_self-test
+
+En x86, existen dos estándares de firmware: el “Sistema Básico de Entrada/Salida” (**[BIOS]**) y la más reciente “Interfaz de Firmware Extensible Unificada” (**[UEFI]**). El estándar BIOS es antiguo y está desactualizado, pero es simple y está bien soportado en cualquier máquina x86 desde los años 80. UEFI, en contraste, es más moderno y tiene muchas más funciones, pero es más complejo de configurar (al menos en mi opinión).
+
+[BIOS]: https://en.wikipedia.org/wiki/BIOS
+[UEFI]: https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface
+
+Actualmente, solo proporcionamos soporte para BIOS, pero también planeamos agregar soporte para UEFI. Si te gustaría ayudarnos con esto, revisa el [issue en Github](https://github.com/phil-opp/blog_os/issues/349).
+
+### Arranque con BIOS
+Casi todos los sistemas x86 tienen soporte para arranque con BIOS, incluyendo máquinas más recientes basadas en UEFI que usan un BIOS emulado. Esto es excelente, porque puedes usar la misma lógica de arranque en todas las máquinas del último siglo. Sin embargo, esta amplia compatibilidad también es la mayor desventaja del arranque con BIOS, ya que significa que la CPU se coloca en un modo de compatibilidad de 16 bits llamado [modo real] antes de arrancar, para que los bootloaders arcaicos de los años 80 sigan funcionando.
+
+Pero comencemos desde el principio:
+
+Cuando enciendes una computadora, carga el BIOS desde una memoria flash especial ubicada en la placa madre. El BIOS ejecuta rutinas de autoprueba e inicialización del hardware, y luego busca discos arrancables. Si encuentra uno, transfiere el control a su _bootloader_ (_cargador de arranque_), que es una porción de código ejecutable de 512 bytes almacenada al inicio del disco. La mayoría de los bootloaders son más grandes que 512 bytes, por lo que suelen dividirse en una pequeña primera etapa, que cabe en esos 512 bytes, y una segunda etapa que se carga posteriormente.
+
+El bootloader debe determinar la ubicación de la imagen del kernel en el disco y cargarla en la memoria. Tambien necesita cambiar la CPU del [modo real] de 16 bits primero al [modo protegido] de 32 bits, y luego al [modo largo] de 64 bits, donde están disponibles los registros de 64 bits y toda la memoria principal. Su tercera tarea es consultar cierta información (como un mapa de memoria) desde el BIOS y pasársela al kernel del sistema operativo.
+
+[modo real]: https://en.wikipedia.org/wiki/Real_mode
+[modo protegido]: https://en.wikipedia.org/wiki/Protected_mode
+[modo largo]: https://en.wikipedia.org/wiki/Long_mode
+[segmentación de memoria]: https://en.wikipedia.org/wiki/X86_memory_segmentation
+
+Escribir un bootloader es un poco tedioso, ya que requiere lenguaje ensamblador y muchos pasos poco claros como “escribir este valor mágico en este registro del procesador”. Por ello, no cubrimos la creación de bootloaders en este artículo y en su lugar proporcionamos una herramienta llamada [bootimage] que automatiza el proceso de creación de un bootloader.
+
+[bootimage]: https://github.com/rust-osdev/bootimage
+
+Si te interesa construir tu propio bootloader: ¡Estén atentos! Un conjunto de artículos sobre este tema está en camino.
+
+#### El Estándar Multiboot
+Para evitar que cada sistema operativo implemente su propio bootloader, que sea compatible solo con un único sistema, la [Free Software Foundation] creó en 1995 un estándar abierto de bootloaders llamado [Multiboot]. El estándar define una interfaz entre el bootloader y el sistema operativo, de modo que cualquier bootloader compatible con Multiboot pueda cargar cualquier sistema operativo compatible con Multiboot. La implementación de referencia es [GNU GRUB], que es el bootloader más popular para sistemas Linux.
+
+[Free Software Foundation]: https://en.wikipedia.org/wiki/Free_Software_Foundation
+[Multiboot]: https://wiki.osdev.org/Multiboot
+[GNU GRUB]: https://en.wikipedia.org/wiki/GNU_GRUB
+
+Para hacer un kernel compatible con Multiboot, solo necesitas insertar un llamado [encabezado Multiboot] al inicio del archivo del kernel. Esto hace que arrancar un sistema operativo desde GRUB sea muy sencillo. Sin embargo, GRUB y el estándar Multiboot también tienen algunos problemas:
+
+[encabezado Multiboot]: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#OS-image-format
+
+- Solo soportan el modo protegido de 32 bits. Esto significa que aún tienes que configurar la CPU para cambiar al modo largo de 64 bits.
+- Están diseñados para simplificar el cargador de arranque en lugar del kernel. Por ejemplo, el kernel necesita vincularse con un [tamaño de página predeterminado ajustado], porque GRUB no puede encontrar el encabezado Multiboot de otro modo. Otro ejemplo es que la [información de arranque], que se pasa al kernel, contiene muchas estructuras dependientes de la arquitectura en lugar de proporcionar abstracciones limpias.
+- Tanto GRUB como el estándar Multiboot están escasamente documentados.
+- GRUB necesita instalarse en el sistema host para crear una imagen de disco arrancable a partir del archivo del kernel. Esto dificulta el desarrollo en Windows o Mac.
+
+[tamaño de página predeterminado ajustado]: https://wiki.osdev.org/Multiboot#Multiboot_2
+[información de arranque]: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html#Boot-information-format
+
+Debido a estas desventajas, decidimos no usar GRUB ni el estándar Multiboot. Sin embargo, planeamos agregar soporte para Multiboot a nuestra herramienta [bootimage], para que sea posible cargar tu kernel en un sistema GRUB también. Si te interesa escribir un kernel compatible con Multiboot, revisa la [primera edición] de esta serie de blogs.
+
+[primera edición]: @/edition-1/_index.md
+
+### UEFI
+
+(Por el momento no proporcionamos soporte para UEFI, ¡pero nos encantaría hacerlo! Si deseas ayudar, por favor háznoslo saber en el [issue de Github](https://github.com/phil-opp/blog_os/issues/349).)
+
+## Un Kernel Mínimo
+Ahora que tenemos una idea general de cómo arranca una computadora, es momento de crear nuestro propio kernel mínimo. Nuestro objetivo es crear una imagen de disco que, al arrancar, imprima “Hello World!” en la pantalla. Para esto, extendemos el [un binario Rust autónomo] del artículo anterior.
+
+Como recordarás, construimos el binario independiente mediante `cargo`, pero dependiendo del sistema operativo, necesitábamos diferentes nombres de punto de entrada y banderas de compilación. Esto se debe a que `cargo` construye por defecto para el _sistema anfitrión_, es decir, el sistema en el que estás ejecutando el comando. Esto no es lo que queremos para nuestro kernel, ya que un kernel que funcione encima, por ejemplo, de Windows, no tiene mucho sentido. En su lugar, queremos compilar para un _sistema destino_ claramente definido.
+
+### Instalación de Rust Nightly {#instalacion-de-rust-nightly}
+Rust tiene tres canales de lanzamiento: _stable_, _beta_ y _nightly_. El libro de Rust explica muy bien la diferencia entre estos canales, así que tómate un momento para [revisarlo](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html#choo-choo-release-channels-and-riding-the-trains). Para construir un sistema operativo, necesitaremos algunas características experimentales que solo están disponibles en el canal nightly, por lo que debemos instalar una versión nightly de Rust.
+
+Para administrar instalaciones de Rust, recomiendo ampliamente [rustup]. Este permite instalar compiladores nightly, beta y estable lado a lado, y facilita mantenerlos actualizados. Con rustup, puedes usar un compilador nightly en el directorio actual ejecutando `rustup override set nightly`. Alternativamente, puedes agregar un archivo llamado `rust-toolchain` con el contenido `nightly` en el directorio raíz del proyecto. Puedes verificar que tienes una versión nightly instalada ejecutando `rustc --version`: el número de versión debería contener `-nightly` al final.
+
+[rustup]: https://www.rustup.rs/
+
+El compilador nightly nos permite activar varias características experimentales utilizando las llamadas _banderas de características_ al inicio de nuestro archivo. Por ejemplo, podríamos habilitar el macro experimental [`asm!`] para ensamblador en línea agregando `#![feature(asm)]` en la parte superior de nuestro archivo `main.rs`. Ten en cuenta que estas características experimentales son completamente inestables, lo que significa que futuras versiones de Rust podrían cambiarlas o eliminarlas sin previo aviso. Por esta razón, solo las utilizaremos si son absolutamente necesarias.
+
+[`asm!`]: https://doc.rust-lang.org/stable/reference/inline-assembly.html
+
+### Especificación del Objetivo
+Cargo soporta diferentes sistemas destino mediante el parámetro `--target`. El destino se describe mediante un _[tripleta de destino]_, que especifica la arquitectura de la CPU, el proveedor, el sistema operativo y el [ABI]. Por ejemplo, el tripleta de destino `x86_64-unknown-linux-gnu` describe un sistema con una CPU `x86_64`, sin un proveedor claro, y un sistema operativo Linux con el ABI GNU. Rust soporta [muchas tripleta de destino diferentes][platform-support], incluyendo `arm-linux-androideabi` para Android o [`wasm32-unknown-unknown` para WebAssembly](https://www.hellorust.com/setup/wasm-target/).
+
+[tripleta de destino]: https://clang.llvm.org/docs/CrossCompilation.html#target-triple
+[ABI]: https://stackoverflow.com/a/2456882
+[platform-support]: https://forge.rust-lang.org/release/platform-support.html
+[custom-targets]: https://doc.rust-lang.org/nightly/rustc/targets/custom.html
+
+Para nuestro sistema destino, sin embargo, requerimos algunos parámetros de configuración especiales (por ejemplo, sin un sistema operativo subyacente), por lo que ninguno de los [tripletas de destino existentes][platform-support] encaja. Afortunadamente, Rust nos permite definir [nuestros propios objetivos][custom-targets] mediante un archivo JSON. Por ejemplo, un archivo JSON que describe el objetivo `x86_64-unknown-linux-gnu` se ve así:
+
+```json
+{
+ "llvm-target": "x86_64-unknown-linux-gnu",
+ "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
+ "arch": "x86_64",
+ "target-endian": "little",
+ "target-pointer-width": "64",
+ "target-c-int-width": "32",
+ "os": "linux",
+ "executables": true,
+ "linker-flavor": "gcc",
+ "pre-link-args": ["-m64"],
+ "morestack": false
+}
+```
+
+La mayoría de los campos son requeridos por LLVM para generar código para esa plataforma. Por ejemplo, el campo [`data-layout`] define el tamaño de varios tipos de enteros, números de punto flotante y punteros. Luego, hay campos que Rust utiliza para la compilación condicional, como `target-pointer-width`. El tercer tipo de campo define cómo debe construirse el crate. Por ejemplo, el campo `pre-link-args` especifica argumentos que se pasan al [linker].
+
+[`data-layout`]: https://llvm.org/docs/LangRef.html#data-layout
+[linker]: https://en.wikipedia.org/wiki/Linker_(computing)
+
+Nuestro kernel también tiene como objetivo los sistemas `x86_64`, por lo que nuestra especificación de objetivo será muy similar a la anterior. Comencemos creando un archivo llamado `x86_64-blog_os.json` (puedes elegir el nombre que prefieras) con el siguiente contenido común:
+
+```json
+{
+ "llvm-target": "x86_64-unknown-none",
+ "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
+ "arch": "x86_64",
+ "target-endian": "little",
+ "target-pointer-width": "64",
+ "target-c-int-width": "32",
+ "os": "none",
+ "executables": true
+}
+```
+
+Ten en cuenta que cambiamos el sistema operativo en el campo `llvm-target` y en el campo `os` a `none`, porque nuestro kernel se ejecutará directamente sobre hardware sin un sistema operativo subyacente.
+
+Agregamos las siguientes entradas relacionadas con la construcción:
+
+
+```json
+"linker-flavor": "ld.lld",
+"linker": "rust-lld",
+```
+
+En lugar de usar el enlazador predeterminado de la plataforma (que podría no soportar objetivos de Linux), utilizamos el enlazador multiplataforma [LLD] que se incluye con Rust para enlazar nuestro kernel.
+
+[LLD]: https://lld.llvm.org/
+
+```json
+"panic-strategy": "abort",
+```
+
+Esta configuración especifica que el objetivo no soporta [stack unwinding] en caso de un pánico, por lo que el programa debería abortar directamente. Esto tiene el mismo efecto que la opción `panic = "abort"` en nuestro archivo Cargo.toml, por lo que podemos eliminarla de ahí. (Ten en cuenta que, a diferencia de la opción en Cargo.toml, esta opción del destino también se aplica cuando recompilamos la biblioteca `core` más adelante en este artículo. Por lo tanto, incluso si prefieres mantener la opción en Cargo.toml, asegúrate de incluir esta opción.)
+
+[stack unwinding]: https://www.bogotobogo.com/cplusplus/stackunwinding.php
+
+```json
+"disable-redzone": true,
+```
+
+Estamos escribiendo un kernel, por lo que en algún momento necesitaremos manejar interrupciones. Para hacerlo de manera segura, debemos deshabilitar una optimización del puntero de pila llamada _“red zone”_, ya que de lo contrario podría causar corrupción en la pila. Para más información, consulta nuestro artículo sobre [cómo deshabilitar la red zone].
+
+[deshabilitar la red zone]: @/edition-2/posts/02-minimal-rust-kernel/disable-red-zone/index.md
+
+```json
+"features": "-mmx,-sse,+soft-float",
+```
+
+El campo `features` habilita o deshabilita características del destinos. Deshabilitamos las características `mmx` y `sse` anteponiéndoles un signo menos y habilitamos la característica `soft-float` anteponiéndole un signo más. Ten en cuenta que no debe haber espacios entre las diferentes banderas, ya que de lo contrario LLVM no podrá interpretar correctamente la cadena de características.
+
+Las características `mmx` y `sse` determinan el soporte para instrucciones [Single Instruction Multiple Data (SIMD)], que a menudo pueden acelerar significativamente los programas. Sin embargo, el uso de los registros SIMD en kernels de sistemas operativos genera problemas de rendimiento. Esto se debe a que el kernel necesita restaurar todos los registros a su estado original antes de continuar un programa interrumpido. Esto implica que el kernel debe guardar el estado completo de SIMD en la memoria principal en cada llamada al sistema o interrupción de hardware. Dado que el estado SIMD es muy grande (512–1600 bytes) y las interrupciones pueden ocurrir con mucha frecuencia, estas operaciones adicionales de guardar/restaurar afectan considerablemente el rendimiento. Para evitar esto, deshabilitamos SIMD para nuestro kernel (pero no para las aplicaciones que se ejecutan encima).
+
+[Single Instruction Multiple Data (SIMD)]: https://en.wikipedia.org/wiki/SIMD
+
+Un problema al deshabilitar SIMD es que las operaciones de punto flotante en `x86_64` requieren registros SIMD por defecto. Para resolver este problema, agregamos la característica `soft-float`, que emula todas las operaciones de punto flotante mediante funciones de software basadas en enteros normales.
+
+Para más información, consulta nuestro artículo sobre [cómo deshabilitar SIMD](@/edition-2/posts/02-minimal-rust-kernel/disable-simd/index.md).
+
+#### Juntándolo Todo
+Nuestro archivo de especificación de objetivo ahora se ve así:
+
+
+```json
+{
+ "llvm-target": "x86_64-unknown-none",
+ "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
+ "arch": "x86_64",
+ "target-endian": "little",
+ "target-pointer-width": "64",
+ "target-c-int-width": "32",
+ "os": "none",
+ "executables": true,
+ "linker-flavor": "ld.lld",
+ "linker": "rust-lld",
+ "panic-strategy": "abort",
+ "disable-redzone": true,
+ "features": "-mmx,-sse,+soft-float"
+}
+```
+
+### Construyendo nuestro Kernel
+Compilar para nuestro nuevo objetivo usará convenciones de Linux, ya que la opción de enlazador `ld.lld` instruye a LLVM a compilar con la bandera `-flavor gnu` (para más opciones del enlazador, consulta [la documentación de rustc](https://doc.rust-lang.org/rustc/codegen-options/index.html#linker-flavor)). Esto significa que necesitamos un punto de entrada llamado `_start`, como se describió en el [artículo anterior]:
+
+[artículo anterior]: @/edition-2/posts/01-freestanding-rust-binary/index.md
+
+```rust
+// src/main.rs
+
+#![no_std] // no enlazar con la biblioteca estándar de Rust
+#![no_main] // deshabilitar todos los puntos de entrada a nivel de Rust
+
+use core::panic::PanicInfo;
+
+/// Esta función se llama cuando ocurre un pánico.
+#[panic_handler]
+fn panic(_info: &PanicInfo) -> ! {
+ loop {}
+}
+
+#[no_mangle] // no modificar el nombre de esta función
+pub extern "C" fn _start() -> ! {
+ // esta función es el punto de entrada, ya que el enlazador busca una función
+ // llamada `_start` por defecto
+ loop {}
+}
+```
+
+Ten en cuenta que el punto de entrada debe llamarse `_start` sin importar el sistema operativo anfitrión.
+
+Ahora podemos construir el kernel para nuestro nuevo objetivo pasando el nombre del archivo JSON como `--target`:
+
+```
+> cargo build --target x86_64-blog_os.json
+
+error[E0463]: can't find crate for `core`
+```
+
+¡Falla! El error nos indica que el compilador de Rust ya no encuentra la [biblioteca `core`]. Esta biblioteca contiene tipos básicos de Rust como `Result`, `Option` e iteradores, y se vincula implícitamente a todos los crates con `no_std`.
+
+[biblioteca `core`]: https://doc.rust-lang.org/nightly/core/index.html
+
+El problema es que la biblioteca `core` se distribuye junto con el compilador de Rust como una biblioteca _precompilada_. Por lo tanto, solo es válida para tripletas de anfitrión soportados (por ejemplo, `x86_64-unknown-linux-gnu`), pero no para nuestro objetivo personalizado. Si queremos compilar código para otros objetivos, necesitamos recompilar `core` para esos objetivos primero.
+
+#### La Opción `build-std`
+
+Aquí es donde entra en juego la característica [`build-std`] de cargo. Esta permite recompilar `core` y otras bibliotecas estándar bajo demanda, en lugar de usar las versiones precompiladas que vienen con la instalación de Rust. Esta característica es muy nueva y aún no está terminada, por lo que está marcada como "inestable" y solo está disponible en los [compiladores de Rust nightly].
+
+[`build-std`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std
+[compiladores de Rust nightly]: #instalacion-de-rust-nightly
+
+Para usar esta característica, necesitamos crear un archivo de configuración local de [cargo] en `.cargo/config.toml` (la carpeta `.cargo` debería estar junto a tu carpeta `src`) con el siguiente contenido:
+
+[cargo]: https://doc.rust-lang.org/cargo/reference/config.html
+
+```toml
+# en .cargo/config.toml
+
+[unstable]
+build-std = ["core", "compiler_builtins"]
+```
+
+Esto le indica a cargo que debe recompilar las bibliotecas `core` y `compiler_builtins`. Esta última es necesaria porque es una dependencia de `core`. Para poder recompilar estas bibliotecas, cargo necesita acceso al código fuente de Rust, el cual podemos instalar ejecutando `rustup component add rust-src`.
+
+
+
+**Nota:** La clave de configuración `unstable.build-std` requiere al menos la versión de Rust nightly del 15 de julio de 2020.
+
+
+
+Después de configurar la clave `unstable.build-std` e instalar el componente `rust-src`, podemos ejecutar nuevamente nuestro comando de construcción:
+
+```
+> cargo build --target x86_64-blog_os.json
+ Compiling core v0.0.0 (/…/rust/src/libcore)
+ Compiling rustc-std-workspace-core v1.99.0 (/…/rust/src/tools/rustc-std-workspace-core)
+ Compiling compiler_builtins v0.1.32
+ Compiling blog_os v0.1.0 (/…/blog_os)
+ Finished dev [unoptimized + debuginfo] target(s) in 0.29 secs
+```
+
+Vemos que `cargo build` ahora recompila las bibliotecas `core`, `rustc-std-workspace-core` (una dependencia de `compiler_builtins`) y `compiler_builtins` para nuestro objetivo personalizado.
+
+#### Intrínsecos Relacionados con la Memoria
+
+El compilador de Rust asume que un cierto conjunto de funciones integradas está disponible para todos los sistemas. La mayoría de estas funciones son proporcionadas por el crate `compiler_builtins`, que acabamos de recompilar. Sin embargo, hay algunas funciones relacionadas con la memoria en ese crate que no están habilitadas por defecto, ya que normalmente son proporcionadas por la biblioteca C del sistema. Estas funciones incluyen `memset`, que establece todos los bytes de un bloque de memoria a un valor dado, `memcpy`, que copia un bloque de memoria a otro, y `memcmp`, que compara dos bloques de memoria. Aunque no necesitamos estas funciones para compilar nuestro kernel en este momento, serán necesarias tan pronto como agreguemos más código (por ejemplo, al copiar estructuras).
+
+Dado que no podemos vincularnos a la biblioteca C del sistema operativo, necesitamos una forma alternativa de proporcionar estas funciones al compilador. Una posible solución podría ser implementar nuestras propias funciones `memset`, `memcpy`, etc., y aplicarles el atributo `#[no_mangle]` (para evitar el renombramiento automático durante la compilación). Sin embargo, esto es peligroso, ya que el más mínimo error en la implementación de estas funciones podría conducir a un comportamiento indefinido. Por ejemplo, implementar `memcpy` con un bucle `for` podría resultar en una recursión infinita, ya que los bucles `for` llaman implícitamente al método del trait [`IntoIterator::into_iter`], que podría invocar nuevamente a `memcpy`. Por lo tanto, es una buena idea reutilizar implementaciones existentes y bien probadas.
+
+[`IntoIterator::into_iter`]: https://doc.rust-lang.org/stable/core/iter/trait.IntoIterator.html#tymethod.into_iter
+
+Afortunadamente, el crate `compiler_builtins` ya contiene implementaciones para todas las funciones necesarias, pero están deshabilitadas por defecto para evitar conflictos con las implementaciones de la biblioteca C. Podemos habilitarlas configurando la bandera [`build-std-features`] de cargo como `["compiler-builtins-mem"]`. Al igual que la bandera `build-std`, esta bandera puede pasarse como un flag `-Z` en la línea de comandos o configurarse en la tabla `unstable` en el archivo `.cargo/config.toml`. Dado que siempre queremos compilar con esta bandera, la opción de archivo de configuración tiene más sentido para nosotros:
+
+[`build-std-features`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std-features
+
+```toml
+# en .cargo/config.toml
+
+[unstable]
+build-std-features = ["compiler-builtins-mem"]
+build-std = ["core", "compiler_builtins"]
+```
+
+(El soporte para la característica `compiler-builtins-mem` fue [añadido muy recientemente](https://github.com/rust-lang/rust/pull/77284), por lo que necesitas al menos Rust nightly `2020-09-30` para usarla).
+
+Detrás de escena, esta bandera habilita la [característica `mem`] del crate `compiler_builtins`. El efecto de esto es que el atributo `#[no_mangle]` se aplica a las [implementaciones de `memcpy`, etc.] del crate, lo que las hace disponibles para el enlazador.
+
+[característica `mem`]: https://github.com/rust-lang/compiler-builtins/blob/eff506cd49b637f1ab5931625a33cef7e91fbbf6/Cargo.toml#L54-L55
+[implementaciones de `memcpy`, etc.]: https://github.com/rust-lang/compiler-builtins/blob/eff506cd49b637f1ab5931625a33cef7e91fbbf6/src/mem.rs#L12-L69
+
+Con este cambio, nuestro kernel tiene implementaciones válidas para todas las funciones requeridas por el compilador, por lo que continuará compilándose incluso si nuestro código se vuelve más complejo.
+
+#### Configurar un Objetivo Predeterminado
+
+Para evitar pasar el parámetro `--target` en cada invocación de `cargo build`, podemos sobrescribir el objetivo predeterminado. Para hacer esto, añadimos lo siguiente a nuestro archivo de [configuración de cargo] en `.cargo/config.toml`:
+
+[configuración de cargo]: https://doc.rust-lang.org/cargo/reference/config.html
+
+```toml
+# en .cargo/config.toml
+
+[build]
+target = "x86_64-blog_os.json"
+```
+
+Esto le indica a `cargo` que use nuestro objetivo `x86_64-blog_os.json` cuando no se pase explícitamente el argumento `--target`. Esto significa que ahora podemos construir nuestro kernel con un simple `cargo build`. Para más información sobre las opciones de configuración de cargo, consulta la [documentación oficial][configuración de cargo].
+
+Ahora podemos construir nuestro kernel para un objetivo bare metal con un simple `cargo build`. Sin embargo, nuestro punto de entrada `_start`, que será llamado por el cargador de arranque, aún está vacío. Es momento de mostrar algo en la pantalla desde ese punto.
+
+### Imprimiendo en Pantalla
+La forma más sencilla de imprimir texto en la pantalla en esta etapa es usando el [búfer de texto VGA]. Es un área de memoria especial mapeada al hardware VGA que contiene el contenido mostrado en pantalla. Normalmente consta de 25 líneas, cada una con 80 celdas de caracteres. Cada celda de carácter muestra un carácter ASCII con algunos colores de primer plano y fondo. La salida en pantalla se ve así:
+
+[búfer de texto VGA]: https://en.wikipedia.org/wiki/VGA-compatible_text_mode
+
+
+
+Discutiremos el diseño exacto del búfer VGA en el próximo artículo, donde escribiremos un primer controlador pequeño para él. Para imprimir “Hello World!”, solo necesitamos saber que el búfer está ubicado en la dirección `0xb8000` y que cada celda de carácter consta de un byte ASCII y un byte de color.
+
+La implementación se ve así:
+
+```rust
+static HELLO: &[u8] = b"Hello World!";
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ let vga_buffer = 0xb8000 as *mut u8;
+
+ for (i, &byte) in HELLO.iter().enumerate() {
+ unsafe {
+ *vga_buffer.offset(i as isize * 2) = byte;
+ *vga_buffer.offset(i as isize * 2 + 1) = 0xb;
+ }
+ }
+
+ loop {}
+}
+```
+
+Primero, convertimos el entero `0xb8000` en un [raw pointer]. Luego, [iteramos] sobre los bytes de la [cadena de bytes estática] `HELLO`. Usamos el método [`enumerate`] para obtener adicionalmente una variable de conteo `i`. En el cuerpo del bucle `for`, utilizamos el método [`offset`] para escribir el byte de la cadena y el byte de color correspondiente (`0xb` representa un cian claro).
+
+[iteramos]: https://doc.rust-lang.org/stable/book/ch13-02-iterators.html
+[raw pointer]: https://doc.rust-lang.org/stable/book/ch19-01-unsafe-rust.html#dereferencing-a-raw-pointer
+[estática]: https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#the-static-lifetime
+[`enumerate`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.enumerate
+[cadena de bytes]: https://doc.rust-lang.org/reference/tokens.html#byte-string-literals
+[`offset`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.offset
+
+Ten en cuenta que hay un bloque [`unsafe`] alrededor de todas las escrituras de memoria. Esto se debe a que el compilador de Rust no puede probar que los punteros crudos que creamos son válidos. Podrían apuntar a cualquier lugar y causar corrupción de datos. Al poner estas operaciones en un bloque `unsafe`, básicamente le decimos al compilador que estamos absolutamente seguros de que las operaciones son válidas. Sin embargo, un bloque `unsafe` no desactiva las verificaciones de seguridad de Rust; simplemente permite realizar [cinco operaciones adicionales].
+
+[`unsafe`]: https://doc.rust-lang.org/stable/book/ch19-01-unsafe-rust.html
+[cinco operaciones adicionales]: https://doc.rust-lang.org/stable/book/ch19-01-unsafe-rust.html#unsafe-superpowers
+
+Quiero enfatizar que **esta no es la forma en que queremos hacer las cosas en Rust**. Es muy fácil cometer errores al trabajar con punteros crudos dentro de bloques `unsafe`. Por ejemplo, podríamos escribir más allá del final del búfer si no somos cuidadosos.
+
+Por lo tanto, queremos minimizar el uso de `unsafe` tanto como sea posible. Rust nos permite lograr esto creando abstracciones seguras. Por ejemplo, podríamos crear un tipo de búfer VGA que encapsule toda la inseguridad y garantice que sea _imposible_ hacer algo incorrecto desde el exterior. De esta manera, solo necesitaríamos cantidades mínimas de código `unsafe` y podríamos estar seguros de no violar la [seguridad de la memoria]. Crearemos una abstracción segura para el búfer VGA en el próximo artículo.
+
+[seguridad de la memoria]: https://en.wikipedia.org/wiki/Memory_safety
+
+## Ejecutando Nuestro Kernel
+
+Ahora que tenemos un ejecutable que realiza algo perceptible, es momento de ejecutarlo. Primero, necesitamos convertir nuestro kernel compilado en una imagen de disco arrancable vinculándolo con un cargador de arranque. Luego, podemos ejecutar la imagen de disco en la máquina virtual [QEMU] o iniciarla en hardware real usando una memoria USB.
+
+### Creando una Bootimage
+
+Para convertir nuestro kernel compilado en una imagen de disco arrancable, debemos vincularlo con un cargador de arranque. Como aprendimos en la [sección sobre el proceso de arranque], el cargador de arranque es responsable de inicializar la CPU y cargar nuestro kernel.
+
+[sección sobre el proceso de arranque]: #el-proceso-de-arranque
+
+En lugar de escribir nuestro propio cargador de arranque, lo cual es un proyecto en sí mismo, usamos el crate [`bootloader`]. Este crate implementa un cargador de arranque básico para BIOS sin dependencias en C, solo Rust y ensamblador en línea. Para usarlo y arrancar nuestro kernel, necesitamos agregarlo como dependencia:
+
+[`bootloader`]: https://crates.io/crates/bootloader
+
+```toml
+# en Cargo.toml
+
+[dependencies]
+bootloader = "0.9"
+```
+
+**Nota:** Este artículo solo es compatible con `bootloader v0.9`. Las versiones más recientes usan un sistema de construcción diferente y generarán errores de compilación al seguir este artículo.
+
+Agregar el bootloader como dependencia no es suficiente para crear una imagen de disco arrancable. El problema es que necesitamos vincular nuestro kernel con el bootloader después de la compilación, pero cargo no tiene soporte para [scripts post-compilación].
+
+[scripts post-compilación]: https://github.com/rust-lang/cargo/issues/545
+
+Para resolver este problema, creamos una herramienta llamada `bootimage` que primero compila el kernel y el bootloader, y luego los vincula para crear una imagen de disco arrancable. Para instalar esta herramienta, dirígete a tu directorio de inicio (o cualquier directorio fuera de tu proyecto de cargo) y ejecuta el siguiente comando en tu terminal:
+
+```
+cargo install bootimage
+```
+
+Para ejecutar `bootimage` y compilar el bootloader, necesitas tener instalado el componente `llvm-tools-preview` de rustup. Puedes hacerlo ejecutando el comando correspondiente.
+
+Después de instalar `bootimage` y agregar el componente `llvm-tools-preview`, puedes crear una imagen de disco arrancable regresando al directorio de tu proyecto de cargo y ejecutando:
+
+```
+> cargo bootimage
+```
+
+Vemos que la herramienta recompila nuestro kernel usando `cargo build`, por lo que automáticamente aplicará cualquier cambio que realices. Después, compila el bootloader, lo cual puede tardar un poco. Como ocurre con todas las dependencias de los crates, solo se compila una vez y luego se almacena en caché, por lo que las compilaciones posteriores serán mucho más rápidas. Finalmente, `bootimage` combina el bootloader y tu kernel en una imagen de disco arrancable.
+
+Después de ejecutar el comando, deberías ver una imagen de disco arrancable llamada `bootimage-blog_os.bin` en tu directorio `target/x86_64-blog_os/debug`. Puedes arrancarla en una máquina virtual o copiarla a una unidad USB para arrancarla en hardware real. (Ten en cuenta que esta no es una imagen de CD, que tiene un formato diferente, por lo que grabarla en un CD no funcionará).
+
+#### ¿Cómo funciona?
+La herramienta `bootimage` realiza los siguientes pasos detrás de escena:
+
+- Compila nuestro kernel en un archivo [ELF].
+- Compila la dependencia del bootloader como un ejecutable independiente.
+- Vincula los bytes del archivo ELF del kernel con el bootloader.
+
+[ELF]: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
+[rust-osdev/bootloader]: https://github.com/rust-osdev/bootloader
+
+Al arrancar, el bootloader lee y analiza el archivo ELF anexado. Luego, mapea los segmentos del programa a direcciones virtuales en las tablas de páginas, inicializa a cero la sección `.bss` y configura una pila. Finalmente, lee la dirección del punto de entrada (nuestra función `_start`) y salta a ella.
+
+### Arrancando en QEMU
+
+Ahora podemos arrancar la imagen de disco en una máquina virtual. Para arrancarla en [QEMU], ejecuta el comando correspondiente.
+
+[QEMU]: https://www.qemu.org/
+
+```
+> qemu-system-x86_64 -drive format=raw,file=target/x86_64-blog_os/debug/bootimage-blog_os.bin
+```
+
+Esto abre una ventana separada que debería verse similar a esto:
+
+
+
+Vemos que nuestro "Hello World!" es visible en la pantalla.
+
+### Máquina Real
+
+También es posible escribir la imagen a una memoria USB y arrancarla en una máquina real, **pero ten mucho cuidado** al elegir el nombre correcto del dispositivo, porque **todo en ese dispositivo será sobrescrito**:
+
+```
+> dd if=target/x86_64-blog_os/debug/bootimage-blog_os.bin of=/dev/sdX && sync
+```
+
+Donde `sdX` es el nombre del dispositivo de tu memoria USB.
+
+Después de escribir la imagen en la memoria USB, puedes ejecutarla en hardware real iniciando desde ella. Probablemente necesitarás usar un menú de arranque especial o cambiar el orden de arranque en la configuración del BIOS para iniciar desde la memoria USB. Ten en cuenta que actualmente no funciona para máquinas UEFI, ya que el crate `bootloader` aún no tiene soporte para UEFI.
+
+### Usando `cargo run`
+
+Para facilitar la ejecución de nuestro kernel en QEMU, podemos configurar la clave de configuración `runner` para cargo:
+
+```toml
+# en .cargo/config.toml
+
+[target.'cfg(target_os = "none")']
+runner = "bootimage runner"
+```
+
+La tabla `target.'cfg(target_os = "none")'` se aplica a todos los objetivos cuyo campo `"os"` en el archivo de configuración del objetivo esté configurado como `"none"`. Esto incluye nuestro objetivo `x86_64-blog_os.json`. La clave `runner` especifica el comando que debe ejecutarse para `cargo run`. El comando se ejecuta después de una compilación exitosa, con la ruta del ejecutable pasada como el primer argumento. Consulta la [documentación de cargo][configuración de cargo] para más detalles.
+
+El comando `bootimage runner` está específicamente diseñado para ser utilizado como un ejecutable `runner`. Vincula el ejecutable dado con la dependencia del bootloader del proyecto y luego lanza QEMU. Consulta el [README de `bootimage`] para más detalles y posibles opciones de configuración.
+
+[README de `bootimage`]: https://github.com/rust-osdev/bootimage
+
+Ahora podemos usar `cargo run` para compilar nuestro kernel e iniciarlo en QEMU.
+
+## ¿Qué sigue?
+
+En el próximo artículo, exploraremos el búfer de texto VGA con más detalle y escribiremos una interfaz segura para él. También añadiremos soporte para el macro `println`.
diff --git a/blog/content/edition-2/posts/03-vga-text-buffer/index.es.md b/blog/content/edition-2/posts/03-vga-text-buffer/index.es.md
new file mode 100644
index 00000000..ec6f7b12
--- /dev/null
+++ b/blog/content/edition-2/posts/03-vga-text-buffer/index.es.md
@@ -0,0 +1,343 @@
++++
+title = "Modo de Texto VGA"
+weight = 3
+path = "es/modo-texto-vga"
+date = 2018-02-26
+
+[extra]
+chapter = "Fundamentos"
+
+# GitHub usernames of the people that translated this post
+translators = ["dobleuber"]
++++
+
+El [modo de texto VGA] es una forma sencilla de imprimir texto en la pantalla. En esta publicación, creamos una interfaz que hace que su uso sea seguro y simple al encapsular toda la inseguridad en un módulo separado. También implementamos soporte para los [macros de formato] de Rust.
+
+[modo de texto VGA]: https://en.wikipedia.org/wiki/VGA-compatible_text_mode
+[macros de formato]: https://doc.rust-lang.org/std/fmt/#related-macros
+
+
+
+Este blog se desarrolla abiertamente en [GitHub]. Si tienes algún problema o pregunta, por favor abre un issue allí. También puedes dejar comentarios [al final]. El código fuente completo para esta publicación se puede encontrar en la rama [`post-03`][rama del post].
+
+[GitHub]: https://github.com/phil-opp/blog_os
+[al final]: #comments
+
+[rama del post]: https://github.com/phil-opp/blog_os/tree/post-03
+
+
+
+## El Buffer de Texto VGA
+Para imprimir un carácter en la pantalla en modo de texto VGA, uno tiene que escribirlo en el buffer de texto del hardware VGA. El buffer de texto VGA es un arreglo bidimensional con típicamente 25 filas y 80 columnas, que se renderiza directamente en la pantalla. Cada entrada del arreglo describe un solo carácter de pantalla a través del siguiente formato:
+
+| Bit(s) | Valor |
+| ------ | --------------------- |
+| 0-7 | Código de punto ASCII |
+| 8-11 | Color de primer plano |
+| 12-14 | Color de fondo |
+| 15 | Parpadeo |
+
+El primer byte representa el carácter que debe imprimirse en la [codificación ASCII]. Para ser más específicos, no es exactamente ASCII, sino un conjunto de caracteres llamado [_página de códigos 437_] con algunos caracteres adicionales y ligeras modificaciones. Para simplificar, procederemos a llamarlo un carácter ASCII en esta publicación.
+
+[codificación ASCII]: https://en.wikipedia.org/wiki/ASCII
+[_página de códigos 437_]: https://en.wikipedia.org/wiki/Code_page_437
+
+El segundo byte define cómo se muestra el carácter. Los primeros cuatro bits definen el color de primer plano, los siguientes tres bits el color de fondo, y el último bit si el carácter debe parpadear. Los siguientes colores están disponibles:
+
+| Número | Color | Número + Bit de Brillo | Color Brillante |
+| ------ | ---------- | ---------------------- | --------------- |
+| 0x0 | Negro | 0x8 | Gris Oscuro |
+| 0x1 | Azul | 0x9 | Azul Claro |
+| 0x2 | Verde | 0xa | Verde Claro |
+| 0x3 | Cian | 0xb | Cian Claro |
+| 0x4 | Rojo | 0xc | Rojo Claro |
+| 0x5 | Magenta | 0xd | Magenta Claro |
+| 0x6 | Marrón | 0xe | Amarillo |
+| 0x7 | Gris Claro | 0xf | Blanco |
+
+Bit 4 es el _bit de brillo_, que convierte, por ejemplo, azul en azul claro. Para el color de fondo, este bit se reutiliza como el bit de parpadeo.
+
+El buffer de texto VGA es accesible a través de [E/S mapeada en memoria] a la dirección `0xb8000`. Esto significa que las lecturas y escrituras a esa dirección no acceden a la RAM, sino que acceden directamente al buffer de texto en el hardware VGA. Esto significa que podemos leer y escribir a través de operaciones de memoria normales a esa dirección.
+
+[E/S mapeada en memoria]: https://en.wikipedia.org/wiki/Memory-mapped_I/O
+
+Ten en cuenta que el hardware mapeado en memoria podría no soportar todas las operaciones normales de RAM. Por ejemplo, un dispositivo podría soportar solo lecturas por byte y devolver basura cuando se lee un `u64`. Afortunadamente, el buffer de texto [soporta lecturas y escrituras normales], por lo que no tenemos que tratarlo de una manera especial.
+
+[soporta lecturas y escrituras normales]: https://web.stanford.edu/class/cs140/projects/pintos/specs/freevga/vga/vgamem.htm#manip
+
+## Un Módulo de Rust
+Ahora que sabemos cómo funciona el buffer VGA, podemos crear un módulo de Rust para manejar la impresión:
+
+```rust
+// en src/main.rs
+mod vga_buffer;
+```
+
+Para el contenido de este módulo, creamos un nuevo archivo `src/vga_buffer.rs`. Todo el código a continuación va en nuestro nuevo módulo (a menos que se especifique lo contrario).
+
+### Colores
+Primero, representamos los diferentes colores usando un enum:
+
+```rust
+// en src/vga_buffer.rs
+
+#[allow(dead_code)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(u8)]
+pub enum Color {
+ Black = 0,
+ Blue = 1,
+ Green = 2,
+ Cyan = 3,
+ Red = 4,
+ Magenta = 5,
+ Brown = 6,
+ LightGray = 7,
+ DarkGray = 8,
+ LightBlue = 9,
+ LightGreen = 10,
+ LightCyan = 11,
+ LightRed = 12,
+ Pink = 13,
+ Yellow = 14,
+ White = 15,
+}
+```
+Usamos un [enum similar a C] aquí para especificar explícitamente el número para cada color. Debido al atributo `repr(u8)`, cada variante del enum se almacena como un `u8`. En realidad, 4 bits serían suficientes, pero Rust no tiene un tipo `u4`.
+
+[enum similar a C]: https://doc.rust-lang.org/rust-by-example/custom_types/enum/c_like.html
+
+Normalmente, el compilador emitiría una advertencia por cada variante no utilizada. Al usar el atributo `#[allow(dead_code)]`, deshabilitamos estas advertencias para el enum `Color`.
+
+Al [derivar] los rasgos [`Copy`], [`Clone`], [`Debug`], [`PartialEq`], y [`Eq`], habilitamos la [semántica de copia] para el tipo y lo hacemos imprimible y comparable.
+
+[derivar]: https://doc.rust-lang.org/rust-by-example/trait/derive.html
+[`Copy`]: https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html
+[`Clone`]: https://doc.rust-lang.org/nightly/core/clone/trait.Clone.html
+
+Para representar un código de color completo que especifique el color de primer plano y de fondo, creamos un [nuevo tipo] sobre `u8`:
+
+[nuevo tipo]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html
+
+```rust
+// en src/vga_buffer.rs
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(transparent)]
+struct ColorCode(u8);
+
+impl ColorCode {
+ fn new(foreground: Color, background: Color) -> ColorCode {
+ ColorCode((background as u8) << 4 | (foreground as u8))
+ }
+}
+```
+La estructura `ColorCode` contiene el byte de color completo, que incluye el color de primer plano y de fondo. Como antes, derivamos los rasgos `Copy` y `Debug` para él. Para asegurar que `ColorCode` tenga el mismo diseño de datos exacto que un `u8`, usamos el atributo [`repr(transparent)`].
+
+[`repr(transparent)`]: https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent
+
+### Buffer de Texto
+Ahora podemos agregar estructuras para representar un carácter de pantalla y el buffer de texto:
+
+```rust
+// en src/vga_buffer.rs
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(C)]
+struct ScreenChar {
+ ascii_character: u8,
+ color_code: ColorCode,
+}
+```
+Dado que el orden de los campos en las estructuras predeterminadas no está definido en Rust, necesitamos el atributo [`repr(C)`]. Garantiza que los campos de la estructura se dispongan exactamente como en una estructura C y, por lo tanto, garantiza el orden correcto de los campos. Para la estructura `Buffer`, usamos [`repr(transparent)`] nuevamente para asegurar que tenga el mismo diseño de memoria que su único campo.
+
+[`repr(C)`]: https://doc.rust-lang.org/nightly/nomicon/other-reprs.html#reprc
+
+Para escribir en pantalla, ahora creamos un tipo de escritor:
+
+```rust
+// en src/vga_buffer.rs
+
+pub struct Writer {
+ column_position: usize,
+ color_code: ColorCode,
+ buffer: &'static mut Buffer,
+}
+```
+El escritor siempre escribirá en la última línea y desplazará las líneas hacia arriba cuando una línea esté llena (o en `\n`). El campo `column_position` lleva un seguimiento de la posición actual en la última fila. Los colores de primer plano y de fondo actuales están especificados por `color_code` y una referencia al buffer VGA está almacenada en `buffer`. Ten en cuenta que necesitamos una [vida útil explícita] aquí para decirle al compilador cuánto tiempo es válida la referencia. La vida útil [`'static`] especifica que la referencia es válida durante todo el tiempo de ejecución del programa (lo cual es cierto para el buffer de texto VGA).
+
+[vida útil explícita]: https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#lifetime-annotation-syntax
+[`'static`]: https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#the-static-lifetime
+
+### Impresión
+Ahora podemos usar el `Writer` para modificar los caracteres del buffer. Primero creamos un método para escribir un solo byte ASCII:
+
+```rust
+// en src/vga_buffer.rs
+
+impl Writer {
+ pub fn write_byte(&mut self, byte: u8) {
+ match byte {
+ b'\n' => self.new_line(),
+ byte => {
+ if self.column_position >= BUFFER_WIDTH {
+ self.new_line();
+ }
+
+ let row = BUFFER_HEIGHT - 1;
+ let col = self.column_position;
+
+ let color_code = self.color_code;
+ self.buffer.chars[row][col].write(ScreenChar {
+ ascii_character: byte,
+ color_code,
+ });
+ self.column_position += 1;
+ }
+ }
+ }
+
+ fn new_line(&mut self) {/* TODO */}
+}
+```
+Si el byte es el byte de [nueva línea] `\n`, el escritor no imprime nada. En su lugar, llama a un método `new_line`, que implementaremos más tarde. Otros bytes se imprimen en la pantalla en el segundo caso de `match`.
+
+[nueva línea]: https://en.wikipedia.org/wiki/Newline
+
+Al imprimir un byte, el escritor verifica si la línea actual está llena. En ese caso, se usa una llamada a `new_line` para envolver la línea. Luego escribe un nuevo `ScreenChar` en el buffer en la posición actual. Finalmente, se avanza la posición de la columna actual.
+
+Para imprimir cadenas completas, podemos convertirlas en bytes e imprimirlas una por una:
+
+```rust
+// en src/vga_buffer.rs
+
+impl Writer {
+ pub fn write_string(&mut self, s: &str) {
+ for byte in s.bytes() {
+ match byte {
+ // byte ASCII imprimible o nueva línea
+ 0x20..=0x7e | b'\n' => self.write_byte(byte),
+ // no es parte del rango ASCII imprimible
+ _ => self.write_byte(0xfe),
+ }
+
+ }
+ }
+}
+```
+
+El buffer de texto VGA solo soporta ASCII y los bytes adicionales de [página de códigos 437]. Las cadenas de Rust son [UTF-8] por defecto, por lo que podrían contener bytes que no son soportados por el buffer de texto VGA. Usamos un `match` para diferenciar los bytes ASCII imprimibles (una nueva línea o cualquier cosa entre un carácter de espacio y un carácter `~`) y los bytes no imprimibles. Para los bytes no imprimibles, imprimimos un carácter `■`, que tiene el código hexadecimal `0xfe` en el hardware VGA.
+
+[página de códigos 437]: https://en.wikipedia.org/wiki/Code_page_437
+[UTF-8]: https://www.fileformat.info/info/unicode/utf8.htm
+
+#### ¡Pruébalo!
+Para escribir algunos caracteres en la pantalla, puedes crear una función temporal:
+
+```rust
+// en src/vga_buffer.rs
+
+pub fn print_something() {
+ let mut writer = Writer {
+ column_position: 0,
+ color_code: ColorCode::new(Color::Yellow, Color::Black),
+ buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
+ };
+
+ writer.write_byte(b'H');
+ writer.write_string("ello ");
+ writer.write_string("Wörld!");
+}
+```
+Primero crea un nuevo Writer que apunta al buffer VGA en `0xb8000`. La sintaxis para esto podría parecer un poco extraña: Primero, convertimos el entero `0xb8000` como un [puntero sin procesar] mutable. Luego lo convertimos en una referencia mutable al desreferenciarlo (a través de `*`) y tomarlo prestado inmediatamente (a través de `&mut`). Esta conversión requiere un [bloque `unsafe`], ya que el compilador no puede garantizar que el puntero sin procesar sea válido.
+
+[puntero sin procesar]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#dereferencing-a-raw-pointer
+[bloque `unsafe`]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html
+
+Luego escribe el byte `b'H'` en él. El prefijo `b` crea un [literal de byte], que representa un carácter ASCII. Al escribir las cadenas `"ello "` y `"Wörld!"`, probamos nuestro método `write_string` y el manejo de caracteres no imprimibles. Para ver la salida, necesitamos llamar a la función `print_something` desde nuestra función `_start`:
+
+```rust
+// en src/main.rs
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ vga_buffer::print_something();
+
+ loop {}
+}
+```
+
+Cuando ejecutamos nuestro proyecto ahora, se debería imprimir un `Hello W■■rld!` en la esquina inferior izquierda de la pantalla en amarillo:
+
+[literal de byte]: https://doc.rust-lang.org/reference/tokens.html#byte-literals
+
+
+
+Observa que la `ö` se imprime como dos caracteres `■`. Eso es porque `ö` está representado por dos bytes en [UTF-8], los cuales no caen en el rango ASCII imprimible. De hecho, esta es una propiedad fundamental de UTF-8: los bytes individuales de valores multibyte nunca son ASCII válidos.
+
+### Volátil
+Acabamos de ver que nuestro mensaje se imprimió correctamente. Sin embargo, podría no funcionar con futuros compiladores de Rust que optimicen más agresivamente.
+
+El problema es que solo escribimos en el `Buffer` y nunca leemos de él nuevamente. El compilador no sabe que realmente accedemos a la memoria del buffer VGA (en lugar de la RAM normal) y no sabe nada sobre el efecto secundario de que algunos caracteres aparezcan en la pantalla. Por lo tanto, podría decidir que estas escrituras son innecesarias y pueden omitirse. Para evitar esta optimización errónea, necesitamos especificar estas escrituras como _[volátiles]_. Esto le dice al compilador que la escritura tiene efectos secundarios y no debe ser optimizada.
+
+[volátiles]: https://en.wikipedia.org/wiki/Volatile_(computer_programming)
+
+Para usar escrituras volátiles para el buffer VGA, usamos la biblioteca [volatile][crate volatile]. Este _crate_ (así es como se llaman los paquetes en el mundo de Rust) proporciona un tipo de envoltura `Volatile` con métodos `read` y `write`. Estos métodos usan internamente las funciones [read_volatile] y [write_volatile] de la biblioteca principal y, por lo tanto, garantizan que las lecturas/escrituras no sean optimizadas.
+
+[crate volatile]: https://docs.rs/volatile
+[read_volatile]: https://doc.rust-lang.org/nightly/core/ptr/fn.read_volatile.html
+[write_volatile]: https://doc.rust-lang.org/nightly/core/ptr/fn.write_volatile.html
+
+Podemos agregar una dependencia en el crate `volatile` agregándolo a la sección `dependencies` de nuestro `Cargo.toml`:
+
+```toml
+# en Cargo.toml
+
+[dependencies]
+volatile = "0.2.6"
+```
+
+Asegúrate de especificar la versión `0.2.6` de `volatile`. Las versiones más nuevas del crate no son compatibles con esta publicación.
+`0.2.6` es el número de versión [semántica]. Para más información, consulta la guía [Especificar Dependencias] de la documentación de cargo.
+
+[semántica]: https://semver.org/
+[Especificar Dependencias]: https://doc.crates.io/specifying-dependencies.html
+
+Vamos a usarlo para hacer que las escrituras al buffer VGA sean volátiles. Actualizamos nuestro tipo `Buffer` de la siguiente manera:
+
+```rust
+// en src/vga_buffer.rs
+
+use volatile::Volatile;
+
+struct Buffer {
+ chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT],
+}
+```
+En lugar de un `ScreenChar`, ahora estamos usando un `Volatile`. (El tipo `Volatile` es [genérico] y puede envolver (casi) cualquier tipo). Esto asegura que no podamos escribir accidentalmente en él “normalmente”. En su lugar, ahora tenemos que usar el método `write`.
+
+[genérico]: https://doc.rust-lang.org/book/ch10-01-syntax.html
+
+Esto significa que tenemos que actualizar nuestro método `Writer::write_byte`:
+
+```rust
+// en src/vga_buffer.rs
+
+impl Writer {
+ pub fn write_byte(&mut self, byte: u8) {
+ match byte {
+ b'\n' => self.new_line(),
+ byte => {
+ ...
+
+ self.buffer.chars[row][col].write(ScreenChar {
+ ascii_character: byte,
+ color_code,
+ });
+ ...
+ }
+ }
+ }
+ ...
+}
+```
diff --git a/blog/content/edition-2/posts/04-testing/index.es.md b/blog/content/edition-2/posts/04-testing/index.es.md
new file mode 100644
index 00000000..bc282618
--- /dev/null
+++ b/blog/content/edition-2/posts/04-testing/index.es.md
@@ -0,0 +1,1028 @@
++++
+title = "Pruebas"
+weight = 4
+path = "es/testing"
+date = 2019-04-27
+
+[extra]
+chapter = "Fundamentos"
+comments_search_term = 1009
+
+# GitHub usernames of the people that translated this post
+translators = ["dobleuber"]
++++
+
+Esta publicación explora las pruebas unitarias e integración en ejecutables `no_std`. Utilizaremos el soporte de Rust para marcos de prueba personalizados para ejecutar funciones de prueba dentro de nuestro núcleo. Para reportar los resultados fuera de QEMU, utilizaremos diferentes características de QEMU y la herramienta `bootimage`.
+
+
+
+Este blog se desarrolla de manera abierta en [GitHub]. Si tienes algún problema o pregunta, por favor abre un problema allí. También puedes dejar comentarios [en la parte inferior]. El código fuente completo de esta publicación se puede encontrar en la rama [`post-04`][post branch].
+
+[GitHub]: https://github.com/phil-opp/blog_os
+[en la parte inferior]: #comments
+
+[post branch]: https://github.com/phil-opp/blog_os/tree/post-04
+
+
+
+## Requisitos
+
+Esta publicación reemplaza las publicaciones (_Pruebas Unitarias_) y (_Pruebas de Integración_) (ahora obsoletas). Se asume que has seguido la publicación (_Un Núcleo Rust Mínimo_) después del 2019-04-27. Principalmente, requiere que tengas un archivo `.cargo/config.toml` que [establezca un objetivo predeterminado] y [defina un ejecutable de runner].
+
+[_Pruebas Unitarias_]: @/edition-2/posts/deprecated/04-unit-testing/index.md
+[_Pruebas de Integración_]: @/edition-2/posts/deprecated/05-integration-tests/index.md
+[_Un Núcleo Rust Mínimo_]: @/edition-2/posts/02-minimal-rust-kernel/index.md
+[establezca un objetivo predeterminado]: @/edition-2/posts/02-minimal-rust-kernel/index.md#set-a-default-target
+[defina un ejecutable de runner]: @/edition-2/posts/02-minimal-rust-kernel/index.md#using-cargo-run
+
+## Pruebas en Rust
+
+Rust tiene un [marco de prueba incorporado] que es capaz de ejecutar pruebas unitarias sin la necesidad de configurar nada. Solo crea una función que verifique algunos resultados mediante afirmaciones y añade el atributo `#[test]` al encabezado de la función. Luego, `cargo test` encontrará y ejecutará automáticamente todas las funciones de prueba de tu crate.
+
+[marco de prueba incorporado]: https://doc.rust-lang.org/book/ch11-00-testing.html
+
+Desafortunadamente, es un poco más complicado para aplicaciones `no_std` como nuestro núcleo. El problema es que el marco de prueba de Rust utiliza implícitamente la biblioteca incorporada [`test`], que depende de la biblioteca estándar. Esto significa que no podemos usar el marco de prueba predeterminado para nuestro núcleo `#[no_std]`.
+
+[`test`]: https://doc.rust-lang.org/test/index.html
+
+Podemos ver esto cuando intentamos ejecutar `cargo test` en nuestro proyecto:
+
+```
+> cargo test
+ Compiling blog_os v0.1.0 (/…/blog_os)
+error[E0463]: can't find crate for `test`
+```
+
+Dado que el crate `test` depende de la biblioteca estándar, no está disponible para nuestro objetivo de metal desnudo. Si bien portar el crate `test` a un contexto `#[no_std]` [es posible][utest], es altamente inestable y requiere algunos hacks, como redefinir el macro `panic`.
+
+[utest]: https://github.com/japaric/utest
+
+### Marcos de Prueba Personalizados
+
+Afortunadamente, Rust soporta reemplazar el marco de prueba predeterminado a través de la característica inestable [`custom_test_frameworks`]. Esta característica no requiere bibliotecas externas y, por lo tanto, también funciona en entornos `#[no_std]`. Funciona recopilando todas las funciones anotadas con un atributo `#[test_case]` y luego invocando una función runner especificada por el usuario con la lista de pruebas como argumento. Así, proporciona a la implementación un control máximo sobre el proceso de prueba.
+
+[`custom_test_frameworks`]: https://doc.rust-lang.org/unstable-book/language-features/custom-test-frameworks.html
+
+La desventaja en comparación con el marco de prueba predeterminado es que muchas características avanzadas, como las pruebas [`should_panic`], no están disponibles. En su lugar, depende de la implementación proporcionar tales características sí es necesario. Esto es ideal para nosotros ya que tenemos un entorno de ejecución muy especial en el que las implementaciones predeterminadas de tales características avanzadas probablemente no funcionarían de todos modos. Por ejemplo, el atributo `#[should_panic]` depende de desenrollar la pila para capturar los pánicos, lo cual hemos deshabilitado para nuestro núcleo.
+
+[`should_panic`]: https://doc.rust-lang.org/book/ch11-01-writing-tests.html#checking-for-panics-with-should_panic
+
+Para implementar un marco de prueba personalizado para nuestro núcleo, añadimos lo siguiente a nuestro `main.rs`:
+
+```rust
+// en src/main.rs
+
+#![feature(custom_test_frameworks)]
+#![test_runner(crate::test_runner)]
+
+#[cfg(test)]
+pub fn test_runner(tests: &[&dyn Fn()]) {
+ println!("Ejecutando {} pruebas", tests.len());
+ for test in tests {
+ test();
+ }
+}
+```
+
+Nuestro runner solo imprime un breve mensaje de depuración y luego llama a cada función de prueba en la lista. El tipo de argumento `&[&dyn Fn()]` es un [_slice_] de referencias de [_trait object_] del trait [_Fn()_]. Es básicamente una lista de referencias a tipos que pueden ser llamados como una función. Dado que la función es inútil para ejecuciones que no son de prueba, usamos el atributo `#[cfg(test)]` para incluirlo solo para pruebas.
+
+[_slice_]: https://doc.rust-lang.org/std/primitive.slice.html
+[_trait object_]: https://doc.rust-lang.org/1.30.0/book/first-edition/trait-objects.html
+[_Fn()_]: https://doc.rust-lang.org/std/ops/trait.Fn.html
+
+Cuando ejecutamos `cargo test` ahora, vemos que ahora tiene éxito (si no lo tiene, consulta la nota a continuación). Sin embargo, todavía vemos nuestro "¡Hola Mundo!" en lugar del mensaje de nuestro `test_runner`. La razón es que nuestra función `_start` todavía se utiliza como punto de entrada. La característica de marcos de prueba personalizados genera una función `main` que llama a `test_runner`, pero esta función se ignora porque usamos el atributo `#[no_main]` y proporcionamos nuestra propia entrada.
+
+
+
+**Nota:** Actualmente hay un error en cargo que conduce a errores de "elemento lang duplicado" en `cargo test` en algunos casos. Ocurre cuando has establecido `panic = "abort"` para un perfil en tu `Cargo.toml`. Intenta eliminarlo, luego `cargo test` debería funcionar. Alternativamente, si eso no funciona, añade `panic-abort-tests = true` a la sección `[unstable]` de tu archivo `.cargo/config.toml`. Consulta el [problema de cargo](https://github.com/rust-lang/cargo/issues/7359) para más información sobre esto.
+
+
+
+Para solucionarlo, primero necesitamos cambiar el nombre de la función generada a algo diferente de `main` mediante el atributo `reexport_test_harness_main`. Luego podemos llamar a la función renombrada desde nuestra función `_start`:
+
+```rust
+// en src/main.rs
+
+#![reexport_test_harness_main = "test_main"]
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ println!("¡Hola Mundo{}!", "!");
+
+ #[cfg(test)]
+ test_main();
+
+ loop {}
+}
+```
+
+Establecemos el nombre de la función de entrada del marco de prueba en `test_main` y la llamamos desde nuestro punto de entrada `_start`. Usamos [compilación condicional] para añadir la llamada a `test_main` solo en contextos de prueba porque la función no se genera en una ejecución normal.
+
+Cuando ejecutamos `cargo test` ahora, vemos el mensaje "Ejecutando 0 pruebas" en la pantalla. Ahora estamos listos para crear nuestra primera función de prueba:
+
+```rust
+// en src/main.rs
+
+#[test_case]
+fn trivial_assertion() {
+ print!("aserción trivial... ");
+ assert_eq!(1, 1);
+ println!("[ok]");
+}
+```
+
+Cuando ejecutamos `cargo test` ahora, vemos la siguiente salida:
+
+![QEMU imprimiendo "¡Hola Mundo!", "Ejecutando 1 pruebas" y "aserción trivial... [ok]"](qemu-test-runner-output.png)
+
+El slice `tests` pasado a nuestra función `test_runner` ahora contiene una referencia a la función `trivial_assertion`. A partir de la salida `aserción trivial... [ok]` en la pantalla, vemos que la prueba fue llamada y que tuvo éxito.
+
+Después de ejecutar las pruebas, nuestro `test_runner` regresa a la función `test_main`, que a su vez regresa a nuestra función de entrada `_start`. Al final de `_start`, entramos en un bucle infinito porque la función de entrada no puede retornar. Este es un problema, porque queremos que `cargo test` salga después de ejecutar todas las pruebas.
+
+## Salida de QEMU
+
+En este momento, tenemos un bucle infinito al final de nuestra función `_start` y necesitamos cerrar QEMU manualmente en cada ejecución de `cargo test`. Esto es desafortunado porque también queremos ejecutar `cargo test` en scripts sin interacción del usuario. La solución limpia a esto sería implementar una forma adecuada de apagar nuestro OS. Desafortunadamente, esto es relativamente complejo porque requiere implementar soporte para el estándar de gestión de energía [APM] o [ACPI].
+
+[APM]: https://wiki.osdev.org/APM
+[ACPI]: https://wiki.osdev.org/ACPI
+
+Afortunadamente, hay una salida: QEMU soporta un dispositivo especial `isa-debug-exit`, que proporciona una forma fácil de salir de QEMU desde el sistema invitado. Para habilitarlo, necesitamos pasar un argumento `-device` a QEMU. Podemos hacerlo añadiendo una clave de configuración `package.metadata.bootimage.test-args` en nuestro `Cargo.toml`:
+
+```toml
+# en Cargo.toml
+
+[package.metadata.bootimage]
+test-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04"]
+```
+
+La aplicación `bootimage runner` agrega los `test-args` al comando predeterminado de QEMU para todos los ejecutables de prueba. Para un `cargo run` normal, los argumentos se ignoran.
+
+Junto con el nombre del dispositivo (`isa-debug-exit`), pasamos los dos parámetros `iobase` y `iosize` que especifican el _puerto de E/S_ a través del cual se puede alcanzar el dispositivo desde nuestro núcleo.
+
+### Puertos de E/S
+
+Hay dos enfoques diferentes para comunicar entre la CPU y el hardware periférico en x86, **E/S mapeada en memoria** y **E/S mapeada en puerto**. Ya hemos utilizado E/S mapeada en memoria para acceder al [buffer de texto VGA] a través de la dirección de memoria `0xb8000`. Esta dirección no está mapeada a RAM, sino a alguna memoria en el dispositivo VGA.
+
+[buffer de texto VGA]: @/edition-2/posts/03-vga-text-buffer/index.md
+
+En contraste, la E/S mapeada en puerto utiliza un bus de E/S separado para la comunicación. Cada periférico conectado tiene uno o más números de puerto. Para comunicarse con dicho puerto de E/S, existen instrucciones especiales de la CPU llamadas `in` y `out`, que toman un número de puerto y un byte de datos (también hay variaciones de estos comandos que permiten enviar un `u16` o `u32`).
+
+El dispositivo `isa-debug-exit` utiliza E/S mapeada en puerto. El parámetro `iobase` especifica en qué dirección de puerto debe residir el dispositivo (`0xf4` es un puerto [generalmente no utilizado][list of x86 I/O ports] en el bus de E/S de x86) y el `iosize` especifica el tamaño de puerto (`0x04` significa cuatro bytes).
+
+[list of x86 I/O ports]: https://wiki.osdev.org/I/O_Ports#The_list
+
+### Usando el Dispositivo de Salida
+
+La funcionalidad del dispositivo `isa-debug-exit` es muy simple. Cuando se escribe un `valor` en el puerto de E/S especificado por `iobase`, provoca que QEMU salga con un [código de salida] `(valor << 1) | 1`. Por lo tanto, cuando escribimos `0` en el puerto, QEMU saldrá con un código de salida `(0 << 1) | 1 = 1`, y cuando escribimos `1` en el puerto, saldrá con un código de salida `(1 << 1) | 1 = 3`.
+
+[código de salida]: https://en.wikipedia.org/wiki/Exit_status
+
+En lugar de invocar manualmente las instrucciones de ensamblaje `in` y `out`, utilizamos las abstracciones provistas por la crate [`x86_64`]. Para añadir una dependencia en esa crate, la añadimos a la sección de `dependencies` en nuestro `Cargo.toml`:
+
+[`x86_64`]: https://docs.rs/x86_64/0.14.2/x86_64/
+
+```toml
+# en Cargo.toml
+
+[dependencies]
+x86_64 = "0.14.2"
+```
+
+Ahora podemos usar el tipo [`Port`] proporcionado por la crate para crear una función `exit_qemu`:
+
+[`Port`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/port/struct.Port.html
+
+```rust
+// en src/main.rs
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(u32)]
+pub enum QemuExitCode {
+ Success = 0x10,
+ Failed = 0x11,
+}
+
+pub fn exit_qemu(exit_code: QemuExitCode) {
+ use x86_64::instructions::port::Port;
+
+ unsafe {
+ let mut port = Port::new(0xf4);
+ port.write(exit_code as u32);
+ }
+}
+```
+
+La función crea un nuevo [`Port`] en `0xf4`, que es el `iobase` del dispositivo `isa-debug-exit`. Luego escribe el código de salida pasado al puerto. Usamos `u32` porque especificamos el `iosize` del dispositivo `isa-debug-exit` como 4 bytes. Ambas operaciones son inseguras porque escribir en un puerto de E/S puede resultar en un comportamiento arbitrario.
+
+Para especificar el código de salida, creamos un enum `QemuExitCode`. La idea es salir con el código de salida de éxito si todas las pruebas tuvieron éxito y con el código de salida de fallo de otro modo. El enum está marcado como `#[repr(u32)]` para representar cada variante como un entero `u32`. Usamos el código de salida `0x10` para éxito y `0x11` para fallo. Los códigos de salida reales no importan mucho, siempre y cuando no choquen con los códigos de salida predeterminados de QEMU. Por ejemplo, usar el código de salida `0` para éxito no es una buena idea porque se convierte en `(0 << 1) | 1 = 1` después de la transformación, que es el código de salida predeterminado cuando QEMU falla al ejecutarse. Así que no podríamos diferenciar un error de QEMU de una ejecución de prueba exitosa.
+
+Ahora podemos actualizar nuestro `test_runner` para salir de QEMU después de que se hayan ejecutado todas las pruebas:
+
+```rust
+// en src/main.rs
+
+fn test_runner(tests: &[&dyn Fn()]) {
+ println!("Ejecutando {} pruebas", tests.len());
+ for test in tests {
+ test();
+ }
+ /// nuevo
+ exit_qemu(QemuExitCode::Success);
+}
+```
+
+Cuando ejecutamos `cargo test` ahora, vemos que QEMU se cierra inmediatamente después de ejecutar las pruebas. El problema es que `cargo test` interpreta la prueba como fallida aunque pasamos nuestro código de salida de éxito:
+
+```
+> cargo test
+ Finished dev [unoptimized + debuginfo] target(s) in 0.03s
+ Running target/x86_64-blog_os/debug/deps/blog_os-5804fc7d2dd4c9be
+Building bootloader
+ Compiling bootloader v0.5.3 (/home/philipp/Documents/bootloader)
+ Finished release [optimized + debuginfo] target(s) in 1.07s
+Running: `qemu-system-x86_64 -drive format=raw,file=/…/target/x86_64-blog_os/debug/
+ deps/bootimage-blog_os-5804fc7d2dd4c9be.bin -device isa-debug-exit,iobase=0xf4,
+ iosize=0x04`
+error: test failed, to rerun pass '--bin blog_os'
+```
+
+El problema es que `cargo test` considera todos los códigos de error que no sean `0` como fallidos.
+
+### Código de salida de éxito
+
+Para solucionar esto, `bootimage` proporciona una clave de configuración `test-success-exit-code` que mapea un código de salida especificado al código de salida `0`:
+
+```toml
+# en Cargo.toml
+
+[package.metadata.bootimage]
+test-args = […]
+test-success-exit-code = 33 # (0x10 << 1) | 1
+```
+
+Con esta configuración, `bootimage` mapea nuestro código de salida de éxito al código de salida 0, de modo que `cargo test` reconozca correctamente el caso de éxito y no cuente la prueba como fallida.
+
+Nuestro runner de pruebas ahora cierra automáticamente QEMU y reporta correctamente los resultados de las pruebas. Aún vemos que la ventana de QEMU permanece abierta por un breve período de tiempo, pero no es suficiente para leer los resultados. Sería agradable si pudiéramos imprimir los resultados de las pruebas en la consola en su lugar, para que podamos seguir viéndolos después de que QEMU salga.
+
+## Imprimiendo en la Consola
+
+Para ver la salida de las pruebas en la consola, necesitamos enviar los datos desde nuestro núcleo al sistema host de alguna manera. Hay varias formas de lograr esto, por ejemplo, enviando los datos a través de una interfaz de red TCP. Sin embargo, configurar una pila de red es una tarea bastante compleja, por lo que elegiremos una solución más simple.
+
+### Puerto Serial
+
+Una forma simple de enviar los datos es usar el [puerto serial], un estándar de interfaz antiguo que ya no se encuentra en computadoras modernas. Es fácil de programar y QEMU puede redirigir los bytes enviados a través del serial a la salida estándar del host o a un archivo.
+
+[puerto serial]: https://en.wikipedia.org/wiki/Serial_port
+
+Los chips que implementan una interfaz serial se llaman [UARTs]. Hay [muchos modelos de UART] en x86, pero afortunadamente las únicas diferencias entre ellos son algunas características avanzadas que no necesitamos. Los UART comunes hoy en día son todos compatibles con el [UART 16550], así que utilizaremos ese modelo para nuestro framework de pruebas.
+
+[UARTs]: https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter
+[muchos modelos de UART]: https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter#UART_models
+[UART 16550]: https://en.wikipedia.org/wiki/16550_UART
+
+Usaremos la crate [`uart_16550`] para inicializar el UART y enviar datos a través del puerto serial. Para añadirlo como dependencia, actualizamos nuestro `Cargo.toml` y `main.rs`:
+
+[`uart_16550`]: https://docs.rs/uart_16550
+
+```toml
+# en Cargo.toml
+
+[dependencies]
+uart_16550 = "0.2.0"
+```
+
+La crate `uart_16550` contiene una estructura `SerialPort` que representa los registros del UART, pero aún necesitamos construir una instancia de ella nosotros mismos. Para eso, creamos un nuevo módulo `serial` con el siguiente contenido:
+
+```rust
+// en src/main.rs
+
+mod serial;
+```
+
+```rust
+// en src/serial.rs
+
+use uart_16550::SerialPort;
+use spin::Mutex;
+use lazy_static::lazy_static;
+
+lazy_static! {
+ pub static ref SERIAL1: Mutex = {
+ let mut serial_port = unsafe { SerialPort::new(0x3F8) };
+ serial_port.init();
+ Mutex::new(serial_port)
+ };
+}
+```
+
+Al igual que con el [buffer de texto VGA][vga lazy-static], usamos `lazy_static` y un spinlock para crear una instancia `static` de escritor. Usando `lazy_static` podemos asegurarnos de que el método `init` se llame exactamente una vez en su primer uso.
+
+Al igual que el dispositivo `isa-debug-exit`, el UART se programa usando E/S de puerto. Dado que el UART es más complejo, utiliza varios puertos de E/S para programar diferentes registros del dispositivo. La función insegura `SerialPort::new` espera la dirección del primer puerto de E/S del UART como argumento, desde la cual puede calcular las direcciones de todos los puertos necesarios. Estamos pasando la dirección de puerto `0x3F8`, que es el número de puerto estándar para la primera interfaz serial.
+
+[vga lazy-static]: @/edition-2/posts/03-vga-text-buffer/index.md#lazy-statics
+
+Para hacer que el puerto serial sea fácilmente utilizable, añadimos los macros `serial_print!` y `serial_println!`:
+
+```rust
+// en src/serial.rs
+
+#[doc(hidden)]
+pub fn _print(args: ::core::fmt::Arguments) {
+ use core::fmt::Write;
+ SERIAL1.lock().write_fmt(args).expect("Error al imprimir en serial");
+}
+
+/// Imprime en el host a través de la interfaz serial.
+#[macro_export]
+macro_rules! serial_print {
+ ($($arg:tt)*) => {
+ $crate::serial::_print(format_args!($($arg)*));
+ };
+}
+
+/// Imprime en el host a través de la interfaz serial, añadiendo una nueva línea.
+#[macro_export]
+macro_rules! serial_println {
+ () => ($crate::serial_print!("\n"));
+ ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n")));
+ ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!(
+ concat!($fmt, "\n"), $($arg)*));
+}
+```
+
+La implementación es muy similar a la implementación de nuestros macros `print` y `println`. Dado que el tipo `SerialPort` ya implementa el trait [`fmt::Write`], no necesitamos proporcionar nuestra propia implementación.
+
+[`fmt::Write`]: https://doc.rust-lang.org/nightly/core/fmt/trait.Write.html
+
+Ahora podemos imprimir en la interfaz serial en lugar de en el buffer de texto VGA en nuestro código de prueba:
+
+```rust
+// en src/main.rs
+
+#[cfg(test)]
+fn test_runner(tests: &[&dyn Fn()]) {
+ serial_println!("Ejecutando {} pruebas", tests.len());
+ […]
+}
+
+#[test_case]
+fn trivial_assertion() {
+ serial_print!("aserción trivial... ");
+ assert_eq!(1, 1);
+ serial_println!("[ok]");
+}
+```
+
+Ten en cuenta que el macro `serial_println` vive directamente en el espacio de nombres raíz porque usamos el atributo `#[macro_export]`, por lo que importarlo a través de `use crate::serial::serial_println` no funcionará.
+
+### Argumentos de QEMU
+
+Para ver la salida serial de QEMU, necesitamos usar el argumento `-serial` para redirigir la salida a stdout:
+
+```toml
+# en Cargo.toml
+
+[package.metadata.bootimage]
+test-args = [
+ "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio"
+]
+```
+
+Cuando ejecutamos `cargo test` ahora, vemos la salida de las pruebas directamente en la consola:
+
+```
+> cargo test
+ Finished dev [unoptimized + debuginfo] target(s) in 0.02s
+ Running target/x86_64-blog_os/debug/deps/blog_os-7b7c37b4ad62551a
+Building bootloader
+ Finished release [optimized + debuginfo] target(s) in 0.02s
+Running: `qemu-system-x86_64 -drive format=raw,file=/…/target/x86_64-blog_os/debug/
+ deps/bootimage-blog_os-7b7c37b4ad62551a.bin -device
+ isa-debug-exit,iobase=0xf4,iosize=0x04 -serial stdio`
+Ejecutando 1 pruebas
+aserción trivial... [ok]
+```
+
+Sin embargo, cuando una prueba falla, todavía vemos la salida dentro de QEMU porque nuestro manejador de pánicos todavía usa `println`. Para simular esto, podemos cambiar la afirmación en nuestra prueba de `trivial_assertion` a `assert_eq!(0, 1)`:
+
+
+
+Vemos que el mensaje de pánico todavía se imprime en el buffer de VGA, mientras que la otra salida de prueba se imprime en el puerto serial. El mensaje de pánico es bastante útil, así que sería útil verlo también en la consola.
+
+### Imprimir un Mensaje de Error en el Pánico
+
+Para salir de QEMU con un mensaje de error en un pánico, podemos usar [compilación condicional] para usar un manejador de pánicos diferente en modo de prueba:
+
+[compilación condicional]: https://doc.rust-lang.org/1.30.0/book/first-edition/conditional-compilation.html
+
+```rust
+// en src/main.rs
+
+// nuestro manejador de pánico existente
+#[cfg(not(test))] // nuevo atributo
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ println!("{}", info);
+ loop {}
+}
+
+// nuestro manejador de pánico en modo de prueba
+#[cfg(test)]
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ serial_println!("[fallido]\n");
+ serial_println!("Error: {}\n", info);
+ exit_qemu(QemuExitCode::Failed);
+ loop {}
+}
+```
+
+Para nuestro manejador de pánico en las pruebas, usamos `serial_println` en lugar de `println` y luego salimos de QEMU con un código de salida de error. Ten en cuenta que aún necesitamos un bucle infinito después de la llamada a `exit_qemu` porque el compilador no sabe que el dispositivo `isa-debug-exit` provoca una salida del programa.
+
+Ahora QEMU también saldrá para pruebas fallidas e imprimirá un mensaje de error útil en la consola:
+
+```
+> cargo test
+ Finished dev [unoptimized + debuginfo] target(s) in 0.02s
+ Running target/x86_64-blog_os/debug/deps/blog_os-7b7c37b4ad62551a
+Building bootloader
+ Finished release [optimized + debuginfo] target(s) in 0.02s
+Running: `qemu-system-x86_64 -drive format=raw,file=/…/target/x86_64-blog_os/debug/
+ deps/bootimage-blog_os-7b7c37b4ad62551a.bin -device
+ isa-debug-exit,iobase=0xf4,iosize=0x04 -serial stdio`
+Ejecutando 1 pruebas
+aserción trivial... [fallido]
+
+Error: panicked at 'assertion failed: `(left == right)`
+ left: `0`,
+ right: `1`', src/main.rs:65:5
+```
+
+Dado que ahora vemos toda la salida de prueba en la consola, ya no necesitamos la ventana de QEMU que aparece por un corto período de tiempo. Así que podemos ocultarla completamente.
+
+### Ocultando QEMU
+
+Dado que reportamos todos los resultados de las pruebas utilizando el dispositivo `isa-debug-exit` y el puerto serial, ya no necesitamos la ventana de QEMU. Podemos ocultarla pasando el argumento `-display none` a QEMU:
+
+```toml
+# en Cargo.toml
+
+[package.metadata.bootimage]
+test-args = [
+ "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio",
+ "-display", "none"
+]
+```
+
+Ahora QEMU se ejecuta completamente en segundo plano y no se abre ninguna ventana. Esto no solo es menos molesto, sino que también permite que nuestro framework de pruebas se ejecute en entornos sin una interfaz gráfica, como servicios CI o conexiones [SSH].
+
+[SSH]: https://en.wikipedia.org/wiki/Secure_Shell
+
+### Timeouts
+
+Dado que `cargo test` espera hasta que el runner de pruebas salga, una prueba que nunca retorna puede bloquear el runner de pruebas para siempre. Eso es desafortunado, pero no es un gran problema en la práctica, ya que generalmente es fácil evitar bucles infinitos. En nuestro caso, sin embargo, pueden ocurrir bucles infinitos en varias situaciones:
+
+- El cargador de arranque no logra cargar nuestro núcleo, lo que provoca que el sistema reinicie indefinidamente.
+- El firmware BIOS/UEFI no logra cargar el cargador de arranque, lo que provoca el mismo reinicio infinito.
+- La CPU entra en una instrucción `loop {}` al final de algunas de nuestras funciones, por ejemplo, porque el dispositivo de salida QEMU no funciona correctamente.
+- El hardware provoca un reinicio del sistema, por ejemplo, cuando una excepción de CPU no es capturada (explicado en una publicación futura).
+
+Dado que los bucles infinitos pueden ocurrir en tantas situaciones, la herramienta `bootimage` establece un tiempo de espera de 5 minutos para cada ejecutable de prueba de manera predeterminada. Si la prueba no termina dentro de este tiempo, se marca como fallida y se imprime un error de "Tiempo de espera". Esta función asegura que las pruebas que están atrapadas en un bucle infinito no bloqueen `cargo test` para siempre.
+
+Puedes intentarlo tú mismo añadiendo una instrucción `loop {}` en la prueba `trivial_assertion`. Cuando ejecutes `cargo test`, verás que la prueba se marca como expirado después de 5 minutos. La duración del tiempo de espera es [configurable][bootimage config] a través de una clave `test-timeout` en el Cargo.toml:
+
+[bootimage config]: https://github.com/rust-osdev/bootimage#configuration
+
+```toml
+# en Cargo.toml
+
+[package.metadata.bootimage]
+test-timeout = 300 # (en segundos)
+```
+
+Si no quieres esperar 5 minutos para que la prueba `trivial_assertion` expire, puedes reducir temporalmente el valor anterior.
+
+### Insertar Impresión Automáticamente
+
+Nuestra prueba `trivial_assertion` actualmente necesita imprimir su propia información de estado usando `serial_print!`/`serial_println!`:
+
+```rust
+#[test_case]
+fn trivial_assertion() {
+ serial_print!("aserción trivial... ");
+ assert_eq!(1, 1);
+ serial_println!("[ok]");
+}
+```
+
+Añadir manualmente estas declaraciones de impresión para cada prueba que escribimos es engorroso, así que actualicemos nuestro `test_runner` para imprimir estos mensajes automáticamente. Para hacer eso, necesitamos crear un nuevo trait `Testable`:
+
+```rust
+// en src/main.rs
+
+pub trait Testable {
+ fn run(&self) -> ();
+}
+```
+
+El truco ahora es implementar este trait para todos los tipos `T` que implementan el trait [`Fn()`]:
+
+[`Fn()` trait]: https://doc.rust-lang.org/stable/core/ops/trait.Fn.html
+
+```rust
+// en src/main.rs
+
+impl Testable for T
+where
+ T: Fn(),
+{
+ fn run(&self) {
+ serial_print!("{}...\t", core::any::type_name::());
+ self();
+ serial_println!("[ok]");
+ }
+}
+```
+
+Implementamos la función `run` imprimiendo primero el nombre de la función utilizando la función [`any::type_name`] . Esta función se implementa directamente en el compilador y devuelve una descripción de cadena de cada tipo. Para las funciones, el tipo es su nombre, así que esto es exactamente lo que queremos en este caso. El carácter `\t` es el [carácter de tabulación], que añade algo de alineación a los mensajes `[ok]`.
+
+[`any::type_name`]: https://doc.rust-lang.org/stable/core/any/fn.type_name.html
+[carácter de tabulación]: https://en.wikipedia.org/wiki/Tab_key#Tab_characters
+
+Después de imprimir el nombre de la función, invocamos la función de prueba a través de `self()`. Esto solo funciona porque requerimos que `self` implemente el trait `Fn()`. Después de que la función de prueba retorna, imprimimos `[ok]` para indicar que la función no provocó un pánico.
+
+El último paso es actualizar nuestro `test_runner` para usar el nuevo trait `Testable`:
+
+```rust
+// en src/main.rs
+
+#[cfg(test)]
+pub fn test_runner(tests: &[&dyn Testable]) { // nuevo
+ serial_println!("Ejecutando {} pruebas", tests.len());
+ for test in tests {
+ test.run(); // nuevo
+ }
+ exit_qemu(QemuExitCode::Success);
+}
+```
+
+Los únicos dos cambios son el tipo del argumento `tests` de `&[&dyn Fn()]` a `&[&dyn Testable]` y el hecho de que ahora llamamos a `test.run()` en lugar de `test()`.
+
+Ahora podemos eliminar las declaraciones de impresión de nuestra prueba `trivial_assertion` ya que ahora se imprimen automáticamente:
+
+```rust
+// en src/main.rs
+
+#[test_case]
+fn trivial_assertion() {
+ assert_eq!(1, 1);
+}
+```
+
+La salida de `cargo test` ahora se ve así:
+
+```
+Ejecutando 1 pruebas
+blog_os::trivial_assertion... [ok]
+```
+
+El nombre de la función ahora incluye la ruta completa a la función, que es útil cuando las funciones de prueba en diferentes módulos tienen el mismo nombre. De lo contrario, la salida se ve igual que antes, pero ya no necesitamos agregar declaraciones de impresión a nuestras pruebas manualmente.
+
+## Pruebas del Buffer VGA
+
+Ahora que tenemos un marco de pruebas funcional, podemos crear algunas pruebas para nuestra implementación del buffer VGA. Primero, creamos una prueba muy simple para verificar que `println` funciona sin provocar un pánico:
+
+```rust
+// en src/vga_buffer.rs
+
+#[test_case]
+fn test_println_simple() {
+ println!("salida de test_println_simple");
+}
+```
+
+La prueba simplemente imprime algo en el buffer VGA. Si termina sin provocar un pánico, significa que la invocación de `println` tampoco provocó un pánico.
+
+Para asegurarnos de que no se produzca un pánico incluso si se imprimen muchas líneas y las líneas se desplazan de la pantalla, podemos crear otra prueba:
+
+```rust
+// en src/vga_buffer.rs
+
+#[test_case]
+fn test_println_many() {
+ for _ in 0..200 {
+ println!("salida de test_println_many");
+ }
+}
+```
+
+También podemos crear una función de prueba para verificar que las líneas impresas realmente aparecen en la pantalla:
+
+```rust
+// en src/vga_buffer.rs
+
+#[test_case]
+fn test_println_output() {
+ let s = "Alguna cadena de prueba que cabe en una única línea";
+ println!("{}", s);
+ for (i, c) in s.chars().enumerate() {
+ let screen_char = WRITER.lock().buffer.chars[BUFFER_HEIGHT - 2][i].read();
+ assert_eq!(char::from(screen_char.ascii_character), c);
+ }
+}
+```
+
+La función define una cadena de prueba, la imprime usando `println`, y luego itera sobre los caracteres de pantalla del estático `WRITER`, que representa el buffer de texto VGA. Dado que `println` imprime en la última línea de pantalla y luego inmediatamente agrega una nueva línea, la cadena debería aparecer en la línea `BUFFER_HEIGHT - 2`.
+
+Usando [`enumerate`], contamos el número de iteraciones en la variable `i`, que luego utilizamos para cargar el carácter de pantalla correspondiente a `c`. Al comparar el `ascii_character` del carácter de pantalla con `c`, nos aseguramos de que cada carácter de la cadena realmente aparece en el buffer de texto VGA.
+
+[`enumerate`]: https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.enumerate
+
+Como puedes imaginar, podríamos crear muchas más funciones de prueba. Por ejemplo, una función que teste que no se produzca un pánico al imprimir líneas muy largas y que se envuelvan correctamente, o una función que pruebe que se manejan correctamente nuevas líneas, caracteres no imprimibles y caracteres no unicode.
+
+Para el resto de esta publicación, sin embargo, explicaremos cómo crear _pruebas de integración_ para probar la interacción de diferentes componentes juntos.
+
+## Pruebas de Integración
+
+La convención para las [pruebas de integración] en Rust es ponerlas en un directorio `tests` en la raíz del proyecto (es decir, junto al directorio `src`). Tanto el marco de prueba predeterminado como los marcos de prueba personalizados recogerán y ejecutarán automáticamente todas las pruebas en ese directorio.
+
+[pruebas de integración]: https://doc.rust-lang.org/book/ch11-03-test-organization.html#integration-tests
+
+Todas las pruebas de integración son sus propios ejecutables y completamente separadas de nuestro `main.rs`. Esto significa que cada prueba necesita definir su propia función de punto de entrada. Creemos una prueba de integración de ejemplo llamada `basic_boot` para ver cómo funciona en detalle:
+
+```rust
+// en tests/basic_boot.rs
+
+#![no_std]
+#![no_main]
+#![feature(custom_test_frameworks)]
+#![test_runner(crate::test_runner)]
+#![reexport_test_harness_main = "test_main"]
+
+use core::panic::PanicInfo;
+
+#[no_mangle] // no modificar el nombre de esta función
+pub extern "C" fn _start() -> ! {
+ test_main();
+
+ loop {}
+}
+
+fn test_runner(tests: &[&dyn Fn()]) {
+ unimplemented!();
+}
+
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ loop {}
+}
+```
+
+Dado que las pruebas de integración son ejecutables separados, necesitamos proporcionar todos los atributos de crate nuevamente (`no_std`, `no_main`, `test_runner`, etc.). También necesitamos crear una nueva función de punto de entrada `_start`, que llama a la función de punto de entrada de prueba `test_main`. No necesitamos ningún atributo `cfg(test)` porque los ejecutables de prueba de integración nunca se construyen en modo no prueba.
+
+Usamos el macro [`unimplemented`] que siempre provoca un pánico como un marcador de posición para la función `test_runner` y simplemente hacemos `loop` en el manejador de pánico por ahora. Idealmente, queremos implementar estas funciones exactamente como lo hicimos en nuestro `main.rs` utilizando el macro `serial_println` y la función `exit_qemu`. El problema es que no tenemos acceso a estas funciones ya que las pruebas se construyen completamente por separado de nuestro ejecutable `main.rs`.
+
+[`unimplemented`]: https://doc.rust-lang.org/core/macro.unimplemented.html
+
+Si ejecutas `cargo test` en esta etapa, te quedarás atrapado en un bucle infinito porque el manejador de pánicos se queda en un bucle indefinidamente. Necesitas usar el atajo de teclado `ctrl+c` para salir de QEMU.
+
+### Crear una Biblioteca
+
+Para que las funciones requeridas estén disponibles para nuestra prueba de integración, necesitamos separar una biblioteca de nuestro `main.rs`, que pueda ser incluida por otros crates y ejecutables de pruebas de integración. Para hacer esto, creamos un nuevo archivo `src/lib.rs`:
+
+```rust
+// src/lib.rs
+
+#![no_std]
+
+```
+
+Al igual que `main.rs`, `lib.rs` es un archivo especial que es automáticamente reconocido por cargo. La biblioteca es una unidad de compilación separada, por lo que necesitamos especificar el atributo `#![no_std]` nuevamente.
+
+Para que nuestra biblioteca funcione con `cargo test`, también necesitamos mover las funciones y atributos de prueba de `main.rs` a `lib.rs`:
+
+```rust
+// en src/lib.rs
+
+#![cfg_attr(test, no_main)]
+#![feature(custom_test_frameworks)]
+#![test_runner(crate::test_runner)]
+#![reexport_test_harness_main = "test_main"]
+
+use core::panic::PanicInfo;
+
+pub trait Testable {
+ fn run(&self) -> ();
+}
+
+impl Testable for T
+where
+ T: Fn(),
+{
+ fn run(&self) {
+ serial_print!("{}...\t", core::any::type_name::());
+ self();
+ serial_println!("[ok]");
+ }
+}
+
+pub fn test_runner(tests: &[&dyn Testable]) {
+ serial_println!("Ejecutando {} pruebas", tests.len());
+ for test in tests {
+ test.run();
+ }
+ exit_qemu(QemuExitCode::Success);
+}
+
+pub fn test_panic_handler(info: &PanicInfo) -> ! {
+ serial_println!("[fallido]\n");
+ serial_println!("Error: {}\n", info);
+ exit_qemu(QemuExitCode::Failed);
+ loop {}
+}
+
+/// Punto de entrada para `cargo test`
+#[cfg(test)]
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ test_main();
+ loop {}
+}
+
+#[cfg(test)]
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ test_panic_handler(info)
+}
+```
+
+Para hacer que nuestra `test_runner` esté disponible para los ejecutables y pruebas de integración, la hacemos pública y no le aplicamos el atributo `cfg(test)`. También extraemos la implementación de nuestro manejador de pánicos en una función pública `test_panic_handler`, para que esté disponible para los ejecutables también.
+
+Dado que nuestra `lib.rs` se prueba independientemente de `main.rs`, necesitamos añadir una función de entrada `_start` y un manejador de pánico cuando la biblioteca se compila en modo de prueba. Usando el atributo [`cfg_attr`] de crate, habilitamos condicionalmente el atributo `no_main` en este caso.
+
+[`cfg_attr`]: https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute
+
+También movemos el enum `QemuExitCode` y la función `exit_qemu` y los hacemos públicos:
+
+```rust
+// en src/lib.rs
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(u32)]
+pub enum QemuExitCode {
+ Success = 0x10,
+ Failed = 0x11,
+}
+
+pub fn exit_qemu(exit_code: QemuExitCode) {
+ use x86_64::instructions::port::Port;
+
+ unsafe {
+ let mut port = Port::new(0xf4);
+ port.write(exit_code as u32);
+ }
+}
+```
+
+Ahora los ejecutables y las pruebas de integración pueden importar estas funciones de la biblioteca y no necesitan definir sus propias implementaciones. Para también hacer que `println` y `serial_println` estén disponibles, movemos también las declaraciones de módulo:
+
+```rust
+// en src/lib.rs
+
+pub mod serial;
+pub mod vga_buffer;
+```
+
+Hacemos que los módulos sean públicos para que sean utilizables fuera de nuestra biblioteca. Esto también es necesario para hacer que nuestros macros `println` y `serial_println` sean utilizables ya que utilizan las funciones `_print` de los módulos.
+
+Ahora podemos actualizar nuestro `main.rs` para usar la biblioteca:
+
+```rust
+// en src/main.rs
+
+#![no_std]
+#![no_main]
+#![feature(custom_test_frameworks)]
+#![test_runner(blog_os::test_runner)]
+#![reexport_test_harness_main = "test_main"]
+
+use core::panic::PanicInfo;
+use blog_os::println;
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ println!("¡Hola Mundo{}!", "!");
+
+ #[cfg(test)]
+ test_main();
+
+ loop {}
+}
+
+/// Esta función se llama en caso de pánico.
+#[cfg(not(test))]
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ println!("{}", info);
+ loop {}
+}
+
+#[cfg(test)]
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ blog_os::test_panic_handler(info)
+}
+```
+
+La biblioteca es utilizable como si fuera una crate externa normal. Se llama `blog_os`, como nuestra crate. El código anterior utiliza la función `test_runner` de `blog_os` en el atributo `test_runner` y la función `test_panic_handler` de `blog_os` en nuestro manejador de pánicos `cfg(test)`. También importa el macro `println` para hacerlo disponible en nuestras funciones `_start` y `panic`.
+
+En este punto, `cargo run` y `cargo test` deberían funcionar nuevamente. Por supuesto, `cargo test` todavía se queda atrapado en un bucle infinito (puedes salir con `ctrl+c`). Vamos a solucionar esto usando las funciones de biblioteca requeridas en nuestra prueba de integración.
+
+### Completar la Prueba de Integración
+
+Al igual que nuestro `src/main.rs`, nuestro ejecutable `tests/basic_boot.rs` puede importar tipos de nuestra nueva biblioteca. Esto nos permite importar los componentes faltantes para completar nuestra prueba:
+
+```rust
+// en tests/basic_boot.rs
+
+#![test_runner(blog_os::test_runner)]
+
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ blog_os::test_panic_handler(info)
+}
+```
+
+En lugar de reimplementar el runner de prueba, usamos la función `test_runner` de nuestra biblioteca cambiando el atributo `#![test_runner(crate::test_runner)]` a `#![test_runner(blog_os::test_runner)]`. Ya no necesitamos la función de sanidad `test_runner` de referencia en `basic_boot.rs`, así que podemos eliminarla. Para nuestro manejador de pánicos, llamamos a la función `blog_os::test_panic_handler` como hicimos en nuestro archivo `main.rs`.
+
+Ahora `cargo test` sale normalmente nuevamente. Cuando lo ejecutas, verás que construye y ejecuta las pruebas para `lib.rs`, `main.rs` y `basic_boot.rs` por separado después de cada uno. Para `main.rs` y las pruebas de integración `basic_boot`, informa "Ejecutando 0 pruebas" ya que estos archivos no tienen funciones anotadas con `#[test_case]`.
+
+Ahora podemos añadir pruebas a nuestro `basic_boot.rs`. Por ejemplo, podemos probar que `println` funciona sin provocar un pánico, como hicimos en las pruebas del buffer VGA:
+
+```rust
+// en tests/basic_boot.rs
+
+use blog_os::println;
+
+#[test_case]
+fn test_println() {
+ println!("salida de test_println");
+}
+```
+
+Cuando ejecutamos `cargo test` ahora, vemos que encuentra y ejecuta la función de prueba.
+
+La prueba podría parecer un poco inútil ahora ya que es casi idéntica a una de las pruebas del buffer VGA. Sin embargo, en el futuro, las funciones `_start` de nuestros `main.rs` y `lib.rs` podrían crecer y llamar a varias rutinas de inicialización antes de ejecutar la función `test_main`, de modo que las dos pruebas se ejecuten en entornos muy diferentes.
+
+Al probar `println` en un entorno de `basic_boot` sin llamar a ninguna rutina de inicialización en `_start`, podemos asegurarnos de que `println` funcione justo después de arrancar. Esto es importante porque nos basamos en ello, por ejemplo, para imprimir mensajes de pánico.
+
+### Pruebas Futuras
+
+El poder de las pruebas de integración es que se tratan como ejecutables completamente separados. Esto les da el control total sobre el entorno, lo que hace posible probar que el código interactúa correctamente con la CPU o dispositivos de hardware.
+
+Nuestra prueba `basic_boot` es un ejemplo muy simple de una prueba de integración. En el futuro, nuestro núcleo se volverá mucho más funcional e interactuará con el hardware de varias maneras. Al añadir pruebas de integración, podemos asegurarnos de que estas interacciones funcionen (y sigan funcionando) como se espera. Algunas ideas para posibles pruebas futuras son:
+
+- **Excepciones de CPU**: Cuando el código realiza operaciones inválidas (por ejemplo, division por cero), la CPU lanza una excepción. El núcleo puede registrar funciones de manejo para tales excepciones. Una prueba de integración podría verificar que se llame al controlador de excepciones correcto cuando ocurre una excepción de CPU o que la ejecución continúe correctamente después de una excepción recuperable.
+- **Tablas de Páginas**: Las tablas de páginas definen qué regiones de memoria son válidas y accesibles. Al modificar las tablas de páginas, es posible asignar nuevas regiones de memoria, por ejemplo, al lanzar programas. Una prueba de integración podría modificar las tablas de páginas en la función `_start` y verificar que las modificaciones tengan los efectos deseados en las funciones `#[test_case]`.
+- **Programas en Espacio de Usuario**: Los programas en espacio de usuario son programas con acceso limitado a los recursos del sistema. Por ejemplo, no tienen acceso a las estructuras de datos del núcleo ni a la memoria de otros programas. Una prueba de integración podría lanzar programas en espacio de usuario que realicen operaciones prohibidas y verificar que el núcleo las prevenga todas.
+
+Como puedes imaginar, son posibles muchas más pruebas. Al añadir tales pruebas, podemos asegurarnos de no romperlas accidentalmente al añadir nuevas características a nuestro núcleo o refactorizar nuestro código. Esto es especialmente importante cuando nuestro núcleo se vuelve más grande y complejo.
+
+### Pruebas que Deberían Fallar
+
+El marco de pruebas de la biblioteca estándar admite un atributo [`#[should_panic]`](https://doc.rust-lang.org/rust-by-example/testing/unit_testing.html#testing-panics) que permite construir funciones de prueba que deberían fallar. Esto es útil, por ejemplo, para verificar que una función falle cuando se pasa un argumento inválido. Desafortunadamente, este atributo no está soportado en crates `#[no_std]` ya que requiere soporte de la biblioteca estándar.
+
+Si bien no podemos usar el atributo `#[should_panic]` en nuestro núcleo, podemos obtener un comportamiento similar creando una prueba de integración que salga con un código de error de éxito desde el manejador de pánicos. Comencemos a crear tal prueba con el nombre `should_panic`:
+
+```rust
+// en tests/should_panic.rs
+
+#![no_std]
+#![no_main]
+
+use core::panic::PanicInfo;
+use blog_os::{QemuExitCode, exit_qemu, serial_println};
+
+#[panic_handler]
+fn panic(_info: &PanicInfo) -> ! {
+ serial_println!("[ok]");
+ exit_qemu(QemuExitCode::Success);
+ loop {}
+}
+```
+
+Esta prueba aún está incompleta ya que no define una función `_start` ni ninguno de los atributos del marco de prueba personalizados que faltan. Añadamos las partes que faltan:
+
+```rust
+// en tests/should_panic.rs
+
+#![feature(custom_test_frameworks)]
+#![test_runner(test_runner)]
+#![reexport_test_harness_main = "test_main"]
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ test_main();
+
+ loop {}
+}
+
+pub fn test_runner(tests: &[&dyn Fn()]) {
+ serial_println!("Ejecutando {} pruebas", tests.len());
+ for test in tests {
+ test();
+ serial_println!("[la prueba no falló]");
+ exit_qemu(QemuExitCode::Failed);
+ }
+ exit_qemu(QemuExitCode::Success);
+}
+```
+
+En lugar de reutilizar el `test_runner` de `lib.rs`, la prueba define su propia función `test_runner` que sale con un código de error de fallo cuando una prueba retorna sin provocar un pánico (queremos que nuestras pruebas fallen). Si no se define ninguna función de prueba, el runner sale con un código de éxito. Dado que el runner siempre sale después de ejecutar una sola prueba, no tiene sentido definir más de una función `#[test_case]`.
+
+Ahora podemos crear una prueba que debería fallar:
+
+```rust
+// en tests/should_panic.rs
+
+use blog_os::serial_print;
+
+#[test_case]
+fn should_fail() {
+ serial_print!("should_panic::should_fail...\t");
+ assert_eq!(0, 1);
+}
+```
+
+La prueba utiliza `assert_eq` para afirmar que `0` y `1` son iguales. Por supuesto, esto falla, por lo que nuestra prueba provoca un pánico como se deseaba. Ten en cuenta que necesitamos imprimir manualmente el nombre de la función usando `serial_print!` aquí porque no usamos el trait `Testable`.
+
+Cuando ejecutamos la prueba a través de `cargo test --test should_panic` vemos que es exitosa porque la prueba se produjo como se esperaba. Cuando comentamos la afirmación y ejecutamos la prueba nuevamente, vemos que, de hecho, falla con el mensaje _"la prueba no falló"_.
+
+Una gran desventaja de este enfoque es que solo funciona para una única función de prueba. Con múltiples funciones `#[test_case]`, solo se ejecuta la primera función porque la ejecución no puede continuar después de que se ha llamado al manejador de pánicos. Actualmente no sé una buena manera de resolver este problema, ¡así que házmelo saber si tienes una idea!
+
+### Pruebas Sin Harness
+
+Para las pruebas de integración que solo tienen una única función de prueba (como nuestra prueba `should_panic`), el runner de prueba no es realmente necesario. Para casos como este, podemos deshabilitar completamente el runner de pruebas y ejecutar nuestra prueba directamente en la función `_start`.
+
+La clave para esto es deshabilitar la bandera `harness` para la prueba en el `Cargo.toml`, que define si se usa un runner de prueba para una prueba de integración. Cuando está configurada como `false`, se desactivan tanto el marco de prueba predeterminado como la característica de marcos de prueba personalizados, por lo que la prueba se trata como un ejecutable normal.
+
+Deshabilitemos la bandera `harness` para nuestra prueba `should_panic`:
+
+```toml
+# en Cargo.toml
+
+[[test]]
+name = "should_panic"
+harness = false
+```
+
+Ahora simplificamos enormemente nuestra prueba `should_panic` al eliminar el código relacionado con el `test_runner`. El resultado se ve así:
+
+```rust
+// en tests/should_panic.rs
+
+#![no_std]
+#![no_main]
+
+use core::panic::PanicInfo;
+use blog_os::{exit_qemu, serial_print, serial_println, QemuExitCode};
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ should_fail();
+ serial_println!("[la prueba no falló]");
+ exit_qemu(QemuExitCode::Failed);
+ loop{}
+}
+
+fn should_fail() {
+ serial_print!("should_panic::should_fail...\t");
+ assert_eq!(0, 1);
+}
+
+#[panic_handler]
+fn panic(_info: &PanicInfo) -> ! {
+ serial_println!("[ok]");
+ exit_qemu(QemuExitCode::Success);
+ loop {}
+}
+```
+
+Ahora llamamos a la función `should_fail` directamente desde nuestra función `_start` y salimos con un código de error de fallo si retorna. Cuando ejecutamos `cargo test --test should_panic` ahora, vemos que la prueba se comporta exactamente como antes.
+
+Además de crear pruebas `should_panic`, deshabilitar el atributo `harness` también puede ser útil para pruebas de integración complejas, por ejemplo, cuando las funciones de prueba individuales tienen efectos secundarios y necesitan ejecutarse en un orden específico.
+
+## Resumen
+
+Las pruebas son una técnica muy útil para asegurarse de que ciertos componentes tengan el comportamiento deseado. Aunque no pueden mostrar la ausencia de errores, siguen siendo una herramienta útil para encontrarlos y especialmente para evitar regresiones.
+
+Esta publicación explicó cómo configurar un marco de pruebas para nuestro núcleo Rust. Utilizamos la característica de marcos de prueba personalizados de Rust para implementar el soporte para un simple atributo `#[test_case]` en nuestro entorno de metal desnudo. Usando el dispositivo `isa-debug-exit` de QEMU, nuestro runner de pruebas puede salir de QEMU después de ejecutar las pruebas y reportar el estado de las pruebas. Para imprimir mensajes de error en la consola en lugar de en el buffer de VGA, creamos un controlador básico para el puerto serial.
+
+Después de crear algunas pruebas para nuestro macro `println`, exploramos las pruebas de integración en la segunda mitad de la publicación. Aprendimos que viven en el directorio `tests` y se tratan como ejecutables completamente separados. Para dar acceso a la función `exit_qemu` y al macro `serial_println`, movimos la mayor parte de nuestro código a una biblioteca que pueden importar todos los ejecutables y pruebas de integración. Dado que las pruebas de integración se ejecutan en su propio entorno separado, permiten probar interacciones con el hardware o crear pruebas que deberían provocar pánicos.
+
+Ahora tenemos un marco de pruebas que se ejecuta en un entorno realista dentro de QEMU. Al crear más pruebas en publicaciones futuras, podemos mantener nuestro núcleo manejable a medida que se vuelva más complejo.
+
+## ¿Qué sigue?
+
+En la próxima publicación, exploraremos _excepciones de CPU_. Estas excepciones son lanzadas por la CPU cuando ocurre algo ilegal, como una división por cero o un acceso a una página de memoria no mapeada (una llamada "falta de página"). Poder capturar y examinar estas excepciones es muy importante para depurar futuros errores. El manejo de excepciones también es muy similar al manejo de interrupciones de hardware, que es necesario para el soporte del teclado.
diff --git a/blog/content/edition-2/posts/05-cpu-exceptions/index.es.md b/blog/content/edition-2/posts/05-cpu-exceptions/index.es.md
new file mode 100644
index 00000000..960c24d4
--- /dev/null
+++ b/blog/content/edition-2/posts/05-cpu-exceptions/index.es.md
@@ -0,0 +1,471 @@
++++
+title = "Excepciones de CPU"
+weight = 5
+path = "es/cpu-exceptions"
+date = 2018-06-17
+
+[extra]
+chapter = "Interrupciones"
+
+# GitHub usernames of the people that translated this post
+translators = ["dobleuber"]
++++
+
+Las excepciones de CPU ocurren en diversas situaciones erróneas, por ejemplo, al acceder a una dirección de memoria inválida o al dividir por cero. Para reaccionar ante ellas, tenemos que configurar una _tabla de descriptores de interrupción_ (IDT, por sus siglas en inglés) que proporcione funciones manejadoras. Al final de esta publicación, nuestro núcleo será capaz de capturar [excepciones de punto de interrupción] y reanudar la ejecución normal después.
+
+[excepciones de punto de interrupción]: https://wiki.osdev.org/Exceptions#Breakpoint
+
+
+
+Este blog se desarrolla abiertamente en [GitHub]. Si tiene algún problema o pregunta, por favor abra un problema allí. También puede dejar comentarios [al final]. El código fuente completo de esta publicación se puede encontrar en la rama [`post-05`][post branch].
+
+[GitHub]: https://github.com/phil-opp/blog_os
+[al final]: #comments
+
+[post branch]: https://github.com/phil-opp/blog_os/tree/post-05
+
+
+
+## Descripción general
+Una excepción indica que algo está mal con la instrucción actual. Por ejemplo, la CPU emite una excepción si la instrucción actual intenta dividir por 0. Cuando se produce una excepción, la CPU interrumpe su trabajo actual y llama inmediatamente a una función manejadora de excepciones específica, dependiendo del tipo de excepción.
+
+En x86, hay alrededor de 20 tipos diferentes de excepciones de CPU. Las más importantes son:
+
+- **Fallo de página**: Un fallo de página ocurre en accesos a memoria ilegales. Por ejemplo, si la instrucción actual intenta leer de una página no mapeada o intenta escribir en una página de solo lectura.
+- **Código de operación inválido**: Esta excepción ocurre cuando la instrucción actual es inválida, por ejemplo, cuando intentamos usar nuevas [instrucciones SSE] en una CPU antigua que no las soporta.
+- **Fallo de protección general**: Esta es la excepción con el rango más amplio de causas. Ocurre en varios tipos de violaciones de acceso, como intentar ejecutar una instrucción privilegiada en código de nivel de usuario o escribir en campos reservados en registros de configuración.
+- **Doble fallo**: Cuando ocurre una excepción, la CPU intenta llamar a la función manejadora correspondiente. Si ocurre otra excepción _mientras se llama a la función manejadora de excepciones_, la CPU genera una excepción de doble fallo. Esta excepción también ocurre cuando no hay una función manejadora registrada para una excepción.
+- **Triple fallo**: Si ocurre una excepción mientras la CPU intenta llamar a la función manejadora de doble fallo, emite un _triple fallo_ fatal. No podemos capturar o manejar un triple fallo. La mayoría de los procesadores reaccionan reiniciándose y reiniciando el sistema operativo.
+
+[instrucciones SSE]: https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions
+
+Para ver la lista completa de excepciones, consulte la [wiki de OSDev][exceptions].
+
+[exceptions]: https://wiki.osdev.org/Exceptions
+
+### La tabla de descriptores de interrupción
+Para poder capturar y manejar excepciones, tenemos que configurar una llamada _tabla de descriptores de interrupción_ (IDT). En esta tabla, podemos especificar una función manejadora para cada excepción de CPU. El hardware utiliza esta tabla directamente, por lo que necesitamos seguir un formato predefinido. Cada entrada debe tener la siguiente estructura de 16 bytes:
+
+| Tipo | Nombre | Descripción |
+| ---- | ------------------------- | ----------------------------------------------------------------------- |
+| u16 | Puntero a función [0:15] | Los bits más bajos del puntero a la función manejadora. |
+| u16 | Selector GDT | Selector de un segmento de código en la [tabla de descriptores global]. |
+| u16 | Opciones | (ver abajo) |
+| u16 | Puntero a función [16:31] | Los bits del medio del puntero a la función manejadora. |
+| u32 | Puntero a función [32:63] | Los bits restantes del puntero a la función manejadora. |
+| u32 | Reservado |
+
+[tabla de descriptores global]: https://en.wikipedia.org/wiki/Global_Descriptor_Table
+
+El campo de opciones tiene el siguiente formato:
+
+| Bits | Nombre | Descripción |
+| ----- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
+| 0-2 | Índice de tabla de pila de interrupción | 0: No cambiar pilas, 1-7: Cambiar a la n-ésima pila en la Tabla de Pila de Interrupción cuando se llama a este manejador. |
+| 3-7 | Reservado |
+| 8 | 0: Puerta de interrupción, 1: Puerta de trampa | Si este bit es 0, las interrupciones están deshabilitadas cuando se llama a este manejador. |
+| 9-11 | debe ser uno |
+| 12 | debe ser cero |
+| 13‑14 | Nivel de privilegio del descriptor (DPL) | El nivel mínimo de privilegio requerido para llamar a este manejador. |
+| 15 | Presente |
+
+Cada excepción tiene un índice de IDT predefinido. Por ejemplo, la excepción de código de operación inválido tiene índice de tabla 6 y la excepción de fallo de página tiene índice de tabla 14. Así, el hardware puede cargar automáticamente la entrada de IDT correspondiente para cada excepción. La [Tabla de Excepciones][exceptions] en la wiki de OSDev muestra los índices de IDT de todas las excepciones en la columna “Vector nr.”.
+
+Cuando ocurre una excepción, la CPU realiza aproximadamente lo siguiente:
+
+1. Empuja algunos registros en la pila, incluyendo el puntero de instrucción y el registro [RFLAGS]. (Usaremos estos valores más adelante en esta publicación.)
+2. Lee la entrada correspondiente de la tabla de descriptores de interrupción (IDT). Por ejemplo, la CPU lee la 14ª entrada cuando ocurre un fallo de página.
+3. Verifica si la entrada está presente y, si no, genera un doble fallo.
+4. Deshabilita las interrupciones de hardware si la entrada es una puerta de interrupción (bit 40 no establecido).
+5. Carga el selector [GDT] especificado en el CS (segmento de código).
+6. Salta a la función manejadora especificada.
+
+[RFLAGS]: https://en.wikipedia.org/wiki/FLAGS_register
+[GDT]: https://en.wikipedia.org/wiki/Global_Descriptor_Table
+
+No se preocupe por los pasos 4 y 5 por ahora; aprenderemos sobre la tabla de descriptores global y las interrupciones de hardware en publicaciones futuras.
+
+## Un tipo de IDT
+En lugar de crear nuestro propio tipo de IDT, utilizaremos la estructura [`InterruptDescriptorTable`] del crate `x86_64`, que luce así:
+
+[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html
+
+``` rust
+#[repr(C)]
+pub struct InterruptDescriptorTable {
+ pub divide_by_zero: Entry,
+ pub debug: Entry,
+ pub non_maskable_interrupt: Entry,
+ pub breakpoint: Entry,
+ pub overflow: Entry,
+ pub bound_range_exceeded: Entry,
+ pub invalid_opcode: Entry,
+ pub device_not_available: Entry,
+ pub double_fault: Entry,
+ pub invalid_tss: Entry,
+ pub segment_not_present: Entry,
+ pub stack_segment_fault: Entry,
+ pub general_protection_fault: Entry,
+ pub page_fault: Entry,
+ pub x87_floating_point: Entry,
+ pub alignment_check: Entry,
+ pub machine_check: Entry,
+ pub simd_floating_point: Entry,
+ pub virtualization: Entry,
+ pub security_exception: Entry,
+ // algunos campos omitidos
+}
+```
+
+Los campos tienen el tipo [`idt::Entry`], que es una estructura que representa los campos de una entrada de IDT (ver tabla anterior). El parámetro de tipo `F` define el tipo esperado de la función manejadora. Vemos que algunas entradas requieren un [`HandlerFunc`] y algunas entradas requieren un [`HandlerFuncWithErrCode`]. El fallo de página incluso tiene su propio tipo especial: [`PageFaultHandlerFunc`].
+
+[`idt::Entry`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.Entry.html
+[`HandlerFunc`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/type.HandlerFunc.html
+[`HandlerFuncWithErrCode`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/type.HandlerFuncWithErrCode.html
+[`PageFaultHandlerFunc`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/type.PageFaultHandlerFunc.html
+
+Veamos primero el tipo `HandlerFunc`:
+
+```rust
+type HandlerFunc = extern "x86-interrupt" fn(_: InterruptStackFrame);
+```
+
+Es un [alias de tipo] para un tipo de `extern "x86-interrupt" fn`. La palabra clave `extern` define una función con una [convención de llamada foránea] y se utiliza a menudo para comunicarse con código C (`extern "C" fn`). Pero, ¿cuál es la convención de llamada `x86-interrupt`?
+
+[alias de tipo]: https://doc.rust-lang.org/book/ch19-04-advanced-types.html#creating-type-synonyms-with-type-aliases
+[convención de llamada foránea]: https://doc.rust-lang.org/nomicon/ffi.html#foreign-calling-conventions
+
+## La convención de llamada de interrupción
+Las excepciones son bastante similares a las llamadas a funciones: la CPU salta a la primera instrucción de la función llamada y la ejecuta. Después, la CPU salta a la dirección de retorno y continúa la ejecución de la función madre.
+
+Sin embargo, hay una gran diferencia entre excepciones y llamadas a funciones: una llamada a función es invocada voluntariamente por una instrucción `call` insertada por el compilador, mientras que una excepción puede ocurrir en _cualquier_ instrucción. Para entender las consecuencias de esta diferencia, necesitamos examinar las llamadas a funciones en más detalle.
+
+[Convenciones de llamada] especifican los detalles de una llamada a función. Por ejemplo, especifican dónde se colocan los parámetros de la función (por ejemplo, en registros o en la pila) y cómo se devuelven los resultados. En x86_64 Linux, se aplican las siguientes reglas para funciones C (especificadas en el [ABI de System V]):
+
+[Convenciones de llamada]: https://en.wikipedia.org/wiki/Calling_convention
+[ABI de System V]: https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
+
+- los primeros seis argumentos enteros se pasan en los registros `rdi`, `rsi`, `rdx`, `rcx`, `r8`, `r9`
+- argumentos adicionales se pasan en la pila
+- los resultados se devuelven en `rax` y `rdx`
+
+Tenga en cuenta que Rust no sigue el ABI de C (de hecho, [ni siquiera hay un ABI de Rust todavía][rust abi]), por lo que estas reglas solo se aplican a funciones declaradas como `extern "C" fn`.
+
+[rust abi]: https://github.com/rust-lang/rfcs/issues/600
+
+### Registros preservados y de uso
+La convención de llamada divide los registros en dos partes: registros _preservados_ y registros _de uso_.
+
+Los valores de los registros _preservados_ deben permanecer sin cambios a través de llamadas a funciones. Por lo tanto, una función llamada (la _“llamada”_) solo puede sobrescribir estos registros si restaura sus valores originales antes de retornar. Por ello, estos registros se llaman _“guardados por el llamado”_. Un patrón común es guardar estos registros en la pila al inicio de la función y restaurarlos justo antes de retornar.
+
+En contraste, una función llamada puede sobrescribir registros _de uso_ sin restricciones. Si el llamador quiere preservar el valor de un registro de uso a través de una llamada a función, necesita respaldarlo y restaurarlo antes de la llamada a la función (por ejemplo, empujándolo a la pila). Así, los registros de uso son _guardados por el llamador_.
+
+En x86_64, la convención de llamada C especifica los siguientes registros preservados y de uso:
+
+| registros preservados | registros de uso |
+| ----------------------------------------------- | ----------------------------------------------------------- |
+| `rbp`, `rbx`, `rsp`, `r12`, `r13`, `r14`, `r15` | `rax`, `rcx`, `rdx`, `rsi`, `rdi`, `r8`, `r9`, `r10`, `r11` |
+| _guardados por el llamado_ | _guardados por el llamador_ |
+
+El compilador conoce estas reglas, por lo que genera el código en consecuencia. Por ejemplo, la mayoría de las funciones comienzan con un `push rbp`, que respalda `rbp` en la pila (porque es un registro guardado por el llamado).
+
+### Preservando todos los registros
+A diferencia de las llamadas a funciones, las excepciones pueden ocurrir en _cualquier_ instrucción. En la mayoría de los casos, ni siquiera sabemos en tiempo de compilación si el código generado causará una excepción. Por ejemplo, el compilador no puede saber si una instrucción provoca un desbordamiento de pila o un fallo de página.
+
+Dado que no sabemos cuándo ocurrirá una excepción, no podemos respaldar ningún registro antes. Esto significa que no podemos usar una convención de llamada que dependa de registros guardados por el llamador para los manejadores de excepciones. En su lugar, necesitamos una convención de llamada que preserve _todos los registros_. La convención de llamada `x86-interrupt` es una de esas convenciones, por lo que garantiza que todos los valores de los registros se restauren a sus valores originales al retornar de la función.
+
+Tenga en cuenta que esto no significa que todos los registros se guarden en la pila al ingresar la función. En su lugar, el compilador solo respalda los registros que son sobrescritos por la función. De esta manera, se puede generar un código muy eficiente para funciones cortas que solo utilizan unos pocos registros.
+
+### El marco de pila de interrupción
+En una llamada a función normal (usando la instrucción `call`), la CPU empuja la dirección de retorno antes de saltar a la función objetivo. Al retornar de la función (usando la instrucción `ret`), la CPU extrae esta dirección de retorno y salta a ella. Por lo tanto, el marco de pila de una llamada a función normal se ve así:
+
+
+
+Sin embargo, para los manejadores de excepciones e interrupciones, empujar una dirección de retorno no sería suficiente, ya que los manejadores de interrupción a menudo se ejecutan en un contexto diferente (puntero de pila, flags de CPU, etc.). En cambio, la CPU realiza los siguientes pasos cuando ocurre una interrupción:
+
+0. **Guardando el antiguo puntero de pila**: La CPU lee los valores del puntero de pila (`rsp`) y del registro del segmento de pila (`ss`) y los recuerda en un búfer interno.
+1. **Alineando el puntero de pila**: Una interrupción puede ocurrir en cualquier instrucción, por lo que el puntero de pila también puede tener cualquier valor. Sin embargo, algunas instrucciones de CPU (por ejemplo, algunas instrucciones SSE) requieren que el puntero de pila esté alineado en un límite de 16 bytes, por lo que la CPU realiza tal alineación inmediatamente después de la interrupción.
+2. **Cambiando de pilas** (en algunos casos): Se produce un cambio de pila cuando cambia el nivel de privilegio de la CPU, por ejemplo, cuando ocurre una excepción de CPU en un programa en modo usuario. También es posible configurar los cambios de pila para interrupciones específicas utilizando la llamada _Tabla de Pila de Interrupción_ (descrita en la próxima publicación).
+3. **Empujando el antiguo puntero de pila**: La CPU empuja los valores `rsp` y `ss` del paso 0 a la pila. Esto hace posible restaurar el puntero de pila original al retornar de un manejador de interrupción.
+4. **Empujando y actualizando el registro `RFLAGS`**: El registro [`RFLAGS`] contiene varios bits de control y estado. Al entrar en la interrupción, la CPU cambia algunos bits y empuja el antiguo valor.
+5. **Empujando el puntero de instrucción**: Antes de saltar a la función manejadora de la interrupción, la CPU empuja el puntero de instrucción (`rip`) y el segmento de código (`cs`). Esto es comparable al empuje de la dirección de retorno de una llamada a función normal.
+6. **Empujando un código de error** (para algunas excepciones): Para algunas excepciones específicas, como los fallos de página, la CPU empuja un código de error, que describe la causa de la excepción.
+7. **Invocando el manejador de interrupción**: La CPU lee la dirección y el descriptor de segmento de la función manejadora de interrupción del campo correspondiente en la IDT. Luego, invoca este manejador cargando los valores en los registros `rip` y `cs`.
+
+[`RFLAGS`]: https://en.wikipedia.org/wiki/FLAGS_register
+
+Así, el _marco de pila de interrupción_ se ve así:
+
+
+
+En el crate `x86_64`, el marco de pila de interrupción está representado por la estructura [`InterruptStackFrame`]. Se pasa a los manejadores de interrupción como `&mut` y se puede utilizar para recuperar información adicional sobre la causa de la excepción. La estructura no contiene un campo de código de error, ya que solo algunas pocas excepciones empujan un código de error. Estas excepciones utilizan el tipo de función separado [`HandlerFuncWithErrCode`], que tiene un argumento adicional `error_code`.
+
+[`InterruptStackFrame`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.InterruptStackFrame.html
+
+### Detrás de las escenas {#demasiada-magia}
+La convención de llamada `x86-interrupt` es una potente abstracción que oculta casi todos los detalles desordenados del proceso de manejo de excepciones. Sin embargo, a veces es útil saber lo que sucede tras el telón. Aquí hay un breve resumen de las cosas que la convención de llamada `x86-interrupt` maneja:
+
+- **Recuperando los argumentos**: La mayoría de las convenciones de llamada esperan que los argumentos se pasen en registros. Esto no es posible para los manejadores de excepciones, ya que no debemos sobrescribir los valores de ningún registro antes de respaldarlos en la pila. En cambio, la convención de llamada `x86-interrupt` es consciente de que los argumentos ya están en la pila en un desplazamiento específico.
+- **Retornando usando `iretq`**: Dado que el marco de pila de interrupción difiere completamente de los marcos de pila de llamadas a funciones normales, no podemos retornar de las funciones manejadoras a través de la instrucción `ret` normal. Así que en su lugar, se debe usar la instrucción `iretq`.
+- **Manejando el código de error**: El código de error, que se empuja para algunas excepciones, hace que las cosas sean mucho más complejas. Cambia la alineación de la pila (vea el siguiente punto) y debe ser extraído de la pila antes de retornar. La convención de llamada `x86-interrupt` maneja toda esa complejidad. Sin embargo, no sabe qué función manejadora se utiliza para qué excepción, por lo que necesita deducir esa información del número de argumentos de función. Esto significa que el programador sigue siendo responsable de utilizar el tipo de función correcto para cada excepción. Afortunadamente, el tipo `InterruptDescriptorTable` definido por el crate `x86_64` asegura que se utilicen los tipos de función correctos.
+- **Alineando la pila**: Algunas instrucciones (especialmente las instrucciones SSE) requieren que la pila esté alineada a 16 bytes. La CPU asegura esta alineación cada vez que ocurre una excepción, pero para algunas excepciones, puede destruirla de nuevo más tarde cuando empuja un código de error. La convención de llamada `x86-interrupt` se encarga de esto al realinear la pila en este caso.
+
+Si está interesado en más detalles, también tenemos una serie de publicaciones que explican el manejo de excepciones utilizando [funciones desnudas] vinculadas [al final de esta publicación][too-much-magic].
+
+[funciones desnudas]: https://github.com/rust-lang/rfcs/blob/master/text/1201-naked-fns.md
+[too-much-magic]: #demasiada-magia
+
+## Implementación
+Ahora que hemos entendido la teoría, es hora de manejar las excepciones de CPU en nuestro núcleo. Comenzaremos creando un nuevo módulo de interrupciones en `src/interrupts.rs`, que primero crea una función `init_idt` que crea una nueva `InterruptDescriptorTable`:
+
+``` rust
+// en src/lib.rs
+
+pub mod interrupts;
+
+// en src/interrupts.rs
+
+use x86_64::structures::idt::InterruptDescriptorTable;
+
+pub fn init_idt() {
+ let mut idt = InterruptDescriptorTable::new();
+}
+```
+
+Ahora podemos agregar funciones manejadoras. Comenzamos agregando un manejador para la [excepción de punto de interrupción]. La excepción de punto de interrupción es la excepción perfecta para probar el manejo de excepciones. Su único propósito es pausar temporalmente un programa cuando se ejecuta la instrucción de punto de interrupción `int3`.
+
+[excepción de punto de interrupción]: https://wiki.osdev.org/Exceptions#Breakpoint
+
+La excepción de punto de interrupción se utiliza comúnmente en depuradores: cuando el usuario establece un punto de interrupción, el depurador sobrescribe la instrucción correspondiente con la instrucción `int3` para que la CPU lance la excepción de punto de interrupción al llegar a esa línea. Cuando el usuario quiere continuar el programa, el depurador reemplaza la instrucción `int3` con la instrucción original nuevamente y continúa el programa. Para más detalles, vea la serie ["_Cómo funcionan los depuradores_"].
+
+["_Cómo funcionan los depuradores_"]: https://eli.thegreenplace.net/2011/01/27/how-debuggers-work-part-2-breakpoints
+
+Para nuestro caso de uso, no necesitamos sobrescribir instrucciones. En su lugar, solo queremos imprimir un mensaje cuando la instrucción de punto de interrupción se ejecute y luego continuar el programa. Así que creemos una simple función `breakpoint_handler` y la agreguemos a nuestra IDT:
+
+```rust
+// en src/interrupts.rs
+
+use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame};
+use crate::println;
+
+pub fn init_idt() {
+ let mut idt = InterruptDescriptorTable::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+}
+
+extern "x86-interrupt" fn breakpoint_handler(
+ stack_frame: InterruptStackFrame)
+{
+ println!("EXCEPCIÓN: PUNTO DE INTERRUPCIÓN\n{:#?}", stack_frame);
+}
+```
+
+Nuestro manejador simplemente muestra un mensaje y imprime en formato bonito el marco de pila de interrupción.
+
+Cuando intentamos compilarlo, ocurre el siguiente error:
+
+```
+error[E0658]: la ABI de x86-interrupt es experimental y está sujeta a cambios (ver issue #40180)
+ --> src/main.rs:53:1
+ |
+53 | / extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) {
+54 | | println!("EXCEPCIÓN: PUNTO DE INTERRUPCIÓN\n{:#?}", stack_frame);
+55 | | }
+ | |_^
+ |
+ = ayuda: añade #![feature(abi_x86_interrupt)] a los atributos del crate para habilitarlo
+```
+
+Este error ocurre porque la convención de llamada `x86-interrupt` sigue siendo inestable. Para utilizarla de todos modos, tenemos que habilitarla explícitamente añadiendo `#![feature(abi_x86_interrupt)]` en la parte superior de nuestro `lib.rs`.
+
+### Cargando la IDT
+Para que la CPU utilice nuestra nueva tabla de descriptores de interrupción, necesitamos cargarla usando la instrucción [`lidt`]. La estructura `InterruptDescriptorTable` del crate `x86_64` proporciona un método [`load`][InterruptDescriptorTable::load] para eso. Intentemos usarlo:
+
+[`lidt`]: https://www.felixcloutier.com/x86/lgdt:lidt
+[InterruptDescriptorTable::load]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html#method.load
+
+```rust
+// en src/interrupts.rs
+
+pub fn init_idt() {
+ let mut idt = InterruptDescriptorTable::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ idt.load();
+}
+```
+
+Cuando intentamos compilarlo ahora, ocurre el siguiente error:
+
+```
+error: `idt` no vive lo suficiente
+ --> src/interrupts/mod.rs:43:5
+ |
+43 | idt.load();
+ | ^^^ no vive lo suficiente
+44 | }
+ | - el valor prestado solo es válido hasta aquí
+ |
+ = nota: el valor prestado debe ser válido durante la vida estática...
+```
+
+Así que el método `load` espera un `&'static self`, es decir, una referencia válida para la duración completa del programa. La razón es que la CPU accederá a esta tabla en cada interrupción hasta que se cargue una IDT diferente. Por lo tanto, usar una vida más corta que `'static` podría llevar a errores de uso después de liberar.
+
+De hecho, esto es exactamente lo que sucede aquí. Nuestra `idt` se crea en la pila, por lo que solo es válida dentro de la función `init`. Después, la memoria de la pila se reutiliza para otras funciones, por lo que la CPU podría interpretar una memoria aleatoria de la pila como IDT. Afortunadamente, el método `load` de `InterruptDescriptorTable` codifica este requisito de vida en su definición de función, para que el compilador de Rust pueda prevenir este posible error en tiempo de compilación.
+
+Para solucionar este problema, necesitamos almacenar nuestra `idt` en un lugar donde tenga una vida `'static`. Para lograr esto, podríamos asignar nuestra IDT en el montón usando [`Box`] y luego convertirla en una referencia `'static`, pero estamos escribiendo un núcleo de sistema operativo y, por lo tanto, no tenemos un montón (todavía).
+
+[`Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html
+
+Como alternativa, podríamos intentar almacenar la IDT como una `static`:
+
+```rust
+static IDT: InterruptDescriptorTable = InterruptDescriptorTable::new();
+
+pub fn init_idt() {
+ IDT.breakpoint.set_handler_fn(breakpoint_handler);
+ IDT.load();
+}
+```
+
+Sin embargo, hay un problema: las estáticas son inmutables, por lo que no podemos modificar la entrada de punto de interrupción desde nuestra función `init`. Podríamos resolver este problema utilizando un [`static mut`]:
+
+[`static mut`]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable
+
+```rust
+static mut IDT: InterruptDescriptorTable = InterruptDescriptorTable::new();
+
+pub fn init_idt() {
+ unsafe {
+ IDT.breakpoint.set_handler_fn(breakpoint_handler);
+ IDT.load();
+ }
+}
+```
+
+Esta variante se compila sin errores, pero está lejos de ser idiomática. Las variables `static mut` son muy propensas a condiciones de carrera, por lo que necesitamos un bloque [`unsafe`] en cada acceso.
+
+[`unsafe`]: https://doc.rust-lang.org/1.30.0/book/second-edition/ch19-01-unsafe-rust.html#unsafe-superpowers
+
+#### Las estáticas perezosas al rescate
+Afortunadamente, existe el macro `lazy_static`. En lugar de evaluar una `static` en tiempo de compilación, el macro realiza la inicialización de cuando la `static` es referenciada por primera vez. Por lo tanto, podemos hacer casi todo en el bloque de inicialización e incluso ser capaces de leer valores en tiempo de ejecución.
+
+Ya importamos el crate `lazy_static` cuando [creamos una abstracción para el búfer de texto VGA][vga text buffer lazy static]. Así que podemos utilizar directamente el macro `lazy_static!` para crear nuestra IDT estática:
+
+[vga text buffer lazy static]: @/edition-2/posts/03-vga-text-buffer/index.md#lazy-statics
+
+```rust
+// en src/interrupts.rs
+
+use lazy_static::lazy_static;
+
+lazy_static! {
+ static ref IDT: InterruptDescriptorTable = {
+ let mut idt = InterruptDescriptorTable::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ idt
+ };
+}
+
+pub fn init_idt() {
+ IDT.load();
+}
+```
+
+Tenga en cuenta cómo esta solución no requiere bloques `unsafe`. El macro `lazy_static!` utiliza `unsafe` detrás de escena, pero está abstraído en una interfaz segura.
+
+### Ejecutándolo
+
+El último paso para hacer que las excepciones funcionen en nuestro núcleo es llamar a la función `init_idt` desde nuestro `main.rs`. En lugar de llamarla directamente, introducimos una función de inicialización general en nuestro `lib.rs`:
+
+```rust
+// en src/lib.rs
+
+pub fn init() {
+ interrupts::init_idt();
+}
+```
+
+Con esta función, ahora tenemos un lugar central para las rutinas de inicialización que se pueden compartir entre las diferentes funciones `_start` en nuestro `main.rs`, `lib.rs` y pruebas de integración.
+
+Ahora podemos actualizar la función `_start` de nuestro `main.rs` para llamar a `init` y luego activar una excepción de punto de interrupción:
+
+```rust
+// en src/main.rs
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ println!("¡Hola Mundo{}", "!");
+
+ blog_os::init(); // nueva
+
+ // invocar una excepción de punto de interrupción
+ x86_64::instructions::interrupts::int3(); // nueva
+
+ // como antes
+ #[cfg(test)]
+ test_main();
+
+ println!("¡No se bloqueó!");
+ loop {}
+}
+```
+
+Cuando lo ejecutamos en QEMU ahora (usando `cargo run`), vemos lo siguiente:
+
+
+
+¡Funciona! La CPU invoca exitosamente nuestro manejador de punto de interrupción, que imprime el mensaje, y luego devuelve a la función `_start`, donde se imprime el mensaje `¡No se bloqueó!`.
+
+Vemos que el marco de pila de interrupción nos indica los punteros de instrucción y de pila en el momento en que ocurrió la excepción. Esta información es muy útil al depurar excepciones inesperadas.
+
+### Agregando una prueba
+
+Creemos una prueba que asegure que lo anterior sigue funcionando. Primero, actualizamos la función `_start` para que también llame a `init`:
+
+```rust
+// en src/lib.rs
+
+/// Punto de entrada para `cargo test`
+#[cfg(test)]
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ init(); // nueva
+ test_main();
+ loop {}
+}
+```
+
+Recuerde, esta función `_start` se utiliza al ejecutar `cargo test --lib`, ya que Rust prueba el `lib.rs` completamente de forma independiente de `main.rs`. Necesitamos llamar a `init` aquí para configurar una IDT antes de ejecutar las pruebas.
+
+Ahora podemos crear una prueba `test_breakpoint_exception`:
+
+```rust
+// en src/interrupts.rs
+
+#[test_case]
+fn test_breakpoint_exception() {
+ // invocar una excepción de punto de interrupción
+ x86_64::instructions::interrupts::int3();
+}
+```
+
+La prueba invoca la función `int3` para activar una excepción de punto de interrupción. Al verificar que la ejecución continúa después, verificamos que nuestro manejador de punto de interrupción está funcionando correctamente.
+
+Puedes probar esta nueva prueba ejecutando `cargo test` (todas las pruebas) o `cargo test --lib` (solo las pruebas de `lib.rs` y sus módulos). Deberías ver lo siguiente en la salida:
+
+```
+blog_os::interrupts::test_breakpoint_exception... [ok]
+```
+
+## ¿Demasiada magia?
+La convención de llamada `x86-interrupt` y el tipo [`InterruptDescriptorTable`] hicieron que el proceso de manejo de excepciones fuera relativamente sencillo y sin dolor. Si esto fue demasiada magia para ti y te gusta aprender todos los detalles sucios del manejo de excepciones, tenemos cubiertos: Nuestra serie ["Manejo de Excepciones con Funciones Desnudas"] muestra cómo manejar excepciones sin la convención de llamada `x86-interrupt` y también crea su propio tipo de IDT. Históricamente, estas publicaciones eran las principales publicaciones sobre manejo de excepciones antes de que existieran la convención de llamada `x86-interrupt` y el crate `x86_64`. Tenga en cuenta que estas publicaciones se basan en la [primera edición] de este blog y pueden estar desactualizadas.
+
+["Manejo de Excepciones con Funciones Desnudas"]: @/edition-1/extra/naked-exceptions/_index.md
+[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html
+[primera edición]: @/edition-1/_index.md
+
+## ¿Qué sigue?
+¡Hemos capturado con éxito nuestra primera excepción y regresamos de ella! El siguiente paso es asegurarnos de que capturamos todas las excepciones porque una excepción no capturada causa un [triple fallo] fatal, lo que lleva a un reinicio del sistema. La próxima publicación explica cómo podemos evitar esto al capturar correctamente [dobles fallos].
+
+[triple fallo]: https://wiki.osdev.org/Triple_Fault
+[dobles fallos]: https://wiki.osdev.org/Double_Fault#Double_Fault
diff --git a/blog/content/edition-2/posts/06-double-faults/index.es.md b/blog/content/edition-2/posts/06-double-faults/index.es.md
new file mode 100644
index 00000000..1ac4f44d
--- /dev/null
+++ b/blog/content/edition-2/posts/06-double-faults/index.es.md
@@ -0,0 +1,552 @@
++++
+title = "Excepciones de Doble Fallo"
+weight = 6
+path = "es/double-fault-exceptions"
+date = 2018-06-18
+
+[extra]
+chapter = "Interrupciones"
+
+# GitHub usernames of the people that translated this post
+translators = ["dobleuber"]
++++
+
+Esta publicación explora en detalle la excepción de doble fallo, que ocurre cuando la CPU no logra invocar un controlador de excepciones. Al manejar esta excepción, evitamos fallos _triples_ fatales que causan un reinicio del sistema. Para prevenir fallos triples en todos los casos, también configuramos una _Tabla de Pila de Interrupciones_ (IST) para capturar dobles fallos en una pila de núcleo separada.
+
+
+
+Este blog se desarrolla abiertamente en [GitHub]. Si tienes problemas o preguntas, abre un issue allí. También puedes dejar comentarios [al final]. El código fuente completo de esta publicación se puede encontrar en la rama [`post-06`][post branch].
+
+[GitHub]: https://github.com/phil-opp/blog_os
+[al final]: #comments
+
+[post branch]: https://github.com/phil-opp/blog_os/tree/post-06
+
+
+
+## ¿Qué es un Doble Fallo?
+En términos simplificados, un doble fallo es una excepción especial que ocurre cuando la CPU no logra invocar un controlador de excepciones. Por ejemplo, ocurre cuando se activa un fallo de página pero no hay un controlador de fallo de página registrado en la [Tabla de Descriptores de Interrupciones][IDT] (IDT). Así que es un poco similar a los bloques de captura de "cosecha todo" en lenguajes de programación con excepciones, por ejemplo, `catch(...)` en C++ o `catch(Exception e)` en Java o C#.
+
+[IDT]: @/edition-2/posts/05-cpu-exceptions/index.md#the-interrupt-descriptor-table
+
+Un doble fallo se comporta como una excepción normal. Tiene el número de vector `8` y podemos definir una función controladora normal para él en la IDT. Es realmente importante proporcionar un controlador de doble fallo, porque si un doble fallo no se maneja, ocurre un fallo _triple_ fatal. Los fallos triples no se pueden capturar, y la mayoría del hardware reacciona con un reinicio del sistema.
+
+### Provocando un Doble Fallo
+Provocamos un doble fallo al activar una excepción para la cual no hemos definido una función controladora:
+
+```rust
+// en src/main.rs
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ println!("Hello World{}", "!");
+
+ blog_os::init();
+
+ // provocar un fallo de página
+ unsafe {
+ *(0xdeadbeef as *mut u8) = 42;
+ };
+
+ // como antes
+ #[cfg(test)]
+ test_main();
+
+ println!("¡No se colapsó!");
+ loop {}
+}
+```
+
+Usamos `unsafe` para escribir en la dirección inválida `0xdeadbeef`. La dirección virtual no está mapeada a una dirección física en las tablas de páginas, por lo que ocurre un fallo de página. No hemos registrado un controlador de fallo de página en nuestra [IDT], así que ocurre un doble fallo.
+
+Cuando iniciamos nuestro núcleo ahora, vemos que entra en un bucle de arranque interminable. La razón del bucle de arranque es la siguiente:
+
+1. La CPU intenta escribir en `0xdeadbeef`, lo que causa un fallo de página.
+2. La CPU consulta la entrada correspondiente en la IDT y ve que no se especifica ninguna función controladora. Por lo tanto, no puede llamar al controlador de fallo de página y ocurre un doble fallo.
+3. La CPU consulta la entrada de la IDT del controlador de doble fallo, pero esta entrada tampoco especifica una función controladora. Por lo tanto, ocurre un fallo _triple_.
+4. Un fallo triple es fatal. QEMU reacciona a esto como la mayoría del hardware real y emite un reinicio del sistema.
+
+Por lo tanto, para prevenir este fallo triple, necesitamos proporcionar una función controladora para los fallos de página o un controlador de doble fallo. Queremos evitar los fallos triples en todos los casos, así que empecemos con un controlador de doble fallo que se invoca para todos los tipos de excepciones no manejadas.
+
+## Un Controlador de Doble Fallo
+Un doble fallo es una excepción normal con un código de error, por lo que podemos especificar una función controladora similar a nuestra función controladora de punto de interrupción:
+
+```rust
+// en src/interrupts.rs
+
+lazy_static! {
+ static ref IDT: InterruptDescriptorTable = {
+ let mut idt = InterruptDescriptorTable::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ idt.double_fault.set_handler_fn(double_fault_handler); // nuevo
+ idt
+ };
+}
+
+// nuevo
+extern "x86-interrupt" fn double_fault_handler(
+ stack_frame: InterruptStackFrame, _error_code: u64) -> !
+{
+ panic!("EXCEPCIÓN: DOBLE FALLO\n{:#?}", stack_frame);
+}
+```
+
+Nuestro controlador imprime un corto mensaje de error y volcado del marco de pila de excepciones. El código de error del controlador de doble fallo siempre es cero, así que no hay razón para imprimirlo. Una diferencia con el controlador de punto de interrupción es que el controlador de doble fallo es [_divergente_]. La razón es que la arquitectura `x86_64` no permite devolver de una excepción de doble fallo.
+
+[_divergente_]: https://doc.rust-lang.org/stable/rust-by-example/fn/diverging.html
+
+Cuando iniciamos nuestro núcleo ahora, deberíamos ver que se invoca el controlador de doble fallo:
+
+
+
+¡Funcionó! Aquí está lo que sucedió esta vez:
+
+1. La CPU intenta escribir en `0xdeadbeef`, lo que causa un fallo de página.
+2. Como antes, la CPU consulta la entrada correspondiente en la IDT y ve que no se define ninguna función controladora. Así que ocurre un doble fallo.
+3. La CPU salta al – ahora presente – controlador de doble fallo.
+
+El fallo triple (y el bucle de arranque) ya no ocurre, ya que la CPU ahora puede llamar al controlador de doble fallo.
+
+¡Eso fue bastante directo! Entonces, ¿por qué necesitamos una publicación completa sobre este tema? Bueno, ahora podemos capturar la mayoría de los dobles fallos, pero hay algunos casos en los que nuestro enfoque actual no es suficiente.
+
+## Causas de Doble Fallos
+Antes de mirar los casos especiales, necesitamos conocer las causas exactas de los dobles fallos. Arriba, usamos una definición bastante vaga:
+
+> Un doble fallo es una excepción especial que ocurre cuando la CPU no logra invocar un controlador de excepciones.
+
+¿Qué significa exactamente _“no logra invocar”_? ¿No está presente el controlador? ¿El controlador está [intercambiado]? ¿Y qué sucede si un controlador causa excepciones a su vez?
+
+[intercambiado]: http://pages.cs.wisc.edu/~remzi/OSTEP/vm-beyondphys.pdf
+
+Por ejemplo, ¿qué ocurre si:
+
+1. ocurre una excepción de punto de interrupción, pero la función controladora correspondiente está intercambiada?
+2. ocurre un fallo de página, pero el controlador de fallo de página está intercambiado?
+3. un controlador de división por cero causa una excepción de punto de interrupción, pero el controlador de punto de interrupción está intercambiado?
+4. nuestro núcleo desborda su pila y se activa la _página de guardia_?
+
+Afortunadamente, el manual de AMD64 ([PDF][AMD64 manual]) tiene una definición exacta (en la Sección 8.2.9). Según él, una “excepción de doble fallo _puede_ ocurrir cuando una segunda excepción ocurre durante el manejo de un controlador de excepción previo (primera)”. El _“puede”_ es importante: Solo combinaciones muy específicas de excepciones conducen a un doble fallo. Estas combinaciones son:
+
+| Primera Excepción | Segunda Excepción |
+| ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
+| [División por cero], [TSS No Válido], [Segmento No Presente], [Fallo de Segmento de Pila], [Fallo de Protección General] | [TSS No Válido], [Segmento No Presente], [Fallo de Segmento de Pila], [Fallo de Protección General] |
+| [Fallo de Página] | [Fallo de Página], [TSS No Válido], [Segmento No Presente], [Fallo de Segmento de Pila], [Fallo de Protección General] |
+
+[División por cero]: https://wiki.osdev.org/Exceptions#Division_Error
+[TSS No Válido]: https://wiki.osdev.org/Exceptions#Invalid_TSS
+[Segmento No Presente]: https://wiki.osdev.org/Exceptions#Segment_Not_Present
+[Fallo de Segmento de Pila]: https://wiki.osdev.org/Exceptions#Stack-Segment_Fault
+[Fallo de Protección General]: https://wiki.osdev.org/Exceptions#General_Protection_Fault
+[Fallo de Página]: https://wiki.osdev.org/Exceptions#Page_Fault
+
+
+[AMD64 manual]: https://www.amd.com/system/files/TechDocs/24593.pdf
+
+Así que, por ejemplo, un fallo de división por cero seguido de un fallo de página está bien (se invoca el controlador de fallo de página), pero un fallo de división por cero seguido de un fallo de protección general conduce a un doble fallo.
+
+Con la ayuda de esta tabla, podemos responder las tres primeras preguntas anteriores:
+
+1. Si ocurre una excepción de punto de interrupción y la función controladora correspondiente está intercambiada, ocurre un _fallo de página_ y se invoca el _controlador de fallo de página_.
+2. Si ocurre un fallo de página y el controlador de fallo de página está intercambiado, ocurre un _doble fallo_ y se invoca el _controlador de doble fallo_.
+3. Si un controlador de división por cero causa una excepción de punto de interrupción, la CPU intenta invocar el controlador de punto de interrupción. Si el controlador de punto de interrupción está intercambiado, ocurre un _fallo de página_ y se invoca el _controlador de fallo de página_.
+
+De hecho, incluso el caso de una excepción sin una función controladora en la IDT sigue este esquema: Cuando ocurre la excepción, la CPU intenta leer la entrada correspondiente de la IDT. Dado que la entrada es 0, que no es una entrada válida de la IDT, ocurre un _fallo de protección general_. No definimos una función controladora para el fallo de protección general tampoco, así que ocurre otro fallo de protección general. Según la tabla, esto conduce a un doble fallo.
+
+### Desbordamiento de Pila del Núcleo
+Veamos la cuarta pregunta:
+
+> ¿Qué ocurre si nuestro núcleo desborda su pila y se activa la página de guardia?
+
+Una página de guardia es una página de memoria especial en la parte inferior de una pila que permite detectar desbordamientos de pila. La página no está mapeada a ningún marco físico, por lo que acceder a ella provoca un fallo de página en lugar de corromper silenciosamente otra memoria. El cargador de arranque establece una página de guardia para nuestra pila de núcleo, así que un desbordamiento de pila provoca un _fallo de página_.
+
+Cuando ocurre un fallo de página, la CPU busca el controlador de fallo de página en la IDT e intenta empujar el [marco de pila de interrupción] en la pila. Sin embargo, el puntero de pila actual aún apunta a la página de guardia no presente. Por lo tanto, ocurre un segundo fallo de página, que causa un doble fallo (según la tabla anterior).
+
+[marco de pila de interrupción]: @/edition-2/posts/05-cpu-exceptions/index.md#the-interrupt-stack-frame
+
+Así que la CPU intenta llamar al _controlador de doble fallo_ ahora. Sin embargo, en un doble fallo, la CPU también intenta empujar el marco de pila de excepción. El puntero de pila aún apunta a la página de guardia, por lo que ocurre un _tercer_ fallo de página, que causa un _fallo triple_ y un reinicio del sistema. Así que nuestro actual controlador de doble fallo no puede evitar un fallo triple en este caso.
+
+¡Probémoslo nosotros mismos! Podemos provocar fácilmente un desbordamiento de pila del núcleo llamando a una función que recursivamente se llame a sí misma sin fin:
+
+```rust
+// en src/main.rs
+
+#[no_mangle] // no mangles el nombre de esta función
+pub extern "C" fn _start() -> ! {
+ println!("Hello World{}", "!");
+
+ blog_os::init();
+
+ fn stack_overflow() {
+ stack_overflow(); // por cada recursión, se empuja la dirección de retorno
+ }
+
+ // provocar un desbordamiento de pila
+ stack_overflow();
+
+ […] // test_main(), println(…), y loop {}
+}
+```
+
+Cuando intentamos este código en QEMU, vemos que el sistema entra en un bucle de arranque nuevamente.
+
+Entonces, ¿cómo podemos evitar este problema? No podemos omitir el empuje del marco de pila de excepción, ya que la CPU lo hace ella misma. Así que necesitamos asegurarnos de alguna manera de que la pila sea siempre válida cuando ocurra una excepción de doble fallo. Afortunadamente, la arquitectura `x86_64` tiene una solución a este problema.
+
+## Cambio de Pilas
+La arquitectura `x86_64` es capaz de cambiar a una pila conocida y predefinida cuando ocurre una excepción. Este cambio se realiza a nivel de hardware, así que se puede hacer antes de que la CPU empuje el marco de pila de excepción.
+
+El mecanismo de cambio se implementa como una _Tabla de Pila de Interrupciones_ (IST). La IST es una tabla de 7 punteros a pilas conocidas y válidas. En pseudocódigo estilo Rust:
+
+```rust
+struct InterruptStackTable {
+ stack_pointers: [Option; 7],
+}
+```
+
+Para cada controlador de excepciones, podemos elegir una pila de la IST a través del campo `stack_pointers` en la entrada correspondiente de la [IDT]. Por ejemplo, nuestro controlador de doble fallo podría usar la primera pila en la IST. Entonces, la CPU cambia automáticamente a esta pila cada vez que ocurre un doble fallo. Este cambio ocurriría antes de que se empuje cualquier cosa, previniendo el fallo triple.
+
+[IDT entry]: @/edition-2/posts/05-cpu-exceptions/index.md#the-interrupt-descriptor-table
+
+### La IST y TSS
+La Tabla de Pila de Interrupciones (IST) es parte de una estructura antigua llamada _[Segmento de Estado de Tarea]_ (TSS). La TSS solía contener varias piezas de información (por ejemplo, el estado de registro del procesador) sobre una tarea en modo de 32 bits y se usaba, por ejemplo, para [cambio de contexto de hardware]. Sin embargo, el cambio de contexto de hardware ya no se admite en modo de 64 bits y el formato de la TSS ha cambiado completamente.
+
+[Segmento de Estado de Tarea]: https://en.wikipedia.org/wiki/Task_state_segment
+[cambio de contexto de hardware]: https://wiki.osdev.org/Context_Switching#Hardware_Context_Switching
+
+En `x86_64`, la TSS ya no contiene ninguna información específica de tarea. En su lugar, contiene dos tablas de pilas (la IST es una de ellas). El único campo común entre la TSS de 32 bits y 64 bits es el puntero al [bitmap de permisos de puertos de E/S].
+
+[bitmap de permisos de puertos de E/S]: https://en.wikipedia.org/wiki/Task_state_segment#I.2FO_port_permissions
+
+La TSS de 64 bits tiene el siguiente formato:
+
+| Campo | Tipo |
+| --------------------------------------------- | ---------- |
+| (reservado) | `u32` |
+| Tabla de Pilas de Privilegio | `[u64; 3]` |
+| (reservado) | `u64` |
+| Tabla de Pila de Interrupciones | `[u64; 7]` |
+| (reservado) | `u64` |
+| (reservado) | `u16` |
+| Dirección Base del Mapa de E/S | `u16` |
+
+La _Tabla de Pilas de Privilegio_ es usada por la CPU cuando cambia el nivel de privilegio. Por ejemplo, si ocurre una excepción mientras la CPU está en modo usuario (nivel de privilegio 3), la CPU normalmente cambia a modo núcleo (nivel de privilegio 0) antes de invocar el controlador de excepciones. En ese caso, la CPU cambiaría a la 0ª pila en la Tabla de Pilas de Privilegio (ya que 0 es el nivel de privilegio de destino). Aún no tenemos programas en modo usuario, así que ignoraremos esta tabla por ahora.
+
+### Creando una TSS
+Creemos una nueva TSS que contenga una pila de doble fallo separada en su tabla de pila de interrupciones. Para ello, necesitamos una estructura TSS. Afortunadamente, la crate `x86_64` ya contiene una [`struct TaskStateSegment`] que podemos usar.
+
+[`struct TaskStateSegment`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/tss/struct.TaskStateSegment.html
+
+Creamos la TSS en un nuevo módulo `gdt` (el nombre tendrá sentido más adelante):
+
+```rust
+// en src/lib.rs
+
+pub mod gdt;
+
+// en src/gdt.rs
+
+use x86_64::VirtAddr;
+use x86_64::structures::tss::TaskStateSegment;
+use lazy_static::lazy_static;
+
+pub const DOUBLE_FAULT_IST_INDEX: u16 = 0;
+
+lazy_static! {
+ static ref TSS: TaskStateSegment = {
+ let mut tss = TaskStateSegment::new();
+ tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = {
+ const STACK_SIZE: usize = 4096 * 5;
+ static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
+
+ let stack_start = VirtAddr::from_ptr(unsafe { &STACK });
+ let stack_end = stack_start + STACK_SIZE;
+ stack_end
+ };
+ tss
+ };
+}
+```
+
+Usamos `lazy_static` porque el evaluador de const de Rust aún no es lo suficientemente potente como para hacer esta inicialización en tiempo de compilación. Definimos que la entrada 0 de la IST es la pila de doble fallo (cualquier otro índice de IST también funcionaría). Luego, escribimos la dirección superior de una pila de doble fallo en la entrada 0. Escribimos la dirección superior porque las pilas en `x86` crecen hacia abajo, es decir, de direcciones altas a bajas.
+
+No hemos implementado la gestión de memoria aún, así que no tenemos una forma adecuada de asignar una nueva pila. En su lugar, usamos un array `static mut` como almacenamiento de pila por ahora. El `unsafe` es requerido porque el compilador no puede garantizar la ausencia de condiciones de carrera cuando se accede a estáticos mutables. Es importante que sea un `static mut` y no un `static` inmutable, porque de lo contrario el cargador de arranque lo mapeará a una página de solo lectura. Reemplazaremos esto con una asignación de pila adecuada en una publicación posterior, entonces el `unsafe` ya no será necesario en este lugar.
+
+Ten en cuenta que esta pila de doble fallo no tiene página de guardia que proteja contra el desbordamiento de pila. Esto significa que no deberíamos hacer nada intensivo en pila en nuestro controlador de doble fallo porque un desbordamiento de pila podría corromper la memoria debajo de la pila.
+
+#### Cargando la TSS
+Ahora que hemos creado una nueva TSS, necesitamos una forma de decirle a la CPU que debe usarla. Desafortunadamente, esto es un poco engorroso ya que la TSS utiliza el sistema de segmentación (por razones históricas). En lugar de cargar la tabla directamente, necesitamos agregar un nuevo descriptor de segmento a la [Tabla Global de Descriptores] (GDT). Luego podemos cargar nuestra TSS invocando la instrucción [`ltr`] con el índice correspondiente de la GDT. (Esta es la razón por la que llamamos a nuestro módulo `gdt`).
+
+[Tabla Global de Descriptores]: https://web.archive.org/web/20190217233448/https://www.flingos.co.uk/docs/reference/Global-Descriptor-Table/
+[`ltr`]: https://www.felixcloutier.com/x86/ltr
+
+### La Tabla Global de Descriptores
+La Tabla Global de Descriptores (GDT) es un reliquia que se usaba para [segmentación de memoria] antes de que la paginación se convirtiera en el estándar de facto. Sin embargo, todavía se necesita en modo de 64 bits para varias cosas, como la configuración del modo núcleo/usuario o la carga de la TSS.
+
+[segmentación de memoria]: https://en.wikipedia.org/wiki/X86_memory_segmentation
+
+La GDT es una estructura que contiene los _segmentos_ del programa. Se usaba en arquitecturas más antiguas para aislar programas unos de otros antes de que la paginación se convirtiera en el estándar. Para más información sobre segmentación, consulta el capítulo del mismo nombre en el libro gratuito [“Three Easy Pieces”]. Mientras que la segmentación ya no se admite en modo de 64 bits, la GDT sigue existiendo. Se utiliza principalmente para dos cosas: cambiar entre espacio de núcleo y espacio de usuario, y cargar una estructura TSS.
+
+[“Three Easy Pieces”]: http://pages.cs.wisc.edu/~remzi/OSTEP/
+
+#### Creando una GDT
+Creemos una GDT estática que incluya un segmento para nuestra TSS estática:
+
+```rust
+// en src/gdt.rs
+
+use x86_64::structures::gdt::{GlobalDescriptorTable, Descriptor};
+
+lazy_static! {
+ static ref GDT: GlobalDescriptorTable = {
+ let mut gdt = GlobalDescriptorTable::new();
+ gdt.add_entry(Descriptor::kernel_code_segment());
+ gdt.add_entry(Descriptor::tss_segment(&TSS));
+ gdt
+ };
+}
+```
+
+Como antes, usamos `lazy_static` de nuevo. Creamos una nueva GDT con un segmento de código y un segmento de TSS.
+
+#### Cargando la GDT
+
+Para cargar nuestra GDT, creamos una nueva función `gdt::init` que llamamos desde nuestra función `init`:
+
+```rust
+// en src/gdt.rs
+
+pub fn init() {
+ GDT.load();
+}
+
+// en src/lib.rs
+
+pub fn init() {
+ gdt::init();
+ interrupts::init_idt();
+}
+```
+
+Ahora nuestra GDT está cargada (ya que la función `_start` llama a `init`), pero aún vemos el bucle de arranque en el desbordamiento de pila.
+
+### Los Pasos Finales
+
+El problema es que los segmentos de la GDT aún no están activos porque los registros de segmento y TSS aún contienen los valores de la antigua GDT. También necesitamos modificar la entrada de IDT de doble fallo para que use la nueva pila.
+
+En resumen, necesitamos hacer lo siguiente:
+
+1. **Recargar el registro de segmento de código**: Hemos cambiado nuestra GDT, así que deberíamos recargar `cs`, el registro del segmento de código. Esto es necesario porque el antiguo selector de segmento podría ahora apuntar a un descriptor de GDT diferente (por ejemplo, un descriptor de TSS).
+2. **Cargar la TSS**: Cargamos una GDT que contiene un selector de TSS, pero aún necesitamos decirle a la CPU que debe usar esa TSS.
+3. **Actualizar la entrada de IDT**: Tan pronto como nuestra TSS esté cargada, la CPU tendrá acceso a una tabla de pila de interrupciones (IST) válida. Luego podemos decirle a la CPU que debe usar nuestra nueva pila de doble fallo modificando nuestra entrada de IDT de doble fallo.
+
+Para los dos primeros pasos, necesitamos acceso a las variables `code_selector` y `tss_selector` en nuestra función `gdt::init`. Podemos lograr esto haciéndolas parte de la estática a través de una nueva estructura `Selectors`:
+
+```rust
+// en src/gdt.rs
+
+use x86_64::structures::gdt::SegmentSelector;
+
+lazy_static! {
+ static ref GDT: (GlobalDescriptorTable, Selectors) = {
+ let mut gdt = GlobalDescriptorTable::new();
+ let code_selector = gdt.add_entry(Descriptor::kernel_code_segment());
+ let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS));
+ (gdt, Selectors { code_selector, tss_selector })
+ };
+}
+
+struct Selectors {
+ code_selector: SegmentSelector,
+ tss_selector: SegmentSelector,
+}
+```
+
+Ahora podemos usar los selectores para recargar el registro `cs` y cargar nuestra `TSS`:
+
+```rust
+// en src/gdt.rs
+
+pub fn init() {
+ use x86_64::instructions::tables::load_tss;
+ use x86_64::instructions::segmentation::{CS, Segment};
+
+ GDT.0.load();
+ unsafe {
+ CS::set_reg(GDT.1.code_selector);
+ load_tss(GDT.1.tss_selector);
+ }
+}
+```
+
+Recargamos el registro de segmento de código usando [`CS::set_reg`] y cargamos la TSS usando [`load_tss`]. Las funciones están marcadas como `unsafe`, así que necesitamos un bloque `unsafe` para invocarlas. La razón es que podría ser posible romper la seguridad de la memoria al cargar selectores inválidos.
+
+[`CS::set_reg`]: https://docs.rs/x86_64/0.14.5/x86_64/instructions/segmentation/struct.CS.html#method.set_reg
+[`load_tss`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/tables/fn.load_tss.html
+
+Ahora que hemos cargado una TSS válida y una tabla de pila de interrupciones, podemos establecer el índice de pila para nuestro controlador de doble fallo en la IDT:
+
+```rust
+// en src/interrupts.rs
+
+use crate::gdt;
+
+lazy_static! {
+ static ref IDT: InterruptDescriptorTable = {
+ let mut idt = InterruptDescriptorTable::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ unsafe {
+ idt.double_fault.set_handler_fn(double_fault_handler)
+ .set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX); // nuevo
+ }
+
+ idt
+ };
+}
+```
+
+El método `set_stack_index` es inseguro porque el llamador debe asegurarse de que el índice utilizado es válido y no ya está usado para otra excepción.
+
+¡Eso es todo! Ahora la CPU debería cambiar a la pila de doble fallo cada vez que ocurra un doble fallo. Así que podemos capturar _todos_ los dobles fallos, incluidos los desbordamientos de pila del núcleo:
+
+
+
+A partir de ahora, ¡no deberíamos ver un fallo triple nuevamente! Para asegurar que no rompamos accidentalmente lo anterior, deberíamos agregar una prueba para esto.
+
+## Una Prueba de Desbordamiento de Pila
+
+Para probar nuestro nuevo módulo `gdt` y asegurarnos de que el controlador de doble fallo se llama correctamente en un desbordamiento de pila, podemos agregar una prueba de integración. La idea es provocar un doble fallo en la función de prueba y verificar que se llama al controlador de doble fallo.
+
+Comencemos con un esqueleto mínimo:
+
+```rust
+// en tests/stack_overflow.rs
+
+#![no_std]
+#![no_main]
+
+use core::panic::PanicInfo;
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ unimplemented!();
+}
+
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ blog_os::test_panic_handler(info)
+}
+```
+
+Al igual que nuestra prueba de `panic_handler`, la prueba se ejecutará [sin un arnés de prueba]. La razón es que no podemos continuar la ejecución después de un doble fallo, así que más de una prueba no tiene sentido. Para desactivar el arnés de prueba para la prueba, agregamos lo siguiente a nuestro `Cargo.toml`:
+
+```toml
+# en Cargo.toml
+
+[[test]]
+name = "stack_overflow"
+harness = false
+```
+
+[sin un arnés de prueba]: @/edition-2/posts/04-testing/index.md#no-harness-tests
+
+Ahora `cargo test --test stack_overflow` debería compilar con éxito. La prueba falla, por supuesto, ya que el macro `unimplemented` provoca un pánico.
+
+### Implementando `_start`
+
+La implementación de la función `_start` se ve así:
+
+```rust
+// en tests/stack_overflow.rs
+
+use blog_os::serial_print;
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ serial_print!("stack_overflow::stack_overflow...\t");
+
+ blog_os::gdt::init();
+ init_test_idt();
+
+ // provocar un desbordamiento de pila
+ stack_overflow();
+
+ panic!("La ejecución continuó después del desbordamiento de pila");
+}
+
+#[allow(unconditional_recursion)]
+fn stack_overflow() {
+ stack_overflow(); // por cada recursión, la dirección de retorno es empujada
+ volatile::Volatile::new(0).read(); // prevenir optimizaciones de recursión de cola
+}
+```
+
+Llamamos a nuestra función `gdt::init` para inicializar una nueva GDT. En lugar de llamar a nuestra función `interrupts::init_idt`, llamamos a una función `init_test_idt` que se explicará en un momento. La función `stack_overflow` es casi idéntica a la función en nuestro `main.rs`. La única diferencia es que al final de la función, realizamos una lectura [volátil] adicional usando el tipo [`Volatile`] para prevenir una optimización del compilador llamada [_eliminación de llamadas de cola_]. Entre otras cosas, esta optimización permite al compilador transformar una función cuya última declaración es una llamada recursiva a una normal. Por lo tanto, no se crea un marco de pila adicional para la llamada a la función, así que el uso de la pila permanece constante.
+
+[volátil]: https://en.wikipedia.org/wiki/Volatile_(computer_programming)
+[`Volatile`]: https://docs.rs/volatile/0.2.6/volatile/struct.Volatile.html
+[_eliminación de llamadas de cola_]: https://en.wikipedia.org/wiki/Tail_call
+
+En nuestro caso, sin embargo, queremos que el desbordamiento de pila ocurra, así que agregamos una declaración de lectura volátil ficticia al final de la función, que el compilador no puede eliminar. Por lo tanto, la función ya no es _tail recursive_, y se previene la transformación en un bucle. También agregamos el atributo `allow(unconditional_recursion)` para silenciar la advertencia del compilador de que la función recurre sin fin.
+
+### La IDT de Prueba
+
+Como se mencionó anteriormente, la prueba necesita su propia IDT con un controlador de doble fallo personalizado. La implementación se ve así:
+
+```rust
+// en tests/stack_overflow.rs
+
+use lazy_static::lazy_static;
+use x86_64::structures::idt::InterruptDescriptorTable;
+
+lazy_static! {
+ static ref TEST_IDT: InterruptDescriptorTable = {
+ let mut idt = InterruptDescriptorTable::new();
+ unsafe {
+ idt.double_fault
+ .set_handler_fn(test_double_fault_handler)
+ .set_stack_index(blog_os::gdt::DOUBLE_FAULT_IST_INDEX);
+ }
+
+ idt
+ };
+}
+
+pub fn init_test_idt() {
+ TEST_IDT.load();
+}
+```
+
+La implementación es muy similar a nuestra IDT normal en `interrupts.rs`. Al igual que en la IDT normal, establecemos un índice de pila en la IST para el controlador de doble fallo con el fin de cambiar a una pila separada. La función `init_test_idt` carga la IDT en la CPU a través del método `load`.
+
+### El Controlador de Doble Fallo
+
+La única pieza que falta es nuestro controlador de doble fallo. Se ve así:
+
+```rust
+// en tests/stack_overflow.rs
+
+use blog_os::{exit_qemu, QemuExitCode, serial_println};
+use x86_64::structures::idt::InterruptStackFrame;
+
+extern "x86-interrupt" fn test_double_fault_handler(
+ _stack_frame: InterruptStackFrame,
+ _error_code: u64,
+) -> ! {
+ serial_println!("[ok]");
+ exit_qemu(QemuExitCode::Success);
+ loop {}
+}
+```
+
+Cuando se llama al controlador de doble fallo, salimos de QEMU con un código de salida de éxito, lo que marca la prueba como pasada. Dado que las pruebas de integración son ejecutables completamente separadas, necesitamos establecer el atributo `#![feature(abi_x86_interrupt)]` nuevamente en la parte superior de nuestro archivo de prueba.
+
+Ahora podemos ejecutar nuestra prueba a través de `cargo test --test stack_overflow` (o `cargo test` para ejecutar todas las pruebas). Como era de esperar, vemos la salida de `stack_overflow... [ok]` en la consola. Intenta comentar la línea `set_stack_index`; debería hacer que la prueba falle.
+
+## Resumen
+En esta publicación, aprendimos qué es un doble fallo y bajo qué condiciones ocurre. Agregamos un controlador básico de doble fallo que imprime un mensaje de error y añadimos una prueba de integración para ello.
+
+También habilitamos el cambio de pila soportado por hardware en excepciones de doble fallo para que también funcione en desbordamientos de pila. Mientras lo implementábamos, aprendimos sobre el segmento de estado de tarea (TSS), la tabla de pila de interrupciones (IST) que contiene, y la tabla global de descriptores (GDT), que se usaba para segmentación en arquitecturas anteriores.
+
+## ¿Qué sigue?
+La próxima publicación explica cómo manejar interrupciones de dispositivos externos como temporizadores, teclados o controladores de red. Estas interrupciones de hardware son muy similares a las excepciones, por ejemplo, también se despachan a través de la IDT. Sin embargo, a diferencia de las excepciones, no surgen directamente en la CPU. En su lugar, un _controlador de interrupciones_ agrega estas interrupciones y las reenvía a la CPU según su prioridad. En la próxima publicación, exploraremos el [Intel 8259] (“PIC”) controlador de interrupciones y aprenderemos cómo implementar soporte para teclado.
+
+[Intel 8259]: https://en.wikipedia.org/wiki/Intel_8259
diff --git a/blog/content/edition-2/posts/07-hardware-interrupts/index.es.md b/blog/content/edition-2/posts/07-hardware-interrupts/index.es.md
new file mode 100644
index 00000000..e809f207
--- /dev/null
+++ b/blog/content/edition-2/posts/07-hardware-interrupts/index.es.md
@@ -0,0 +1,737 @@
++++
+title = "Interrupciones de Hardware"
+weight = 7
+path = "es/hardware-interrupts"
+date = 2018-10-22
+
+[extra]
+chapter = "Interrupciones"
+
+# GitHub usernames of the people that translated this post
+translators = ["dobleuber"]
++++
+
+En esta publicación, configuramos el controlador de interrupciones programable para redirigir correctamente las interrupciones de hardware a la CPU. Para manejar estas interrupciones, agregamos nuevas entradas a nuestra tabla de descriptores de interrupciones, tal como lo hicimos con nuestros manejadores de excepciones. Aprenderemos cómo obtener interrupciones de temporizador periódicas y cómo recibir entrada del teclado.
+
+
+
+Este blog se desarrolla abiertamente en [GitHub]. Si tienes algún problema o pregunta, por favor abre un problema allí. También puedes dejar comentarios [al final]. El código fuente completo de esta publicación se puede encontrar en la rama [`post-07`][post branch].
+
+[GitHub]: https://github.com/phil-opp/blog_os
+[al final]: #comments
+
+[post branch]: https://github.com/phil-opp/blog_os/tree/post-07
+
+
+
+## Visión General
+
+Las interrupciones proporcionan una forma de notificar a la CPU sobre dispositivos de hardware conectados. Así que, en lugar de permitir que el kernel verifique periódicamente el teclado en busca de nuevos caracteres (un proceso llamado [_polling_]), el teclado puede notificar al kernel sobre cada pulsación de tecla. Esto es mucho más eficiente porque el kernel solo necesita actuar cuando algo ha sucedido. También permite tiempos de reacción más rápidos, ya que el kernel puede reaccionar inmediatamente y no solo en la siguiente consulta.
+
+[_polling_]: https://en.wikipedia.org/wiki/Polling_(computer_science)
+
+Conectar todos los dispositivos de hardware directamente a la CPU no es posible. En su lugar, un _controlador de interrupciones_ (interrupt controller) separado agrega las interrupciones de todos los dispositivos y luego notifica a la CPU:
+
+```
+ ____________ _____
+ Temporizador ------------> | | | |
+ Teclado ---------> | Interrupt |---------> | CPU |
+ Otro Hardware ---> | Controller | |_____|
+ Etc. -------------> |____________|
+
+```
+
+La mayoría de los controladores de interrupciones son programables, lo que significa que admiten diferentes niveles de prioridad para las interrupciones. Por ejemplo, esto permite dar a las interrupciones del temporizador una prioridad más alta que a las interrupciones del teclado para asegurar un mantenimiento del tiempo preciso.
+
+A diferencia de las excepciones, las interrupciones de hardware ocurren _de manera asincrónica_. Esto significa que son completamente independientes del código ejecutado y pueden ocurrir en cualquier momento. Por lo tanto, de repente tenemos una forma de concurrencia en nuestro kernel con todos los posibles errores relacionados con la concurrencia. El estricto modelo de propiedad de Rust nos ayuda aquí porque prohíbe el estado global mutable. Sin embargo, los bloqueos mutuos (deadlocks) siguen siendo posibles, como veremos más adelante en esta publicación.
+
+## El 8259 PIC
+
+El [Intel 8259] es un controlador de interrupciones programable (PIC) introducido en 1976. Ha sido reemplazado durante mucho tiempo por el nuevo [APIC], pero su interfaz aún se admite en sistemas actuales por razones de compatibilidad hacia atrás. El PIC 8259 es significativamente más fácil de configurar que el APIC, así que lo utilizaremos para introducirnos a las interrupciones antes de cambiar al APIC en una publicación posterior.
+
+[APIC]: https://en.wikipedia.org/wiki/Intel_APIC_Architecture
+
+El 8259 tiene ocho líneas de interrupción y varias líneas para comunicarse con la CPU. Los sistemas típicos de aquella época estaban equipados con dos instancias del PIC 8259, uno primario y uno secundario, conectados a una de las líneas de interrupción del primario:
+
+[Intel 8259]: https://en.wikipedia.org/wiki/Intel_8259
+
+```
+ ____________ ____________
+Reloj en Tiempo Real --> | | Temporizador -------------> | |
+ACPI -------------> | | Teclado-----------> | | _____
+Disponible --------> | Secundario |----------------------> | Primario | | |
+Disponible --------> | Interrupt | Puerto Serial 2 -----> | Interrupt |---> | CPU |
+Ratón ------------> | Controller | Puerto Serial 1 -----> | Controller | |_____|
+Co-Procesador -----> | | Puerto Paralelo 2/3 -> | |
+ATA Primario ------> | | Disco flexible -------> | |
+ATA Secundario ----> |____________| Puerto Paralelo 1----> |____________|
+
+```
+
+Esta gráfica muestra la asignación típica de líneas de interrupción. Vemos que la mayoría de las 15 líneas tienen un mapeo fijo, por ejemplo, la línea 4 del PIC secundario está asignada al ratón.
+
+Cada controlador se puede configurar a través de dos [puertos de I/O], un puerto “comando” y un puerto “datos”. Para el controlador primario, estos puertos son `0x20` (comando) y `0x21` (datos). Para el controlador secundario, son `0xa0` (comando) y `0xa1` (datos). Para más información sobre cómo se pueden configurar los PIC, consulta el [artículo en osdev.org].
+
+[puertos de I/O]: @/edition-2/posts/04-testing/index.md#i-o-ports
+[artículo en osdev.org]: https://wiki.osdev.org/8259_PIC
+
+### Implementación
+
+La configuración predeterminada de los PIC no es utilizable porque envía números de vector de interrupción en el rango de 0–15 a la CPU. Estos números ya están ocupados por excepciones de la CPU. Por ejemplo, el número 8 corresponde a una doble falla. Para corregir este problema de superposición, necesitamos volver a asignar las interrupciones del PIC a números diferentes. El rango real no importa siempre que no se superponga con las excepciones, pero típicamente se elige el rango de 32–47, porque estos son los primeros números libres después de los 32 espacios de excepción.
+
+La configuración se realiza escribiendo valores especiales en los puertos de comando y datos de los PIC. Afortunadamente, ya existe una crate llamada [`pic8259`], por lo que no necesitamos escribir la secuencia de inicialización nosotros mismos. Sin embargo, si estás interesado en cómo funciona, consulta [su código fuente][pic crate source]. Es bastante pequeño y está bien documentado.
+
+[pic crate source]: https://docs.rs/crate/pic8259/0.10.1/source/src/lib.rs
+
+Para agregar la crate como una dependencia, agregamos lo siguiente a nuestro proyecto:
+
+[`pic8259`]: https://docs.rs/pic8259/0.10.1/pic8259/
+
+```toml
+# en Cargo.toml
+
+[dependencies]
+pic8259 = "0.10.1"
+```
+
+La principal abstracción proporcionada por la crate es la estructura [`ChainedPics`] que representa la disposición primario/secundario del PIC que vimos arriba. Está diseñada para ser utilizada de la siguiente manera:
+
+[`ChainedPics`]: https://docs.rs/pic8259/0.10.1/pic8259/struct.ChainedPics.html
+
+```rust
+// en src/interrupts.rs
+
+use pic8259::ChainedPics;
+use spin;
+
+pub const PIC_1_OFFSET: u8 = 32;
+pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8;
+
+pub static PICS: spin::Mutex =
+ spin::Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) });
+```
+
+Como se mencionó anteriormente, estamos estableciendo los desplazamientos para los PIC en el rango de 32–47. Al envolver la estructura `ChainedPics` en un `Mutex`, podemos obtener un acceso mutable seguro (a través del método [`lock`][spin mutex lock]), que necesitamos en el siguiente paso. La función `ChainedPics::new` es insegura porque desplazamientos incorrectos podrían causar un comportamiento indefinido.
+
+[spin mutex lock]: https://docs.rs/spin/0.5.2/spin/struct.Mutex.html#method.lock
+
+Ahora podemos inicializar el PIC 8259 en nuestra función `init`:
+
+```rust
+// en src/lib.rs
+
+pub fn init() {
+ gdt::init();
+ interrupts::init_idt();
+ unsafe { interrupts::PICS.lock().initialize() }; // nuevo
+}
+```
+
+Usamos la función [`initialize`] para realizar la inicialización del PIC. Al igual que la función `ChainedPics::new`, esta función también es insegura porque puede causar un comportamiento indefinido si el PIC está mal configurado.
+
+[`initialize`]: https://docs.rs/pic8259/0.10.1/pic8259/struct.ChainedPics.html#method.initialize
+
+Si todo va bien, deberíamos seguir viendo el mensaje "¡No se ha bloqueado!" al ejecutar `cargo run`.
+
+## Habilitando Interrupciones
+
+Hasta ahora, nada sucedió porque las interrupciones todavía están deshabilitadas en la configuración de la CPU. Esto significa que la CPU no escucha al controlador de interrupciones en absoluto, por lo que ninguna interrupción puede llegar a la CPU. Cambiemos eso:
+
+```rust
+// en src/lib.rs
+
+pub fn init() {
+ gdt::init();
+ interrupts::init_idt();
+ unsafe { interrupts::PICS.lock().initialize() };
+ x86_64::instructions::interrupts::enable(); // nuevo
+}
+```
+
+La función `interrupts::enable` de la crate `x86_64` ejecuta la instrucción especial `sti` (“set interrupts”) para habilitar las interrupciones externas. Cuando intentamos `cargo run` ahora, vemos que ocurre una doble falla:
+
+
+
+La razón de esta doble falla es que el temporizador de hardware (el [Intel 8253], para ser exactos) está habilitado por defecto, por lo que comenzamos a recibir interrupciones de temporizador tan pronto como habilitamos las interrupciones. Dado que aún no hemos definido una función de manejador para ello, se invoca nuestro manejador de doble falla.
+
+[Intel 8253]: https://en.wikipedia.org/wiki/Intel_8253
+
+## Manejando Interrupciones de Temporizador
+
+Como vemos en la gráfica [arriba](#el-8259-pic), el temporizador utiliza la línea 0 del PIC primario. Esto significa que llega a la CPU como interrupción 32 (0 + desplazamiento 32). En lugar de codificar rígidamente el índice 32, lo almacenamos en un enum `InterruptIndex`:
+
+```rust
+// en src/interrupts.rs
+
+#[derive(Debug, Clone, Copy)]
+#[repr(u8)]
+pub enum InterruptIndex {
+ Temporizador = PIC_1_OFFSET,
+}
+
+impl InterruptIndex {
+ fn as_u8(self) -> u8 {
+ self as u8
+ }
+
+ fn as_usize(self) -> usize {
+ usize::from(self.as_u8())
+ }
+}
+```
+
+El enum es un [enum tipo C] para que podamos especificar directamente el índice para cada variante. El atributo `repr(u8)` especifica que cada variante se representa como un `u8`. Agregaremos más variantes para otras interrupciones en el futuro.
+
+[enum tipo C]: https://doc.rust-lang.org/reference/items/enumerations.html#custom-discriminant-values-for-fieldless-enumerations
+
+Ahora podemos agregar una función de manejador para la interrupción del temporizador:
+
+```rust
+// en src/interrupts.rs
+
+use crate::print;
+
+lazy_static! {
+ static ref IDT: InterruptDescriptorTable = {
+ let mut idt = InterruptDescriptorTable::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ […]
+ idt[InterruptIndex::Temporizador.as_usize()]
+ .set_handler_fn(timer_interrupt_handler); // nuevo
+
+ idt
+ };
+}
+
+extern "x86-interrupt" fn timer_interrupt_handler(
+ _stack_frame: InterruptStackFrame)
+{
+ print!(".");
+}
+```
+
+Nuestro `timer_interrupt_handler` tiene la misma firma que nuestros manejadores de excepciones, porque la CPU reacciona de manera idéntica a las excepciones y a las interrupciones externas (la única diferencia es que algunas excepciones empujan un código de error). La estructura [`InterruptDescriptorTable`] implementa el rasgo [`IndexMut`], por lo que podemos acceder a entradas individuales a través de la sintaxis de indexación de arrays.
+
+[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.InterruptDescriptorTable.html
+[`IndexMut`]: https://doc.rust-lang.org/core/ops/trait.IndexMut.html
+
+En nuestro manejador de interrupciones del temporizador, imprimimos un punto en la pantalla. Como la interrupción del temporizador ocurre periódicamente, esperaríamos ver un punto apareciendo en cada tick del temporizador. Sin embargo, cuando lo ejecutamos, vemos que solo se imprime un solo punto:
+
+
+
+### Fin de la Interrupción
+
+La razón es que el PIC espera una señal explícita de “fin de interrupción” (EOI) de nuestro manejador de interrupciones. Esta señal le dice al controlador que la interrupción ha sido procesada y que el sistema está listo para recibir la siguiente interrupción. Así que el PIC piensa que todavía estamos ocupados procesando la primera interrupción del temporizador y espera pacientemente la señal EOI antes de enviar la siguiente.
+
+Para enviar el EOI, usamos nuestra estructura estática `PICS` nuevamente:
+
+```rust
+// en src/interrupts.rs
+
+extern "x86-interrupt" fn timer_interrupt_handler(
+ _stack_frame: InterruptStackFrame)
+{
+ print!(".");
+
+ unsafe {
+ PICS.lock()
+ .notify_end_of_interrupt(InterruptIndex::Temporizador.as_u8());
+ }
+}
+```
+
+El método `notify_end_of_interrupt` determina si el PIC primario o secundario envió la interrupción y luego utiliza los puertos de `comando` y `datos` para enviar una señal EOI a los controladores respectivos. Si el PIC secundario envió la interrupción, ambos PIC deben ser notificados porque el PIC secundario está conectado a una línea de entrada del PIC primario.
+
+Debemos tener cuidado de usar el número de vector de interrupción correcto; de lo contrario, podríamos eliminar accidentalmente una interrupción no enviada importante o hacer que nuestro sistema se cuelgue. Esta es la razón por la que la función es insegura.
+
+Cuando ejecutamos ahora `cargo run`, vemos puntos apareciendo periódicamente en la pantalla:
+
+
+
+### Configurando el Temporizador
+
+El temporizador de hardware que usamos se llama _Temporizador de Intervalo Programable_ (Programmable Interval Timer), o PIT, para abreviar. Como su nombre indica, es posible configurar el intervalo entre dos interrupciones. No entraremos en detalles aquí porque pronto pasaremos al [temporizador APIC], pero la wiki de OSDev tiene un artículo extenso sobre la [configuración del PIT].
+
+[temporizador APIC]: https://wiki.osdev.org/APIC_timer
+[configuración del PIT]: https://wiki.osdev.org/Programmable_Interval_Timer
+
+## Bloqueos Mutuos
+
+Ahora tenemos una forma de concurrencia en nuestro kernel: Las interrupciones del temporizador ocurren de manera asincrónica, por lo que pueden interrumpir nuestra función `_start` en cualquier momento. Afortunadamente, el sistema de propiedad de Rust previene muchos tipos de errores relacionados con la concurrencia en tiempo de compilación. Una notable excepción son los bloqueos mutuos (deadlocks). Los bloqueos mutuos ocurren si un hilo intenta adquirir un bloqueo que nunca se liberará. Así, el hilo se cuelga indefinidamente.
+
+Ya podemos provocar un bloqueo mutuo en nuestro kernel. Recuerda que nuestra macro `println` llama a la función `vga_buffer::_print`, que [bloquea un `WRITER` global][vga spinlock] utilizando un spinlock:
+
+[vga spinlock]: @/edition-2/posts/03-vga-text-buffer/index.md#spinlocks
+
+```rust
+// en src/vga_buffer.rs
+
+[…]
+
+#[doc(hidden)]
+pub fn _print(args: fmt::Arguments) {
+ use core::fmt::Write;
+ WRITER.lock().write_fmt(args).unwrap();
+}
+```
+
+Bloquea el `WRITER`, llama a `write_fmt` en él y lo desbloquea implícitamente al final de la función. Ahora imagina que una interrupción ocurre mientras `WRITER` está bloqueado y el manejador de interrupciones intenta imprimir algo también:
+
+| Timestep | _start | manejador_interrupcion |
+| -------- | ------------------------ | -------------------------------------------------------------- |
+| 0 | llama a `println!` | |
+| 1 | `print` bloquea `WRITER` | |
+| 2 | | **ocurre la interrupción**, el manejador comienza a ejecutarse |
+| 3 | | llama a `println!` |
+| 4 | | `print` intenta bloquear `WRITER` (ya bloqueado) |
+| 5 | | `print` intenta bloquear `WRITER` (ya bloqueado) |
+| … | | … |
+| _nunca_ | _desbloquear `WRITER`_ |
+
+El `WRITER` está bloqueado, así que el manejador de interrupciones espera hasta que se libere. Pero esto nunca sucede, porque la función `_start` solo continúa ejecutándose después de que el manejador de interrupciones regrese. Así, todo el sistema se cuelga.
+
+### Provocando un Bloqueo Mutuo
+
+Podemos provocar fácilmente un bloqueo mutuo así en nuestro kernel imprimiendo algo en el bucle al final de nuestra función `_start`:
+
+```rust
+// en src/main.rs
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ […]
+ loop {
+ use blog_os::print;
+ print!("-"); // nuevo
+ }
+}
+```
+
+Cuando lo ejecutamos en QEMU, obtenemos una salida de la forma:
+
+
+
+Vemos que solo se imprimen un número limitado de guiones hasta que ocurre la primera interrupción del temporizador. Entonces el sistema se cuelga porque el manejador de interrupciones del temporizador provoca un bloqueo mutuo cuando intenta imprimir un punto. Esta es la razón por la que no vemos puntos en la salida anterior.
+
+El número real de guiones varía entre ejecuciones porque la interrupción del temporizador ocurre de manera asincrónica. Esta no determinación es lo que hace que los errores relacionados con la concurrencia sean tan difíciles de depurar.
+
+### Solucionando el Bloqueo Mutuo
+
+Para evitar este bloqueo mutuo, podemos deshabilitar las interrupciones mientras el `Mutex` está bloqueado:
+
+```rust
+// en src/vga_buffer.rs
+
+/// Imprime la cadena formateada dada en el búfer de texto VGA
+/// a través de la instancia global `WRITER`.
+#[doc(hidden)]
+pub fn _print(args: fmt::Arguments) {
+ use core::fmt::Write;
+ use x86_64::instructions::interrupts; // nuevo
+
+ interrupts::without_interrupts(|| { // nuevo
+ WRITER.lock().write_fmt(args).unwrap();
+ });
+}
+```
+
+La función [`without_interrupts`] toma un [closure] y lo ejecuta en un entorno sin interrupciones. La usamos para asegurarnos de que no se produzca ninguna interrupción mientras el `Mutex` esté bloqueado. Cuando ejecutamos nuestro kernel ahora, vemos que sigue funcionando sin colgarse. (Todavía no notamos ningún punto, pero esto es porque están deslizándose demasiado rápido. Intenta ralentizar la impresión, por ejemplo, poniendo un `for _ in 0..10000 {}` dentro del bucle).
+
+[`without_interrupts`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/interrupts/fn.without_interrupts.html
+[closure]: https://doc.rust-lang.org/book/ch13-01-closures.html
+
+Podemos aplicar el mismo cambio a nuestra función de impresión serial para asegurarnos de que tampoco ocurran bloqueos mutuos con ella:
+
+```rust
+// en src/serial.rs
+
+#[doc(hidden)]
+pub fn _print(args: ::core::fmt::Arguments) {
+ use core::fmt::Write;
+ use x86_64::instructions::interrupts; // nuevo
+
+ interrupts::without_interrupts(|| { // nuevo
+ SERIAL1
+ .lock()
+ .write_fmt(args)
+ .expect("Error al imprimir por serie");
+ });
+}
+```
+
+Ten en cuenta que deshabilitar interrupciones no debería ser una solución general. El problema es que aumenta la latencia de interrupción en el peor de los casos, es decir, el tiempo hasta que el sistema reacciona a una interrupción. Por lo tanto, las interrupciones solo deben deshabilitarse por un tiempo muy corto.
+
+## Solucionando una Condición de Carrera
+
+Si ejecutas `cargo test`, podrías ver que la prueba `test_println_output` falla:
+
+```
+> cargo test --lib
+[…]
+Ejecutando 4 pruebas
+test_breakpoint_exception...[ok]
+test_println... [ok]
+test_println_many... [ok]
+test_println_output... [failed]
+
+Error: se bloqueó en 'assertion failed: `(left == right)`
+ left: `'.'`,
+ right: `'S'`', src/vga_buffer.rs:205:9
+```
+
+La razón es una _condición de carrera_ entre la prueba y nuestro manejador de temporizador. Recuerda que la prueba se ve así:
+
+```rust
+// en src/vga_buffer.rs
+
+#[test_case]
+fn test_println_output() {
+ let s = "Una cadena de prueba que cabe en una sola línea";
+ println!("{}", s);
+ for (i, c) in s.chars().enumerate() {
+ let screen_char = WRITER.lock().buffer.chars[BUFFER_HEIGHT - 2][i].read();
+ assert_eq!(char::from(screen_char.ascii_character), c);
+ }
+}
+```
+
+La condición de carrera ocurre porque el manejador de interrupciones del temporizador podría ejecutarse entre el `println` y la lectura de los caracteres en la pantalla. Ten en cuenta que esto no es una peligrosa _data race_, que Rust previene completamente en tiempo de compilación. Consulta el [_Rustonomicon_][nomicon-races] para más detalles.
+
+[nomicon-races]: https://doc.rust-lang.org/nomicon/races.html
+
+Para solucionar esto, necesitamos mantener el `WRITER` bloqueado durante toda la duración de la prueba, para que el manejador de temporizador no pueda escribir un carácter en la pantalla en medio. La prueba corregida se ve así:
+
+```rust
+// en src/vga_buffer.rs
+
+#[test_case]
+fn test_println_output() {
+ use core::fmt::Write;
+ use x86_64::instructions::interrupts;
+
+ let s = "Una cadena de prueba que cabe en una sola línea";
+ interrupts::without_interrupts(|| {
+ let mut writer = WRITER.lock();
+ writeln!(writer, "\n{}", s).expect("writeln falló");
+ for (i, c) in s.chars().enumerate() {
+ let screen_char = writer.buffer.chars[BUFFER_HEIGHT - 2][i].read();
+ assert_eq!(char::from(screen_char.ascii_character), c);
+ }
+ });
+}
+```
+
+Hemos realizado los siguientes cambios:
+
+- Mantenemos el escritor bloqueado durante toda la prueba utilizando el método `lock()` explícitamente. En lugar de `println`, usamos la macro [`writeln`] que permite imprimir en un escritor que ya está bloqueado.
+- Para evitar otro bloqueo mutuo, deshabilitamos las interrupciones durante la duración de la prueba. De lo contrario, la prueba podría ser interrumpida mientras el escritor sigue bloqueado.
+- Dado que el manejador de interrupciones del temporizador aún puede ejecutarse antes de la prueba, imprimimos una nueva línea adicional `\n` antes de imprimir la cadena `s`. De esta manera, evitamos fallar en la prueba cuando el manejador de temporizador ya ha impreso algunos puntos en la línea actual.
+
+[`writeln`]: https://doc.rust-lang.org/core/macro.writeln.html
+
+Con los cambios anteriores, `cargo test` ahora tiene éxito de manera determinista.
+
+Esta fue una condición de carrera muy inofensiva que solo causó una falla en la prueba. Como puedes imaginar, otras condiciones de carrera pueden ser mucho más difíciles de depurar debido a su naturaleza no determinista. Afortunadamente, Rust nos previene de condiciones de data race, que son la clase más seria de condiciones de carrera, ya que pueden causar todo tipo de comportamientos indefinidos, incluyendo bloqueos del sistema y corrupción silenciosa de memoria.
+
+## La Instrucción `hlt`
+
+Hasta ahora, hemos utilizado una simple instrucción de bucle vacío al final de nuestras funciones `_start` y `panic`. Esto hace que la CPU gire sin descanso, y por lo tanto funciona como se espera. Pero también es muy ineficiente, porque la CPU sigue funcionando a toda velocidad incluso cuando no hay trabajo que hacer. Puedes ver este problema en tu administrador de tareas cuando ejecutas tu kernel: el proceso de QEMU necesita cerca del 100% de CPU todo el tiempo.
+
+Lo que realmente queremos hacer es detener la CPU hasta que llegue la próxima interrupción. Esto permite que la CPU entre en un estado de sueño en el que consume mucho menos energía. La instrucción [`hlt`] hace exactamente eso. Vamos a usar esta instrucción para crear un bucle infinito eficiente en energía:
+
+[`hlt`]: https://en.wikipedia.org/wiki/HLT_(x86_instruction)
+
+```rust
+// en src/lib.rs
+
+pub fn hlt_loop() -> ! {
+ loop {
+ x86_64::instructions::hlt();
+ }
+}
+```
+
+La función `instructions::hlt` es solo un [delgado envoltorio] alrededor de la instrucción de ensamblador. Es segura porque no hay forma de que comprometa la seguridad de la memoria.
+
+[delgado envoltorio]: https://github.com/rust-osdev/x86_64/blob/5e8e218381c5205f5777cb50da3ecac5d7e3b1ab/src/instructions/mod.rs#L16-L22
+
+Ahora podemos utilizar este `hlt_loop` en lugar de los bucles infinitos en nuestras funciones `_start` y `panic`:
+
+```rust
+// en src/main.rs
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ […]
+
+ println!("¡No se ha bloqueado!");
+ blog_os::hlt_loop(); // nuevo
+}
+
+
+#[cfg(not(test))]
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+ println!("{}", info);
+ blog_os::hlt_loop(); // nuevo
+}
+
+```
+
+Actualicemos también nuestro `lib.rs`:
+
+```rust
+// en src/lib.rs
+
+/// Punto de entrada para `cargo test`
+#[cfg(test)]
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ init();
+ test_main();
+ hlt_loop(); // nuevo
+}
+
+pub fn test_panic_handler(info: &PanicInfo) -> ! {
+ serial_println!("[falló]\n");
+ serial_println!("Error: {}\n", info);
+ exit_qemu(QemuExitCode::Failed);
+ hlt_loop(); // nuevo
+}
+```
+
+Cuando ejecutamos nuestro kernel ahora en QEMU, vemos un uso de CPU mucho más bajo.
+
+## Entrada del Teclado
+
+Ahora que podemos manejar interrupciones de dispositivos externos, finalmente podemos agregar soporte para la entrada del teclado. Esto nos permitirá interactuar con nuestro kernel por primera vez.
+
+
+
+[PS/2]: https://en.wikipedia.org/wiki/PS/2_port
+
+Al igual que el temporizador de hardware, el controlador del teclado ya está habilitado por defecto. Así que cuando presionas una tecla, el controlador del teclado envía una interrupción al PIC, que la reenvía a la CPU. La CPU busca una función de manejador en la IDT, pero la entrada correspondiente está vacía. Por lo tanto, ocurre una doble falla.
+
+Así que agreguemos una función de manejador para la interrupción del teclado. Es bastante similar a cómo definimos el manejador para la interrupción del temporizador; solo utiliza un número de interrupción diferente:
+
+```rust
+// en src/interrupts.rs
+
+#[derive(Debug, Clone, Copy)]
+#[repr(u8)]
+pub enum InterruptIndex {
+ Temporizador = PIC_1_OFFSET,
+ Teclado, // nuevo
+}
+
+lazy_static! {
+ static ref IDT: InterruptDescriptorTable = {
+ let mut idt = InterruptDescriptorTable::new();
+ idt.breakpoint.set_handler_fn(breakpoint_handler);
+ […]
+ // nuevo
+ idt[InterruptIndex::Teclado.as_usize()]
+ .set_handler_fn(keyboard_interrupt_handler);
+
+ idt
+ };
+}
+
+extern "x86-interrupt" fn keyboard_interrupt_handler(
+ _stack_frame: InterruptStackFrame)
+{
+ print!("k");
+
+ unsafe {
+ PICS.lock()
+ .notify_end_of_interrupt(InterruptIndex::Teclado.as_u8());
+ }
+}
+```
+
+Como vemos en la gráfica [arriba](#el-8259-pic), el teclado utiliza la línea 1 del PIC primario. Esto significa que llega a la CPU como interrupción 33 (1 + desplazamiento 32). Agregamos este índice como una nueva variante `Teclado` al enum `InterruptIndex`. No necesitamos especificar el valor explícitamente, ya que de forma predeterminada toma el valor anterior más uno, que también es 33. En el manejador de interrupciones, imprimimos una `k` y enviamos la señal de fin de interrupción al controlador de interrupciones.
+
+Ahora vemos que una `k` aparece en la pantalla cuando presionamos una tecla. Sin embargo, esto solo funciona para la primera tecla que presionamos. Incluso si seguimos presionando teclas, no aparecen más `k`s en la pantalla. Esto se debe a que el controlador del teclado no enviará otra interrupción hasta que hayamos leído el llamado _scancode_ de la tecla presionada.
+
+### Leyendo los Scancodes
+
+Para averiguar _qué_ tecla fue presionada, necesitamos consultar al controlador del teclado. Hacemos esto leyendo desde el puerto de datos del controlador PS/2, que es el [puerto de I/O] con el número `0x60`:
+
+[puerto de I/O]: @/edition-2/posts/04-testing/index.md#i-o-ports
+
+```rust
+// en src/interrupts.rs
+
+extern "x86-interrupt" fn keyboard_interrupt_handler(
+ _stack_frame: InterruptStackFrame)
+{
+ use x86_64::instructions::port::Port;
+
+ let mut port = Port::new(0x60);
+ let scancode: u8 = unsafe { port.read() };
+ print!("{}", scancode);
+
+ unsafe {
+ PICS.lock()
+ .notify_end_of_interrupt(InterruptIndex::Teclado.as_u8());
+ }
+}
+```
+
+Usamos el tipo [`Port`] de la crate `x86_64` para leer un byte del puerto de datos del teclado. Este byte se llama [_scancode_] y representa la pulsación/liberación de la tecla. Aún no hacemos nada con el scancode, excepto imprimirlo en la pantalla:
+
+[`Port`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/port/struct.Port.html
+[_scancode_]: https://en.wikipedia.org/wiki/Scancode
+
+
+
+La imagen anterior muestra que estoy escribiendo lentamente "123". Vemos que las teclas adyacentes tienen scancodes adyacentes y que presionar una tecla causa un scancode diferente al soltarla. Pero, ¿cómo traducimos los scancodes a las acciones de las teclas exactamente?
+
+### Interpretando los Scancodes
+Existen tres estándares diferentes para el mapeo entre scancodes y teclas, los llamados _conjuntos de scancode_. Los tres se remontan a los teclados de las primeras computadoras IBM: el [IBM XT], el [IBM 3270 PC] y el [IBM AT]. Afortunadamente, las computadoras posteriores no continuaron con la tendencia de definir nuevos conjuntos de scancode, sino que emularon los conjuntos existentes y los ampliaron. Hoy en día, la mayoría de los teclados pueden configurarse para emular cualquiera de los tres conjuntos.
+
+[IBM XT]: https://en.wikipedia.org/wiki/IBM_Personal_Computer_XT
+[IBM 3270 PC]: https://en.wikipedia.org/wiki/IBM_3270_PC
+[IBM AT]: https://en.wikipedia.org/wiki/IBM_Personal_Computer/AT
+
+Por defecto, los teclados PS/2 emulan el conjunto de scancode 1 ("XT"). En este conjunto, los 7 bits inferiores de un byte de scancode definen la tecla, y el bit más significativo define si se trata de una pulsación ("0") o una liberación ("1"). Las teclas que no estaban presentes en el teclado original de [IBM XT], como la tecla de entrada en el teclado numérico, generan dos scancodes en sucesión: un byte de escape `0xe0` seguido de un byte que representa la tecla. Para obtener una lista de todos los scancodes del conjunto 1 y sus teclas correspondientes, consulta la [Wiki de OSDev][scancode set 1].
+
+[scancode set 1]: https://wiki.osdev.org/Keyboard#Scan_Code_Set_1
+
+Para traducir los scancodes a teclas, podemos usar una instrucción `match`:
+
+```rust
+// en src/interrupts.rs
+
+extern "x86-interrupt" fn keyboard_interrupt_handler(
+ _stack_frame: InterruptStackFrame)
+{
+ use x86_64::instructions::port::Port;
+
+ let mut port = Port::new(0x60);
+ let scancode: u8 = unsafe { port.read() };
+
+ // nuevo
+ let key = match scancode {
+ 0x02 => Some('1'),
+ 0x03 => Some('2'),
+ 0x04 => Some('3'),
+ 0x05 => Some('4'),
+ 0x06 => Some('5'),
+ 0x07 => Some('6'),
+ 0x08 => Some('7'),
+ 0x09 => Some('8'),
+ 0x0a => Some('9'),
+ 0x0b => Some('0'),
+ _ => None,
+ };
+ if let Some(key) = key {
+ print!("{}", key);
+ }
+
+ unsafe {
+ PICS.lock()
+ .notify_end_of_interrupt(InterruptIndex::Teclado.as_u8());
+ }
+}
+```
+
+El código anterior traduce las pulsaciones de las teclas numéricas 0-9 y ignora todas las otras teclas. Utiliza una declaración [match] para asignar un carácter o `None` a cada scancode. Luego, utiliza [`if let`] para desestructurar la opción `key`. Al usar el mismo nombre de variable `key` en el patrón, [somos sombras de] la declaración anterior, lo cual es un patrón común para desestructurar tipos `Option` en Rust.
+
+[match]: https://doc.rust-lang.org/book/ch06-02-match.html
+[`if let`]: https://doc.rust-lang.org/book/ch18-01-all-the-places-for-patterns.html#conditional-if-let-expressions
+[sombra]: https://doc.rust-lang.org/book/ch03-01-variables-and-mutabilidad.html#shadowing
+
+Ahora podemos escribir números:
+
+
+
+Traducir las otras teclas funciona de la misma manera. Afortunadamente, existe una crate llamada [`pc-keyboard`] para traducir los scancodes de los conjuntos de scancode 1 y 2, así que no tenemos que implementar esto nosotros mismos. Para usar la crate, la añadimos a nuestro `Cargo.toml` e importamos en nuestro `lib.rs`:
+
+[`pc-keyboard`]: https://docs.rs/pc-keyboard/0.7.0/pc_keyboard/
+
+```toml
+# en Cargo.toml
+
+[dependencies]
+pc-keyboard = "0.7.0"
+```
+
+Ahora podemos usar esta crate para reescribir nuestro `keyboard_interrupt_handler`:
+
+```rust
+// en src/interrupts.rs
+
+extern "x86-interrupt" fn keyboard_interrupt_handler(
+ _stack_frame: InterruptStackFrame)
+{
+ use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1};
+ use spin::Mutex;
+ use x86_64::instructions::port::Port;
+
+ lazy_static! {
+ static ref KEYBOARD: Mutex> =
+ Mutex::new(Keyboard::new(ScancodeSet1::new(),
+ layouts::Us104Key, HandleControl::Ignore)
+ );
+ }
+
+ let mut keyboard = KEYBOARD.lock();
+ let mut port = Port::new(0x60);
+
+ let scancode: u8 = unsafe { port.read() };
+ if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
+ if let Some(key) = keyboard.process_keyevent(key_event) {
+ match key {
+ DecodedKey::Unicode(character) => print!("{}", character),
+ DecodedKey::RawKey(key) => print!("{:?}", key),
+ }
+ }
+ }
+
+ unsafe {
+ PICS.lock()
+ .notify_end_of_interrupt(InterruptIndex::Teclado.as_u8());
+ }
+}
+```
+
+Usamos la macro `lazy_static` para crear un objeto estático [`Keyboard`] protegido por un Mutex. Inicializamos el `Keyboard` con un diseño de teclado estadounidense y el conjunto de scancode 1. El parámetro [`HandleControl`] permite mapear `ctrl+[a-z]` a los caracteres Unicode `U+0001` a `U+001A`. No queremos hacer eso, así que usamos la opción `Ignore` para manejar el `ctrl` como teclas normales.
+
+[`HandleControl`]: https://docs.rs/pc-keyboard/0.7.0/pc_keyboard/enum.HandleControl.html
+
+En cada interrupción, bloqueamos el Mutex, leemos el scancode del controlador del teclado y lo pasamos al método [`add_byte`], que traduce el scancode en un `Option`. El [`KeyEvent`] contiene la tecla que causó el evento y si fue un evento de pulsación o liberación.
+
+[`Keyboard`]: https://docs.rs/pc-keyboard/0.7.0/pc_keyboard/struct.Keyboard.html
+[`add_byte`]: https://docs.rs/pc-keyboard/0.7.0/pc_keyboard/struct.Keyboard.html#method.add_byte
+[`KeyEvent`]: https://docs.rs/pc-keyboard/0.7.0/pc_keyboard/struct.KeyEvent.html
+
+Para interpretar este evento de tecla, lo pasamos al método [`process_keyevent`], que traduce el evento de tecla a un carácter, si es posible. Por ejemplo, traduce un evento de pulsación de la tecla `A` a un carácter minúscula `a` o un carácter mayúscula `A`, dependiendo de si la tecla de mayúsculas (shift) estaba presionada.
+
+[`process_keyevent`]: https://docs.rs/pc-keyboard/0.7.0/pc_keyboard/struct.Keyboard.html#method.process_keyevent
+
+Con este manejador de interrupciones modificado, ahora podemos escribir texto:
+
+
+
+### Configurando el Teclado
+
+Es posible configurar algunos aspectos de un teclado PS/2, por ejemplo, qué conjunto de scancode debe usar. No lo cubriremos aquí porque esta publicación ya es lo suficientemente larga, pero la Wiki de OSDev tiene una visión general de los posibles [comandos de configuración].
+
+[comandos de configuración]: https://wiki.osdev.org/PS/2_Keyboard#Commands
+
+## Resumen
+
+Esta publicación explicó cómo habilitar y manejar interrupciones externas. Aprendimos sobre el PIC 8259 y su disposición primario/secundario, la reasignación de los números de interrupción y la señal de "fin de interrupción". Implementamos manejadores para el temporizador de hardware y el teclado y aprendimos sobre la instrucción `hlt`, que detiene la CPU hasta la siguiente interrupción.
+
+Ahora podemos interactuar con nuestro kernel y tenemos algunos bloques fundamentales para crear una pequeña terminal o juegos simples.
+
+## ¿Qué sigue?
+
+Las interrupciones de temporizador son esenciales para un sistema operativo porque proporcionan una manera de interrumpir periódicamente el proceso en ejecución y permitir que el kernel recupere el control. El kernel puede luego cambiar a un proceso diferente y crear la ilusión de que varios procesos se están ejecutando en paralelo.
+
+Pero antes de que podamos crear procesos o hilos, necesitamos una forma de asignar memoria para ellos. Las próximas publicaciones explorarán la gestión de memoria para proporcionar este bloque fundamental.
diff --git a/blog/content/edition-2/posts/08-paging-introduction/index.es.md b/blog/content/edition-2/posts/08-paging-introduction/index.es.md
new file mode 100644
index 00000000..7afb617c
--- /dev/null
+++ b/blog/content/edition-2/posts/08-paging-introduction/index.es.md
@@ -0,0 +1,418 @@
++++
+title = "Introducción a la Paginación"
+weight = 8
+path = "es/paging-introduction"
+date = 2019-01-14
+
+[extra]
+chapter = "Gestión de Memoria"
+
+# GitHub usernames of the people that translated this post
+translators = ["dobleuber"]
++++
+
+Esta publicación introduce la _paginación_ (paging), un esquema de gestión de memoria muy común que también utilizaremos para nuestro sistema operativo. Explica por qué se necesita la aislamiento de memoria, cómo funciona la _segmentación_ (segmentation), qué es la _memoria virtual_ (virtual memory) y cómo la paginación soluciona los problemas de fragmentación de memoria. También explora el diseño de las tablas de páginas multinivel en la arquitectura x86_64.
+
+
+
+Este blog se desarrolla abiertamente en [GitHub]. Si tienes algún problema o pregunta, por favor abre un issue allí. También puedes dejar comentarios [al final]. El código fuente completo de esta publicación se puede encontrar en la rama [`post-08`][post branch].
+
+[GitHub]: https://github.com/phil-opp/blog_os
+[al final]: #comments
+
+[post branch]: https://github.com/phil-opp/blog_os/tree/post-08
+
+
+
+## Protección de Memoria
+
+Una de las principales tareas de un sistema operativo es aislar programas entre sí. Tu navegador web no debería poder interferir con tu editor de texto, por ejemplo. Para lograr este objetivo, los sistemas operativos utilizan funcionalidades de hardware para asegurarse de que las áreas de memoria de un proceso no sean accesibles por otros procesos. Hay diferentes enfoques dependiendo del hardware y la implementación del sistema operativo.
+
+Como ejemplo, algunos procesadores ARM Cortex-M (usados en sistemas embebidos) tienen una _Unidad de Protección de Memoria_ (Memory Protection Unit, MPU), que permite definir un pequeño número (por ejemplo, 8) de regiones de memoria con diferentes permisos de acceso (por ejemplo, sin acceso, solo lectura, lectura-escritura). En cada acceso a la memoria, la MPU asegura que la dirección esté en una región con permisos de acceso correctos y lanza una excepción en caso contrario. Al cambiar las regiones y los permisos de acceso en cada cambio de proceso, el sistema operativo puede asegurarse de que cada proceso solo acceda a su propia memoria y, por lo tanto, aísla los procesos entre sí.
+
+[_Unidad de Protección de Memoria_]: https://developer.arm.com/docs/ddi0337/e/memory-protection-unit/about-the-mpu
+
+En x86, el hardware admite dos enfoques diferentes para la protección de memoria: [segmentación] y [paginación].
+
+[segmentación]: https://en.wikipedia.org/wiki/X86_memory_segmentation
+[paginación]: https://en.wikipedia.org/wiki/Virtual_memory#Paged_virtual_memory
+
+## Segmentación
+
+La segmentación fue introducida en 1978, originalmente para aumentar la cantidad de memoria direccionable. La situación en ese entonces era que las CPU solo usaban direcciones de 16 bits, lo que limitaba la cantidad de memoria direccionable a 64 KiB. Para hacer accesibles más de estos 64 KiB, se introdujeron registros de segmento adicionales, cada uno conteniendo una dirección de desplazamiento. La CPU sumaba automáticamente este desplazamiento en cada acceso a la memoria, de modo que hasta 1 MiB de memoria era accesible.
+
+El registro del segmento es elegido automáticamente por la CPU dependiendo del tipo de acceso a la memoria: para obtener instrucciones, se utiliza el segmento de código `CS`, y para operaciones de pila (push/pop), se utiliza el segmento de pila `SS`. Otras instrucciones utilizan el segmento de datos `DS` o el segmento adicional `ES`. Más tarde, se añadieron dos registros de segmento adicionales, `FS` y `GS`, que pueden ser utilizados libremente.
+
+En la primera versión de la segmentación, los registros de segmento contenían directamente el desplazamiento y no se realizaba control de acceso. Esto se cambió más tarde con la introducción del _modo protegido_ (protected mode). Cuando la CPU funciona en este modo, los descriptores de segmento contienen un índice a una _tabla de descriptores_ local o global, que contiene – además de una dirección de desplazamiento – el tamaño del segmento y los permisos de acceso. Al cargar tablas de descriptores globales/locales separadas para cada proceso, que confinan los accesos de memoria a las áreas de memoria del propio proceso, el sistema operativo puede aislar los procesos entre sí.
+
+[_modo protegido_]: https://en.wikipedia.org/wiki/X86_memory_segmentation#Protected_mode
+[_tabla de descriptores_]: https://en.wikipedia.org/wiki/Global_Descriptor_Table
+
+Al modificar las direcciones de memoria antes del acceso real, la segmentación ya utilizaba una técnica que ahora se usa casi en todas partes: _memoria virtual_ (virtual memory).
+
+### Memoria Virtual
+
+La idea detrás de la memoria virtual es abstraer las direcciones de memoria del dispositivo de almacenamiento físico subyacente. En lugar de acceder directamente al dispositivo de almacenamiento, se realiza primero un paso de traducción. Para la segmentación, el paso de traducción consiste en agregar la dirección de desplazamiento del segmento activo. Imagina un programa que accede a la dirección de memoria `0x1234000` en un segmento con un desplazamiento de `0x1111000`: La dirección que realmente se accede es `0x2345000`.
+
+Para diferenciar los dos tipos de direcciones, se llaman _virtuales_ a las direcciones antes de la traducción, y _físicas_ a las direcciones después de la traducción. Una diferencia importante entre estos dos tipos de direcciones es que las direcciones físicas son únicas y siempre se refieren a la misma ubicación de memoria distinta. Las direcciones virtuales, en cambio, dependen de la función de traducción. Es completamente posible que dos direcciones virtuales diferentes se refieran a la misma dirección física. Además, direcciones virtuales idénticas pueden referirse a diferentes direcciones físicas cuando utilizan diferentes funciones de traducción.
+
+Un ejemplo donde esta propiedad es útil es ejecutar el mismo programa en paralelo dos veces:
+
+
+
+
+Aquí el mismo programa se ejecuta dos veces, pero con diferentes funciones de traducción. La primera instancia tiene un desplazamiento de segmento de 100, de manera que sus direcciones virtuales 0–150 se traducen a las direcciones físicas 100–250. La segunda instancia tiene un desplazamiento de 300, que traduce sus direcciones virtuales 0–150 a direcciones físicas 300–450. Esto permite que ambos programas ejecuten el mismo código y utilicen las mismas direcciones virtuales sin interferir entre sí.
+
+Otra ventaja es que los programas ahora se pueden colocar en ubicaciones de memoria física arbitrarias, incluso si utilizan direcciones virtuales completamente diferentes. Por lo tanto, el sistema operativo puede utilizar la cantidad total de memoria disponible sin necesidad de recompilar programas.
+
+### Fragmentación
+
+La diferenciación entre direcciones virtuales y físicas hace que la segmentación sea realmente poderosa. Sin embargo, tiene el problema de la fragmentación. Como ejemplo, imagina que queremos ejecutar una tercera copia del programa que vimos anteriormente:
+
+
+
+No hay forma de mapear la tercera instancia del programa a la memoria virtual sin superposición, a pesar de que hay más que suficiente memoria libre disponible. El problema es que necesitamos memoria _continua_ y no podemos utilizar los pequeños fragmentos libres.
+
+Una forma de combatir esta fragmentación es pausar la ejecución, mover las partes utilizadas de la memoria más cerca entre sí, actualizar la traducción y luego reanudar la ejecución:
+
+
+
+Ahora hay suficiente espacio continuo para iniciar la tercera instancia de nuestro programa.
+
+La desventaja de este proceso de desfragmentación es que necesita copiar grandes cantidades de memoria, lo que disminuye el rendimiento. También necesita hacerse regularmente antes de que la memoria se fragmenta demasiado. Esto hace que el rendimiento sea impredecible, ya que los programas son pausados en momentos aleatorios y podrían volverse no responsivos.
+
+El problema de la fragmentación es una de las razones por las que la segmentación ya no se utiliza en la mayoría de los sistemas. De hecho, la segmentación ni siquiera es compatible en el modo de 64 bits en x86. En su lugar, se utiliza _paginación_ (paging), que evita por completo el problema de la fragmentación.
+
+## Paginación
+
+La idea es dividir tanto el espacio de memoria virtual como el físico en bloques pequeños de tamaño fijo. Los bloques del espacio de memoria virtual se llaman _páginas_ (pages), y los bloques del espacio de direcciones físicas se llaman _marcos_ (frames). Cada página puede ser mapeada individualmente a un marco, lo que hace posible dividir regiones de memoria más grandes a través de marcos físicos no consecutivos.
+
+La ventaja de esto se ve claramente si recapitulamos el ejemplo del espacio de memoria fragmentado, pero usamos paginación en lugar de segmentación esta vez:
+
+
+
+En este ejemplo, tenemos un tamaño de página de 50 bytes, lo que significa que cada una de nuestras regiones de memoria se divide en tres páginas. Cada página se mapea a un marco individualmente, por lo que una región de memoria virtual continua puede ser mapeada a marcos físicos no continuos. Esto nos permite iniciar la tercera instancia del programa sin realizar ninguna desfragmentación antes.
+
+### Fragmentación Oculta
+
+En comparación con la segmentación, la paginación utiliza muchas pequeñas regiones de memoria de tamaño fijo en lugar de unas pocas grandes regiones de tamaño variable. Dado que cada marco tiene el mismo tamaño, no hay marcos que sean demasiado pequeños para ser utilizados, por lo que no ocurre fragmentación.
+
+O _parece_ que no ocurre fragmentación. Aún existe algún tipo oculto de fragmentación, la llamada _fragmentación interna_ (internal fragmentation). La fragmentación interna ocurre porque no cada región de memoria es un múltiplo exacto del tamaño de la página. Imagina un programa de tamaño 101 en el ejemplo anterior: aún necesitaría tres páginas de tamaño 50, por lo que ocuparía 49 bytes más de lo necesario. Para diferenciar los dos tipos de fragmentación, el tipo de fragmentación que ocurre al usar segmentación se llama _fragmentación externa_ (external fragmentation).
+
+La fragmentación interna es desafortunada pero a menudo es mejor que la fragmentación externa que ocurre con la segmentación. Aún desperdicia memoria, pero no requiere desfragmentación y hace que la cantidad de fragmentación sea predecible (en promedio, media página por región de memoria).
+
+### Tablas de Páginas
+
+Vimos que cada una de las potencialmente millones de páginas se mapea individualmente a un marco. Esta información de mapeo necesita ser almacenada en algún lugar. La segmentación utiliza un registro de selector de segmento individual para cada región de memoria activa, lo cual no es posible para la paginación, ya que hay muchas más páginas que registros. En su lugar, la paginación utiliza una estructura tabular llamada _tabla de páginas_ (page table) para almacenar la información de mapeo.
+
+Para nuestro ejemplo anterior, las tablas de páginas se verían así:
+
+
+
+Vemos que cada instancia del programa tiene su propia tabla de páginas. Un puntero a la tabla actualmente activa se almacena en un registro especial de la CPU. En `x86`, este registro se llama `CR3`. Es trabajo del sistema operativo cargar este registro con el puntero a la tabla de páginas correcta antes de ejecutar cada instancia del programa.
+
+En cada acceso a la memoria, la CPU lee el puntero de la tabla del registro y busca el marco mapeado para la página accedida en la tabla. Esto se realiza completamente en hardware y es completamente invisible para el programa en ejecución. Para agilizar el proceso de traducción, muchas arquitecturas de CPU tienen una caché especial que recuerda los resultados de las últimas traducciones.
+
+Dependiendo de la arquitectura, las entradas de las tablas de páginas también pueden almacenar atributos como permisos de acceso en un campo de banderas. En el ejemplo anterior, la bandera "r/w" hace que la página sea tanto legible como escribible.
+
+### Tablas de Páginas multinivel
+
+Las simples tablas de páginas que acabamos de ver tienen un problema en espacios de direcciones más grandes: desperdician memoria. Por ejemplo, imagina un programa que utiliza las cuatro páginas virtuales `0`, `1_000_000`, `1_000_050` y `1_000_100` (usamos `_` como separador de miles):
+
+
+
+Solo necesita 4 marcos físicos, pero la tabla de páginas tiene más de un millón de entradas. No podemos omitir las entradas vacías porque entonces la CPU ya no podría saltar directamente a la entrada correcta en el proceso de traducción (por ejemplo, ya no se garantiza que la cuarta página use la cuarta entrada).
+
+Para reducir la memoria desperdiciada, podemos usar una **tabla de páginas de dos niveles**. La idea es que utilizamos diferentes tablas de páginas para diferentes regiones de direcciones. Una tabla adicional llamada tabla de páginas _nivel 2_ (level 2) contiene el mapeo entre las regiones de direcciones y las tablas de páginas (nivel 1).
+
+Esto se explica mejor con un ejemplo. Supongamos que cada tabla de páginas de nivel 1 es responsable de una región de tamaño `10_000`. Entonces, las siguientes tablas existirían para el mapeo anterior:
+
+
+
+La página 0 cae en la primera región de `10_000` bytes, por lo que utiliza la primera entrada de la tabla de páginas de nivel 2. Esta entrada apunta a la tabla de páginas de nivel 1 T1, que especifica que la página `0` apunta al marco `0`.
+
+Las páginas `1_000_000`, `1_000_050` y `1_000_100` caen todas en la entrada número 100 de la región de `10_000` bytes, por lo que utilizan la entrada 100 de la tabla de páginas de nivel 2. Esta entrada apunta a una tabla de páginas de nivel 1 diferente T2, que mapea las tres páginas a los marcos `100`, `150` y `200`. Ten en cuenta que la dirección de página en las tablas de nivel 1 no incluye el desplazamiento de región. Por ejemplo, la entrada para la página `1_000_050` es solo `50`.
+
+Aún tenemos 100 entradas vacías en la tabla de nivel 2, pero muchas menos que el millón de entradas vacías de antes. La razón de este ahorro es que no necesitamos crear tablas de páginas de nivel 1 para las regiones de memoria no mapeadas entre `10_000` y `1_000_000`.
+
+El principio de las tablas de páginas de dos niveles se puede extender a tres, cuatro o más niveles. Luego, el registro de la tabla de páginas apunta a la tabla de nivel más alto, que apunta a la tabla de nivel más bajo, que apunta a la siguiente tabla de nivel inferior, y así sucesivamente. La tabla de páginas de nivel 1 luego apunta al marco mapeado. El principio en general se llama _tabla de páginas multinivel_ (multilevel page table) o _jerárquica_.
+
+Ahora que sabemos cómo funcionan la paginación y las tablas de páginas multinivel, podemos ver cómo se implementa la paginación en la arquitectura x86_64 (suponemos en lo siguiente que la CPU funciona en modo de 64 bits).
+
+## Paginación en x86_64
+
+La arquitectura x86_64 utiliza una tabla de páginas de 4 niveles y un tamaño de página de 4 KiB. Cada tabla de páginas, independientemente del nivel, tiene un tamaño fijo de 512 entradas. Cada entrada tiene un tamaño de 8 bytes, por lo que cada tabla tiene un tamaño de 512 * 8 B = 4 KiB y, por lo tanto, encaja exactamente en una página.
+
+El índice de la tabla de páginas para cada nivel se deriva directamente de la dirección virtual:
+
+
+
+Vemos que cada índice de tabla consta de 9 bits, lo que tiene sentido porque cada tabla tiene 2^9 = 512 entradas. Los 12 bits más bajos son el desplazamiento en la página de 4 KiB (2^12 bytes = 4 KiB). Los bits 48 a 64 se descartan, lo que significa que x86_64 no es realmente de 64 bits, ya que solo admite direcciones de 48 bits.
+
+A pesar de que se descartan los bits 48 a 64, no pueden establecerse en valores arbitrarios. En cambio, todos los bits en este rango deben ser copias del bit 47 para mantener las direcciones únicas y permitir extensiones futuras como la tabla de páginas de 5 niveles. Esto se llama _extensión de signo_ (sign-extension) porque es muy similar a la [extensión de signo en complemento a dos]. Cuando una dirección no está correctamente extendida de signo, la CPU lanza una excepción.
+
+[extensión de signo en complemento a dos]: https://en.wikipedia.org/wiki/Two's_complement#Sign_extension
+
+Cabe destacar que los recientes procesadores Intel "Ice Lake" admiten opcionalmente [tablas de páginas de 5 niveles] para extender las direcciones virtuales de 48 bits a 57 bits. Dado que optimizar nuestro núcleo para una CPU específica no tiene sentido en esta etapa, solo trabajaremos con tablas de páginas de 4 niveles estándar en esta publicación.
+
+[tablas de páginas de 5 niveles]: https://en.wikipedia.org/wiki/Intel_5-level_paging
+
+### Ejemplo de Traducción
+
+Pasemos por un ejemplo para entender cómo funciona el proceso de traducción en detalle:
+
+
+
+La dirección física de la tabla de páginas de nivel 4 actualmente activa, que es la raíz de la tabla de páginas de 4 niveles, se almacena en el registro `CR3`. Cada entrada de la tabla de nivel 1 luego apunta al marco físico de la tabla del siguiente nivel. La entrada de la tabla de nivel 1 luego apunta al marco mapeado. Ten en cuenta que todas las direcciones en las tablas de páginas son físicas en lugar de virtuales, porque de lo contrario la CPU también necesitaría traducir esas direcciones (lo que podría provocar una recursión interminable).
+
+La jerarquía de tablas de páginas anterior mapea dos páginas (en azul). A partir de los índices de la tabla de páginas, podemos deducir que las direcciones virtuales de estas dos páginas son `0x803FE7F000` y `0x803FE00000`. Veamos qué sucede cuando el programa intenta leer desde la dirección `0x803FE7F5CE`. Primero, convertimos la dirección a binario y determinamos los índices de la tabla de páginas y el desplazamiento de la página para la dirección:
+
+
+
+Con estos índices, ahora podemos recorrer la jerarquía de la tabla de páginas para determinar el marco mapeado para la dirección:
+
+- Comenzamos leyendo la dirección de la tabla de nivel 4 del registro `CR3`.
+- El índice de nivel 4 es 1, así que miramos la entrada en el índice 1 de esa tabla, que nos dice que la tabla de nivel 3 se almacena en la dirección 16 KiB.
+- Cargamos la tabla de nivel 3 desde esa dirección y miramos la entrada en el índice 0, que nos apunta a la tabla de nivel 2 en 24 KiB.
+- El índice de nivel 2 es 511, así que miramos la última entrada de esa página para averiguar la dirección de la tabla de nivel 1.
+- A través de la entrada en el índice 127 de la tabla de nivel 1, finalmente descubrimos que la página está mapeada al marco de 12 KiB, o 0x3000 en hexadecimal.
+- El paso final es agregar el desplazamiento de la página a la dirección del marco para obtener la dirección física 0x3000 + 0x5ce = 0x35ce.
+
+
+
+Los permisos para la página en la tabla de nivel 1 son `r`, lo que significa que es solo de lectura. El hardware hace cumplir estos permisos y lanzaría una excepción si intentáramos escribir en esa página. Los permisos en las páginas de niveles superiores restringen los posibles permisos en niveles inferiores, por lo que si establecemos la entrada de nivel 3 como solo lectura, ninguna página que use esta entrada puede ser escribible, incluso si los niveles inferiores especifican permisos de lectura/escritura.
+
+Es importante tener en cuenta que, aunque este ejemplo utilizó solo una instancia de cada tabla, normalmente hay múltiples instancias de cada nivel en cada espacio de direcciones. En el máximo, hay:
+
+- una tabla de nivel 4,
+- 512 tablas de nivel 3 (porque la tabla de nivel 4 tiene 512 entradas),
+- 512 * 512 tablas de nivel 2 (porque cada una de las 512 tablas de nivel 3 tiene 512 entradas), y
+- 512 * 512 * 512 tablas de nivel 1 (512 entradas para cada tabla de nivel 2).
+
+### Formato de la Tabla de Páginas
+
+Las tablas de páginas en la arquitectura x86_64 son básicamente un array de 512 entradas. En sintaxis de Rust:
+
+```rust
+#[repr(align(4096))]
+pub struct PageTable {
+ entries: [PageTableEntry; 512],
+}
+```
+
+Como se indica por el atributo `repr`, las tablas de páginas necesitan estar alineadas a la página, es decir, alineadas en un límite de 4 KiB. Este requisito garantiza que una tabla de páginas siempre llene una página completa y permite una optimización que hace que las entradas sean muy compactas.
+
+Cada entrada tiene un tamaño de 8 bytes (64 bits) y tiene el siguiente formato:
+
+| Bit(s) | Nombre | Significado |
+| ------ | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
+| 0 | presente | la página está actualmente en memoria |
+| 1 | escribible | se permite escribir en esta página |
+| 2 | accesible por el usuario | si no se establece, solo el código en modo núcleo puede acceder a esta página |
+| 3 | caché de escritura a través | las escrituras van directamente a la memoria |
+| 4 | desactivar caché | no se utiliza caché para esta página |
+| 5 | accedido | la CPU establece este bit cuando se utiliza esta página |
+| 6 | sucio | la CPU establece este bit cuando se realiza una escritura en esta página |
+| 7 | página enorme/null | debe ser 0 en P1 y P4, crea una página de 1 GiB en P3, crea una página de 2 MiB en P2 |
+| 8 | global | la página no se borra de las cachés al cambiar el espacio de direcciones (el bit PGE del registro CR4 debe estar establecido) |
+| 9-11 | disponible | puede ser utilizado libremente por el sistema operativo |
+| 12-51 | dirección física | la dirección física alineada de 52 bits del marco o de la siguiente tabla de páginas |
+| 52-62 | disponible | puede ser utilizado libremente por el sistema operativo |
+| 63 | no ejecutar | prohibir la ejecución de código en esta página (el bit NXE en el registro EFER debe estar establecido) |
+
+Vemos que solo los bits 12–51 se utilizan para almacenar la dirección física del marco. Los bits restantes se utilizan como banderas o pueden ser utilizados libremente por el sistema operativo. Esto es posible porque siempre apuntamos a una dirección alineada a 4096 bytes, ya sea a una tabla de páginas alineada a la página o al inicio de un marco mapeado. Esto significa que los bits 0–11 son siempre cero, por lo que no hay razón para almacenar estos bits porque el hardware puede simplemente configurarlos en cero antes de usar la dirección. Lo mismo es cierto para los bits 52–63, ya que la arquitectura x86_64 solo admite direcciones físicas de 52 bits (similar a como solo admite direcciones virtuales de 48 bits).
+
+Veamos más de cerca las banderas disponibles:
+
+- La bandera `presente` diferencia las páginas mapeadas de las no mapeadas. Puede usarse para intercambiar temporalmente páginas en disco cuando la memoria principal se llena. Cuando la página se accede posteriormente, ocurre una excepción especial llamada _fallo de página_ (page fault), a la cual el sistema operativo puede reaccionar volviendo a cargar la página faltante desde el disco y luego continuar el programa.
+- Las banderas `escribible` y `no ejecutar` controlan si el contenido de la página es escribible o contiene instrucciones ejecutables, respectivamente.
+- Las banderas `accedido` y `sucio` son automáticamente configuradas por la CPU cuando se produce una lectura o escritura en la página. Esta información puede ser utilizada por el sistema operativo, por ejemplo, para decidir qué páginas intercambiar o si el contenido de la página ha sido modificado desde el último guardado en disco.
+- Las banderas `caché de escritura a través` y `desactivar caché` permiten el control de cachés para cada página individualmente.
+- La bandera `accesible por el usuario` hace que una página esté disponible para el código de espacio de usuario, de lo contrario, solo es accesible cuando la CPU está en modo núcleo. Esta característica puede utilizarse para hacer [llamadas al sistema] más rápidas manteniendo el núcleo mapeado mientras un programa de espacio de usuario se está ejecutando. Sin embargo, la vulnerabilidad [Spectre] puede permitir que los programas de espacio de usuario lean estas páginas, sin embargo.
+- La bandera `global` le indica al hardware que una página está disponible en todos los espacios de direcciones y, por lo tanto, no necesita ser eliminada de la caché de traducción (ver la sección sobre el TLB a continuación) al cambiar de espacio de direcciones. Esta bandera se utiliza comúnmente junto con una bandera `accesible por el usuario` desactivada para mapear el código del núcleo a todos los espacios de direcciones.
+- La bandera `página enorme` permite la creación de páginas de tamaños más grandes al permitir que las entradas de las tablas de nivel 2 o nivel 3 apunten directamente a un marco mapeado. Con este bit establecido, el tamaño de la página aumenta por un factor de 512 a 2 MiB = 512 * 4 KiB para las entradas de nivel 2 o incluso 1 GiB = 512 * 2 MiB para las entradas de nivel 3. La ventaja de usar páginas más grandes es que se necesitan menos líneas de la caché de traducción y menos tablas de páginas.
+
+[llamadas al sistema]: https://en.wikipedia.org/wiki/System_call
+[Spectre]: https://en.wikipedia.org/wiki/Spectre_(security_vulnerability)
+
+El crate `x86_64` proporciona tipos para [tablas de páginas] y sus [entradas], por lo que no necesitamos crear estas estructuras nosotros mismos.
+
+[tablas de páginas]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/page_table/struct.PageTable.html
+[entradas]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/page_table/struct.PageTableEntry.html
+
+### El Buffer de Traducción (TLB)
+
+Una tabla de páginas de 4 niveles hace que la traducción de direcciones virtuales sea costosa porque cada traducción requiere cuatro accesos a la memoria. Para mejorar el rendimiento, la arquitectura x86_64 almacena en caché las últimas traducciones en el denominado _buffer de traducción_ (translation lookaside buffer, TLB). Esto permite omitir la traducción cuando todavía está en caché.
+
+A diferencia de las demás cachés de la CPU, el TLB no es completamente transparente y no actualiza ni elimina traducciones cuando cambian los contenidos de las tablas de páginas. Esto significa que el núcleo debe actualizar manualmente el TLB cada vez que modifica una tabla de páginas. Para hacer esto, hay una instrucción especial de la CPU llamada [`invlpg`] ("invalidar página") que elimina la traducción para la página especificada del TLB, de modo que se vuelva a cargar desde la tabla de páginas en el siguiente acceso. El crate `x86_64` proporciona funciones en Rust para ambas variantes en el [`módulo tlb`].
+
+[`invlpg`]: https://www.felixcloutier.com/x86/INVLPG.html
+[`módulo tlb`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/tlb/index.html
+
+Es importante recordar limpiar el TLB en cada modificación de tabla de páginas porque de lo contrario, la CPU podría seguir utilizando la vieja traducción, lo que puede llevar a errores no determinísticos que son muy difíciles de depurar.
+
+## Implementación
+
+Una cosa que aún no hemos mencionado: **Nuestro núcleo ya se ejecuta sobre paginación**. El bootloader (cargador de arranque) que añadimos en la publicación ["Un núcleo mínimo de Rust"] ya ha configurado una jerarquía de paginación de 4 niveles que mapea cada página de nuestro núcleo a un marco físico. El bootloader hace esto porque la paginación es obligatoria en el modo de 64 bits en x86_64.
+
+["Un núcleo mínimo de Rust"]: @/edition-2/posts/02-minimal-rust-kernel/index.md#creating-a-bootimage
+
+Esto significa que cada dirección de memoria que utilizamos en nuestro núcleo era una dirección virtual. Acceder al búfer VGA en la dirección `0xb8000` solo funcionó porque el bootloader _mapeó por identidad_ esa página de memoria, lo que significa que mapeó la página virtual `0xb8000` al marco físico `0xb8000`.
+
+La paginación hace que nuestro núcleo ya sea relativamente seguro, ya que cada acceso a memoria que está fuera de límites causa una excepción de fallo de página en lugar de escribir en la memoria física aleatoria. El bootloader incluso establece los permisos de acceso correctos para cada página, lo que significa que solo las páginas que contienen código son ejecutables y solo las páginas de datos son escribibles.
+
+### Fallos de Página
+
+Intentemos causar un fallo de página accediendo a alguna memoria fuera de nuestro núcleo. Primero, creamos un controlador de fallos de página y lo registramos en nuestra IDT, para que veamos una excepción de fallo de página en lugar de un fallo doble genérico:
+
+[fallo doble]: @/edition-2/posts/06-double-faults/index.md
+
+```rust
+// en src/interrupts.rs
+
+lazy_static! {
+ static ref IDT: InterruptDescriptorTable = {
+ let mut idt = InterruptDescriptorTable::new();
+
+ […]
+
+ idt.page_fault.set_handler_fn(page_fault_handler); // nuevo
+
+ idt
+ };
+}
+
+use x86_64::structures::idt::PageFaultErrorCode;
+use crate::hlt_loop;
+
+extern "x86-interrupt" fn page_fault_handler(
+ stack_frame: InterruptStackFrame,
+ error_code: PageFaultErrorCode,
+) {
+ use x86_64::registers::control::Cr2;
+
+ println!("EXCEPCIÓN: FALLO DE PÁGINA");
+ println!("Dirección Accedida: {:?}", Cr2::read());
+ println!("Código de Error: {:?}", error_code);
+ println!("{:#?}", stack_frame);
+ hlt_loop();
+}
+```
+
+El registro [`CR2`] se configura automáticamente por la CPU en un fallo de página y contiene la dirección virtual accedida que provocó el fallo de página. Usamos la función [`Cr2::read`] del crate `x86_64` para leerla e imprimirla. El tipo [`PageFaultErrorCode`] proporciona más información sobre el tipo de acceso a la memoria que causó el fallo de página, por ejemplo, si fue causado por una operación de lectura o escritura. Por esta razón, también la imprimimos. No podemos continuar la ejecución sin resolver el fallo de página, por lo que entramos en un [`hlt_loop`] al final.
+
+[`CR2`]: https://en.wikipedia.org/wiki/Control_register#CR2
+[`Cr2::read`]: https://docs.rs/x86_64/0.14.2/x86_64/registers/control/struct.Cr2.html#method.read
+[`PageFaultErrorCode`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.PageFaultErrorCode.html
+[bug de LLVM]: https://github.com/rust-lang/rust/issues/57270
+[`hlt_loop`]: @/edition-2/posts/07-hardware-interrupts/index.md#the-hlt-instruction
+
+Ahora podemos intentar acceder a alguna memoria fuera de nuestro núcleo:
+
+```rust
+// en src/main.rs
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ println!("¡Hola Mundo{}", "!");
+
+ blog_os::init();
+
+ // nuevo
+ let ptr = 0xdeadbeaf as *mut u8;
+ unsafe { *ptr = 42; }
+
+ // como antes
+ #[cfg(test)]
+ test_main();
+
+ println!("¡No se estrelló!");
+ blog_os::hlt_loop();
+}
+```
+
+Cuando lo ejecutamos, vemos que se llama a nuestro controlador de fallos de página:
+
+
+
+El registro `CR2` efectivamente contiene `0xdeadbeaf`, la dirección que intentamos acceder. El código de error nos dice a través del [`CAUSED_BY_WRITE`] que la falla ocurrió mientras intentábamos realizar una operación de escritura. También nos dice más a través de los [bits que _no_ están establecidos][`PageFaultErrorCode`]. Por ejemplo, el hecho de que la bandera `PROTECTION_VIOLATION` no esté establecida significa que el fallo de página ocurrió porque la página objetivo no estaba presente.
+
+[`CAUSED_BY_WRITE`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.PageFaultErrorCode.html#associatedconstant.CAUSED_BY_WRITE
+
+Vemos que el puntero de instrucciones actual es `0x2031b2`, así que sabemos que esta dirección apunta a una página de código. Las páginas de código están mapeadas como solo lectura por el bootloader, así que leer desde esta dirección funciona, pero escribir causa un fallo de página. Puedes intentar esto cambiando el puntero `0xdeadbeaf` a `0x2031b2`:
+
+```rust
+// Nota: La dirección real podría ser diferente para ti. Usa la dirección que
+// informa tu controlador de fallos de página.
+let ptr = 0x2031b2 as *mut u8;
+
+// leer desde una página de código
+unsafe { let x = *ptr; }
+println!("la lectura funcionó");
+
+// escribir en una página de código
+unsafe { *ptr = 42; }
+println!("la escritura funcionó");
+```
+
+Al comentar la última línea, vemos que el acceso de lectura funciona, pero el acceso de escritura causa un fallo de página:
+
+
+
+Vemos que el mensaje _"la lectura funcionó"_ se imprime, lo que indica que la operación de lectura no causó errores. Sin embargo, en lugar del mensaje _"la escritura funcionó"_, ocurre un fallo de página. Esta vez la bandera [`PROTECTION_VIOLATION`] está establecida además de la bandera [`CAUSED_BY_WRITE`], lo que indica que la página estaba presente, pero la operación no estaba permitida en ella. En este caso, las escrituras a la página no están permitidas ya que las páginas de código están mapeadas como solo lectura.
+
+[`PROTECTION_VIOLATION`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.PageFaultErrorCode.html#associatedconstant.PROTECTION_VIOLATION
+
+### Accediendo a las Tablas de Páginas
+
+Intentemos echar un vistazo a las tablas de páginas que definen cómo está mapeado nuestro núcleo:
+
+```rust
+// en src/main.rs
+
+#[no_mangle]
+pub extern "C" fn _start() -> ! {
+ println!("¡Hola Mundo{}", "!");
+
+ blog_os::init();
+
+ use x86_64::registers::control::Cr3;
+
+ let (level_4_page_table, _) = Cr3::read();
+ println!("Tabla de páginas de nivel 4 en: {:?}", level_4_page_table.start_address());
+
+ […] // test_main(), println(…), y hlt_loop()
+}
+```
+
+La función [`Cr3::read`] del `x86_64` devuelve la tabla de páginas de nivel 4 actualmente activa desde el registro `CR3`. Devuelve una tupla de un tipo [`PhysFrame`] y un tipo [`Cr3Flags`]. Solo nos interesa el marco, así que ignoramos el segundo elemento de la tupla.
+
+[`Cr3::read`]: https://docs.rs/x86_64/0.14.2/x86_64/registers/control/struct.Cr3.html#method.read
+[`PhysFrame`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/paging/frame/struct.PhysFrame.html
+[`Cr3Flags`]: https://docs.rs/x86_64/0.14.2/x86_64/registers/control/struct.Cr3Flags.html
+
+Cuando lo ejecutamos, vemos la siguiente salida:
+
+```
+Tabla de páginas de nivel 4 en: PhysAddr(0x1000)
+```
+
+Entonces, la tabla de páginas de nivel 4 actualmente activa se almacena en la dirección `0x1000` en _memoria física_, como indica el tipo de wrapper [`PhysAddr`]. La pregunta ahora es: ¿cómo podemos acceder a esta tabla desde nuestro núcleo?
+
+[`PhysAddr`]: https://docs.rs/x86_64/0.14.2/x86_64/addr/struct.PhysAddr.html
+
+Acceder a la memoria física directamente no es posible cuando la paginación está activa, ya que los programas podrían fácilmente eludir la protección de memoria y acceder a la memoria de otros programas de lo contrario. Así que la única forma de acceder a la tabla es a través de alguna página virtual que esté mapeada al marco físico en la dirección `0x1000`. Este problema de crear mapeos para los marcos de tabla de páginas es un problema general ya que el núcleo necesita acceder a las tablas de páginas regularmente, por ejemplo, al asignar una pila para un nuevo hilo.
+
+Las soluciones a este problema se explican en detalle en la siguiente publicación.
+
+## Resumen
+
+Esta publicación introdujo dos técnicas de protección de memoria: segmentación y paginación. Mientras que la primera utiliza regiones de memoria de tamaño variable y sufre de fragmentación externa, la segunda utiliza páginas de tamaño fijo y permite un control mucho más detallado sobre los permisos de acceso.
+
+La paginación almacena la información de mapeo para las páginas en tablas de páginas con uno o más niveles. La arquitectura x86_64 utiliza tablas de páginas de 4 niveles y un tamaño de página de 4 KiB. El hardware recorre automáticamente las tablas de páginas y almacena en caché las traducciones resultantes en el buffer de traducción (TLB). Este buffer no se actualiza de manera transparente y necesita ser limpiado manualmente en cambios de tabla de páginas.
+
+Aprendimos que nuestro núcleo ya se ejecuta sobre paginación y que los accesos ilegales a la memoria provocan excepciones de fallo de página. Intentamos acceder a las tablas de páginas actualmente activas, pero no pudimos hacerlo porque el registro CR3 almacena una dirección física que no podemos acceder directamente desde nuestro núcleo.
+
+## ¿Qué sigue?
+
+La siguiente publicación explica cómo implementar soporte para la paginación en nuestro núcleo. Presenta diferentes formas de acceder a la memoria física desde nuestro núcleo, lo que hace posible acceder a las tablas de páginas en las que se ejecuta nuestro núcleo. En este momento, seremos capaces de implementar funciones para traducir direcciones virtuales a físicas y para crear nuevos mapeos en las tablas de páginas.
diff --git a/blog/content/edition-2/posts/09-paging-implementation/index.es.md b/blog/content/edition-2/posts/09-paging-implementation/index.es.md
new file mode 100644
index 00000000..90554048
--- /dev/null
+++ b/blog/content/edition-2/posts/09-paging-implementation/index.es.md
@@ -0,0 +1,950 @@
++++
+title = "Implementación de Paginación"
+weight = 9
+path = "es/implementacion-de-paginacion"
+date = 2019-03-14
+
+[extra]
+chapter = "Gestión de la Memoria"
+
+# GitHub usernames of the people that translated this post
+translators = ["dobleuber"]
++++
+
+Esta publicación muestra cómo implementar soporte para paginación en nuestro núcleo. Primero explora diferentes técnicas para hacer accesibles los marcos de la tabla de páginas físicas al núcleo y discute sus respectivas ventajas y desventajas. Luego implementa una función de traducción de direcciones y una función para crear un nuevo mapeo.
+
+
+
+Este blog se desarrolla abiertamente en [GitHub]. Si tienes algún problema o pregunta, abre un problema allí. También puedes dejar comentarios [al final]. El código fuente completo de esta publicación se puede encontrar en la rama [`post-09`][post branch].
+
+[GitHub]: https://github.com/phil-opp/blog_os
+[al final]: #comments
+
+[post branch]: https://github.com/phil-opp/blog_os/tree/post-09
+
+
+
+## Introducción
+
+La [publicación anterior] dio una introducción al concepto de paginación. Motivó la paginación comparándola con la segmentación, explicó cómo funcionan la paginación y las tablas de páginas, y luego introdujo el diseño de tabla de páginas de 4 niveles de `x86_64`. Descubrimos que el bootloader (cargador de arranque) ya configuró una jerarquía de tablas de páginas para nuestro núcleo, lo que significa que nuestro núcleo ya se ejecuta en direcciones virtuales. Esto mejora la seguridad, ya que los accesos ilegales a la memoria causan excepciones de falta de página en lugar de modificar la memoria física arbitraria.
+
+[publicación anterior]: @/edition-2/posts/08-paging-introduction/index.md
+
+La publicación terminó con el problema de que [no podemos acceder a las tablas de páginas desde nuestro núcleo][end of previous post] porque se almacenan en la memoria física y nuestro núcleo ya se ejecuta en direcciones virtuales. Esta publicación explora diferentes enfoques para hacer los marcos de la tabla de páginas accesibles a nuestro núcleo. Discutiremos las ventajas y desventajas de cada enfoque y luego decidiremos un enfoque para nuestro núcleo.
+
+[end of previous post]: @/edition-2/posts/08-paging-introduction/index.md#accessing-the-page-tables
+
+Para implementar el enfoque, necesitaremos el soporte del bootloader, así que lo configuraremos primero. Después, implementaremos una función que recorra la jerarquía de tablas de páginas para traducir direcciones virtuales a físicas. Finalmente, aprenderemos a crear nuevos mapeos en las tablas de páginas y a encontrar marcos de memoria no utilizados para crear nuevas tablas de páginas.
+
+## Accediendo a las Tablas de Páginas {#accediendo-a-las-tablas-de-paginas}
+
+Acceder a las tablas de páginas desde nuestro núcleo no es tan fácil como podría parecer. Para entender el problema, echemos un vistazo a la jerarquía de tablas de páginas de 4 niveles del artículo anterior nuevamente:
+
+
+
+Lo importante aquí es que cada entrada de página almacena la dirección _física_ de la siguiente tabla. Esto evita la necesidad de hacer una traducción para estas direcciones también, lo cual sería malo para el rendimiento y podría fácilmente causar bucles de traducción infinitos.
+
+El problema para nosotros es que no podemos acceder directamente a las direcciones físicas desde nuestro núcleo, ya que nuestro núcleo también se ejecuta sobre direcciones virtuales. Por ejemplo, cuando accedemos a la dirección `4 KiB`, accedemos a la dirección _virtual_ `4 KiB`, no a la dirección _física_ `4 KiB` donde se almacena la tabla de páginas de nivel 4. Cuando queremos acceder a la dirección física `4 KiB`, solo podemos hacerlo a través de alguna dirección virtual que mapea a ella.
+
+Así que, para acceder a los marcos de la tabla de páginas, necesitamos mapear algunas páginas virtuales a ellos. Hay diferentes formas de crear estos mapeos que nos permiten acceder a marcos arbitrarios de la tabla de páginas.
+
+### Mapeo de Identidad
+
+Una solución simple es **mapear de identidad todas las tablas de páginas**:
+
+
+
+En este ejemplo, vemos varios marcos de tablas de páginas mapeados de identidad. De esta manera, las direcciones físicas de las tablas de páginas también son direcciones virtuales válidas, por lo que podemos acceder fácilmente a las tablas de páginas de todos los niveles comenzando desde el registro CR3.
+
+Sin embargo, esto desordena el espacio de direcciones virtuales y dificulta encontrar regiones de memoria continuas de tamaños más grandes. Por ejemplo, imagina que queremos crear una región de memoria virtual de tamaño 1000 KiB en el gráfico anterior, por ejemplo, para [mapeo de una memoria de archivo]. No podemos comenzar la región en `28 KiB` porque colisionaría con la página ya mapeada en `1004 KiB`. Así que tenemos que buscar más hasta que encontremos un área suficientemente grande sin mapear, por ejemplo, en `1008 KiB`. Este es un problema de fragmentación similar al de la [segmentación].
+
+[mapeo de una memoria de archivo]: https://en.wikipedia.org/wiki/Memory-mapped_file
+[segmentación]: @/edition-2/posts/08-paging-introduction/index.md#fragmentation
+
+Igualmente, hace que sea mucho más difícil crear nuevas tablas de páginas porque necesitamos encontrar marcos físicos cuyos correspondientes páginas no estén ya en uso. Por ejemplo, asumamos que reservamos la región de memoria _virtual_ de 1000 KiB comenzando en `1008 KiB` para nuestro archivo mapeado en memoria. Ahora no podemos usar ningún marco con una dirección _física_ entre `1000 KiB` y `2008 KiB`, porque no podemos mapear de identidad.
+
+### Mapear en un Desplazamiento Fijo
+
+Para evitar el problema de desordenar el espacio de direcciones virtuales, podemos **usar una región de memoria separada para los mapeos de la tabla de páginas**. Así que en lugar de mapear de identidad los marcos de las tablas de páginas, los mapeamos en un desplazamiento fijo en el espacio de direcciones virtuales. Por ejemplo, el desplazamiento podría ser de 10 TiB:
+
+
+
+Al usar la memoria virtual en el rango `10 TiB..(10 TiB + tamaño de la memoria física)` exclusivamente para mapeos de tablas de páginas, evitamos los problemas de colisión del mapeo de identidad. Reservar una región tan grande del espacio de direcciones virtuales solo es posible si el espacio de direcciones virtuales es mucho más grande que el tamaño de la memoria física. Esto no es un problema en `x86_64` ya que el espacio de direcciones de 48 bits es de 256 TiB.
+
+Este enfoque aún tiene la desventaja de que necesitamos crear un nuevo mapeo cada vez que creamos una nueva tabla de páginas. Además, no permite acceder a las tablas de páginas de otros espacios de direcciones, lo que sería útil al crear un nuevo proceso.
+
+### Mapear la Memoria Física Completa
+
+Podemos resolver estos problemas **mapeando la memoria física completa** en lugar de solo los marcos de la tabla de páginas:
+
+
+
+Este enfoque permite a nuestro núcleo acceder a memoria física arbitraria, incluyendo marcos de la tabla de páginas de otros espacios de direcciones. La región de memoria virtual reservada tiene el mismo tamaño que antes, con la diferencia de que ya no contiene páginas sin mapear.
+
+La desventaja de este enfoque es que se necesitan tablas de páginas adicionales para almacenar el mapeo de la memoria física. Estas tablas de páginas deben almacenarse en alguna parte, por lo que ocupan parte de la memoria física, lo que puede ser un problema en dispositivos con poca memoria.
+
+En `x86_64`, sin embargo, podemos utilizar [páginas grandes] con un tamaño de 2 MiB para el mapeo, en lugar de las páginas de 4 KiB por defecto. De esta manera, mapear 32 GiB de memoria física solo requiere 132 KiB para las tablas de páginas, ya que solo se necesita una tabla de nivel 3 y 32 tablas de nivel 2. Las páginas grandes también son más eficientes en caché, ya que utilizan menos entradas en el buffer de traducción (TLB).
+
+[páginas grandes]: https://en.wikipedia.org/wiki/Page_%28computer_memory%29#Multiple_page_sizes
+
+### Mapeo Temporal
+
+Para dispositivos con cantidades muy pequeñas de memoria física, podríamos **mapear los marcos de la tabla de páginas solo temporalmente** cuando necesitemos acceder a ellos. Para poder crear los mapeos temporales, solo necesitamos una única tabla de nivel 1 mapeada de identidad:
+
+
+
+La tabla de nivel 1 en este gráfico controla los primeros 2 MiB del espacio de direcciones virtuales. Esto se debe a que es accesible comenzando en el registro CR3 y siguiendo la entrada 0 en las tablas de páginas de niveles 4, 3 y 2. La entrada con índice `8` mapea la página virtual en la dirección `32 KiB` al marco físico en la dirección `32 KiB`, mapeando de identidad la tabla de nivel 1 misma. El gráfico muestra este mapeo de identidad mediante la flecha horizontal en `32 KiB`.
+
+Al escribir en la tabla de nivel 1 mapeada de identidad, nuestro núcleo puede crear hasta 511 mapeos temporales (512 menos la entrada requerida para el mapeo de identidad). En el ejemplo anterior, el núcleo creó dos mapeos temporales:
+
+- Al mapear la 0ª entrada de la tabla de nivel 1 al marco con dirección `24 KiB`, creó un mapeo temporal de la página virtual en `0 KiB` al marco físico de la tabla de nivel 2, indicado por la línea de puntos.
+- Al mapear la 9ª entrada de la tabla de nivel 1 al marco con dirección `4 KiB`, creó un mapeo temporal de la página virtual en `36 KiB` al marco físico de la tabla de nivel 4, indicado por la línea de puntos.
+
+Ahora el núcleo puede acceder a la tabla de nivel 2 escribiendo en la página `0 KiB` y a la tabla de nivel 4 escribiendo en la página `36 KiB`.
+
+El proceso para acceder a un marco de tabla de páginas arbitrario con mapeos temporales sería:
+
+- Buscar una entrada libre en la tabla de nivel 1 mapeada de identidad.
+- Mapear esa entrada al marco físico de la tabla de páginas que queremos acceder.
+- Acceder al marco objetivo a través de la página virtual que se mapea a la entrada.
+- Reestablecer la entrada como no utilizada, eliminando así el mapeo temporal nuevamente.
+
+Este enfoque reutiliza las mismas 512 páginas virtuales para crear los mapeos y, por lo tanto, requiere solo 4 KiB de memoria física. La desventaja es que es un poco engorroso, especialmente porque un nuevo mapeo podría requerir modificaciones en múltiples niveles de la tabla, lo que significa que tendríamos que repetir el proceso anterior múltiples veces.
+
+### Tablas de Páginas Recursivas
+
+Otro enfoque interesante, que no requiere tablas de páginas adicionales, es **mapear la tabla de páginas de manera recursiva**. La idea detrás de este enfoque es mapear una entrada de la tabla de nivel 4 a la misma tabla de nivel 4. Al hacer esto, reservamos efectivamente una parte del espacio de direcciones virtuales y mapeamos todos los marcos de tablas de páginas actuales y futuros a ese espacio.
+
+Veamos un ejemplo para entender cómo funciona todo esto:
+
+
+
+La única diferencia con el [ejemplo al principio de este artículo] es la entrada adicional en el índice `511` en la tabla de nivel 4, que está mapeada al marco físico `4 KiB`, el marco de la tabla de nivel 4 misma.
+
+[ejemplo al principio de este artículo]: #accediendo-a-las-tablas-de-paginas
+
+Al permitir que la CPU siga esta entrada en una traducción, no llega a una tabla de nivel 3, sino a la misma tabla de nivel 4 nuevamente. Esto es similar a una función recursiva que se llama a sí misma; por lo tanto, esta tabla se llama _tabla de páginas recursiva_. Lo importante es que la CPU asume que cada entrada en la tabla de nivel 4 apunta a una tabla de nivel 3, por lo que ahora trata la tabla de nivel 4 como una tabla de nivel 3. Esto funciona porque las tablas de todos los niveles tienen la misma estructura exacta en `x86_64`.
+
+Al seguir la entrada recursiva una o múltiples veces antes de comenzar la traducción real, podemos efectivamente acortar el número de niveles que la CPU recorre. Por ejemplo, si seguimos la entrada recursiva una vez y luego procedemos a la tabla de nivel 3, la CPU piensa que la tabla de nivel 3 es una tabla de nivel 2. Siguiendo, trata la tabla de nivel 2 como una tabla de nivel 1 y la tabla de nivel 1 como el marco mapeado. Esto significa que ahora podemos leer y escribir la tabla de nivel 1 porque la CPU piensa que es el marco mapeado. El gráfico a continuación ilustra los cinco pasos de traducción:
+
+
+
+De manera similar, podemos seguir la entrada recursiva dos veces antes de comenzar la traducción para reducir el número de niveles recorridos a dos:
+
+
+
+Sigamos paso a paso: Primero, la CPU sigue la entrada recursiva en la tabla de nivel 4 y piensa que llega a una tabla de nivel 3. Luego sigue la entrada recursiva nuevamente y piensa que llega a una tabla de nivel 2. Pero en realidad, todavía está en la tabla de nivel 4. Cuando la CPU ahora sigue una entrada diferente, aterriza en una tabla de nivel 3, pero piensa que ya está en una tabla de nivel 1. Así que mientras la siguiente entrada apunta a una tabla de nivel 2, la CPU piensa que apunta al marco mapeado, lo que nos permite leer y escribir la tabla de nivel 2.
+
+Acceder a las tablas de niveles 3 y 4 funciona de la misma manera. Para acceder a la tabla de nivel 3, seguimos la entrada recursiva tres veces, engañando a la CPU para que piense que ya está en una tabla de nivel 1. Luego seguimos otra entrada y llegamos a una tabla de nivel 3, que la CPU trata como un marco mapeado. Para acceder a la tabla de nivel 4 misma, simplemente seguimos la entrada recursiva cuatro veces hasta que la CPU trate la tabla de nivel 4 como el marco mapeado (en azul en el gráfico a continuación).
+
+
+
+Puede llevar un tiempo asimilar el concepto, pero funciona bastante bien en la práctica.
+
+En la siguiente sección, explicamos cómo construir direcciones virtuales para seguir la entrada recursiva una o múltiples veces. No utilizaremos la paginación recursiva para nuestra implementación, así que no necesitas leerlo para continuar con la publicación. Si te interesa, simplemente haz clic en _"Cálculo de Direcciones"_ para expandirlo.
+
+---
+
+
+