 Hello everyone, my name is Milaine Josson, I worked at Colabora since January and I will try to demystify Linux kernel init calls. First, an introduction with the purpose of init calls and how to debug them. Init calls have been implemented early in Linux kernel development around version 2.4. There is no big changes since, except in 2018 with the tracing report added by Stephen Rosdett. We will see an example of how to trace init calls in next slide. But first, what is the purpose of init calls? The purpose is to allow you to call functions at different stages during the boot process. For that, you can find helpers that define the stages. For example, you have pure init call, core init call, post-core init call, and others. Here, it's the distribution in last v5.8 kernel of the different init calls used to define functions. You can see that the most used is subsys underscore init call and also arc init call and device init call. So now, let's see an example. We create a simple function foo underscore init that is just returning 0. For this example, we declare it using post-core init call helper. Thanks to that, this foo init function will be executed at the post-core stage. In fact, using init calls, it's like marking the execution of a function at a specific level of your kernel. That's why the name of the helpers try to reflect the order of the execution. For example, post-core init call function will be called after-core init call once. About debugging init calls, it has been introduced in version 2.5. In fact, you just have to put init call underscore debug keyword in your command line. You will have debug information printed on your console. As you can see, for each init call, you will have two lines printed, one for its start and another one for its end. Thanks to that, you will be able to know the time spent on its execution. This is a nice information to have if you have to perform some boot time improvements, for example. The issue with that is that it increases the boot time during your debug because all these functions declared by all init call level will print these two lines. So you will have a lot of stuff printed on your console. That makes your boot time increasing a lot. Moreover, it's difficult to retrieve specific data from it as it's printed on your console. This point is improved by using ftrace. Here, it's an example on how to do that. First, you mount debug fs. Then in tracing folder, you can retrieve the available events to trace. You can see that there are three events for init calls, start, finish, and level. Then in your command line, you will have to add trace underscore event keyword and you put all the different events you want to trace. In our case, we will trace the three ones. Then you open the trace file and you will get all information about all init calls. You can check also the time execution of each one. And this is nice because you can also use tools to filter such as grep and so on. So now we will focus on the implementation of init calls. We will see the general implementation, different stages of ordering, the execution of function declared, and finally, our modules are handled. Before starting, a short disclaimer. You will need some ELF understanding and I'm definitely not an expert on both subjects. So I will do my best to answer your question after my presentation. I put some interesting resources about ELF and particularly if you are a visual learner like me, I recommend you to have a look at drawings from Korkami that I really liked. Okay, so let's see the implementation of init calls. All the definition will be in include slash linux slash init editor. You will find the helper definitions such as pure init call, core init call, post-core init call, arch init call, and so on. All init calls are using the same define underscore init call definition, but you can see there is only one difference, the last parameter. For next slide, we will use the post-core init call for many examples, but keep in mind that it will be the same for another init call, only the id is different. Define underscore init call function uses two parameters, the name of the function we want to execute at this level and the id which represents the order of your init calls. Let's see that in our simple example. As we just saw before, post-core underscore init call is defined using a define underscore init call function. If we try to expand that to our example, it means that the function name will be foo underscore init and the id will be equals to two because we choose an post-core init call. Let's look at the definition of define underscore init call. In fact, it's defined by another define underscore init call definition which has a third parameter. This third parameter uses the id parameter. It will be the concatenation of the string dot init call and the id of the init call. Because we choose a post-core init call, the third parameter in our case will be dot init call two. If you change it to a core init call, for example, it will be dot init call one because the id of core init call is one. What about this last define init call? We will look closer at this definition in next slides, but we can see that the third parameter is named sec. So let's see this final definition init call function. It's still using the function name as first parameter. So foo underscore init for our example. The id has a second one which equals to four post-core and the third one is a section that will be used in the object file. In our case, it will be dot init call two as I said before. Let's try to see what this colorful definition is doing. As seen before, the parameters will be foo in underscore init because it's the name of our function. Two because we used the post-core helper and dot init call two. The next line is creating an init call underscore t type variable named according to the parameters. It's underscore underscore init call underscore then the function name and then the id. We will see in next slides what attributes and section keyword ordering, but at least it's concatenated the sec parameters with another string init and all this init call underscore t will be equals to our function name. So what it's doing? In fact, it's creating an init call underscore t entry named underscore underscore init call underscore foo underscore init which is the name of our function two. And so this is using the function name and the id. For the attributes section, it's naming an object file section and as we have seen before it will be the third parameter plus init call init sorry string so it will be dot init call two dot init. We can retrieve that using object dump on the kernel object file. You can see that there is a section dot init call two dot init pointing to init call underscore foo underscore init two. To sum up define underscore init call is creating a section in the object file specific to the init call used that will be different according to the id and this will point to an entry that is a link to our function. We can retrieve all the sections for this particular level by looking at all dot init two dot init section. We could do the same for core init call by filtering two dot init call one dot init. Notice that the addresses of all sections are following each other. We will see later why it's important. So we have seen general implementation of init calls. Let's see how ordering of init call is done. First we will be looking at a specific level so how all functions using postcore helper will be order between each of those. You will see by example that it's depending of the order in macfabs. So let's try to see that using an example. Let's create two RTC drivers one called myDriver and the other one myOtherDriver. We choose RTC subsystem but you could use whatever other subsystem you want. In myDriver we create a function called myDriver underscore funk and in myOtherDriver we create myOtherDriver underscore function that is using postcore init call helper for both of them. In the first case we will put myDriver first and then myOtherDriver in the mac file. If we look at the result of the compilation the section in the object file is ordered by myDriver and myOtherDriver. We can see that the address of myOtherDriver function is after the one from myDriver. And if you put this kernel with debug information you will get myDriver function executed before myOtherDriver function. Let's be sure that it's not because of the alphabetical order and let's switch the order in the mac file. So myOtherDriver will be compiled first and myDriver will be the next one. In the object file the sections are inverted. MyOtherDriver function got the same address and myDriver function during our first case in the example. So in fact in the section myOtherDriver is the first one and then you have myDriver function. And again if you add debug information at your boot while you're booting your kernel myOtherDriver function will be the first one executed and myDriver function will be executed right after it. Now that we have seen the ordering for all functions at one level, so in our case it was post co, we will see how the kernel is ordering all the init calls against each others. We already seen one little int, the id of init calls but we will look deeper on that. All the magic is done in init slash main.c which is pretty cool to have a look at this file in the kernel right. This file hosts an init call underscore levels array. This array has entries called init call something underscore start. Most of the time the something between init call and underscore start is a number but there is an exception for WTFS. I will not talk about this specification in this presentation but I want a blog post. If you want to look at it you will find the answer on how WTFS is handled. Anyway each entry of this array is a pointer to a particular level. As you may imagine it will be linked to the id of init calls. Many of the magic is done using linker scripts. The kernel has a linker header that is creating entries depending on a level. At the end you will have for each level an init call something underscore start entry that will point to the first address of a section depending on the level. For example in post call init call the id is equal to 2 so you will have init call to underscore start entry and it will point to the section depending on this level. For example for a level equals to 2 init call to underscore start and we will point to the first address of the section dot init call to dot init and we already talked about it. If you remember define underscore init call was creating this section name into the object file and this is where the kernel stores the order of each init call levels. So now we have seen how the order of init calls between each others is done. Everything is based on an array and for each level functions are ordering by the mac file. So now we will see how our foo underscore init function will be executed and all the code behind init calls because so far we have seen pretty much only definitions. In the main file we will find a do underscore basic underscore setup function that is calling an interesting function called do init calls. This do init calls is using the array we saw before doing a for loop on it. It means that this function is calling all the init call levels one by one. So first the pure init call with its id of zero then the core init call with its id of one and so on. A function do underscore init call level is then called for each level. This do init call underscore level function is once again performing a for loop. It's using the array but for a particular level now. So the level we are using from the previous for loop. The type of fn variable is an init call underscore entry underscore t. It will be the first address given by the entry underscore underscore in equal to underscore start which means if you remember it's the first address of the section dot in equal to dot init. The loop will then iterate on all the addresses for all the section dot init to dot init thanks to the linker script we have seen before. So let's use an example to understand the value of this fn variable. We have the code here and the object dump output for some postcode functions. What will be the different values of fn during the iteration in the for loop? In the first iteration fn will have the first address of the section dot init call to dot init. So it will be equivalent to zero and in our example it's pointing to an atomic underscore pool underscore init function. It will be the parameter of the function inside the loop. The function is named do one init call. On the next iteration fn will be accremated so fn plus plus in the for loop. It means it will point to the next address which equals four and it points to an entry named mvbu underscore sock underscore device. And on the third iteration fn will be equals to the next address eight that will be a currency late init entry. It will continue this iteration until it reach the next level and then it go back to the above loop. So all fn values are passed to one function do one init call function and let's have a look at this function. This do one init call function is performing two main actions let's say. It starts and finish the tracing function and also it's executing the function we created so far. So now we have seen all the implementation and all the code in the kernel. I will try to summarize it and see try to summarize all we have seen so far. So we have our two drivers myOtherDriver and myDriver with their functions myOtherDriver underscore funk and myDriver underscore funk. They both use postcore init call helper. In the Mac file we add these drivers for the compilation. Postcore helper is defined in include slash linux slash init adder as we have seen before. This helper is in fact defined by two defining init calls function having an ID of two. The last defined underscore init call is creating a section in both object file of the drivers. So you will have myOtherDriver dot o and myDriver dot o and each one will have the section dot init to dot init. Then these object files are grouped into the big object file of the kernel vmlinux dot o. Thanks to the order in Mac file the section that we find in this final object file will be ordered by this Mac file. So you will have myOtherDriver underscore funk followed by myDriver underscore funk. Sorry. In init slash main dot c we find an array that order init calls levels between each other by creating an entry for each level. The entry in this array is called init call to underscore start for postcore. You find the doBasicSetup function. This function is calling do underscore init calls using the above array. With a for loop on each levels it's calling another function named do underscore init call underscore level. And this function is calling all level specific functions thanks to the array and the function pointers. And finally do1 init call function is executing the different function creating in our drivers. This function do1 init call is also printing debug information if we boot this kernel using the debug command line keyword. So at the end we will have our two functions executed at postcore level because we use postcore helpers. Between different others functions that can be in the different drivers and the order will be depending on the Mac file. And once all postcore init calls has been executed it will go to the next level. So the next level of postcore init call is arc init call and so on. So far we covered almost all topics general implementation ordering and also codes execution. So we will now look at modules and how they are handled. There are two different types of modules built-in which is modules that you put an Y on kconfig or loadables modules. It's when you put an M in kconfig. Most of the time modules will not be core drivers. They are not needed for a board to become usable for example. For that module underscore init function will be enough because you don't need your function to be executed at early stage of the boot of your kernel. We will see how it's implemented in the code. First of all we will look at built-in module. In module header you will find the definition of module underscore init. It's using a function called init call. If you look at the definition of this init call function in fact it's using device underscore init call. If you remember the order of the different init calls device init call is one of them executed at the last stage of the boot process. It means that your built-in module function will be executed at the device level of the boot process later than core related init calls which is great because most of the time your module will not be core stuff related. About loadable module in case you didn't use module underscore init function helper sorry but an init call helper such as postcard or whatever in fact they are replaced by module underscore init definition. Its definition is different from built-in module so we'll see. In this case module underscore init is defined like this. To simplify it will create an init underscore module function and this init underscore module function will be an alias to the name of your function. In a script inside the kernel it's adding additional code in module c5. If you try to compile a module you will find them in the kernel as my module name dot mod dot c and if you look at this c file you will find some code that is adding init underscore module function in dot init section. This is important for the rest we'll see. So finally in kernel slash module dot c file do underscore init module function is defined. This function will be executed with a cscoll at the module insertion so when you are doing load mode for example to load a module it will execute a cscoll that will execute do underscore init underscore module function. Its definition is executing a function that we already seen before the name is do underscore 1 underscore init code. If you remember it was the last function called the one that is executing the function and with all the tracing support between the execution of your function and for modules the parameter is the init section of your module. If you remember we have seen just before dot init is equals to init underscore module function which is in fact an alias to your own function. So modules and module underscore init function is in fact reusing what we have seen so far with init calls to execute your function at the last part of the boot process of your of your kernel. To summarize about module underscore init function for built-in module the function will be executed at device level which is nice because most of the time modules are drivers for devices and not architecture or core stuff. Loadable module will execute the function you want to execute at the module insertion using the init calls mechanism we have seen before. It means that if you don't have reasons to execute your function at early stage of the boot process you should use module underscore init. The benefit of it will be that you will save time consumed for booting your kernel and most important it will let more important function being executed earlier than yours. And that's it. I think we have seen a good overview of init calls. I hope you like it and that you learn how init calls are implemented in the kernel. I will be here one for questions and I will do my best to answer you. Thank you for attending my talk and see you around.