mirror of
https://github.com/phil-opp/blog_os.git
synced 2025-12-20 16:07:49 +00:00
Compare commits
4 Commits
2676d69c31
...
50802c8332
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50802c8332 | ||
|
|
ba410f40ba | ||
|
|
a119d36cc9 | ||
|
|
9080e69a09 |
@@ -445,7 +445,7 @@ Afterwards, you can run the tools through `rust-nm`, `rust-objdump`, and `rust-s
|
|||||||
|
|
||||||
[`cargo-binutils`]: https://github.com/rust-embedded/cargo-binutils
|
[`cargo-binutils`]: https://github.com/rust-embedded/cargo-binutils
|
||||||
|
|
||||||
### `nm`
|
### List Symbols using `nm`
|
||||||
|
|
||||||
We defined a `_start` function as the entry point of our kernel.
|
We defined a `_start` function as the entry point of our kernel.
|
||||||
To verify that it is properly exposed in the executable, we can run `nm` to list all the symbols defined in the executable:
|
To verify that it is properly exposed in the executable, we can run `nm` to list all the symbols defined in the executable:
|
||||||
@@ -463,7 +463,7 @@ If we comment out the `_start` function or if we remove the `#[no_mangle]` attri
|
|||||||
|
|
||||||
This way we can ensure that we set the `_start` function correctly.
|
This way we can ensure that we set the `_start` function correctly.
|
||||||
|
|
||||||
### `objdump`
|
### Inspect ELF File using `objdump`
|
||||||
|
|
||||||
The `objdump` tool can inspect different parts of executables that use the [ELF file format]. This is the file format that the `x86_64-unknown-none` target uses, so we can use `objdump` to inspect our kernel executable.
|
The `objdump` tool can inspect different parts of executables that use the [ELF file format]. This is the file format that the `x86_64-unknown-none` target uses, so we can use `objdump` to inspect our kernel executable.
|
||||||
|
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ There are a few notable things:
|
|||||||
|
|
||||||
To verify that the `entry_point` macro worked as expected, we can use the `objdump` tool as [described in the previous post][objdump-prev]. First, we recompile using `cargo build --target x86_64-unknown-none`, then we inspect the section headers using `objdump` or `rust-objdump`:
|
To verify that the `entry_point` macro worked as expected, we can use the `objdump` tool as [described in the previous post][objdump-prev]. First, we recompile using `cargo build --target x86_64-unknown-none`, then we inspect the section headers using `objdump` or `rust-objdump`:
|
||||||
|
|
||||||
[objdump-prev]: @/edition-3/posts/01-minimal-kernel/index.md#objdump
|
[objdump-prev]: @/edition-3/posts/01-minimal-kernel/index.md#inspect-elf-file-using-objdump
|
||||||
|
|
||||||
```bash,hl_lines=8
|
```bash,hl_lines=8
|
||||||
❯ rust-objdump -h target/x86_64-unknown-none/debug/kernel
|
❯ rust-objdump -h target/x86_64-unknown-none/debug/kernel
|
||||||
@@ -1047,7 +1047,7 @@ Since the size, pixel format, and memory location of the framebuffer can vary be
|
|||||||
The easiest way to do this is to read it from the [boot information structure][`BootInfo`] that the bootloader passes as argument to our kernel entry point:
|
The easiest way to do this is to read it from the [boot information structure][`BootInfo`] that the bootloader passes as argument to our kernel entry point:
|
||||||
|
|
||||||
```rust ,hl_lines=3 7-13
|
```rust ,hl_lines=3 7-13
|
||||||
// in src/kernel/main.rs
|
// in kernel/src/main.rs
|
||||||
|
|
||||||
use bootloader_api::BootInfo;
|
use bootloader_api::BootInfo;
|
||||||
|
|
||||||
@@ -1083,7 +1083,7 @@ For now, let's just try setting the whole screen to some color.
|
|||||||
For this, we just set every pixel in the byte slice to some fixed value:
|
For this, we just set every pixel in the byte slice to some fixed value:
|
||||||
|
|
||||||
```rust ,hl_lines=5-7
|
```rust ,hl_lines=5-7
|
||||||
// in src/kernel/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() {
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ Using the [`BootInfo`] provided by the bootloader, we were able to access a spec
|
|||||||
We wrote some example code to display a gray background:
|
We wrote some example code to display a gray background:
|
||||||
|
|
||||||
[previous post]: @/edition-3/posts/02-booting/index.md
|
[previous post]: @/edition-3/posts/02-booting/index.md
|
||||||
[`BootInfo`]: https://docs.rs/bootloader_api/latest/bootloader_api/info/struct.BootInfo.html
|
[`BootInfo`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.BootInfo.html
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// in src/kernel/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() {
|
||||||
@@ -65,22 +65,229 @@ The pixels are laid out line by line, typically starting at the top.
|
|||||||
|
|
||||||
For example, the pixels of an image with width 10 and height 3 would be typically stored in this order:
|
For example, the pixels of an image with width 10 and height 3 would be typically stored in this order:
|
||||||
|
|
||||||
<table><tbody>
|
<table style = "width: fit-content;"><tbody>
|
||||||
<tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td></tr>
|
<tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td></tr>
|
||||||
<tr><td>10</td><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td></tr>
|
<tr><td>10</td><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td></tr>
|
||||||
<tr><td>20</td><td>21</td><td>22</td><td>23</td><td>24</td><td>25</td><td>26</td><td>27</td><td>28</td><td>29</td></tr>
|
<tr><td>20</td><td>21</td><td>22</td><td>23</td><td>24</td><td>25</td><td>26</td><td>27</td><td>28</td><td>29</td></tr>
|
||||||
</tbody></table>
|
</tbody></table>
|
||||||
|
|
||||||
So top left pixel is stored at offset 0 in the bitmap array.
|
So top left pixel is stored at offset 0 in the bitmap array.
|
||||||
The pixel on its right is at offset `pixel_size`.
|
The pixel on its right is at pixel offset 1.
|
||||||
The first pixel of the next line starts at offset `line_length * pixel_size`.
|
The first pixel of the next line starts at pixel offset `line_length`, which is 10 in this case.
|
||||||
|
The last line starts at pixel offset 20, which is `line_length * 2`.
|
||||||
|
|
||||||
### Padding
|
### Padding
|
||||||
|
|
||||||
Depending on the hardware and GPU firmware, it is often more efficient
|
Depending on the hardware and GPU firmware, it is often more efficient to make lines start at well-aligned offsets.
|
||||||
|
Because of this, there is often some additional padding at the end of each line.
|
||||||
|
So the actual memory layout of the 10x3 example image might look like this, with the padding marked as yellow:
|
||||||
|
|
||||||
|
<table style = "width: fit-content;"><tbody>
|
||||||
|
<tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td><td style="background-color:yellow;">10</td><td style="background-color:yellow;">11</td></tr>
|
||||||
|
<tr><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td><td>20</td><td>21</td><td style="background-color:yellow;">22</td><td style="background-color:yellow;">23</td></tr>
|
||||||
|
<tr><td>24</td><td>25</td><td>26</td><td>27</td><td>28</td><td>29</td><td>30</td><td>31</td><td>32</td><td>33</td><td style="background-color:yellow;">34</td><td style="background-color:yellow;">35</td></tr>
|
||||||
|
</tbody></table>
|
||||||
|
|
||||||
|
So now the second line starts at pixel offset 12.
|
||||||
|
The two pixels at the end of each line are considered as padding and ignored.
|
||||||
|
So if we want to set the first pixel of the second line, we need to be aware of the additional padding and set the pixel at offset 12 instead of offset 10.
|
||||||
|
|
||||||
|
The line length plus the padding bytes is typically called the _stride_ or _pitch_ of the buffer.
|
||||||
|
In the example above, the stride is 12 and the line length is 10.
|
||||||
|
|
||||||
|
Since the amount of padding depends on the hardware, the stride is only known at runtime.
|
||||||
|
The `bootloader` crate queries the framebuffer parameters from the UEFI or BIOS firmware and reports them as part of the `BootInfo`.
|
||||||
|
It provides the stride of the framebuffer, among other parameters, in form of a [`FrameBufferInfo`] struct that can be created using the [`FrameBuffer::info`] method.
|
||||||
|
|
||||||
|
[`FrameBufferInfo`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.FrameBufferInfo.html
|
||||||
|
[`FrameBuffer::info`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.FrameBuffer.html#method.info
|
||||||
|
|
||||||
### Color formats
|
### Color formats
|
||||||
|
|
||||||
|
The [`FrameBufferInfo`] also specifies the [`PixelFormat`] of the framebuffer, which also depends on the underlying hardware.
|
||||||
|
Using this information, we can set pixels to different colors.
|
||||||
|
For example, the [`PixelFormat::Rgb`] variant specifies that each pixel is represented in the [RGB color space], which stores the red, green, and blue parts of the pixel as separate bytes.
|
||||||
|
In this model, the color red would be represented as the three bytes `[255, 0, 0]`, or `0xff0000` in [hexadecimal representation].
|
||||||
|
The color yellow is represented the addition of red and green, which results in `[255, 255, 0]` (or `0xffff00` in hexadecimal representation).
|
||||||
|
|
||||||
|
[`PixelFormat`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/enum.PixelFormat.html
|
||||||
|
[`PixelFormat::Rgb`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/enum.PixelFormat.html#variant.Rgb
|
||||||
|
[RGB color space]: https://en.wikipedia.org/wiki/RGB_color_spaces
|
||||||
|
[hexadecimal representation]: https://en.wikipedia.org/wiki/RGB_color_model#Numeric_representations
|
||||||
|
|
||||||
|
While the `Rgb` format is most common, there are also framebuffers that use a different color format.
|
||||||
|
For example, the [`PixelFormat::Bgr`] stores the three colors in inverted order, i.e. blue first and red last.
|
||||||
|
There are also buffers that don't support colors at all and can represent only grayscale pixels.
|
||||||
|
The `bootloader_api` crate reports such buffers as [`PixelFormat::U8`].
|
||||||
|
|
||||||
|
[`PixelFormat::Bgr`]: https://docs.rs/bootloader_api/0.11.5/bootloader_api/info/enum.PixelFormat.html#variant.Bgr
|
||||||
|
[`PixelFormat::U8`]: https://docs.rs/bootloader_api/0.11.5/bootloader_api/info/enum.PixelFormat.html#variant.U8
|
||||||
|
|
||||||
|
Note that there might be some additional padding at the pixel-level as well.
|
||||||
|
For example, an `Rgb` pixel might be stored as 4 bytes instead of 3 to ensure 32-bit alignment.
|
||||||
|
The number of bytes per pixel is reported by the bootloader in the [`FrameBufferInfo::bytes_per_pixel`] field.
|
||||||
|
|
||||||
|
[`FrameBufferInfo::bytes_per_pixel`]: https://docs.rs/bootloader_api/0.11/bootloader_api/info/struct.FrameBufferInfo.html#structfield.bytes_per_pixel
|
||||||
|
|
||||||
|
## Setting specific Pixels
|
||||||
|
|
||||||
|
Based on this above details, we can now create a function to set a specific pixel to a certain color.
|
||||||
|
We start by creating a new `framebuffer` [module]:
|
||||||
|
|
||||||
|
[module]: https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html
|
||||||
|
|
||||||
|
```rust ,hl_lines=3-5
|
||||||
|
// in kernel/src/main.rs
|
||||||
|
|
||||||
|
// declare a submodule -> the compiler will automatically look
|
||||||
|
// for a file named `framebuffer.rs` or `framebuffer/mod.rs`
|
||||||
|
mod framebuffer;
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust ,hl_lines=3-16
|
||||||
|
// in new kernel/src/framebuffer.rs file
|
||||||
|
|
||||||
|
pub struct Position {
|
||||||
|
pub x: usize,
|
||||||
|
pub y: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Color {
|
||||||
|
pub red: u8,
|
||||||
|
pub green: u8,
|
||||||
|
pub blue: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
TODO explain
|
||||||
|
|
||||||
|
```rust ,hl_lines=4-12 14-34
|
||||||
|
// in new kernel/src/framebuffer.rs file
|
||||||
|
|
||||||
|
pub fn set_pixel_in(framebuffer: &mut FrameBuffer, position: Position, color: Color) {
|
||||||
|
// calculate offset to first byte of pixel
|
||||||
|
let byte_offset = {
|
||||||
|
// use stride to calculate pixel offset of target line
|
||||||
|
let line_offset = position.y * framebuffer.info.stride;
|
||||||
|
// add x position to get the absolute pixel offset in buffer
|
||||||
|
let pixel_offset = line_offset + position.x;
|
||||||
|
// convert to byte offset
|
||||||
|
pixel_offset * framebuffer.bytes_per_pixel
|
||||||
|
};
|
||||||
|
|
||||||
|
/// set pixel based on color format
|
||||||
|
match framebuffer.info.pixel_format {
|
||||||
|
PixelFormat::Rgb => {
|
||||||
|
let bytes = &mut framebuffer.buffer_mut()[byte_offset..][..3];
|
||||||
|
bytes[0] = color.red;
|
||||||
|
bytes[1] = color.green;
|
||||||
|
bytes[2] = color.blue;
|
||||||
|
}
|
||||||
|
PixelFormat::Bgr => {
|
||||||
|
let bytes = &mut framebuffer.buffer_mut()[byte_offset..][..3];
|
||||||
|
bytes[0] = color.blue;
|
||||||
|
bytes[1] = color.green;
|
||||||
|
bytes[2] = color.red;
|
||||||
|
}
|
||||||
|
PixelFormat::U8 => {
|
||||||
|
// use a simple average-based grayscale transform
|
||||||
|
let gray = color.red / 3 + color.green / 3 + color.blue / 3;
|
||||||
|
framebuffer.buffer_mut()[byte_offset] = gray;
|
||||||
|
}
|
||||||
|
other => panic!("unknown pixel format {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
TODO explain
|
||||||
|
|
||||||
|
Let's try our new function:
|
||||||
|
|
||||||
|
```rust ,hl_lines=5-7
|
||||||
|
// in kernel/src/main.rs
|
||||||
|
|
||||||
|
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
||||||
|
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
|
||||||
|
let position = framebuffer::Position { x: 200, y: 100 };
|
||||||
|
let color = framebuffer::Color { red: 0, green: 0, blue: 255 };
|
||||||
|
set_pixel_in(framebuffer, position, color);
|
||||||
|
}
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Of course a single pixel is difficult to see, so let's set a square of 10 pixels:
|
||||||
|
|
||||||
|
```rust ,hl_lines=6-11
|
||||||
|
// in kernel/src/main.rs
|
||||||
|
|
||||||
|
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
||||||
|
if let Some(framebuffer) = boot_info.framebuffer.as_mut() {
|
||||||
|
let color = framebuffer::Color { red: 0, green: 0, blue: 255 };
|
||||||
|
for x in 0..10 {
|
||||||
|
for y in 0..10 {
|
||||||
|
let position = framebuffer::Position { x: 200 + x, y: 100 + y};
|
||||||
|
set_pixel_in(framebuffer, position, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we modifications more easily: TODO image
|
||||||
|
|
||||||
|
## The `embedded-graphics` crate
|
||||||
|
|
||||||
|
|
||||||
|
### Implementing `DrawTarget`
|
||||||
|
|
||||||
|
```rust ,hl_lines=3
|
||||||
|
// in kernel/src/framebuffer.rs
|
||||||
|
|
||||||
|
pub struct Display {
|
||||||
|
framebuffer: Framebuffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display {
|
||||||
|
pub fn new(framebuffer: Framebuffer) -> Display {
|
||||||
|
Self { framebuffer }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_pixel(&mut self, pixel: Pixel) {
|
||||||
|
// ignore any pixels that are out of bounds.
|
||||||
|
let (width, height) = {
|
||||||
|
let info = self.framebuffer.info();
|
||||||
|
(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()};
|
||||||
|
set_pixel_in(&mut self.framebuffer, Position { x, y }, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_graphics::draw_target::DrawTarget for Display {
|
||||||
|
type Color = embedded_graphics::pixelcolor::Rgb888;
|
||||||
|
|
||||||
|
/// Drawing operations can never fail.
|
||||||
|
type Error = core::convert::Infallible;
|
||||||
|
|
||||||
|
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = Pixel<Self::Color>>,
|
||||||
|
{
|
||||||
|
for Pixel(coordinates, color) in pixels.into_iter() {
|
||||||
|
self.draw_pixel(pixel);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ html {
|
|||||||
--background-color: #fff;
|
--background-color: #fff;
|
||||||
--text-color: #515151;
|
--text-color: #515151;
|
||||||
--heading-color: #313131;
|
--heading-color: #313131;
|
||||||
--heading-code-color: #a0565c;
|
--heading-code-color: #313131;
|
||||||
--link-color: #268bd2;
|
--link-color: #268bd2;
|
||||||
--hr-color-top: #eee;
|
--hr-color-top: #eee;
|
||||||
--hr-color-bottom: #fff;
|
--hr-color-bottom: #fff;
|
||||||
@@ -181,11 +181,11 @@ h6 {
|
|||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 2rem;
|
font-size: 2.25rem;
|
||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
font-size: 1.5rem;
|
font-size: 1.75rem;
|
||||||
}
|
}
|
||||||
h3 {
|
h3 {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
@@ -196,6 +196,7 @@ h5,
|
|||||||
h6 {
|
h6 {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Body text */
|
/* Body text */
|
||||||
|
|||||||
Reference in New Issue
Block a user