mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-16 22:37:49 +00:00
Compare commits
7 Commits
f28c52ee03
...
e323ea1b9d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e323ea1b9d | ||
|
|
725085abea | ||
|
|
98cf0d4850 | ||
|
|
b9b1ae8c08 | ||
|
|
cf91244de6 | ||
|
|
ffd0555a5a | ||
|
|
0e9f50d773 |
@@ -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)"
|
||||
@@ -236,21 +236,28 @@ Do you have a problem, want to share feedback, or discuss further ideas? Feel fr
|
||||
title = "Writing an OS in Rust"
|
||||
[languages.ar.translations]
|
||||
lang_name = "Arabic"
|
||||
toc = "Table of Contents"
|
||||
all_posts = "« All Posts"
|
||||
comments = "Comments"
|
||||
comments_notice = "Please leave your comments in English if possible."
|
||||
readmore = "read more »"
|
||||
not_translated = "(This post is not translated yet.)"
|
||||
translated_content = "Translated Content:"
|
||||
translated_content_notice = "This is a community translation of the <strong><a href=\"_original.permalink_\">_original.title_</a></strong> post. It might be incomplete, outdated or contain errors. Please report any issues!"
|
||||
translated_by = "Translation by"
|
||||
translation_contributors = "With contributions from"
|
||||
word_separator = "and"
|
||||
|
||||
# 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 <strong><a href=\"_original.permalink_\">_original.title_</a></strong>. 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 = """
|
||||
<h2>Support Me</h2>
|
||||
<p>Creating and maintaining this blog and the associated libraries is a lot of work, but I really enjoy doing it. By supporting me, you allow me to invest more time in new content, new features, and continuous maintenance. The best way to support me is to <a href="https://github.com/sponsors/phil-opp"><em>sponsor me on GitHub</em></a>. Thank you!</p>
|
||||
<h2>Apóyame</h2>
|
||||
<p>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 <a href=\"https://github.com/sponsors/phil-opp\"><em>patrocinarme en GitHub</em></a>. ¡Gracias!</p>
|
||||
"""
|
||||
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 <a href="https://www.rust-lang.org/policies/code-of-conduct">code of conduct</a>. This comment thread directly maps to a <a href="_discussion_url_"><em>discussion on GitHub</em></a>, so you can also comment there if you prefer.
|
||||
¿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 <a href=\"https://www.rust-lang.org/policies/code-of-conduct\">código de conducta</a> de Rust. Este hilo de comentarios se vincula directamente con una <a href=\"_discussion_url_\"><em>discusión en GitHub</em></a>, así que también puedes comentar allí si lo prefieres.
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
+++
|
||||
title = "Un Binario Rust Autónomo"
|
||||
weight = 1
|
||||
path = "freestanding-rust-binary"
|
||||
path = "es/freestanding-rust-binary"
|
||||
date = 2018-02-10
|
||||
|
||||
[extra]
|
||||
@@ -41,7 +41,7 @@ Esto implica que no podemos usar la mayor parte de la [biblioteca estándar de R
|
||||
|
||||
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](#summary)**.
|
||||
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`].
|
||||
@@ -125,11 +125,11 @@ El atributo `panic_handler` define la función que el compilador invoca cuando o
|
||||
[panic]: https://doc.rust-lang.org/stable/book/ch09-01-unrecoverable-errors-with-panic.html
|
||||
|
||||
```rust
|
||||
// in main.rs
|
||||
// en main.rs
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
/// This function is called on panic.
|
||||
/// Esta función se llama cuando ocurre un pánico.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
@@ -206,7 +206,7 @@ Para indicar al compilador de Rust que no queremos usar la cadena normal de punt
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
/// This function is called on panic.
|
||||
/// Esta función se llama cuando ocurre un pánico.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
@@ -426,7 +426,7 @@ Ahora nuestro programa debería compilarse exitosamente en macOS.
|
||||
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
|
||||
# in .cargo/config.toml
|
||||
# en .cargo/config.toml
|
||||
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-arg=-nostartfiles"]
|
||||
@@ -450,26 +450,26 @@ Si deseas crear un binario mínimo que se ejecute sobre un sistema operativo exi
|
||||
|
||||
</details>
|
||||
|
||||
## Resumen
|
||||
## Resumen {#resumen}
|
||||
|
||||
Un binario mínimo autónomo en Rust se ve así:
|
||||
|
||||
`src/main.rs`:
|
||||
|
||||
```rust
|
||||
#![no_std] // don't link the Rust standard library
|
||||
#![no_main] // disable all Rust-level entry points
|
||||
#![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] // don't mangle the name of this function
|
||||
#[no_mangle] // no modificar el nombre de esta función
|
||||
pub extern "C" fn _start() -> ! {
|
||||
// this function is the entry point, since the linker looks for a function
|
||||
// named `_start` by default
|
||||
// esta función es el punto de entrada, ya que el enlazador busca una función
|
||||
// llamada `_start` por defecto
|
||||
loop {}
|
||||
}
|
||||
|
||||
/// This function is called on panic.
|
||||
/// Esta función se llama cuando ocurre un pánico.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
@@ -484,13 +484,13 @@ name = "crate_name"
|
||||
version = "0.1.0"
|
||||
authors = ["Author Name <author@example.com>"]
|
||||
|
||||
# the profile used for `cargo build`
|
||||
# el perfil usado para `cargo build`
|
||||
[profile.dev]
|
||||
panic = "abort" # disable stack unwinding on panic
|
||||
panic = "abort" # deshabilitar el desenrollado de la pila en caso de pánico
|
||||
|
||||
# the profile used for `cargo build --release`
|
||||
# el perfil usado para `cargo build --release`
|
||||
[profile.release]
|
||||
panic = "abort" # disable stack unwinding on panic
|
||||
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`:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
+++
|
||||
title = "Un Kernel Mínimo en Rust"
|
||||
weight = 2
|
||||
path = "minimal-rust-kernel"
|
||||
path = "es/minimal-rust-kernel"
|
||||
date = 2018-02-10
|
||||
|
||||
[extra]
|
||||
@@ -23,7 +23,7 @@ Este blog se desarrolla abiertamente en [GitHub]. Si tienes problemas o pregunta
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## El Proceso de Arranque
|
||||
## 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
|
||||
@@ -88,7 +88,7 @@ Ahora que tenemos una idea general de cómo arranca una computadora, es momento
|
||||
|
||||
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
|
||||
### 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.
|
||||
@@ -219,21 +219,21 @@ Compilar para nuestro nuevo objetivo usará convenciones de Linux, ya que la opc
|
||||
```rust
|
||||
// src/main.rs
|
||||
|
||||
#![no_std] // don't link the Rust standard library
|
||||
#![no_main] // disable all Rust-level entry points
|
||||
#![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;
|
||||
|
||||
/// This function is called on panic.
|
||||
/// Esta función se llama cuando ocurre un pánico.
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[no_mangle] // don't mangle the name of this function
|
||||
#[no_mangle] // no modificar el nombre de esta función
|
||||
pub extern "C" fn _start() -> ! {
|
||||
// this function is the entry point, since the linker looks for a function
|
||||
// named `_start` by default
|
||||
// esta función es el punto de entrada, ya que el enlazador busca una función
|
||||
// llamada `_start` por defecto
|
||||
loop {}
|
||||
}
|
||||
```
|
||||
@@ -259,14 +259,14 @@ El problema es que la biblioteca `core` se distribuye junto con el compilador de
|
||||
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]: #installing-rust-nightly
|
||||
[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
|
||||
# in .cargo/config.toml
|
||||
# en .cargo/config.toml
|
||||
|
||||
[unstable]
|
||||
build-std = ["core", "compiler_builtins"]
|
||||
@@ -306,7 +306,7 @@ Afortunadamente, el crate `compiler_builtins` ya contiene implementaciones para
|
||||
[`build-std-features`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std-features
|
||||
|
||||
```toml
|
||||
# in .cargo/config.toml
|
||||
# en .cargo/config.toml
|
||||
|
||||
[unstable]
|
||||
build-std-features = ["compiler-builtins-mem"]
|
||||
@@ -329,7 +329,7 @@ Para evitar pasar el parámetro `--target` en cada invocación de `cargo build`,
|
||||
[configuración de cargo]: https://doc.rust-lang.org/cargo/reference/config.html
|
||||
|
||||
```toml
|
||||
# in .cargo/config.toml
|
||||
# en .cargo/config.toml
|
||||
|
||||
[build]
|
||||
target = "x86_64-blog_os.json"
|
||||
@@ -396,14 +396,14 @@ Ahora que tenemos un ejecutable que realiza algo perceptible, es momento de ejec
|
||||
|
||||
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]: #the-boot-process
|
||||
[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
|
||||
# in Cargo.toml
|
||||
# en Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
bootloader = "0.9"
|
||||
@@ -478,7 +478,7 @@ Después de escribir la imagen en la memoria USB, puedes ejecutarla en hardware
|
||||
Para facilitar la ejecución de nuestro kernel en QEMU, podemos configurar la clave de configuración `runner` para cargo:
|
||||
|
||||
```toml
|
||||
# in .cargo/config.toml
|
||||
# en .cargo/config.toml
|
||||
|
||||
[target.'cfg(target_os = "none")']
|
||||
runner = "bootimage runner"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
+++
|
||||
title = "Modo de Texto VGA"
|
||||
weight = 3
|
||||
path = "modo-texto-vga"
|
||||
path = "es/modo-texto-vga"
|
||||
date = 2018-02-26
|
||||
|
||||
[extra]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
+++
|
||||
title = "Pruebas"
|
||||
weight = 4
|
||||
path = "testing"
|
||||
path = "es/testing"
|
||||
date = 2019-04-27
|
||||
|
||||
[extra]
|
||||
@@ -67,7 +67,7 @@ La desventaja en comparación con el marco de prueba predeterminado es que mucha
|
||||
Para implementar un marco de prueba personalizado para nuestro núcleo, añadimos lo siguiente a nuestro `main.rs`:
|
||||
|
||||
```rust
|
||||
// in src/main.rs
|
||||
// en src/main.rs
|
||||
|
||||
#![feature(custom_test_frameworks)]
|
||||
#![test_runner(crate::test_runner)]
|
||||
@@ -98,7 +98,7 @@ Cuando ejecutamos `cargo test` ahora, vemos que ahora tiene éxito (si no lo tie
|
||||
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
|
||||
// in src/main.rs
|
||||
// en src/main.rs
|
||||
|
||||
#![reexport_test_harness_main = "test_main"]
|
||||
|
||||
@@ -118,7 +118,7 @@ Establecemos el nombre de la función de entrada del marco de prueba en `test_ma
|
||||
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
|
||||
// in src/main.rs
|
||||
// en src/main.rs
|
||||
|
||||
#[test_case]
|
||||
fn trivial_assertion() {
|
||||
@@ -146,7 +146,7 @@ En este momento, tenemos un bucle infinito al final de nuestra función `_start`
|
||||
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
|
||||
# in Cargo.toml
|
||||
# en Cargo.toml
|
||||
|
||||
[package.metadata.bootimage]
|
||||
test-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04"]
|
||||
@@ -179,7 +179,7 @@ En lugar de invocar manualmente las instrucciones de ensamblaje `in` y `out`, ut
|
||||
[`x86_64`]: https://docs.rs/x86_64/0.14.2/x86_64/
|
||||
|
||||
```toml
|
||||
# in Cargo.toml
|
||||
# en Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
x86_64 = "0.14.2"
|
||||
@@ -190,7 +190,7 @@ Ahora podemos usar el tipo [`Port`] proporcionado por la crate para crear una fu
|
||||
[`Port`]: https://docs.rs/x86_64/0.14.2/x86_64/instructions/port/struct.Port.html
|
||||
|
||||
```rust
|
||||
// in src/main.rs
|
||||
// en src/main.rs
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
@@ -216,7 +216,7 @@ Para especificar el código de salida, creamos un enum `QemuExitCode`. La idea e
|
||||
Ahora podemos actualizar nuestro `test_runner` para salir de QEMU después de que se hayan ejecutado todas las pruebas:
|
||||
|
||||
```rust
|
||||
// in src/main.rs
|
||||
// en src/main.rs
|
||||
|
||||
fn test_runner(tests: &[&dyn Fn()]) {
|
||||
println!("Ejecutando {} pruebas", tests.len());
|
||||
@@ -250,7 +250,7 @@ El problema es que `cargo test` considera todos los códigos de error que no sea
|
||||
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
|
||||
# in Cargo.toml
|
||||
# en Cargo.toml
|
||||
|
||||
[package.metadata.bootimage]
|
||||
test-args = […]
|
||||
@@ -269,20 +269,20 @@ Para ver la salida de las pruebas en la consola, necesitamos enviar los datos de
|
||||
|
||||
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://es.wikipedia.org/wiki/Puerto_serial
|
||||
[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://es.wikipedia.org/wiki/Transmisor-receptor_asíncrono_universal
|
||||
[muchos modelos de UART]: https://es.wikipedia.org/wiki/Transmisor-receptor_asíncrono_universal#Modelos_UART
|
||||
[UART 16550]: https://es.wikipedia.org/wiki/16550_UART
|
||||
[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
|
||||
# in Cargo.toml
|
||||
# en Cargo.toml
|
||||
|
||||
[dependencies]
|
||||
uart_16550 = "0.2.0"
|
||||
@@ -291,13 +291,13 @@ 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
|
||||
// in src/main.rs
|
||||
// en src/main.rs
|
||||
|
||||
mod serial;
|
||||
```
|
||||
|
||||
```rust
|
||||
// in src/serial.rs
|
||||
// en src/serial.rs
|
||||
|
||||
use uart_16550::SerialPort;
|
||||
use spin::Mutex;
|
||||
@@ -321,7 +321,7 @@ Al igual que el dispositivo `isa-debug-exit`, el UART se programa usando E/S de
|
||||
Para hacer que el puerto serial sea fácilmente utilizable, añadimos los macros `serial_print!` y `serial_println!`:
|
||||
|
||||
```rust
|
||||
// in src/serial.rs
|
||||
// en src/serial.rs
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn _print(args: ::core::fmt::Arguments) {
|
||||
@@ -354,7 +354,7 @@ La implementación es muy similar a la implementación de nuestros macros `print
|
||||
Ahora podemos imprimir en la interfaz serial en lugar de en el buffer de texto VGA en nuestro código de prueba:
|
||||
|
||||
```rust
|
||||
// in src/main.rs
|
||||
// en src/main.rs
|
||||
|
||||
#[cfg(test)]
|
||||
fn test_runner(tests: &[&dyn Fn()]) {
|
||||
@@ -377,7 +377,7 @@ Ten en cuenta que el macro `serial_println` vive directamente en el espacio de n
|
||||
Para ver la salida serial de QEMU, necesitamos usar el argumento `-serial` para redirigir la salida a stdout:
|
||||
|
||||
```toml
|
||||
# in Cargo.toml
|
||||
# en Cargo.toml
|
||||
|
||||
[package.metadata.bootimage]
|
||||
test-args = [
|
||||
@@ -414,7 +414,7 @@ Para salir de QEMU con un mensaje de error en un pánico, podemos usar [compilac
|
||||
[compilación condicional]: https://doc.rust-lang.org/1.30.0/book/first-edition/conditional-compilation.html
|
||||
|
||||
```rust
|
||||
// in src/main.rs
|
||||
// en src/main.rs
|
||||
|
||||
// nuestro manejador de pánico existente
|
||||
#[cfg(not(test))] // nuevo atributo
|
||||
@@ -463,7 +463,7 @@ Dado que ahora vemos toda la salida de prueba en la consola, ya no necesitamos l
|
||||
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
|
||||
# in Cargo.toml
|
||||
# en Cargo.toml
|
||||
|
||||
[package.metadata.bootimage]
|
||||
test-args = [
|
||||
@@ -492,7 +492,7 @@ Puedes intentarlo tú mismo añadiendo una instrucción `loop {}` en la prueba `
|
||||
[bootimage config]: https://github.com/rust-osdev/bootimage#configuration
|
||||
|
||||
```toml
|
||||
# in Cargo.toml
|
||||
# en Cargo.toml
|
||||
|
||||
[package.metadata.bootimage]
|
||||
test-timeout = 300 # (en segundos)
|
||||
@@ -516,7 +516,7 @@ fn trivial_assertion() {
|
||||
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
|
||||
// in src/main.rs
|
||||
// en src/main.rs
|
||||
|
||||
pub trait Testable {
|
||||
fn run(&self) -> ();
|
||||
@@ -528,7 +528,7 @@ El truco ahora es implementar este trait para todos los tipos `T` que implementa
|
||||
[`Fn()` trait]: https://doc.rust-lang.org/stable/core/ops/trait.Fn.html
|
||||
|
||||
```rust
|
||||
// in src/main.rs
|
||||
// en src/main.rs
|
||||
|
||||
impl<T> Testable for T
|
||||
where
|
||||
@@ -545,14 +545,14 @@ where
|
||||
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://es.wikipedia.org/wiki/Tecla_tabulador#Caracteres_de_tabulación
|
||||
[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
|
||||
// in src/main.rs
|
||||
// en src/main.rs
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn test_runner(tests: &[&dyn Testable]) { // nuevo
|
||||
@@ -569,7 +569,7 @@ Los únicos dos cambios son el tipo del argumento `tests` de `&[&dyn Fn()]` a `&
|
||||
Ahora podemos eliminar las declaraciones de impresión de nuestra prueba `trivial_assertion` ya que ahora se imprimen automáticamente:
|
||||
|
||||
```rust
|
||||
// in src/main.rs
|
||||
// en src/main.rs
|
||||
|
||||
#[test_case]
|
||||
fn trivial_assertion() {
|
||||
@@ -591,7 +591,7 @@ El nombre de la función ahora incluye la ruta completa a la función, que es ú
|
||||
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
|
||||
// in src/vga_buffer.rs
|
||||
// en src/vga_buffer.rs
|
||||
|
||||
#[test_case]
|
||||
fn test_println_simple() {
|
||||
@@ -604,7 +604,7 @@ La prueba simplemente imprime algo en el buffer VGA. Si termina sin provocar un
|
||||
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
|
||||
// in src/vga_buffer.rs
|
||||
// en src/vga_buffer.rs
|
||||
|
||||
#[test_case]
|
||||
fn test_println_many() {
|
||||
@@ -617,7 +617,7 @@ fn 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
|
||||
// in src/vga_buffer.rs
|
||||
// en src/vga_buffer.rs
|
||||
|
||||
#[test_case]
|
||||
fn test_println_output() {
|
||||
@@ -649,7 +649,7 @@ La convención para las [pruebas de integración] en Rust es ponerlas en un dire
|
||||
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
|
||||
// in tests/basic_boot.rs
|
||||
// en tests/basic_boot.rs
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
@@ -700,7 +700,7 @@ Al igual que `main.rs`, `lib.rs` es un archivo especial que es automáticamente
|
||||
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
|
||||
// in src/lib.rs
|
||||
// en src/lib.rs
|
||||
|
||||
#![cfg_attr(test, no_main)]
|
||||
#![feature(custom_test_frameworks)]
|
||||
@@ -763,7 +763,7 @@ Dado que nuestra `lib.rs` se prueba independientemente de `main.rs`, necesitamos
|
||||
También movemos el enum `QemuExitCode` y la función `exit_qemu` y los hacemos públicos:
|
||||
|
||||
```rust
|
||||
// in src/lib.rs
|
||||
// en src/lib.rs
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
@@ -785,7 +785,7 @@ pub fn exit_qemu(exit_code: QemuExitCode) {
|
||||
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
|
||||
// in src/lib.rs
|
||||
// en src/lib.rs
|
||||
|
||||
pub mod serial;
|
||||
pub mod vga_buffer;
|
||||
@@ -796,7 +796,7 @@ Hacemos que los módulos sean públicos para que sean utilizables fuera de nuest
|
||||
Ahora podemos actualizar nuestro `main.rs` para usar la biblioteca:
|
||||
|
||||
```rust
|
||||
// in src/main.rs
|
||||
// en src/main.rs
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
@@ -841,7 +841,7 @@ En este punto, `cargo run` y `cargo test` deberían funcionar nuevamente. Por su
|
||||
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
|
||||
// in tests/basic_boot.rs
|
||||
// en tests/basic_boot.rs
|
||||
|
||||
#![test_runner(blog_os::test_runner)]
|
||||
|
||||
@@ -858,7 +858,7 @@ Ahora `cargo test` sale normalmente nuevamente. Cuando lo ejecutas, verás que c
|
||||
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
|
||||
// in tests/basic_boot.rs
|
||||
// en tests/basic_boot.rs
|
||||
|
||||
use blog_os::println;
|
||||
|
||||
@@ -893,7 +893,7 @@ El marco de pruebas de la biblioteca estándar admite un atributo [`#[should_pan
|
||||
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
|
||||
// in tests/should_panic.rs
|
||||
// en tests/should_panic.rs
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
@@ -912,7 +912,7 @@ fn panic(_info: &PanicInfo) -> ! {
|
||||
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
|
||||
// in tests/should_panic.rs
|
||||
// en tests/should_panic.rs
|
||||
|
||||
#![feature(custom_test_frameworks)]
|
||||
#![test_runner(test_runner)]
|
||||
@@ -941,7 +941,7 @@ En lugar de reutilizar el `test_runner` de `lib.rs`, la prueba define su propia
|
||||
Ahora podemos crear una prueba que debería fallar:
|
||||
|
||||
```rust
|
||||
// in tests/should_panic.rs
|
||||
// en tests/should_panic.rs
|
||||
|
||||
use blog_os::serial_print;
|
||||
|
||||
@@ -967,7 +967,7 @@ La clave para esto es deshabilitar la bandera `harness` para la prueba en el `Ca
|
||||
Deshabilitemos la bandera `harness` para nuestra prueba `should_panic`:
|
||||
|
||||
```toml
|
||||
# in Cargo.toml
|
||||
# en Cargo.toml
|
||||
|
||||
[[test]]
|
||||
name = "should_panic"
|
||||
@@ -977,7 +977,7 @@ 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
|
||||
// in tests/should_panic.rs
|
||||
// en tests/should_panic.rs
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
+++
|
||||
title = "Excepciones de CPU"
|
||||
weight = 5
|
||||
path = "cpu-exceptions"
|
||||
path = "es/cpu-exceptions"
|
||||
date = 2018-06-17
|
||||
|
||||
[extra]
|
||||
@@ -199,7 +199,7 @@ En el crate `x86_64`, el marco de pila de interrupción está representado por l
|
||||
|
||||
[`InterruptStackFrame`]: https://docs.rs/x86_64/0.14.2/x86_64/structures/idt/struct.InterruptStackFrame.html
|
||||
|
||||
### Detrás de las escenas
|
||||
### 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.
|
||||
@@ -210,7 +210,7 @@ La convención de llamada `x86-interrupt` es una potente abstracción que oculta
|
||||
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]: #too-much-magic
|
||||
[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`:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
+++
|
||||
title = "Excepciones de Doble Fallo"
|
||||
weight = 6
|
||||
path = "double-fault-exceptions"
|
||||
path = "es/double-fault-exceptions"
|
||||
date = 2018-06-18
|
||||
|
||||
[extra]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
+++
|
||||
title = "Interrupciones de Hardware"
|
||||
weight = 7
|
||||
path = "hardware-interrupts"
|
||||
path = "es/hardware-interrupts"
|
||||
date = 2018-10-22
|
||||
|
||||
[extra]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
+++
|
||||
title = "Introducción a la Paginación"
|
||||
weight = 8
|
||||
path = "paging-introduction"
|
||||
path = "es/paging-introduction"
|
||||
date = 2019-01-14
|
||||
|
||||
[extra]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
+++
|
||||
title = "Implementación de Paginación"
|
||||
weight = 9
|
||||
path = "implementacion-de-paginacion"
|
||||
path = "es/implementacion-de-paginacion"
|
||||
date = 2019-03-14
|
||||
|
||||
[extra]
|
||||
@@ -33,7 +33,7 @@ La publicación terminó con el problema de que [no podemos acceder a las tablas
|
||||
|
||||
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 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:
|
||||
|
||||
@@ -118,7 +118,7 @@ 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]: #accessing-page-tables
|
||||
[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`.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
+++
|
||||
title = "Asignación en el Heap"
|
||||
weight = 10
|
||||
path = "heap-allocation"
|
||||
path = "es/heap-allocation"
|
||||
date = 2019-06-26
|
||||
|
||||
[extra]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
+++
|
||||
title = "Diseños de Allocadores"
|
||||
weight = 11
|
||||
path = "allocator-designs"
|
||||
path = "es/allocator-designs"
|
||||
date = 2020-01-20
|
||||
|
||||
[extra]
|
||||
@@ -38,7 +38,7 @@ La responsabilidad de un allocador es gestionar la memoria heap disponible. Nece
|
||||
Aparte de la corrección, hay muchos objetivos secundarios de diseño. Por ejemplo, el allocador debería utilizar de manera efectiva la memoria disponible y mantener baja la [_fragmentación_]. Además, debería funcionar bien para aplicaciones concurrentes y escalar a cualquier número de procesadores. Para un rendimiento máximo, podría incluso optimizar el diseño de la memoria con respecto a los cachés de CPU para mejorar la [localidad de caché] y evitar [compartición falsa].
|
||||
|
||||
[localidad de caché]: https://www.geeksforgeeks.org/locality-of-reference-and-cache-operation-in-cache-memory/
|
||||
[_fragmentación_]: https://es.wikipedia.org/wiki/Fragmentaci%C3%B3n_(computaci%C3%B3n)
|
||||
[_fragmentación_]: https://en.wikipedia.org/wiki/Fragmentation_(computing)
|
||||
[compartición falsa]: https://mechanical-sympathy.blogspot.de/2011/07/false-sharing.html
|
||||
|
||||
Estos requisitos pueden hacer que los buenos allocadores sean muy complejos. Por ejemplo, [jemalloc] tiene más de 30.000 líneas de código. Esta complejidad a menudo es indeseable en el código del kernel, donde un solo error puede conducir a vulnerabilidades de seguridad graves. Afortunadamente, los patrones de asignación del código del kernel son a menudo mucho más simples en comparación con el código de espacio de usuario, de manera que diseños de allocadores relativamente simples suelen ser suficientes.
|
||||
@@ -194,7 +194,7 @@ Afortunadamente, hay una manera de obtener una referencia `&mut self` de una ref
|
||||
[mutabilidad interna]: https://doc.rust-lang.org/book/ch15-05-interior-mutability.html
|
||||
[vga-mutex]: @/edition-2/posts/03-vga-text-buffer/index.md#spinlocks
|
||||
[`spin::Mutex`]: https://docs.rs/spin/0.5.0/spin/struct.Mutex.html
|
||||
[exclusión mutua]: https://es.wikipedia.org/wiki/Exclusi%C3%B3n_mutua
|
||||
[exclusión mutua]: https://en.wikipedia.org/wiki/Mutual_exclusion
|
||||
|
||||
#### Un Tipo Wrapper `Locked`
|
||||
|
||||
@@ -314,7 +314,7 @@ fn align_up(addr: usize, align: usize) -> usize {
|
||||
|
||||
La función primero calcula el [resto] de la división de `addr` entre `align`. Si el resto es `0`, la dirección ya está alineada con la alineación dada. De lo contrario, alineamos la dirección restando el resto (para que el nuevo resto sea 0) y luego sumando la alineación (para que la dirección no se vuelva más pequeña que la dirección original).
|
||||
|
||||
[resto]: https://es.wikipedia.org/wiki/Divisi%C3%B3n_euclidiana
|
||||
[resto]: https://en.wikipedia.org/wiki/Euclidean_division
|
||||
|
||||
Ten en cuenta que esta no es la forma más eficiente de implementar esta función. Una implementación mucho más rápida se ve así:
|
||||
|
||||
@@ -330,16 +330,16 @@ fn align_up(addr: usize, align: usize) -> usize {
|
||||
Este método requiere que `align` sea una potencia de dos, lo que puede ser garantizado utilizando el trait `GlobalAlloc` (y su parámetro [`Layout`]). Esto hace posible crear una [máscara de bits] para alinear la dirección de manera muy eficiente. Para entender cómo funciona, repasemos el proceso paso a paso, comenzando por el lado derecho:
|
||||
|
||||
[`Layout`]: https://doc.rust-lang.org/alloc/alloc/struct.Layout.html
|
||||
[máscara de bits]: https://es.wikipedia.org/wiki/M%C3%A1scara_(computaci%C3%B3n)
|
||||
[máscara de bits]: https://en.wikipedia.org/wiki/Mask_(computing)
|
||||
|
||||
- Dado que `align` es una potencia de dos, su [representación binaria] tiene solo un solo bit establecido (por ejemplo, `0b000100000`). Esto significa que `align - 1` tiene todos los bits inferiores establecidos (por ejemplo, `0b00011111`).
|
||||
- Al crear el [NO bit a bit] a través del operador `!`, obtenemos un número que tiene todos los bits establecidos excepto los bits inferiores a `align` (por ejemplo, `0b…111111111100000`).
|
||||
- Al realizar un [Y bit a bit] en una dirección y `!(align - 1)`, alineamos la dirección _hacia abajo_. Esto funciona borrando todos los bits que están por debajo de `align`.
|
||||
- Dado que queremos alinear hacia arriba en lugar de hacia abajo, incrementamos `addr` en `align - 1` antes de realizar el Y bit a bit. De esta manera, las direcciones ya alineadas permanecen iguales mientras que las direcciones no alineadas se redondean al siguiente límite de alineación.
|
||||
|
||||
[representación binaria]: https://es.wikipedia.org/wiki/N%C3%BAmero_binario#Representaci%C3%B3n
|
||||
[NO bit a bit]: https://es.wikipedia.org/wiki/Operaci%C3%B3n_bit_a_bit#NO
|
||||
[Y bit a bit]: https://es.wikipedia.org/wiki/Operaci%C3%B3n_bit_a_bit#Y
|
||||
[representación binaria]: https://en.wikipedia.org/wiki/Binary_number#Representation
|
||||
[NO bit a bit]: https://en.wikipedia.org/wiki/Bitwise_operation#NOT
|
||||
[Y bit a bit]: https://en.wikipedia.org/wiki/Bitwise_operation#AND
|
||||
|
||||
Qué variante elijas depende de ti. Ambas calculan el mismo resultado, solo que utilizan diferentes métodos.
|
||||
|
||||
@@ -455,7 +455,7 @@ El enfoque de implementación más común es construir una lista enlazada simple
|
||||
|
||||
Cada nodo de la lista contiene dos campos: el tamaño de la región de memoria y un puntero a la siguiente región de memoria no utilizada. Con este enfoque, solo necesitamos un puntero a la primera región no utilizada (llamada `head`) para llevar un registro de todas las regiones no utilizadas, independientemente de su número. La estructura de datos resultante se denomina a menudo [_lista libre_].
|
||||
|
||||
[_lista libre_]: https://es.wikipedia.org/wiki/Free_list
|
||||
[_lista libre_]: https://en.wikipedia.org/wiki/Free_list
|
||||
|
||||
Como puedes adivinar por el nombre, esta es la técnica que utiliza el crate `linked_list_allocator`. Los allocadores que utilizan esta técnica a menudo se llaman también _allocadores de piscina_.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
+++
|
||||
title = "Async/Aait"
|
||||
weight = 12
|
||||
path = "async-await"
|
||||
path = "es/async-await"
|
||||
date = 2020-03-27
|
||||
|
||||
[extra]
|
||||
@@ -906,7 +906,7 @@ La función `from_raw` es insegura porque se puede producir un comportamiento in
|
||||
|
||||
El tipo [`RawWaker`] requiere que el programador defina explícitamente un [_tabla de métodos virtuales_] (_vtable_) que especifica las funciones que deben ser llamadas cuando `RawWaker` se clona, se despierta o se elimina. La disposición de esta vtable es definida por el tipo [`RawWakerVTable`]. Cada función recibe un argumento `*const ()`, que es un puntero _sin tipo_ a algún valor. La razón por la que se utiliza un puntero `*const ()` en lugar de una referencia apropiada es que el tipo `RawWaker` debería ser no genérico pero aún así soportar tipos arbitrarios. El puntero se proporciona colocando `data` en la llamada a [`RawWaker::new`], que simplemente inicializa un `RawWaker`. Luego, el `Waker` utiliza este `RawWaker` para llamar a las funciones de la vtable con `data`.
|
||||
|
||||
[_tabla de métodos virtuales_]: https://es.wikipedia.org/wiki/Tabla_de_metodos_virtuales
|
||||
[_tabla de métodos virtuales_]: https://en.wikipedia.org/wiki/Virtual_method_table
|
||||
[`RawWakerVTable`]: https://doc.rust-lang.org/stable/core/task/struct.RawWakerVTable.html
|
||||
[`RawWaker::new`]: https://doc.rust-lang.org/stable/core/task/struct.RawWaker.html#method.new
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
extend-exclude = [
|
||||
"*.svg",
|
||||
"*.fr.md",
|
||||
"*.es.md",
|
||||
]
|
||||
|
||||
[default.extend-words]
|
||||
|
||||
Reference in New Issue
Block a user