Wednesday, October 21, 2009

Section 7.4.  Boot-Time Initialization Routines










7.4. Boot-Time Initialization Routines


Most initialization routines have two interesting properties:


  • They need to be executed at boot time, when all the kernel components get initialized.

  • They are not needed once they are executed.


The next section, "xxx_initcall Macros," describes the mechanism used to run initialization routines at boot time, taking into consideration these properties as well as priorities among modules. The later section "Memory Optimizations" shows how routines and data structures that are no longer needed can be freed at link time or runtime by using smart tagging.



7.4.1. xxx_initcall Macros







The early phase of the kernel boot consists of two main blocks of initializations:


  • The initialization of various critical and mandatory subsystems that need to be done in a specific order. For instance, the kernel cannot initialize a PCI device driver before the PCI layer has been initialized. See the later section "Example of dependency between initialization routines" for another example.

  • The initialization of other kernel components that do not need a strict order: routines in the same priority level can be run in any order.


The first part is taken care of by the code that comes before do_initcalls in Figure 5-1 in Chapter 5. The second part is taken care of by the invocation of do_initcalls shown close to the end of do_basic_setup in the same figure. The initialization routines of this second part are classified based on their role and priority. The kernel executes those initialization routines one by one, starting from the ones placed in the highest-priority class (core_initcall). The addresses of those routines, which are needed to invoke them, are placed in the .initcallN.init memory sections of Figure 7-3 by tagging them with one of the xxx_initcall macros in Table 7-1.


The area used to store the addresses of the routines tagged with the xxx_initcall macros is delimited by a starting address (_ _initcall_start) and an ending address (_ _initcall_end). In the excerpt of the do_initcalls function that follows, you can see that it simply takes the function addresses one by one from that area and executes the functions they point to:



static void _ _init do_initcalls(void)
{
initcall_t *call;
int count = preempt_count( );

for (call = _ _initcall_start; call < _ _initcall_end; call++) {
... ... ...
(*call)( );
... ... ...
}
flush_scheduled_work( );
}



The routines invoked by do_initcalls are not supposed to change the preemption status or disable IRQs. Because of that, after each routine execution, do_initcalls checks whether the routine has made any changes, and adjusts the preemption and IRQ status if necessary (not shown in the previous snapshot).


It is possible for the xxx_initcall routines to schedule some work that takes place later. This means that the tasks handled by those routines may terminate asynchronously, at unknown times. The call to flush_scheduled_work is used to make do_initcalls wait for those asynchronous tasks to complete before returning.


Note that do_initcalls itself is marked with _ _init: because it is used only once within do_basic_setup during the booting phase, the kernel can discard it once the latter is done.


_ _exitcall is the counterpart of _ _initcall. It is not used much directly, but rather via other macros defined as aliases to it, such as module_exit, which we introduced in the section "Module Initialization Code."



7.4.1.1. Example of _ _initcall and _ _exitcall routines: modules





I said in the section "Module Initialization Code" that the module_init and module_exit macros, respectively, are used to tag routines to be executed when the module is initialized (either at boot time if built into the kernel or at runtime if loaded separately) and unloaded.


This makes a module the perfect candidate for our _ _initcall and _ _exitcall macros: in light of what I just said, the following definition from include/linux/init.h of the macros module_init and module_exit should not come as a surprise:



#ifndef MODULE
... ... ...
#define module_init(x) _ _initcall(x);
#define module_exit(x) _ _exitcall(x);

#else
... ... ...
#endif



module_init is defined as an alias to _ _initcall for code statically linked to the kernel: its input function is classified as a boot-time initialization routine.


module_exit follows the same scheme: when the code is built into the kernel, module_exit becomes a shutdown routine. At the moment, shutdown routines are not called when the system goes down, but the code is in place to allow it.[*]

[*] User-Mode Linux is the only architecture that actually makes use of shutdown routines. It does not use _ _exitcall macros, but defines its own macro, _ _uml_exitcall. The home page of the User-Mode Linux project is http://user-mode-linux.sourceforge.net.




7.4.1.2. Example of dependency between initialization routines

net_dev_init was introduced in Chapter 5. Device drivers register with the kernel with their module_init routine, which, as described in the section "The Big Picture" in Chapter 6, registers its devices with the networking code. Both net_dev_init and the various module_init functions for built-in drivers are invoked at boot time by do_initcalls. Because of that, the kernel needs to make sure no device registrations take place before net_dev_init has been executed. This is enforced transparently thanks to the marking of device driver initialization routines with the macro device_initcall (or its alias, _ _initcall), while net_dev_init is marked with subsys_initcall. In Figure 7-3, you can see that subsys_initcall routines are executed earlier than device_initcall routines (the memory sections are sorted in priority order).




7.4.1.3. Legacy code








Before the introduction of the set of xxx_initcall macros, there was only one macro to mark initialization functions: _ _initcall. The use of only a single macro created a heavy limitation: no execution order could be enforced by simply marking routines with the macro. In many cases, this limitation is not acceptable due to intermodule dependencies, and other considerations. Therefore, the use of _ _initcall could not be extended to all the initialization functions.


_ _initcall used to be employed mostly by device drivers. For backward compatibility with pieces of code not yet updated to the new model, it still exists and is simply defined as an alias to device_initcall.


Another limitation, which is still present in the current model, is that no parameters can be provided to the initialization routines. However, this does not seem to be an important limitation.














No comments: