-
-
Notifications
You must be signed in to change notification settings - Fork 606
Intra Kernel Initialization and Dependencies
this should eventually be more structured, but lets start from somewhere.
The constructors of static C++ objects in the kernel are not run before the kernel starts (as one might expect in a C++ program), but are rather all put into the .init_array section of the ELF (see loader.ld for both x64 and AArch64), and these include of course the constructors needed for the C++ runtime as well and the external dependencies. They are sorted by priority order. During boot time, quite early after the handover from the assembly code to C, all these constructors are run one by one in the premain() function (loader.cc):
for (auto init = inittab.start; init < inittab.start + inittab.count; ++init) {
(*init)();
}
The order in which these constructors are run is determined by the "init_priority" attribute of the object, or "constructor" attribute for an object-less function. These priority values are defined as enums in include/osv/prio.hh. Priorities less than 101 are reserved for the C++ runtime itself. For example, in that file, "cpus" comes before "sched". This means the "cpus" vector is constructed (as an empty vector, but still requires construction), before the smp_init(), which is a constructor function with priority "sched", is called to add more cpus to this list. The order in the list sometimes expresses a real dependency between items, but that is not explicit.
This technique can be used to organize the initialization sequence for subsystems which are modeled as a single class, and which depend on each other based on full class instance construction.
In other cases, single features (a single class method, a normal function etc) depend on complete class constructions, and for these the list does not provide a good reference: they have to be figured out on a case by case basis. In other cases, single features (a single class method, a normal function etc) depend on other single features (a single class method or normal function having been successfully called, etc) and they also have to be figured out on a case by case basis.
Some examples of this follow:
early console output can be obtained with debug_early, debug_early_entry, debug_early_u64. These APIs provide non-buffered, straight to the device output, and are and should remain callable very early (as in directly from the pre-C assembly in boot.S and similar).
These APIs rely on the arch_early_console class instance, which is provided by each architecture in arch/$(arch)/early-console.hh arch/$(arch)/early-console.cc
Those classes need to provide a "write" method, which should do nothing else than writing the input on the console, and should require no prior dependencies on other classes construction.
Console output initialization occurs in 3 stages currently.
First we have the arch_early_console mentioned above and the debug_early APIs, which are used for things that really need to go out on the console early and immediately.
Then we have the initialization priority "console", whose constructor is run inside premain, and that priority initializes the console-multiplexer.
Then we have the main_cont call of arch_setup_console, which sorts out the kernel console options to choose which console/s to initialize and use, and immediately afterwards we have console_init(), which creates the character tty console device and starts the console multiplexer, which starts polling the console for input as well via the dedicated thread.
APIs providing console output at this later stages include debug_ll, debug_write, and of course libc functions writing to stdout, and these can buffer the output as well.
mutexes require threads to be available (locking uses sched::thread::current()) Attempts to lock before s_current is set by the cpu[0] when initializing the first thread (thread::thread()) will cause random behavior if called before TLS initialization (as s_current is a __thread variable), and will instead potentially loop forever on AArch64 (x64 probably not?) if called after setup_tls but before threads are available.
Backtraces use mutexes in ELF (so needs threads (s_current)) and also need s_program to be initialized. They are therefore available in main_cont just after new program::program().