Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Comments for "https://os.phil-opp.com/vga-text-mode/" #405

Closed
utterances-bot opened this issue Mar 10, 2018 · 138 comments
Closed

Comments for "https://os.phil-opp.com/vga-text-mode/" #405

utterances-bot opened this issue Mar 10, 2018 · 138 comments

Comments

@utterances-bot
Copy link

utterances-bot commented Mar 10, 2018

This is a general purpose comment thread for the “VGA Text Mode” post.

@gurpreetshanky
Copy link

Excellent Tutorial series.. As a Rust newbie its a great resource to learn about new things.

@davidvedvick
Copy link

Wow this was all sorts of fun... thanks for putting this together. I've both learned a lot about Rust (in a far gentler fashion than normal) and learned what's involved in building an OS. I look forward to future articles!

By the way, for whatever reason, when I call my implementation of write_str, I don't have to call .unwrap(), yet I need to call it from the macro... either I did something wrong or there's some difference I have yet to find with the tutorial and my code. Here's my write_str implementation:

impl fmt::Write for Writer {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for byte in s.bytes() {
            self.write_byte(byte)
        }
        Ok(())
    }
}

@phil-opp phil-opp changed the title vga-text-mode/ Comments for "vga-text-mode/" Mar 11, 2018
@drgmr
Copy link

drgmr commented Mar 11, 2018

@Danrien I had the same problem - make sure your impl Writer doesn't have a write_str method too - you might be using that instead of the one in the fmt::Write implementation.

@davidvedvick
Copy link

davidvedvick commented Mar 11, 2018 via email

@phil-opp
Copy link
Owner

phil-opp commented Mar 13, 2018

@gurpreetshanky @Danrien Thanks a lot! Glad that you find the posts informative :).

@Danrien @edupc I just pushed #417 to avoid this confusion. Thanks for the feedback!

@SomeAnotherDude
Copy link
Contributor

I think it should be useful to add an empty branch to println! macro. (Standard macro has this branch).

	macro_rules! println {
	    () => (print!("\n"));
	    ($fmt:expr) => (print!(concat!($fmt, "\n")));
	    ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*));
	}

Copy link

fpsiorz commented Apr 1, 2018

I don't think printing strings byte by byte is good in this case. The characters on screen are encoded in Code Page 437, which is identical to UTF-8 (which Rust strings use) only for a certain range of characters.

The write_byte currently prints symbols for all control characters except '◙' (which is on the code point of '\n'). I think it should do something like this instead:

pub fn write_byte(&mut self, byte: u8) {
    match byte {
        b'\n' => self.new_line(),
        b' '...b'~' => {
            // keep current behavior
            ...
        },
        _ => {
            // handle non-ASCII characters
        },
    }
}

I added Unicode to Code Page 437 translation to my implementation, but that's a lot of code.

@phil-opp
Copy link
Owner

phil-opp commented Apr 2, 2018

@fpsiorz Thanks for the suggestion! I opened #425 to add such a check in the write_strings method.

Copy link

At first I got this error:

error[E0658]: const fn is unstable (see issue #24111)
  --> src/vga_buffer.rs:33:5
   |
33 | /     const fn new(foreground: Color, background: Color) -> ColorCode {
34 | |         ColorCode((background as u8) << 4 | (foreground as u8))
35 | |     }
   | |_____^
   |
   = help: add #![feature(const_fn)] to the crate attributes to enable

The reason is obvious: I forgot to add #![feature(const_fn)] to main.rs, but it took me a while to figure that out and at first I simply removed the "const" at the function declaration. The code executed fine without the const.

So I wonder: What are const functions exactly for and why do we need to use a const function here instead of a normal function? I found the (RFC)[https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md] on const functions, but that doesn't make me understand the concept behind const functions.

@phil-opp
Copy link
Owner

phil-opp commented May 1, 2018

@arjanvaneersel A const feature behaves exactly the same as a normal function, but has one additional feature: It can be used for initializing const values, such as a static or an array length. In our case, the idea was that it can be used to initialize the static WRITER. However, we later use lazy_static for initializing it, so the const really isn't needed here.

@phil-opp
Copy link
Owner

phil-opp commented May 1, 2018

@arjanvaneersel I removed the const from the function and added a short explanation of const evaluation in ba266f3.

Copy link

Thank you! This is a lot of fun :).

Typical beginner mistakes, that compile, but lead to blank screen:
(these are all errors I made)

  • using vga buffer address 0x8000 instead of 0xb8000 (missing 'b')
  • in new_line putting the clear_row call inside the outer for-loop

small "debugging" tip: put some character into the blank.ascii_character field in clear_row to see what is actually happening. I chose `b'_'.

Some notes from a beginner perspective:

  • I do not understand what is happening in the unsafe block. At all. Is this by any chance explained somewhere? I am assuming that it is a well know pattern...?
  • The unwrap seems suspicious. If it "should" (read: hopefully,maybe) never occur, why is it there? Is there not even one plausible scenario?

Very kool stuff!

If someone else likes to use cargo-make, like I do, here is a usable Makefile.toml:

[tasks.build]
description = "Build no-std rust binary. Combine it with a preconfigured bootloader from rust-osdev."
command = "bootimage"
args = ["build"]

[tasks.boot]
description="Boot the kernel in a qemu virtual machine."
command="bootimage"
args=["run"]
# build 'bootimage.bin' via the bootimage tool
$ cargo make build
# run 'bootimage.bin' in qemu
$ cargo make boot

@phil-opp
Copy link
Owner

phil-opp commented May 11, 2018

@bugabinga Thanks for your comment!

I do not understand what is happening in the unsafe block.

You mean &mut *(0xb8000 as *mut Buffer)? From the post:

The syntax for this might seem a bit strange: First, we cast the integer 0xb8000 as an mutable raw pointer. Then we convert it to a mutable reference by dereferencing it (through *) and immediately borrowing it again (through &mut). This conversion requires an unsafe block, since the compiler can't guarantee that the raw pointer is valid.

Basically it's a way of transforming an integer to a memory reference. In C, the equivalent would be the cast (Buffer *) 0xb8000.

The unwrap seems suspicious. If it "should" (read: hopefully,maybe) never occur, why is it there? Is there not even one plausible scenario?

The problem is that the fmt::Write trait is defined that way that write_str returns a Result. The reason for this is that some implementations (e.g. for Linux stdout) might return an error. In our implementation, we never return an error, so the unwrap is safe.

@phil-opp phil-opp changed the title Comments for "vga-text-mode/" Comments for "https://os.phil-opp.com/vga-text-mode/" Jun 27, 2018
@cedws
Copy link
Contributor

cedws commented Oct 22, 2018

Hey, thanks for this excellent blog series. I noticed while following along a small adjustment that could be made. Perhaps it was done like this for clarity, but I think the blog series assumes a familiarity with Rust.

let color_code = self.color_code;
self.buffer.chars[row][col] = ScreenChar {
    ascii_character: byte,
-   color_code: color_code,
+   color_code,
};
self.column_position += 1;

I was also prompted by the latest nightly compiler to add #![feature(exclusive_range_pattern)] due to this chunk of code:

match byte {
    0x20..0x7e | b'\n' => self.write_byte(byte),
    _ => self.write_byte(0xfe),
}

I recommend changing it to look like this, so that 0x7e (which is ~) is included in the pattern. This also means that the feature gate I mentioned above won't be required.

- 0x20..0x7e
+ 0x20..=0x7e

@phil-opp
Copy link
Owner

@c-edw Thanks, I agree with both suggestions! Could you open pull request that applies these changes to both src/vga_buffer.rs and blog/content/second-edition/posts/03-vga-text-buffer/index.md?

Copy link

First of all, thanks for the great tutorial! I was always scared by how the things work on low-level (just to read all this code without proper pre-study turns out to be rather difficult), and here's the possibility to get this way of thinking ready.

I was a bit confused by this:

These methods internally use the read_volatile and write_volatile functions of the standard library and thus guarantee that the reads/writes are not optimized away

(emphasis mine). I was thinking for a moment that a bit later we'll run into some issue due to the no_std, but... nothing, It Just Works™. And only after inspecting the links provided, I've understood that *_volatile functions are not in standard library (i.e. std crate), but in the core. Maybe consider fixing this place?

@phil-opp
Copy link
Owner

First of all, thanks for the great tutorial! I was always scared by how the things work on low-level (just to read all this code without proper pre-study turns out to be rather difficult), and here's the possibility to get this way of thinking ready.

Thanks, that's great to hear!

I was a bit confused by this:

Good catch! Fixed in 1b52ff1.

Copy link

amatho commented Oct 30, 2018

Really enjoying these tutorials, thanks for writing them!
I'm just a bit confused about &mut *(0xb8000 as *mut Buffer); while I understand what it does, I'm struggling with finding out why the cast works. Is it because the memory layout of the Buffer struct is the same as the layout of the VGA buffer?

@robert-w-gries
Copy link
Contributor

@amatho Yes, the layout of the Buffer struct is the same as the VGA buffer.

Buffer is declared as an array of ScreenChar's:

struct Buffer {
    chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT],
}

The ScreenChar struct is represented as two u8's:

struct ColorCode(u8);

struct ScreenChar {
    ascii_character: u8,
    color_code: ColorCode,
}

The OSDev Wiki tutorial for printing to screen explains that in Text Mode the VGA buffer accepts a series of two byte inputs, the ascii character and an attribute. In our case, the attribute is the color_code byte.

Copy link

With latest rust nightly, we can ditch the #[macro_use] attribute and replace it with use lazy_static::lazy_static; in vga_buffer.rs.

@phil-opp
Copy link
Owner

@kballard Good suggestion. Done in e7d4012.

@lilyball
Copy link

@phil-opp BTW I still see some remaining instances of #[macro_use] using https://github.com/phil-opp/blog_os/search?q=macro_use&unscoped_q=macro_use

The ones like

#[macro_use]
extern crate blog_os;

can be replaced with e.g.

use blog_os::{print, println, serial_print, serial_println}; // depending on which macros we use

The one in src/lib.rs that looks like

#[macro_use]
pub mod vga_buffer;

can be replaced in the modules that need it with (and this one confused me at first, but exported macros are moved to the crate root):

use crate::{print, println};

And there's also still a couple of references to macro_use in the blog posts.


The need to write something like

use blog_os::{print, println, serial_print, serial_println};

can be mildly annoying. You might consider the idea of adding something like

// src/macros.rs
pub use crate::{print, println, serial_print, serial_println};

because then clients can write

use blog_os::macros::*;

in order to pick up all macros.

@phil-opp
Copy link
Owner

@kballard I tried to remove #[macro_use] for the local println/serial macros as well, but I always got an "unresolved import" error. I created a small example on the playground where the same error occurs: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=8acadc771c8140751ca19aed03b3fd61

What am I doing wrong?

@lilyball
Copy link

@phil-opp AIUI macros defined within the current crate don't participate in the new style of import resolution. Within the crate, they still behave the same as they always did. Which is to say, a macro isn't visible outside of its defining module unless the module itself is annotated with #[macro_use] (which will import the macros into the parent), or the macro is annotated with #[macro_export] (which will hoist the macro up to the crate root). Also with the #[macro_use] approach, the macro is only visible within the parent module to any code that comes after the #[macro_use] mod … declaration. Interestingly, the #[macro_export] approach fixes this one particular issue and makes the macro visible to code in the root from before the mod.

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=802c9be1f5ec0961272c9ebdff32bd89 demonstrates the #[macro_export] approach, as that's what should apply to your macros.

@phil-opp
Copy link
Owner

@DeathBySpork A common approach for this is to embed some kind of special escape code into the string to change the color. The most common standard for this are the ANSI escape codes. Using such escape codes, you can then build an abstraction such as the colored crate, which uses the Display trait to automatically add these escape codes when the string is printed.

Copy link

Sk3pz commented Jul 19, 2020

Ah thank you so much! I will work on embedding this into my code

Copy link

codic12 commented Jul 20, 2020

Any way to print to the top of the screen?

@phil-opp
Copy link
Owner

@codic12 See some other comments in this issue, for example the most recent by @drzewiec: #405 (comment)

@codic12
Copy link

codic12 commented Jul 27, 2020

@phil-opp awesome, thanks!

Copy link

ishlun commented Aug 30, 2020

I just started leaning rust but i understand everything! You are the best!!!!

@phil-opp
Copy link
Owner

@ishlun Great to hear that, thanks for your comment!

Copy link

This is an awesome tutorial! I'm having trouble getting volatile working. cargo run is saying that it is an unresolved import and that the crate may be missing. It is present in my Cargo.toml though...

Copy link

Figured it out by taking a look at someone's github of their tutorial work. I was missing extern crate volatile in main.rs it seems.

@bjorn3
Copy link
Contributor

bjorn3 commented Sep 14, 2020

extern crate isn't necessary anymore in the 2018 edition. Does Cargo.toml contain edition = 2018?

@HamishWHC
Copy link

No, I'm missing that config key. Did I miss that in the tutorial?

@bjorn3
Copy link
Contributor

bjorn3 commented Sep 15, 2020

cargo new should automatically add edition = "2018".

Copy link

Might be worth to update the guide as the latest volatile = "0.4.1" version will throw an error when compiling, complaining with the following error:

error[E0599]: no method named `write` found for struct `Volatile<ScreenChar>` in the current scope
  --> src/vga_buffer.rs:98:45
   |
66 | struct ScreenChar {
   | -----------------
   | |
   | doesn't satisfy `<ScreenChar as __Deref>::Target = _`
   | doesn't satisfy `ScreenChar: __Deref`
...
98 |                 self.buffer.chars[row][col].write(ScreenChar {
   |                                             ^^^^^ method not found in `Volatile<ScreenChar>`
   |
   = note: the method `write` exists but the following trait bounds were not satisfied:
           `<ScreenChar as __Deref>::Target = _`
           `ScreenChar: __Deref`

Might be worth adding the Deref and DerefMut implementations as they are part of core::ops and not standard.

E.g.

use core::ops::{Deref, DerefMut};
...
impl Deref for ScreenChar {
    type Target = ScreenChar;

    fn deref(&self) -> &Self::Target {
        self // TODO: Implement
    }
}

impl DerefMut for ScreenChar {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self // TODO: Implement
    }
}

@phil-opp
Copy link
Owner

phil-opp commented Oct 5, 2020

@martin-bucinskas The volatile crate was completely rewritten in the latest version, so it won't work with the post. Just stick to volatile = "0.2.6" for now, then everything should work.

I'm currently working on a completely new version of this post, which will use a pixel-based framebuffer instead of the VGA text mode. I will use the new volatile release for this upcoming post.

Copy link

Sorry for this dumb question...

// But what does this mean ?
0xb8000 as *mut Buffer

It make sense that 0xb8000 as u8 , But Buffer !?
New to rust...

@bjorn3
Copy link
Contributor

bjorn3 commented Jan 27, 2021

This is casting the address 0xb800 to the pointer *mut Buffer.

@codic12
Copy link

codic12 commented Feb 12, 2021

0xb8000 - a hexadecimal literal. it is the start of VGA text mode memory (0xA0000 is for the raw framebuffer access).
as - indicates a cast, which tells the compiler to treat the left hand side as if it's type is the type given on the right hand side
*mut Buffer - a mutable pointer to the type Buffer. you can then use this buffer as memory to write to, like an array, because it is a pointer; same concept as (buffer_t *)0xb8000 assuming buffer_t is defined.

Copy link
Contributor

ssrlive commented Feb 19, 2021

Compiling error!
In https://os.phil-opp.com/vga-text-mode/#formatting-macros section. I have to stop by the error of method write_fmt not found for this, how to resolve it?

@phil-opp
Copy link
Owner

Did you add the use core::fmt::Write; import?

Copy link
Contributor

ssrlive commented Feb 20, 2021

Fixed. Just add a line use core::fmt::Write;.

Copy link
Contributor

ssrlive commented Feb 20, 2021

Please modify the article to fix the issue.

Fixed. Just add a line use core::fmt::Write;

@drzewiec
Copy link

@ssrlive The article already says that.

Copy link

Is there a setting to change/reason why all the test executables get recompiled every time I run cargo test even if no code actually changes?

@phil-opp
Copy link
Owner

@chrisbroome The bootloader needs to be re-linked whenever the kernel executable changes because it currently directly links it. This is something that we plan to fix in the future by making the bootloader load the kernel from disk separately.

Copy link

I don't really understand why Writer doesn't own the Buffer, but only takes a mutable reference.

@bjorn3
Copy link
Contributor

bjorn3 commented May 3, 2021

Buffer is at a fixed location in memory defined by the hardware/firmware. Writer can be stored anywhere and as such can't have Buffer as field without indirection. Using Box also doesn't make sense as Buffer is not actually owned by Writer, but by the hardware/firmware.

Repository owner locked and limited conversation to collaborators Jun 12, 2021

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests