HNHacker News
TopNewBestAskShowJobs

phil-opp

708 karma · joined October 25, 2015

submissionscomments
phil-opp··on Ask HN: Who wants to be hired? (April 2020)
Location: Karlsruhe, Germany

Remote: yes

Willing to relocate: no

Technologies: Rust, systems programming, operating systems, embedded, open-source software

Resume: https://www.linkedin.com/in/phil-opp/

Email: job@phil-opp.com

After finishing my computer science master in 2019 and focusing the last few months on open-source software, I'm looking for a job now. I specialized on operating systems, embedded, and systems programming. I'm writing a blog about OS development in Rust at https://os.phil-opp.com/.

phil-opp··on Writing an OS in Rust: Async/Await
Great to hear that! I also found the official documentation a bit short on background information, so I decided to write my own explanation rather than link to something existing. Given that the async/await implementation is still quite young, I'm sure that the official docs will improve over time.
phil-opp··on Allocator Designs
For single threaded programs you can e.g. use a Mutex implementation that only disables interrupts for the critical section (https://docs.rust-embedded.org/book/concurrency/#mutexes). Rust does not forbid you from doing these potentially memory unsafe things, it just requires you to use an `unsafe` block for this.

The reason for forbidding these trait implementations is compatibility. For example, the dependency might add an implementation for the trait at some point, so that there would suddenly be conflicting implementations after a semver-compatible update. You can still implement the trait by creating a wrapper type.

phil-opp··on Allocator Designs
Thanks! I created them using draw.io (https://about.draw.io/).
phil-opp··on Allocator Designs
> I'll prepare an update to fix this.

Update in https://github.com/phil-opp/blog_os/pull/738 and https://github.com/phil-opp/blog_os/pull/739

phil-opp··on Allocator Designs
On the other hand, these strict requirements make sure that the global allocator is thread-safe. Also, I only count two compiler errors mentioned in this post and both have a valid reason (you can't modify values behind an immutable reference; you can't implement traits for types of other crates).
phil-opp··on Allocator Designs
> The overflow scenario should be treated the same as the out-of-memory scenario and also return null.

Good point! I'll prepare an update to fix this.

> It can do better, in `dealloc` you can use `ptr` and `layout` to check if the allocation is at the end of the allocated region. If it is, `bump.next` can be reduced by `layout.size()`. This is optimal for lifo/stack style allocation patterns.

You're right. I already created a PR [1] for this shortly after publishing the article, but it seems like I forgot to merge it. There should be now an additional "Fixing the Test?" section that talks about freeing the last allocation.

[1]: https://github.com/phil-opp/blog_os/pull/722

phil-opp··on Writing an OS in Rust: Heap Allocation
Thanks so much!

Yes, I'm eagerly awaiting https://github.com/rust-lang/rust/pull/58457 and your follow-up https://github.com/rust-lang/rust/pull/60703. AFAIK, a global allocator is still required with them (so we still have to initialize it), but it allows us to choose a different allocator for some collections, e.g. to avoid deadlocks in interrupt handlers.

phil-opp··on Utterances: Lightweight comments widget built on GitHub issues
An interesting fact about this is that the author is on the Microsoft docs team, which uses a similar system for docs.microsoft.com [1]. So I don't think that GitHub has a problem with this approach.

For an example of Microsoft's system in use, see the "Feedback" section on [2].

[1]: https://github.com/MicrosoftDocs/feedback/issues/396

[2]: https://docs.microsoft.com/en-us/azure/azure-functions/funct...

phil-opp··on Writing an OS in Rust: Testing
The try_reserve method is exposed on all collection types, e.g. [1],but it is still unstable.

[1]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.try...

I did not write my own stdlib since I didn't need it yet (the blog has no heap allocation yet). And I think infallible allocation is good enough for the beginning.

But I think it should be possible to create a fallible wrapper library around liballoc quite easily. We only need an appropriate call to try_reserve for every wrapped function. For example, `push` would do a `try_reserve(1)` before calling the push function of liballoc.

phil-opp··on Writing an OS in Rust: Testing
There is some minimal support for fallible allocation through the try_reserve method on various collections. See https://github.com/rust-lang/rust/issues/48043 for more information.

Other than that, there is ongoing work for custom allocator support in collections: https://github.com/rust-lang/rust/issues/42774. Maybe that's what you meant with tracking the allocators at the type level?

It's of course also possible to create own fallible collection types, for example as a wrapper around the normal liballoc and try_reserve.

phil-opp··on Writing an OS in Rust: Testing
Great to hear that!
phil-opp··on Writing an OS in Rust: Testing
It's the first time that I hear about Rust on medical devices. Sounds really interesting! I'm glad to hear that my blog was useful to you!

> I have been trying to hack at the utest crate and get on-device integration tests running again for quite some time... good to know there is movement on the custom test runner front :)

The custom test framework feature of Rust was silently introduced in https://github.com/rust-lang/rust/pull/53410 and I didn't know that it even existed a month ago. But it works really well and seems like a good solution for your problem.

phil-opp··on Writing an OS in Rust: Testing
Author here. This post is a rewrite of the previous Unit Testing [1] and Integration Tests [2] posts. It creates a custom test framework that runs test functions inside QEMU, so that they run in a realistic environment (compared to running on the host system).

[1]: https://os.phil-opp.com/unit-testing/ [2]: https://os.phil-opp.com/integration-tests/

The new test framework directly works with `cargo xtest`, which brings the project closer to using standard cargo programs for building, testing, and running. The last remaining step is the `std` Aware Cargo RFC [3]. When the RFC is merged and implemented, cargo-xbuild will no longer be needed and the familiar cargo build, cargo run, and cargo test commands will just work, including IDE integration.

[3]: https://github.com/rust-lang/rfcs/pull/2663

phil-opp··on Writing an OS in Rust: Advanced Paging
Thanks for the detailed reply!

I don't like the traditional GRUB approach because it leaves so much work for the kernel (assembly entry point, stack setup, creation of new page tables, etc). Why not treat the kernel like any normal application and load it like a normal ELF loader? This has the following advantages:

- Each loadable program segment is mapped at its specified virtual address. To create a higher-half kernel, just set the desired virtual address in your linker script. If you don't need a higher-half kernel yet, simply use the default linker script. This is what we do at the moment since we don't have any userspace yet. Like in normal applications, the specified physical addresses do not matter.

- There's no assembly code required at the entry point. You can start directly in Rust because there's already a stack and everything lives at its expected address.

- Less startup overhead, since the page tables are only set up once in the bootloader. With the GRUB approach, the kernel needs to recreate the page tables since GRUB only created rudimentary mappings.

Like you said, the disadvantage is that the kernel doesn't know its physical addresses. But it can still find them out by traversing the page tables.

Overall, I think it's worth it because it makes the startup process much easier from the kernel's perspective. I believe that this is especially important for a teaching project because readers shouldn't have to understand assembly, linker scripts, virtual/physical addresses, higher half kernels, and page tables before they can even boot their Rust kernel. For people that don't like the bootloader's decisions we can add more configuration options or they can use a custom version of the bootloader (e.g. the nebulet project does this [1]).

[1]: https://github.com/nebulet/nebulet/blob/c0f3c993a87e72a95f85...

phil-opp··on Writing an OS in Rust: Advanced Paging
It is possible to access other address spaces, it is just a bit more complicated:

- Set the recursive entry of the active level 4 table to the (recursive) level 4 table of the other address space. - Now the recursive addresses point to the other address space, so you can access its tables freely. - The difficult part is to switch back to the active address space again because can no longer access the original level 4 entry. This can be solved with a temporary mapping to the active level 4 table or by using different level 4 entries for the recursive mapping of active and inactive address spaces.

However, I'm no longer sure whether recursive page tables are a good choice for the blog since they're complicated, difficult to learn and not portable. See my reply to DSMan.

phil-opp··on Writing an OS in Rust: Advanced Paging
Thanks for the feedback!

I chose recursive page tables because the underlying mapping is very simple as it only requires to map a single level 4 page table entry. Thus, the bootloader does not need to force any address space layout on the kernel. If the kernel wants to use a different mapping scheme, it can use the recursive page table to perform that mapping and then unmap a single entry to undo the recursive mapping.

That being said, I like your proposal to map the complete physical address space to some higher-half location. It would allow to introduce a simple `phys_to_virt` function, which completely avoids the bitwise operations required for recursive page tables. It would also make this blog post much easier, which is always a good thing.

The question is how we can support that approach in the `bootloader` and `x86_64` crates without breaking users who wish to continue using recursive paging. I opened an issue in the blog repository with some ideas: https://github.com/phil-opp/blog_os/issues/545

phil-opp··on Writing an OS in Rust: Introduction to Paging
I assume backwards compatibility. It's a much smaller change to existing operating systems than to create a completely new page table format.
phil-opp··on Writing an OS in Rust: Introduction to Paging
Thanks! Fixed in https://github.com/phil-opp/blog_os/commit/f423c068fec803725...
phil-opp··on Writing an OS in Rust: Introduction to Paging
They were created with draw.io (https://about.draw.io/).
phil-opp··on Writing an OS in Rust: Handling Exceptions
No worries! Thanks for correcting it.
phil-opp··on Writing an OS in Rust: Handling Exceptions
Why is this marked as dupe? Yes, there were already posts about exception handling ( "Catching Exceptions" [1]), "Better Exception Messages" [2], and "Returning from Exceptions" [3]). These posts used naked functions and a lot of inline assembly for exception handling.

This is a new post that I published today. It uses the new `x86-interrupt` calling convention [4] instead of naked functions, which is much easier.

[1]: https://os.phil-opp.com/catching-exceptions.html

[2]: https://os.phil-opp.com/better-exception-messages.html

[3]: https://os.phil-opp.com/returning-from-exceptions.html

[4]: https://github.com/rust-lang/rust/pull/39832

phil-opp··on Writing an OS in Rust: Returning from Exceptions
I have some vague ideas for the next posts, but no overarching design. At our current level, there aren't many design choices anyway. It will get much more intesting when we create a threading/concurrency model in the future.
phil-opp··on Writing an OS in Rust: Returning from Exceptions
I definitely plan to integrate the futures library in the kernel somehow. We just need some asynchronous things and threads first :).
phil-opp··on Writing an OS in Rust: Returning from Exceptions
> STI; LRET works too, and it's faster for stupid reasons.

Interesting, didn't know that.

> This is an entirely ordinary sequence of events. [...] It turns out that this actually causes a problem on AMD CPUs

Sometimes I think that the hardware designers intentionally made kernel development complicated :D. Thanks for the heads-up!

phil-opp··on Writing an OS in Rust: Returning from Exceptions
Thank you so much!
phil-opp··on Writing an OS in Rust: Better Exception Messages
> What you probably do not want to do is old-fashioned swap space on disk

Yeah, that's what I meant. I definitely plan to use the various lazy loading tricks for mmaped files, loading executables, etc. But first we need a disk driver :D.

phil-opp··on Writing an OS in Rust: Better Exception Messages
Thanks so much!
phil-opp··on Writing an OS in Rust: Better Exception Messages
> This is the easy part - capturing exceptions. Handling them, and returning control with the machine state intact, is the messy part.

Yeah, I plan to tackle this in the next post: Compiling without red zone, saving/restoring all relevant registers, and disabling SSE instructions in the kernel to speed things up.

> (It's late to be writing an operating system with demand paging. The most paging ever accomplishes is the illusion of 2X the RAM. RAM is cheap today. The price you pay for paging is random large delays in your program. Is it worth it to save some RAM cost? Mobile devices and real-time OSs don't demand-page.)

I'm not sure if I really want to implement demand paging, I've just used it as an example. In the next post we will only implement lazy page mapping, so that we no longer need to preinitialize the complete kernel heap.

phil-opp··on Writing an OS in Rust: Better Exception Messages
See https://doc.rust-lang.org/book/functions.html#diverging-func... and https://doc.rust-lang.org/reference.html#diverging-functions
Page 1 of 2Next →