mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-20 16:07:49 +00:00
Compare commits
19 Commits
50802c8332
...
edition-3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd3550ea87 | ||
|
|
ecb60ec326 | ||
|
|
8a1267477a | ||
|
|
ce01059620 | ||
|
|
4d0c3ac188 | ||
|
|
d565cd125b | ||
|
|
ca86085360 | ||
|
|
5f3d38884c | ||
|
|
f557d1c698 | ||
|
|
0c248d027e | ||
|
|
2cf0675a2d | ||
|
|
916ad36e78 | ||
|
|
3c2e91fa4e | ||
|
|
c9683a2cd9 | ||
|
|
514736b1d8 | ||
|
|
647b509971 | ||
|
|
1118350b16 | ||
|
|
fb096a7484 | ||
|
|
8a41fd65bf |
@@ -56,7 +56,7 @@ UEFI, in contrast, is more modern and has much more features, but also more comp
|
|||||||
### BIOS
|
### BIOS
|
||||||
|
|
||||||
Almost all x86 systems have support for BIOS booting, including most UEFI-based machines that support an emulated BIOS.
|
Almost all x86 systems have support for BIOS booting, including most UEFI-based machines that support an emulated BIOS.
|
||||||
This is great, because you can use the same boot logic across all machines from the last centuries.
|
This is great, because you can use the same boot logic across all machines from the last century.
|
||||||
The drawback is that the standard is very old, for example the CPU is put into a 16-bit compatibility mode called [real mode] before booting so that archaic bootloaders from the 1980s would still work.
|
The drawback is that the standard is very old, for example the CPU is put into a 16-bit compatibility mode called [real mode] before booting so that archaic bootloaders from the 1980s would still work.
|
||||||
Also, BIOS-compatibility will be slowly removed on newer UEFI machines over the next years (see below).
|
Also, BIOS-compatibility will be slowly removed on newer UEFI machines over the next years (see below).
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ This structure has the following general format:
|
|||||||
| 446 | partition entry 1 | 16 |
|
| 446 | partition entry 1 | 16 |
|
||||||
| 462 | partition entry 2 | 16 |
|
| 462 | partition entry 2 | 16 |
|
||||||
| 478 | partition entry 3 | 16 |
|
| 478 | partition entry 3 | 16 |
|
||||||
| 444 | partition entry 4 | 16 |
|
| 494 | partition entry 4 | 16 |
|
||||||
| 510 | boot signature | 2 |
|
| 510 | boot signature | 2 |
|
||||||
|
|
||||||
The bootstrap code is commonly called the _bootloader_ and responsible for loading and starting the operating system kernel.
|
The bootstrap code is commonly called the _bootloader_ and responsible for loading and starting the operating system kernel.
|
||||||
@@ -491,7 +491,7 @@ profile = "default"
|
|||||||
targets = ["x86_64-unknown-none"]
|
targets = ["x86_64-unknown-none"]
|
||||||
```
|
```
|
||||||
|
|
||||||
The `channel` field specifies which [toolchain`] to use.
|
The `channel` field specifies which [`toolchain`] to use.
|
||||||
In our case, we want to use the latest nightly compiler.
|
In our case, we want to use the latest nightly compiler.
|
||||||
We could also specify a specific nightly here, e.g. `nightly-2023-04-30`, which can be useful when there is some breakage in the newest nightly.
|
We could also specify a specific nightly here, e.g. `nightly-2023-04-30`, which can be useful when there is some breakage in the newest nightly.
|
||||||
In the `targets` list, we can specify additional targets that we want to compile to.
|
In the `targets` list, we can specify additional targets that we want to compile to.
|
||||||
@@ -706,7 +706,7 @@ We then use the the `create_uefi_image` and `create_bios_image` methods to creat
|
|||||||
[requires build scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script
|
[requires build scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script
|
||||||
[`join`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.join
|
[`join`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.join
|
||||||
|
|
||||||
We can now use use a simple `cargo build` to cross-compile our kernel, build the bootloader, and combine them to create a bootable disk image:
|
We can now use a simple `cargo build` to cross-compile our kernel, build the bootloader, and combine them to create a bootable disk image:
|
||||||
|
|
||||||
```
|
```
|
||||||
❯ cargo build
|
❯ cargo build
|
||||||
@@ -1011,27 +1011,35 @@ Now we can create our `qemu-uefi` executable at `src/bin/qemu-uefi.rs`:
|
|||||||
|
|
||||||
```rust ,hl_lines=3-15
|
```rust ,hl_lines=3-15
|
||||||
// src/bin/qemu-uefi.rs
|
// src/bin/qemu-uefi.rs
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env, process::{self, Command}
|
||||||
process::{self, Command},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use ovmf_prebuilt::{Arch, FileType, Prebuilt, Source};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let prebuilt =
|
||||||
|
Prebuilt::fetch(Source::LATEST, "target/ovmf").unwrap();
|
||||||
|
let ovmf_code = prebuilt.get_file(Arch::X64, FileType::Code);
|
||||||
|
let ovmf_vars = prebuilt.get_file(Arch::X64, FileType::Vars);
|
||||||
let mut qemu = Command::new("qemu-system-x86_64");
|
let mut qemu = Command::new("qemu-system-x86_64");
|
||||||
qemu.arg("-drive");
|
qemu.args([
|
||||||
qemu.arg(format!("format=raw,file={}", env!("UEFI_IMAGE")));
|
"-drive",
|
||||||
qemu.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi());
|
&format!("format=raw,if=pflash,readonly=on,file={}", ovmf_code.display()),
|
||||||
|
"-drive",
|
||||||
|
&format!("format=raw,if=pflash,file={}", ovmf_vars.display()),
|
||||||
|
"-drive",
|
||||||
|
&format!("format=raw,file={}", env!("UEFI_IMAGE")),
|
||||||
|
]);
|
||||||
let exit_status = qemu.status().unwrap();
|
let exit_status = qemu.status().unwrap();
|
||||||
process::exit(exit_status.code().unwrap_or(-1));
|
process::exit(exit_status.code().unwrap_or(-1));
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
It's very similar to our `qemu-bios` executable.
|
It's very similar to our `qemu-bios` executable.
|
||||||
The only two differences are that it passes an additional `-bios` argument and that it uses the `UEFI_IMAGE` instead of the `BIOS_IMAGE`.
|
The only two differences are that it passes two additional `-drive if=pflash,..` arguments to load UEFI firmware (`OVMF_CODE.fd`) and writable NVRAM (`OVMF_VARS.fd`), and that it uses the `UEFI_IMAGE` instead of the `BIOS_IMAGE`.
|
||||||
Using a quick `cargo run --bin qemu-uefi`, we can confirm that it works as intended.
|
Using a quick `cargo run --bin qemu-uefi`, we can confirm that it works as intended.
|
||||||
|
|
||||||
|
|
||||||
### Screen Output
|
### Screen Output
|
||||||
|
|
||||||
While we see some screen output from the bootloader, our kernel still does nothing.
|
While we see some screen output from the bootloader, our kernel still does nothing.
|
||||||
|
|||||||
@@ -144,94 +144,144 @@ We start by creating a new `framebuffer` [module]:
|
|||||||
mod framebuffer;
|
mod framebuffer;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
In the new module, we create basic structs for representing pixel positions and colors:
|
||||||
|
|
||||||
```rust ,hl_lines=3-16
|
```rust ,hl_lines=3-16
|
||||||
// in new kernel/src/framebuffer.rs file
|
// in new kernel/src/framebuffer.rs file
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
pub x: usize,
|
pub x: usize,
|
||||||
pub y: usize,
|
pub y: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct Color {
|
pub struct Color {
|
||||||
pub red: u8,
|
pub red: u8,
|
||||||
pub green: u8,
|
pub green: u8,
|
||||||
pub blue: u8,
|
pub blue: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
TODO explain
|
By marking the structs and their fields as `pub`, we make them accessible from the parent `kernel` module.
|
||||||
|
We use the `#[derive]` attribute to implement the [`Debug`], [`Clone`], [`Copy`], [`PartialEq`], and [`Eq`] traits of Rust's core library.
|
||||||
|
These traits allow us to duplicate, compare, and print the structs.
|
||||||
|
|
||||||
```rust ,hl_lines=4-12 14-34
|
[`Debug`]: https://doc.rust-lang.org/stable/core/fmt/trait.Debug.html
|
||||||
|
[`Clone`]: https://doc.rust-lang.org/stable/core/clone/trait.Clone.html
|
||||||
|
[`Copy`]: https://doc.rust-lang.org/stable/core/marker/trait.Copy.html
|
||||||
|
[`PartialEq`]: https://doc.rust-lang.org/stable/core/cmp/trait.PartialEq.html
|
||||||
|
[`Eq`]: https://doc.rust-lang.org/stable/core/cmp/trait.Eq.html
|
||||||
|
|
||||||
|
Next, we create a function for setting a specific pixel in the framebuffer to a given color:
|
||||||
|
|
||||||
|
```rust ,hl_lines=3 5-39
|
||||||
// in new kernel/src/framebuffer.rs file
|
// in new kernel/src/framebuffer.rs file
|
||||||
|
|
||||||
|
use bootloader_api::info::{FrameBuffer, PixelFormat};
|
||||||
|
|
||||||
pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) {
|
pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) {
|
||||||
|
let info = framebuffer.info();
|
||||||
|
|
||||||
// calculate offset to first byte of pixel
|
// calculate offset to first byte of pixel
|
||||||
let byte_offset = {
|
let byte_offset = {
|
||||||
// use stride to calculate pixel offset of target line
|
// use stride to calculate pixel offset of target line
|
||||||
let line_offset = position.y * framebuffer.info.stride;
|
let line_offset = position.y * info.stride;
|
||||||
// add x position to get the absolute pixel offset in buffer
|
// add x position to get the absolute pixel offset in buffer
|
||||||
let pixel_offset = line_offset + position.x;
|
let pixel_offset = line_offset + position.x;
|
||||||
// convert to byte offset
|
// convert to byte offset
|
||||||
pixel_offset * framebuffer.bytes_per_pixel
|
pixel_offset * info.bytes_per_pixel
|
||||||
};
|
};
|
||||||
|
|
||||||
/// set pixel based on color format
|
// set pixel based on color format
|
||||||
match framebuffer.info.pixel_format {
|
let pixel_buffer = &mut framebuffer.buffer_mut()[byte_offset..];
|
||||||
|
match info.pixel_format {
|
||||||
PixelFormat::Rgb => {
|
PixelFormat::Rgb => {
|
||||||
let bytes = &mut framebuffer.buffer_mut()[byte_offset..][..3];
|
pixel_buffer[0] = color.red;
|
||||||
bytes[0] = color.red;
|
pixel_buffer[1] = color.green;
|
||||||
bytes[1] = color.green;
|
pixel_buffer[2] = color.blue;
|
||||||
bytes[2] = color.blue;
|
|
||||||
}
|
}
|
||||||
PixelFormat::Bgr => {
|
PixelFormat::Bgr => {
|
||||||
let bytes = &mut framebuffer.buffer_mut()[byte_offset..][..3];
|
pixel_buffer[0] = color.blue;
|
||||||
bytes[0] = color.blue;
|
pixel_buffer[1] = color.green;
|
||||||
bytes[1] = color.green;
|
pixel_buffer[2] = color.red;
|
||||||
bytes[2] = color.red;
|
|
||||||
}
|
}
|
||||||
PixelFormat::U8 => {
|
PixelFormat::U8 => {
|
||||||
// use a simple average-based grayscale transform
|
// use a simple average-based grayscale transform
|
||||||
let gray = color.red / 3 + color.green / 3 + color.blue / 3;
|
let gray = color.red / 3 + color.green / 3 + color.blue / 3;
|
||||||
framebuffer.buffer_mut()[byte_offset] = gray;
|
pixel_buffer[0] = gray;
|
||||||
}
|
}
|
||||||
other => panic!("unknown pixel format {other:?}"),
|
other => panic!("unknown pixel format {other:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
TODO explain
|
The first step is to calculate the byte offset within the framebuffer slice at which the pixel starts.
|
||||||
|
For this, we first calculate the pixel offset of the line by multiplying the `y` position with the stride of the framebuffer, i.e. its line width plus the line padding.
|
||||||
|
We then add the `x` position to get the absolute index of the pixel.
|
||||||
|
As the framebuffer slice is a byte slice, we need to transform the pixel index to a byte offset by multiplying it with the number of `bytes_per_pixel`.
|
||||||
|
|
||||||
Let's try our new function:
|
[`FrameBuffer::buffer_mut`]: https://docs.rs/bootloader_api/0.11.5/bootloader_api/info/struct.FrameBuffer.html#method.buffer_mut
|
||||||
|
|
||||||
```rust ,hl_lines=5-7
|
The second step is to set the pixel to the desired color.
|
||||||
|
We first use the [`FrameBuffer::buffer_mut`] method to get access to the actual bytes of the framebuffer in form of a slice.
|
||||||
|
Then, we use the slicing operator `[byte_offset..]` to get a sub-slice starting at the `byte_offset` of the target pixel.
|
||||||
|
As the write operation depends on the pixel format, we use a [`match`] statement:
|
||||||
|
|
||||||
|
[`match`]: https://doc.rust-lang.org/stable/std/keyword.match.html
|
||||||
|
|
||||||
|
- For `Rgb` framebuffers, we write three bytes; first red, then green, then blue.
|
||||||
|
- For `Bgr` framebuffers, we also write three bytes, but blue first and red last.
|
||||||
|
- For `U8` framebuffers, we first convert the color to grayscale by taking the average of the three color channels.
|
||||||
|
Note that there are multiple [different ways to convert colors to grayscale], so you can also use different factors here.
|
||||||
|
- For all other framebuffer formats, we [panic] for now.
|
||||||
|
|
||||||
|
[different ways to convert colors to grayscale]: https://www.baeldung.com/cs/convert-rgb-to-grayscale#bd-convert-rgb-to-grayscale
|
||||||
|
[panic]: https://doc.rust-lang.org/stable/core/macro.panic.html
|
||||||
|
|
||||||
|
Let's try to use our new function to write a blue pixel in our `kernel_main` function:
|
||||||
|
|
||||||
|
```rust ,hl_lines=5-11
|
||||||
// in kernel/src/main.rs
|
// in kernel/src/main.rs
|
||||||
|
|
||||||
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
||||||
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
|
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
|
||||||
let position = framebuffer::Position { x: 200, y: 100 };
|
let position = framebuffer::Position { x: 20, y: 100 };
|
||||||
let color = framebuffer::Color { red: 0, green: 0, blue: 255 };
|
let color = framebuffer::Color {
|
||||||
set_pixel_in(framebuffer, position, color);
|
red: 0,
|
||||||
|
green: 0,
|
||||||
|
blue: 255,
|
||||||
|
};
|
||||||
|
framebuffer::set_pixel_in(framebuffer, position, color);
|
||||||
}
|
}
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Of course a single pixel is difficult to see, so let's set a square of 10 pixels:
|
When we run our code in QEMU using `cargo run --bin qemu-bios` (or `--bin qemu-uefi`) and look _very closely_, we can see the blue pixel.
|
||||||
|
It's really difficult to see, so I marked with an arrow below:
|
||||||
|
|
||||||
```rust ,hl_lines=6-11
|

|
||||||
|
|
||||||
|
As this single pixel is too difficult to see, let's draw a filled square of 100x100 pixels instead:
|
||||||
|
|
||||||
|
```rust ,hl_lines=10-18
|
||||||
// in kernel/src/main.rs
|
// in kernel/src/main.rs
|
||||||
|
|
||||||
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
||||||
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
|
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
|
||||||
let color = framebuffer::Color { red: 0, green: 0, blue: 255 };
|
let color = framebuffer::Color {
|
||||||
for x in 0..10 {
|
red: 0,
|
||||||
for y in 0..10 {
|
green: 0,
|
||||||
let position = framebuffer::Position { x: 200 + x, y: 100 + y};
|
blue: 255,
|
||||||
set_pixel_in(framebuffer, position, color);
|
};
|
||||||
|
for x in 0..100 {
|
||||||
|
for y in 0..100 {
|
||||||
|
let position = framebuffer::Position {
|
||||||
|
x: 20 + x,
|
||||||
|
y: 100 + y,
|
||||||
|
};
|
||||||
|
framebuffer::set_pixel_in(framebuffer, position, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,40 +289,67 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Now we modifications more easily: TODO image
|
Now we clearly see that our code works as intended:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Feel free to experiment with different positions and colors if you like.
|
||||||
|
You can also try to draw a circle instead of a square, or a line with a certain thickness.
|
||||||
|
|
||||||
|
As you can probably imagine, it would be a lot of work to draw more complex shapes this way.
|
||||||
|
One example for such complex shapes is _text_, i.e. the rendering of letters and punctuation.
|
||||||
|
Fortunately, there is the nice `no_std`-compatible [`embedded-graphics`] crate, which provides draw functions for text, various shapes, and image data.
|
||||||
|
|
||||||
|
[`embedded-graphics`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/index.html
|
||||||
|
|
||||||
## The `embedded-graphics` crate
|
## The `embedded-graphics` crate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Implementing `DrawTarget`
|
### Implementing `DrawTarget`
|
||||||
|
|
||||||
```rust ,hl_lines=3
|
```rust ,hl_lines=3
|
||||||
// in kernel/src/framebuffer.rs
|
// in kernel/src/framebuffer.rs
|
||||||
|
use embedded_graphics::{
|
||||||
|
Pixel,
|
||||||
|
draw_target::DrawTarget,
|
||||||
|
geometry::{OriginDimensions, Size},
|
||||||
|
pixelcolor::{Rgb888, RgbColor},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Display {
|
pub struct Display<'f> {
|
||||||
framebuffer: Framebuffer,
|
framebuffer: &'f mut FrameBuffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display {
|
impl<'f> Display<'f> {
|
||||||
pub fn new(framebuffer: Framebuffer) -> Display {
|
pub fn new(framebuffer: &'f mut FrameBuffer) -> Display {
|
||||||
Self { framebuffer }
|
Display { framebuffer }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_pixel(&mut self, pixel: Pixel) {
|
fn draw_pixel(&mut self, Pixel(coordinates, color): Pixel<Rgb888>) {
|
||||||
// ignore any pixels that are out of bounds.
|
// ignore any out of bounds pixels
|
||||||
let (width, height) = {
|
let (width, height) = {
|
||||||
let info = self.framebuffer.info();
|
let info = self.framebuffer.info();
|
||||||
|
|
||||||
(info.width, info.height)
|
(info.width, info.height)
|
||||||
}
|
};
|
||||||
if let Ok((x @ 0..width, y @ 0..height)) = coordinates.try_into() {
|
|
||||||
let color = Color { red: color.r(), green: color.g(), blue: color.b()};
|
let (x, y) = {
|
||||||
set_pixel_in(&mut self.framebuffer, Position { x, y }, color);
|
let c: (i32, i32) = coordinates.into();
|
||||||
|
(c.0 as usize, c.1 as usize)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (0..width).contains(&x) && (0..height).contains(&y) {
|
||||||
|
let color = Color { red: color.r(), green: color.g(), blue: color.b() };
|
||||||
|
|
||||||
|
set_pixel_in(self.framebuffer, Position { x, y }, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl embedded_graphics::draw_target::DrawTarget for Display {
|
|
||||||
type Color = embedded_graphics::pixelcolor::Rgb888;
|
impl<'f> DrawTarget for Display<'f> {
|
||||||
|
type Color = Rgb888;
|
||||||
|
|
||||||
/// Drawing operations can never fail.
|
/// Drawing operations can never fail.
|
||||||
type Error = core::convert::Infallible;
|
type Error = core::convert::Infallible;
|
||||||
@@ -281,12 +358,21 @@ impl embedded_graphics::draw_target::DrawTarget for Display {
|
|||||||
where
|
where
|
||||||
I: IntoIterator<Item = Pixel<Self::Color>>,
|
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||||
{
|
{
|
||||||
for Pixel(coordinates, color) in pixels.into_iter() {
|
for pixel in pixels.into_iter() {
|
||||||
self.draw_pixel(pixel);
|
self.draw_pixel(pixel);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'f> OriginDimensions for Display<'f> {
|
||||||
|
fn size(&self) -> Size {
|
||||||
|
let info = self.framebuffer.info();
|
||||||
|
|
||||||
|
Size::new(info.width as u32, info.height as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -296,7 +382,7 @@ impl embedded_graphics::draw_target::DrawTarget for Display {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
draw shapes and pixels directly onto the framebuffer. That's fine and all, but how is one able to go from that to displaying text on the screen? Understanding this requires taking a deep dive into how characters are rendered behind the scenes.
|
So far, we have drawn shapes and pixels directly onto the framebuffer. That's fine and all, but how is one able to go from that to displaying text on the screen? Understanding this requires taking a deep dive into how characters are rendered behind the scenes.
|
||||||
|
|
||||||
When a key is pressed on the keyboard, it sends a character code to the CPU. It's the CPU's job at that point to then interpret the character code and match it with an image to draw on the screen. The image is then sent to either the GPU or the framebuffer (the latter in our case) to be drawn on the screen, and the user sees that image as a letter, number, CJK character, emoji, or whatever else he or she wanted to have displayed by pressing that key.
|
When a key is pressed on the keyboard, it sends a character code to the CPU. It's the CPU's job at that point to then interpret the character code and match it with an image to draw on the screen. The image is then sent to either the GPU or the framebuffer (the latter in our case) to be drawn on the screen, and the user sees that image as a letter, number, CJK character, emoji, or whatever else he or she wanted to have displayed by pressing that key.
|
||||||
|
|
||||||
@@ -386,7 +472,7 @@ fn kernel_main(boot_info: &'static mut bootloader_api::BootInfo) -> ! {
|
|||||||
let frame_buffer_struct = frame_buffer_option.unwrap();
|
let frame_buffer_struct = frame_buffer_option.unwrap();
|
||||||
|
|
||||||
// extract the framebuffer info and, to satisfy the borrow checker, clone it
|
// extract the framebuffer info and, to satisfy the borrow checker, clone it
|
||||||
let frame_buffer_info = frame_buffer.info().clone();
|
let frame_buffer_info = frame_buffer_struct.info().clone();
|
||||||
|
|
||||||
// get the framebuffer's mutable raw byte slice
|
// get the framebuffer's mutable raw byte slice
|
||||||
let raw_frame_buffer = frame_buffer_struct.buffer_mut();
|
let raw_frame_buffer = frame_buffer_struct.buffer_mut();
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
Reference in New Issue
Block a user