1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
|
#[1]RSS feed for os.phil-opp.com
[2]Writing an OS in Rust
Philipp Oppermann's blog
[3]« All Posts
Table of Contents
1. [4]The VGA Text Buffer
2. [5]A Rust Module
1. [6]Colors
2. [7]Text Buffer
3. [8]Printing
4. [9]Volatile
5. [10]Formatting Macros
6. [11]Newlines
3. [12]A Global Interface
1. [13]Lazy Statics
2. [14]Spinlocks
3. [15]Safety
4. [16]A println Macro
5. [17]Hello World using println
6. [18]Printing Panic Messages
4. [19]Summary
5. [20]What's next?
6. [21]Comments
VGA Text Mode
Feb 26, 2018
The [22]VGA text mode is a simple way to print text to the screen. In
this post, we create an interface that makes its usage safe and simple
by encapsulating all unsafety in a separate module. We also implement
support for Rust's [23]formatting macros.
This blog is openly developed on [24]GitHub. If you have any problems
or questions, please open an issue there. You can also leave comments
[25]at the bottom. The complete source code for this post can be found
in the [26]post-03 branch.
Table of Contents
* [27]The VGA Text Buffer
* [28]A Rust Module
+ [29]Colors
+ [30]Text Buffer
+ [31]Printing
+ [32]Volatile
+ [33]Formatting Macros
+ [34]Newlines
* [35]A Global Interface
+ [36]Lazy Statics
+ [37]Spinlocks
+ [38]Safety
+ [39]A println Macro
+ [40]Hello World using println
+ [41]Printing Panic Messages
* [42]Summary
* [43]What's next?
* [44]Comments
The VGA Text Buffer
To print a character to the screen in VGA text mode, one has to write
it to the text buffer of the VGA hardware. The VGA text buffer is a
two-dimensional array with typically 25 rows and 80 columns, which is
directly rendered to the screen. Each array entry describes a single
screen character through the following format:
Bit(s) Value
0-7 ASCII code point
8-11 Foreground color
12-14 Background color
15 Blink
The first byte represents the character that should be printed in the
[45]ASCII encoding. To be more specific, it isn't exactly ASCII, but a
character set named [46]code page 437 with some additional characters
and slight modifications. For simplicity, we will proceed to call it an
ASCII character in this post.
The second byte defines how the character is displayed. The first four
bits define the foreground color, the next three bits the background
color, and the last bit whether the character should blink. The
following colors are available:
Number Color Number + Bright Bit Bright Color
0x0 Black 0x8 Dark Gray
0x1 Blue 0x9 Light Blue
0x2 Green 0xa Light Green
0x3 Cyan 0xb Light Cyan
0x4 Red 0xc Light Red
0x5 Magenta 0xd Pink
0x6 Brown 0xe Yellow
0x7 Light Gray 0xf White
Bit 4 is the bright bit, which turns, for example, blue into light
blue. For the background color, this bit is repurposed as the blink
bit.
The VGA text buffer is accessible via [47]memory-mapped I/O to the
address 0xb8000. This means that reads and writes to that address don't
access the RAM but directly access the text buffer on the VGA hardware.
This means we can read and write it through normal memory operations to
that address.
Note that memory-mapped hardware might not support all normal RAM
operations. For example, a device could only support byte-wise reads
and return junk when a u64 is read. Fortunately, the text buffer
[48]supports normal reads and writes, so we don't have to treat it in a
special way.
A Rust Module
Now that we know how the VGA buffer works, we can create a Rust module
to handle printing:
//in src/main.rs
mod vga_buffer;
For the content of this module, we create a new src/vga_buffer.rs file.
All of the code below goes into our new module (unless specified
otherwise).
Colors
First, we represent the different colors using an enum:
// in src/vga_buffer.rs
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Color {
Black = 0,
Blue = 1,
Green = 2,
Cyan = 3,
Red = 4,
Magenta = 5,
Brown = 6,
LightGray = 7,
DarkGray = 8,
LightBlue = 9,
LightGreen = 10,
LightCyan = 11,
LightRed = 12,
Pink = 13,
Yellow = 14,
White = 15,
}
We use a [49]C-like enum here to explicitly specify the number for each
color. Because of the repr(u8) attribute, each enum variant is stored
as a u8. Actually 4 bits would be sufficient, but Rust doesn't have a
u4 type.
Normally the compiler would issue a warning for each unused variant. By
using the #[allow(dead_code)] attribute, we disable these warnings for
the Color enum.
By [50]deriving the [51]Copy, [52]Clone, [53]Debug, [54]PartialEq, and
[55]Eq traits, we enable [56]copy semantics for the type and make it
printable and comparable.
To represent a full color code that specifies foreground and background
color, we create a [57]newtype on top of u8:
// in src/vga_buffer.rs
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
struct ColorCode(u8);
impl ColorCode {
fn new(foreground: Color, background: Color) -> ColorCode {
ColorCode((background as u8) << 4 | (foreground as u8))
}
}
The ColorCode struct contains the full color byte, containing
foreground and background color. Like before, we derive the Copy and
Debug traits for it. To ensure that the ColorCode has the exact same
data layout as a u8, we use the [58]repr(transparent) attribute.
Text Buffer
Now we can add structures to represent a screen character and the text
buffer:
// in src/vga_buffer.rs
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
struct ScreenChar {
ascii_character: u8,
color_code: ColorCode,
}
const BUFFER_HEIGHT: usize = 25;
const BUFFER_WIDTH: usize = 80;
#[repr(transparent)]
struct Buffer {
chars: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT],
}
Since the field ordering in default structs is undefined in Rust, we
need the [59]repr(C) attribute. It guarantees that the struct's fields
are laid out exactly like in a C struct and thus guarantees the correct
field ordering. For the Buffer struct, we use [60]repr(transparent)
again to ensure that it has the same memory layout as its single field.
To actually write to screen, we now create a writer type:
// in src/vga_buffer.rs
pub struct Writer {
column_position: usize,
color_code: ColorCode,
buffer: &'static mut Buffer,
}
The writer will always write to the last line and shift lines up when a
line is full (or on \n). The column_position field keeps track of the
current position in the last row. The current foreground and background
colors are specified by color_code and a reference to the VGA buffer is
stored in buffer. Note that we need an [61]explicit lifetime here to
tell the compiler how long the reference is valid. The [62]'static
lifetime specifies that the reference is valid for the whole program
run time (which is true for the VGA text buffer).
Printing
Now we can use the Writer to modify the buffer's characters. First we
create a method to write a single ASCII byte:
// in src/vga_buffer.rs
impl Writer {
pub fn write_byte(&mut self, byte: u8) {
match byte {
b'\n' => self.new_line(),
byte => {
if self.column_position >= BUFFER_WIDTH {
self.new_line();
}
let row = BUFFER_HEIGHT - 1;
let col = self.column_position;
let color_code = self.color_code;
self.buffer.chars[row][col] = ScreenChar {
ascii_character: byte,
color_code,
};
self.column_position += 1;
}
}
}
fn new_line(&mut self) {/* TODO */}
}
If the byte is the [63]newline byte \n, the writer does not print
anything. Instead, it calls a new_line method, which we'll implement
later. Other bytes get printed to the screen in the second match case.
When printing a byte, the writer checks if the current line is full. In
that case, a new_line call is used to wrap the line. Then it writes a
new ScreenChar to the buffer at the current position. Finally, the
current column position is advanced.
To print whole strings, we can convert them to bytes and print them
one-by-one:
// in src/vga_buffer.rs
impl Writer {
pub fn write_string(&mut self, s: &str) {
for byte in s.bytes() {
match byte {
// printable ASCII byte or newline
0x20..=0x7e | b'\n' => self.write_byte(byte),
// not part of printable ASCII range
_ => self.write_byte(0xfe),
}
}
}
}
The VGA text buffer only supports ASCII and the additional bytes of
[64]code page 437. Rust strings are [65]UTF-8 by default, so they might
contain bytes that are not supported by the VGA text buffer. We use a
match to differentiate printable ASCII bytes (a newline or anything in
between a space character and a ~ character) and unprintable bytes. For
unprintable bytes, we print a fS character, which has the hex code 0xfe
on the VGA hardware.
Try it out!
To write some characters to the screen, you can create a temporary
function:
// in src/vga_buffer.rs
pub fn print_something() {
let mut writer = Writer {
column_position: 0,
color_code: ColorCode::new(Color::Yellow, Color::Black),
buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
};
writer.write_byte(b'H');
writer.write_string("ello ");
writer.write_string("Wörld!");
}
It first creates a new Writer that points to the VGA buffer at 0xb8000.
The syntax for this might seem a bit strange: First, we cast the
integer 0xb8000 as a mutable [66]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
[67]unsafe block, since the compiler can't guarantee that the raw
pointer is valid.
Then it writes the byte b'H' to it. The b prefix creates a [68]byte
literal, which represents an ASCII character. By writing the strings
"ello " and "Wörld!", we test our write_string method and the handling
of unprintable characters. To see the output, we need to call the
print_something function from our _start function:
// in src/main.rs
#[no_mangle]
pub extern "C" fn _start() -> ! {
vga_buffer::print_something();
loop {}
}
When we run our project now, a Hello WfSfSrld! should be printed in the
lower left corner of the screen in yellow:
QEMU output with a yellow Hello WfSfSrld! in the lower left corner
Notice that the ö is printed as two fS characters. That's because ö is
represented by two bytes in [69]UTF-8, which both don't fall into the
printable ASCII range. In fact, this is a fundamental property of
UTF-8: the individual bytes of multi-byte values are never valid ASCII.
Volatile
We just saw that our message was printed correctly. However, it might
not work with future Rust compilers that optimize more aggressively.
The problem is that we only write to the Buffer and never read from it
again. The compiler doesn't know that we really access VGA buffer
memory (instead of normal RAM) and knows nothing about the side effect
that some characters appear on the screen. So it might decide that
these writes are unnecessary and can be omitted. To avoid this
erroneous optimization, we need to specify these writes as
[70]volatile. This tells the compiler that the write has side effects
and should not be optimized away.
In order to use volatile writes for the VGA buffer, we use the
[71]volatile library. This crate (this is how packages are called in
the Rust world) provides a Volatile wrapper type with read and write
methods. These methods internally use the [72]read_volatile and
[73]write_volatile functions of the core library and thus guarantee
that the reads/writes are not optimized away.
We can add a dependency on the volatile crate by adding it to the
dependencies section of our Cargo.toml:
# in Cargo.toml
[dependencies]
volatile = "0.2.6"
Make sure to specify volatile version 0.2.6. Newer versions of the
crate are not compatible with this post. 0.2.6 is the [74]semantic
version number. For more information, see the [75]Specifying
Dependencies guide of the cargo documentation.
Let's use it to make writes to the VGA buffer volatile. We update our
Buffer type as follows:
// in src/vga_buffer.rs
use volatile::Volatile;
struct Buffer {
chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT],
}
Instead of a ScreenChar, we're now using a Volatile<ScreenChar>. (The
Volatile type is [76]generic and can wrap (almost) any type). This
ensures that we can't accidentally write to it "normally". Instead, we
have to use the write method now.
This means that we have to update our Writer::write_byte method:
// in src/vga_buffer.rs
impl Writer {
pub fn write_byte(&mut self, byte: u8) {
match byte {
b'\n' => self.new_line(),
byte => {
...
self.buffer.chars[row][col].write(ScreenChar {
ascii_character: byte,
color_code,
});
...
}
}
}
...
}
Instead of a typical assignment using =, we're now using the write
method. Now we can guarantee that the compiler will never optimize away
this write.
Formatting Macros
It would be nice to support Rust's formatting macros, too. That way, we
can easily print different types, like integers or floats. To support
them, we need to implement the [77]core::fmt::Write trait. The only
required method of this trait is write_str, which looks quite similar
to our write_string method, just with a fmt::Result return type:
// in src/vga_buffer.rs
use core::fmt;
impl fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.write_string(s);
Ok(())
}
}
The Ok(()) is just a Ok Result containing the () type.
Now we can use Rust's built-in write!/writeln! formatting macros:
// in src/vga_buffer.rs
pub fn print_something() {
use core::fmt::Write;
let mut writer = Writer {
column_position: 0,
color_code: ColorCode::new(Color::Yellow, Color::Black),
buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
};
writer.write_byte(b'H');
writer.write_string("ello! ");
write!(writer, "The numbers are {} and {}", 42, 1.0/3.0).unwrap();
}
Now you should see a Hello! The numbers are 42 and 0.3333333333333333
at the bottom of the screen. The write! call returns a Result which
causes a warning if not used, so we call the [78]unwrap function on it,
which panics if an error occurs. This isn't a problem in our case,
since writes to the VGA buffer never fail.
Newlines
Right now, we just ignore newlines and characters that don't fit into
the line anymore. Instead, we want to move every character one line up
(the top line gets deleted) and start at the beginning of the last line
again. To do this, we add an implementation for the new_line method of
Writer:
// in src/vga_buffer.rs
impl Writer {
fn new_line(&mut self) {
for row in 1..BUFFER_HEIGHT {
for col in 0..BUFFER_WIDTH {
let character = self.buffer.chars[row][col].read();
self.buffer.chars[row - 1][col].write(character);
}
}
self.clear_row(BUFFER_HEIGHT - 1);
self.column_position = 0;
}
fn clear_row(&mut self, row: usize) {/* TODO */}
}
We iterate over all the screen characters and move each character one
row up. Note that the upper bound of the range notation (..) is
exclusive. We also omit the 0th row (the first range starts at 1)
because it's the row that is shifted off screen.
To finish the newline code, we add the clear_row method:
// in src/vga_buffer.rs
impl Writer {
fn clear_row(&mut self, row: usize) {
let blank = ScreenChar {
ascii_character: b' ',
color_code: self.color_code,
};
for col in 0..BUFFER_WIDTH {
self.buffer.chars[row][col].write(blank);
}
}
}
This method clears a row by overwriting all of its characters with a
space character.
A Global Interface
To provide a global writer that can be used as an interface from other
modules without carrying a Writer instance around, we try to create a
static WRITER:
// in src/vga_buffer.rs
pub static WRITER: Writer = Writer {
column_position: 0,
color_code: ColorCode::new(Color::Yellow, Color::Black),
buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
};
However, if we try to compile it now, the following errors occur:
error[E0015]: calls in statics are limited to constant functions, tuple structs
and tuple variants
--> src/vga_buffer.rs:7:17
|
7 | color_code: ColorCode::new(Color::Yellow, Color::Black),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0396]: raw pointers cannot be dereferenced in statics
--> src/vga_buffer.rs:8:22
|
8 | buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereference of raw point
er in constant
error[E0017]: references in statics may only refer to immutable values
--> src/vga_buffer.rs:8:22
|
8 | buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ statics require immutabl
e values
error[E0017]: references in statics may only refer to immutable values
--> src/vga_buffer.rs:8:13
|
8 | buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ statics require immuta
ble values
To understand what's happening here, we need to know that statics are
initialized at compile time, in contrast to normal variables that are
initialized at run time. The component of the Rust compiler that
evaluates such initialization expressions is called the "[79]const
evaluator". Its functionality is still limited, but there is ongoing
work to expand it, for example in the "[80]Allow panicking in
constants" RFC.
The issue with ColorCode::new would be solvable by using [81]const
functions, but the fundamental problem here is that Rust's const
evaluator is not able to convert raw pointers to references at compile
time. Maybe it will work someday, but until then, we have to find
another solution.
Lazy Statics
The one-time initialization of statics with non-const functions is a
common problem in Rust. Fortunately, there already exists a good
solution in a crate named [82]lazy_static. This crate provides a
lazy_static! macro that defines a lazily initialized static. Instead of
computing its value at compile time, the static lazily initializes
itself when accessed for the first time. Thus, the initialization
happens at runtime, so arbitrarily complex initialization code is
possible.
Let's add the lazy_static crate to our project:
# in Cargo.toml
[dependencies.lazy_static]
version = "1.0"
features = ["spin_no_std"]
We need the spin_no_std feature, since we don't link the standard
library.
With lazy_static, we can define our static WRITER without problems:
// in src/vga_buffer.rs
use lazy_static::lazy_static;
lazy_static! {
pub static ref WRITER: Writer = Writer {
column_position: 0,
color_code: ColorCode::new(Color::Yellow, Color::Black),
buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
};
}
However, this WRITER is pretty useless since it is immutable. This
means that we can't write anything to it (since all the write methods
take &mut self). One possible solution would be to use a [83]mutable
static. But then every read and write to it would be unsafe since it
could easily introduce data races and other bad things. Using static
mut is highly discouraged. There were even proposals to [84]remove it.
But what are the alternatives? We could try to use an immutable static
with a cell type like [85]RefCell or even [86]UnsafeCell that provides
[87]interior mutability. But these types aren't [88]Sync (with good
reason), so we can't use them in statics.
Spinlocks
To get synchronized interior mutability, users of the standard library
can use [89]Mutex. It provides mutual exclusion by blocking threads
when the resource is already locked. But our basic kernel does not have
any blocking support or even a concept of threads, so we can't use it
either. However, there is a really basic kind of mutex in computer
science that requires no operating system features: the [90]spinlock.
Instead of blocking, the threads simply try to lock it again and again
in a tight loop, thus burning CPU time until the mutex is free again.
To use a spinning mutex, we can add the [91]spin crate as a dependency:
# in Cargo.toml
[dependencies]
spin = "0.5.2"
Then we can use the spinning mutex to add safe [92]interior mutability
to our static WRITER:
// in src/vga_buffer.rs
use spin::Mutex;
...
lazy_static! {
pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer {
column_position: 0,
color_code: ColorCode::new(Color::Yellow, Color::Black),
buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
});
}
Now we can delete the print_something function and print directly from
our _start function:
// in src/main.rs
#[no_mangle]
pub extern "C" fn _start() -> ! {
use core::fmt::Write;
vga_buffer::WRITER.lock().write_str("Hello again").unwrap();
write!(vga_buffer::WRITER.lock(), ", some numbers: {} {}", 42, 1.337).unwrap
();
loop {}
}
We need to import the fmt::Write trait in order to be able to use its
functions.
Safety
Note that we only have a single unsafe block in our code, which is
needed to create a Buffer reference pointing to 0xb8000. Afterwards,
all operations are safe. Rust uses bounds checking for array accesses
by default, so we can't accidentally write outside the buffer. Thus, we
encoded the required conditions in the type system and are able to
provide a safe interface to the outside.
A println Macro
Now that we have a global writer, we can add a println macro that can
be used from anywhere in the codebase. Rust's [93]macro syntax is a bit
strange, so we won't try to write a macro from scratch. Instead, we
look at the source of the [94]println! macro in the standard library:
#[macro_export]
macro_rules! println {
() => (print!("\n"));
($($arg:tt)*) => (print!("{}\n", format_args!($($arg)*)));
}
Macros are defined through one or more rules, similar to match arms.
The println macro has two rules: The first rule is for invocations
without arguments, e.g., println!(), which is expanded to print!("\n")
and thus just prints a newline. The second rule is for invocations with
parameters such as println!("Hello") or println!("Number: {}", 4). It
is also expanded to an invocation of the print! macro, passing all
arguments and an additional newline \n at the end.
The #[macro_export] attribute makes the macro available to the whole
crate (not just the module it is defined in) and external crates. It
also places the macro at the crate root, which means we have to import
the macro through use std::println instead of std::macros::println.
The [95]print! macro is defined as:
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ($crate::io::_print(format_args!($($arg)*)));
}
The macro expands to a call of the [96]_print function in the io
module. The [97]$crate variable ensures that the macro also works from
outside the std crate by expanding to std when it's used in other
crates.
The [98]format_args macro builds a [99]fmt::Arguments type from the
passed arguments, which is passed to _print. The [100]_print function
of libstd calls print_to, which is rather complicated because it
supports different Stdout devices. We don't need that complexity since
we just want to print to the VGA buffer.
To print to the VGA buffer, we just copy the println! and print!
macros, but modify them to use our own _print function:
// in src/vga_buffer.rs
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*)));
}
#[macro_export]
macro_rules! println {
() => ($crate::print!("\n"));
($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
}
#[doc(hidden)]
pub fn _print(args: fmt::Arguments) {
use core::fmt::Write;
WRITER.lock().write_fmt(args).unwrap();
}
One thing that we changed from the original println definition is that
we prefixed the invocations of the print! macro with $crate too. This
ensures that we don't need to import the print! macro too if we only
want to use println.
Like in the standard library, we add the #[macro_export] attribute to
both macros to make them available everywhere in our crate. Note that
this places the macros in the root namespace of the crate, so importing
them via use crate::vga_buffer::println does not work. Instead, we have
to do use crate::println.
The _print function locks our static WRITER and calls the write_fmt
method on it. This method is from the Write trait, which we need to
import. The additional unwrap() at the end panics if printing isn't
successful. But since we always return Ok in write_str, that should not
happen.
Since the macros need to be able to call _print from outside of the
module, the function needs to be public. However, since we consider
this a private implementation detail, we add the [101]doc(hidden)
attribute to hide it from the generated documentation.
Hello World using println
Now we can use println in our _start function:
// in src/main.rs
#[no_mangle]
pub extern "C" fn _start() {
println!("Hello World{}", "!");
loop {}
}
Note that we don't have to import the macro in the main function,
because it already lives in the root namespace.
As expected, we now see a "Hello World!" on the screen:
QEMU printing "Hello World!"
Printing Panic Messages
Now that we have a println macro, we can use it in our panic function
to print the panic message and the location of the panic:
// in main.rs
/// This function is called on panic.
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
println!("{}", info);
loop {}
}
When we now insert panic!("Some panic message"); in our _start
function, we get the following output:
QEMU printing "panicked at `Some panic message', src/main.rs:28:5
So we know not only that a panic has occurred, but also the panic
message and where in the code it happened.
Summary
In this post, we learned about the structure of the VGA text buffer and
how it can be written through the memory mapping at address 0xb8000. We
created a Rust module that encapsulates the unsafety of writing to this
memory-mapped buffer and presents a safe and convenient interface to
the outside.
Thanks to cargo, we also saw how easy it is to add dependencies on
third-party libraries. The two dependencies that we added, lazy_static
and spin, are very useful in OS development and we will use them in
more places in future posts.
What's next?
The next post explains how to set up Rust's built-in unit test
framework. We will then create some basic unit tests for the VGA buffer
module from this post.
Support Me
Creating and [102]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 [103]sponsor me on GitHub, since they
don't charge any fees. If you prefer other platforms, I also have
[104]Patreon and [105]Donorbox accounts. The latter is the most
flexible as it supports multiple currencies and one-time contributions.
Thank you!
__________________________________________________________________
[106]« A Minimal Rust Kernel [107]Testing »
__________________________________________________________________
Comments
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 [108]code of conduct. This comment thread directly maps
to a [109]discussion on GitHub, so you can also comment there if you
prefer.
Instead of authenticating the [110]giscus application, you can also
comment directly [111]on GitHub.
Other Languages
* [112]Chinese (simplified)
* [113]Japanese
* [114]Persian
* [115]Korean
__________________________________________________________________
© 2022. All rights reserved. [116]License [117]Contact
References
Visible links:
1. https://os.phil-opp.com/rss.xml
2. https://os.phil-opp.com/
3. https://os.phil-opp.com/
4. https://os.phil-opp.com/vga-text-mode/#the-vga-text-buffer
5. https://os.phil-opp.com/vga-text-mode/#a-rust-module
6. https://os.phil-opp.com/vga-text-mode/#colors
7. https://os.phil-opp.com/vga-text-mode/#text-buffer
8. https://os.phil-opp.com/vga-text-mode/#printing
9. https://os.phil-opp.com/vga-text-mode/#volatile
10. https://os.phil-opp.com/vga-text-mode/#formatting-macros
11. https://os.phil-opp.com/vga-text-mode/#newlines
12. https://os.phil-opp.com/vga-text-mode/#a-global-interface
13. https://os.phil-opp.com/vga-text-mode/#lazy-statics
14. https://os.phil-opp.com/vga-text-mode/#spinlocks
15. https://os.phil-opp.com/vga-text-mode/#safety
16. https://os.phil-opp.com/vga-text-mode/#a-println-macro
17. https://os.phil-opp.com/vga-text-mode/#hello-world-using-println
18. https://os.phil-opp.com/vga-text-mode/#printing-panic-messages
19. https://os.phil-opp.com/vga-text-mode/#summary
20. https://os.phil-opp.com/vga-text-mode/#what-s-next
21. https://os.phil-opp.com/vga-text-mode/#comments
22. https://en.wikipedia.org/wiki/VGA-compatible_text_mode
23. https://doc.rust-lang.org/std/fmt/#related-macros
24. https://github.com/phil-opp/blog_os
25. https://os.phil-opp.com/vga-text-mode/#comments
26. https://github.com/phil-opp/blog_os/tree/post-03
27. https://os.phil-opp.com/vga-text-mode/#the-vga-text-buffer
28. https://os.phil-opp.com/vga-text-mode/#a-rust-module
29. https://os.phil-opp.com/vga-text-mode/#colors
30. https://os.phil-opp.com/vga-text-mode/#text-buffer
31. https://os.phil-opp.com/vga-text-mode/#printing
32. https://os.phil-opp.com/vga-text-mode/#volatile
33. https://os.phil-opp.com/vga-text-mode/#formatting-macros
34. https://os.phil-opp.com/vga-text-mode/#newlines
35. https://os.phil-opp.com/vga-text-mode/#a-global-interface
36. https://os.phil-opp.com/vga-text-mode/#lazy-statics
37. https://os.phil-opp.com/vga-text-mode/#spinlocks
38. https://os.phil-opp.com/vga-text-mode/#safety
39. https://os.phil-opp.com/vga-text-mode/#a-println-macro
40. https://os.phil-opp.com/vga-text-mode/#hello-world-using-println
41. https://os.phil-opp.com/vga-text-mode/#printing-panic-messages
42. https://os.phil-opp.com/vga-text-mode/#summary
43. https://os.phil-opp.com/vga-text-mode/#what-s-next
44. https://os.phil-opp.com/vga-text-mode/#comments
45. https://en.wikipedia.org/wiki/ASCII
46. https://en.wikipedia.org/wiki/Code_page_437
47. https://en.wikipedia.org/wiki/Memory-mapped_I/O
48. https://web.stanford.edu/class/cs140/projects/pintos/specs/freevga/vga/vgamem.htm#manip
49. https://doc.rust-lang.org/rust-by-example/custom_types/enum/c_like.html
50. https://doc.rust-lang.org/rust-by-example/trait/derive.html
51. https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html
52. https://doc.rust-lang.org/nightly/core/clone/trait.Clone.html
53. https://doc.rust-lang.org/nightly/core/fmt/trait.Debug.html
54. https://doc.rust-lang.org/nightly/core/cmp/trait.PartialEq.html
55. https://doc.rust-lang.org/nightly/core/cmp/trait.Eq.html
56. https://doc.rust-lang.org/1.30.0/book/first-edition/ownership.html#copy-types
57. https://doc.rust-lang.org/rust-by-example/generics/new_types.html
58. https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent
59. https://doc.rust-lang.org/nightly/nomicon/other-reprs.html#reprc
60. https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent
61. https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#lifetime-annotation-syntax
62. https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#the-static-lifetime
63. https://en.wikipedia.org/wiki/Newline
64. https://en.wikipedia.org/wiki/Code_page_437
65. https://www.fileformat.info/info/unicode/utf8.htm
66. https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#dereferencing-a-raw-pointer
67. https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html
68. https://doc.rust-lang.org/reference/tokens.html#byte-literals
69. https://www.fileformat.info/info/unicode/utf8.htm
70. https://en.wikipedia.org/wiki/Volatile_(computer_programming)
71. https://docs.rs/volatile
72. https://doc.rust-lang.org/nightly/core/ptr/fn.read_volatile.html
73. https://doc.rust-lang.org/nightly/core/ptr/fn.write_volatile.html
74. https://semver.org/
75. https://doc.crates.io/specifying-dependencies.html
76. https://doc.rust-lang.org/book/ch10-01-syntax.html
77. https://doc.rust-lang.org/nightly/core/fmt/trait.Write.html
78. https://doc.rust-lang.org/core/result/enum.Result.html#method.unwrap
79. https://rustc-dev-guide.rust-lang.org/const-eval.html
80. https://github.com/rust-lang/rfcs/pull/2345
81. https://doc.rust-lang.org/reference/const_eval.html#const-functions
82. https://docs.rs/lazy_static/1.0.1/lazy_static/
83. https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable
84. https://internals.rust-lang.org/t/pre-rfc-remove-static-mut/1437
85. https://doc.rust-lang.org/book/ch15-05-interior-mutability.html#keeping-track-of-borrows-at-runtime-with-refcellt
86. https://doc.rust-lang.org/nightly/core/cell/struct.UnsafeCell.html
87. https://doc.rust-lang.org/book/ch15-05-interior-mutability.html
88. https://doc.rust-lang.org/nightly/core/marker/trait.Sync.html
89. https://doc.rust-lang.org/nightly/std/sync/struct.Mutex.html
90. https://en.wikipedia.org/wiki/Spinlock
91. https://crates.io/crates/spin
92. https://doc.rust-lang.org/book/ch15-05-interior-mutability.html
93. https://doc.rust-lang.org/nightly/book/ch19-06-macros.html#declarative-macros-with-macro_rules-for-general-metaprogramming
94. https://doc.rust-lang.org/nightly/std/macro.println!.html
95. https://doc.rust-lang.org/nightly/std/macro.print!.html
96. https://github.com/rust-lang/rust/blob/29f5c699b11a6a148f097f82eaa05202f8799bbc/src/libstd/io/stdio.rs#L698
97. https://doc.rust-lang.org/1.30.0/book/first-edition/macros.html#the-variable-crate
98. https://doc.rust-lang.org/nightly/std/macro.format_args.html
99. https://doc.rust-lang.org/nightly/core/fmt/struct.Arguments.html
100. https://github.com/rust-lang/rust/blob/29f5c699b11a6a148f097f82eaa05202f8799bbc/src/libstd/io/stdio.rs#L698
101. https://doc.rust-lang.org/nightly/rustdoc/write-documentation/the-doc-attribute.html#hidden
102. https://os.phil-opp.com/status-update/
103. https://github.com/sponsors/phil-opp
104. https://www.patreon.com/phil_opp
105. https://donorbox.org/phil-opp
106. https://os.phil-opp.com/minimal-rust-kernel/
107. https://os.phil-opp.com/testing/
108. https://www.rust-lang.org/policies/code-of-conduct
109. https://github.com/phil-opp/blog_os/discussions/categories/post-comments?discussions_q=%22VGA%20Text%20Mode%22%20in%3Atitle
110. https://giscus.app/
111. https://github.com/phil-opp/blog_os/discussions/categories/post-comments?discussions_q=%22VGA%20Text%20Mode%22%20in%3Atitle
112. https://os.phil-opp.com/zh-CN/vga-text-mode/
113. https://os.phil-opp.com/ja/vga-text-mode/
114. https://os.phil-opp.com/fa/vga-text-mode/
115. https://os.phil-opp.com/ko/vga-text-mode/
116. https://github.com/phil-opp/blog_os#license
117. https://os.phil-opp.com/contact/
Hidden links:
119. https://os.phil-opp.com/vga-text-mode/#the-vga-text-buffer
120. https://os.phil-opp.com/vga-text-mode/#a-rust-module
121. https://os.phil-opp.com/vga-text-mode/#colors
122. https://os.phil-opp.com/vga-text-mode/#text-buffer
123. https://os.phil-opp.com/vga-text-mode/#printing
124. https://os.phil-opp.com/vga-text-mode/#try-it-out
125. https://os.phil-opp.com/vga-text-mode/#volatile
126. https://os.phil-opp.com/vga-text-mode/#formatting-macros
127. https://os.phil-opp.com/vga-text-mode/#newlines
128. https://os.phil-opp.com/vga-text-mode/#a-global-interface
129. https://os.phil-opp.com/vga-text-mode/#lazy-statics
130. https://os.phil-opp.com/vga-text-mode/#spinlocks
131. https://os.phil-opp.com/vga-text-mode/#safety
132. https://os.phil-opp.com/vga-text-mode/#a-println-macro
133. https://os.phil-opp.com/vga-text-mode/#hello-world-using-println
134. https://os.phil-opp.com/vga-text-mode/#printing-panic-messages
135. https://os.phil-opp.com/vga-text-mode/#summary
136. https://os.phil-opp.com/vga-text-mode/#what-s-next
|