{% raw %}
Philipp Oppermann

There is some interesting discussion on /r/programming and /r/rust.

Matteo Meli

Hi Philipp,

First of all thanks a lot for sharing this series of article. Just a question: do you have an idea why doubling the stack size would not be sufficient to avoid the silent stack overflow you mentioned? To make my code work I had to triple it...

Philipp Oppermann

What are you doing in your main? Maybe there is something stack intensive before the `test_paging` call? My main function looks like this. I removed most of the example code from the previous posts, so maybe that's the reason..

Matteo Meli

You're right! The main difference in my code is that I set up interrupts. It must be that. Thanks

Philipp Oppermann

Nice! I assume that you have some experience with OS development?

Matteo Meli

Not really much actually, but trying to work my way through. Currently stuck trying to load the kernel in the higher half... Do you have any references to point me to?

Philipp Oppermann

Only the stuff from the OSDev wiki but I think that you are aware of it already.

I would link the Rust code to the higher half but keep all startup assembly identity mapped. Then map the Rust code from the long mode assembly and jump to it.

Maybe it's even possible to use the same linker script as before since rustc generates position independent code by default (AFAIK).

Kiley Owen

Is there a typo in the code in the huge pages section?

Philipp Oppermann

Maybe... What's the problem exactly?

FreeFull

`TableEntryFlags` is mentioned exactly once. Where does it come from?

Philipp Oppermann

It should be `EntryFlags`. Thanks for reporting, I pushed an update.

Ted Meyer

In order to decide when a page table should be freed, you can use the 52nd bit in the first 9 entries, to keep a score of how many present entries there are. It might be pretty bad for caching though.

Philipp Oppermann

I like the idea. Alternatively, we could use bits 52 to 61 of the first entry. What's your concern about caching?

Ted Meyer

My main concern with using all the bits in the first entry is that if ever in the future you want to go back and add more things, it would need to be changed.

As far as caching goes, it would depend on how big a cache line is, but if it is smaller than 9 table entries then there could be multiple cache misses on a single update (theoretically 9 bits could be changed for one addition / deletion of a page). Obviously not a _huge_ deal, but I think it's worth pointing out.

Stephen Checkoway

Neither choice seems like it works. Bits 62:MAXPHYADDR (where MAXPHYADDR is at most 52) are reserved and supposed to be set to 0. However, bits 11:9 appear to be free at every level of the page table hierarchy.

Somewhat annoyingly, 10 bits are needed since 513 values need to be represented. Thus one could use three bits from each of the first four entries.

x86-64 has a 64-byte cache line size so the four accesses do fit in a single cache line.

Ryan Campbell

Hey Phillip, started doing this a couple days ago and was able to make it this far. Unfortunately I am now having some compilation issues that appear to be a result of the x86 crate. I get "error: 'raw::Slice' does not name a structure" when compiling raw-cpuid, a dependency for x86. Thoughts?

edit: Ah-ha! I see you mentioned this a few days ago . Thanks for everything!

Philipp Oppermann

Yeah, the `raw::Slice` struct was deprecated and removed in the latest nightlies. However, the current version of the raw-cpuid crate still depends on it. The author is aware of the issue and will publish a new version in the next few days. Until then, you can try an older nightly as a workaround.

Ryan Campbell

Yep, I got it working just editing those couple of lines. Will pull new update tomorrow. Thanks!

Can you explain me this self referencing trick for Page tables ?

Philipp Oppermann

Of course! I tried to do give a short overview in the Recursive Mapping section. Which part of it is unclear? Or do you have a specific question?

I didn't understand how looping once you can access P1 entry..
Can you give a short example..

Philipp Oppermann

The virtual->physical address calculation is done in hardware, which expects 4 levels of page tables. In order to access the entries of a P1 table, we need to remove one level of translation. The trick is that all page tables have the (almost) same format, independent of the table level. So the CPU doesn't see a difference between e.g. a P4 and a P3 table.

This allows us to implement the recursive mapping trick. We lead the CPU to believe that the P2 table is the P1 table and that the P3 table is the P2 table. Thus we end up on the memory page of the P1 table and are able to modify its entries.

Likewise, the CPU interprets the P4 table as P3 table. But which table do we use as P4 table then? Well, we use the same P4 table as before. So our P4 is used twice by the CPU: At the first time, it is interpreted as a P4 table and at the second time as a P3 table.

Now only one piece is missing: We need a special P4 entry, which points to the its own table again. This way we can construct a virtual address for which the P4 table is used twice (once as P4 and once as P3).

I hope it helps :).

Got it.. thanks

Hoschi

Hi everybody!

When I try to compile the code from this article, I get the following error:

error: private trait in public interface (error E0445)

It's fired here: impl<l> Table<l> where L: HierarchicalLevel {...}

This seems to be one of those errors, caused by the fact, Rust is still under development and some features change from time to time.

It compiles if I make the trait public:
pub trait HierarchicalLevel: TableLevel {....}

I wrote the code on my own and to make sure this is not an error caused by myself, I cloned the github repo and tried to compile it with the same result.

Maybe one of the more skilled OS devs here has an idea if it is a problem to mark the trait as public.

Thanks in advance!

Christian

Philipp Oppermann

Thanks a lot for reporting this! I thought that we've fixed this issue, but it seems like we've messed up somehow. I opened this issue for it.

That said, I think that a public HierarchicalLevel is the correct solution. It shouldn't be a problem since you can't do anything bad by implementing HierarchicalLevel (e.g. you still can't construct a `Table`).

Hello,

You said that the P4 recursive loop must be set before paging is enabled.
But I wonder - the memory is currently identity mapped, so what difference is there in P4_table address in with/without paging?

Philipp Oppermann

Hmm, good point! The P4 table is part of the identity mapped area, so it should work even if we do it after enabling paging.

That sentence was added in #246, but I don't know the reason anymore. I just tested it and it still works if I do the recursive mapping after paging is enabled. So maybe we should revert that PR…

Thanks for the clarification :)

Evan Higgins

Hi Phil,
First off, I just want to say thanks for this tutorial, it's really great.
I've run into an issue on this section when implementing the memory::paging::test_paging function. Specifically, with the test for unmap. If everything up to and including the unmap function call is implemented it operates as expected and unmap panics, but if the corresponding println! is added the kernel goes into a boot loop. Based on what you have said in this section that seems to indicate a page fault, but I don't really understand why the existence of that println! causes it. It's more confusing because execution never actually reaches that macro, so it's just the inclusion of that line in the code that triggers it. To make things ever weirder, it quits boot-looping whenever I add a second (or third, etc.) instance of that println! call.

So my question is: Do you have any insight as to what could be the cause of this? Or, if nothing else, some avenues I could pursue for debugging?

Thanks

Matthew

Hey Philipp, regarding the Testing and Bugfixing section, can you explain why only a P2 and a P1 table is created after running map_to? Why isn't a P3 table created?

Matthew

I think I figured it out. Is it because we already mapped index 0 of P4 to a P3 table from boot.asm?

Philipp Oppermann

Yes, exactly.

Warren

I've implemented paging and everything seems to work correctly, but reading from an unmapped page causes a page fault even without flushing the translation lookaside buffer. I also tried it out with your repo and it exhibited the same behavior after I commented out the call to tlb::flush. Is there some QEMU setting that I need to change?

Philipp Oppermann

If the address isn't cached in the TLB, it works without a flush. The cache has limited space, so some translations are evicted when space runs out. Maybe try accessing the to be unmapped page right before unmapping it?

Hi! First of all; This is an amazing project, thank you very much for your time and effort, and the work put down into doing this! I am a complete beginner in Rust, and follow this guide mainly to get a better grasp on operating system "basics". Your tutorials have been very good at explaining the code snippets in a simple manner, but sometimes it gets a bit confusing as to where functions and other code snippets should be placed in the file tree.. If you find the time, could you write the path/filename of where each code snippet goes? I'm sure it would be very helpful to other people who, like me, are not yet intuitive about "what parts go where".

Philipp Oppermann

Thanks for the suggestion! I opened https://github.com/phil-opp/blog_os/issues/382 on Github to track this issue.

Hi Phil, when I try to add the test code to test the unmap with the lines of code below, looks like the system can't boot up, and qemu just keeps rebooting. But if I remove this line. the code works perfectly. Could you please help to have a check.

println!("{:#x}", unsafe { *(Page::containing_address(addr).start_address() as *const u64) });

The issue has been solved, keep rebooting is caused by the page fault, and the root cause is some index is misused. After correct the index, the issue is gone.

Hey Phil. This is a fairly basic question compared to some of the other comments here and I probably am missing something simple. When the Entry::pointed_frame function is made, it uses 0x000fffff_fffff000 to mask bits 12-51. Why that number? It doesn't only mask those bits. What am I missing?

Philipp Oppermann

It clears the lowest and highest 12 bits. So bits 12 to 51 should be the only bits set afterwards.

I realize now I was messing up maths. I was thinking each digit in hex being 16 bits instead of 4 bits. Thanks

Jack Halford

hi phil, quick question

It seems that as soon as I enable x86 paging the VGA buffer is not accessible anymore (because 0xb8000 is not identity mapped yet?). So essentially the test_paging routine doesnt print anything... so my thinking tells me the identity map is the first thing to do after enabling paging, yet its the subject of the next chapter, am I not getting something?

Jack Halford

I didn't realise the boot.asm p2 p3 p4 setup was already a preliminary identity paging with huge pages, that's awesome! I'm working on x86 protected mode so I only have p1,p2 and my huge pages are 4MiB.

Philipp Oppermann

Yeah, paging is enabled since entering longmode (with an identity mapping). Do you have any reason for only choosing protected mode?

{% endraw %}