I usually understand code by fiddling around, or by inserting printfs here and there, but this time around, that isn’t possible, since the end product is an OS, so any suggestions on how to tackle this problem?
This isn’t really specific to an OS, but to any large and/or complex codebase. This remark:
I usually understand code by fiddling around, or by inserting printfs here and there ….
(and from comments)
… without a way to print messages for me, since that’s how I understand code
Adding print statements can be a good way to build your intuition about how something works, either when you’re starting out, or it’s too much trouble to get a debugger to stop in the code you’re worried about. It’s a good technique.
But, it’s not the only technique, isn’t always available (as you’ve found), and you can’t rely on using it as a crutch.
When you can’t use print statements to help you follow the execution path, or see intermediate values, you just need to think – hard! – or write it down on paper, and figure it out yourself.
The intuition you built with the help of print statements in easier settings should help with this. Think if those print statements as a scaffolding, which helped develop your skills and intuition, to the point where you don’t need the scaffolding any more.
Obviously there’s no harm in going back to using it in problems where it is available, if it’s quicker. But you’ll be choosing it as the right tool for a particular job, from your whole toolkit – not relying on it as the only tool you know.
Anyway, that’s a general essay on understanding code. The particular issues around large codebases are that you also have to understand structure. That’s a different skill, which you can only really get by studying large code bases. Particularly, if there aren’t a lot of comments, you have to infer structure and intent from code, which is even harder.
Start by breaking it up. What components do you expect an OS to have? One or more filesystems, a scheduler, drivers, what else? Can you see code modules or libraries corresponding to those large-scale components?
If so pick one (if not, you’ve got real troubles). Have a brief look at the top-level structure and interface of that component – can you see what it’s intended to do, or how it’s intended to be used? If so: good, that’s your high-level view of one component, now onto the next. If not: see if you can find some other code using this component, maybe it’ll demonstrate the author’s intent.
Now, you have a high-level overview of the component parts and some idea how they fit together. If you need more detail about something in particular, dive into the implementation. If you need to eg. write a driver, read a few existing drivers, try to understand their differences and similarities, pick the one most similar to what you need and start from there.