Kernel Infrastructure for Component Initialization

来源:互联网 发布:javascript 除法 编辑:程序博客网 时间:2024/06/03 23:45
  1. How initialization functions are named and identified by special macros
  2. How these macros are defined, based on the kernel configuration, to optimize memory usage and make sure that the various initializations are done in the correct order
  3.  When and how the functions are executed
parse_args is a routine that parses an input string with parameters in the form name_variable=value, looking for specific keywords and invoking the right handlers. parse_args is also used when loading a module, to parse the command-line parameters provided (if any).  (kernel/params.c)

Registering a Keyword

Kernel components can register a keyword and the associated handler with the __setup macro, defined in include/linux/init.h. This is its syntax:
_ _setup(string, function_handler)
where string is the keyword and function_handler is the associated handler. The example just shown instructs the kernel to execute function_handler when the input boot-time string includes string. string has to end with the = character to make the parsing easier for parse_args. Any text following the = will be passed as input to function_handler.

The following is an example from net/core/dev.c, where netdev_boot_setup is registered as the handler for the netdev= keyword:
_ _setup("netdev=", netdev_boot_setup);
The same handler can be associated with different keywords. For instance net/ethernet/eth.c registers the same handler, netdev_boot_setup, for the ether= keyword.

When a piece of code is compiled as a module, the _ _setup macro is ignored (i.e., defined as a no-op).

#define __setup(str, fn)\__setup_param(str, fn, fn, 0)#define __setup_param(str, unique_id, fn, early)\static char __setup_str_##unique_id[] __initdata = str;\static struct obs_kernel_param __setup_##unique_id\__attribute_used__\__attribute__((__section__(".init.setup")))\__attribute__((aligned((sizeof(long)))))\= { __setup_str_##unique_id, fn, early }
GNU C 允许声明函数、变量和类型的特殊属性,以便手工的代码优化和更仔细的代码检查。要指定一个声明的属性,在声明后写

_attribute__ (( ATTRIBUTE ))

其中 ATTRIBUTE 是属性说明,多个属性以逗号分隔。

section ("section-name")

属性 section 用于函数和变量,通常编译器将函数放在 .text 节,变量放在.data 或 .bss 节,使用 section 属性,可以让编译器将函数或变量放在指定的节中

aligned (ALIGNMENT)

属性 aligned 用于变量、结构或联合类型,指定变量、结构域、结构或联合的对

齐量,以字节为单位

used

this attribute, attached to a function, means that code must be emitted for the function even if it appears that the function is not referenced. This is useful, for example, when the function is referenced only in inline assembly.

The reason why start_kernel calls parse_args twice to parse the boot configuration string is that boot-time options are actually divided into two classes, and each call takes care of one class:

Default options
Most options fall into this category. These options are defined with the _ _setup macro and are handled by the second call to parse_args.

Early options
Some options need to be handled earlier than others during the kernel boot. The kernel provides the early_param macro to declare these options instead of _ _setup. They are then taken care of by parse_early_params. The only difference between early_param and _ _setup is that the former sets a special flag so that the kernel will be able to distinguish between the two cases. The flag is part of the obs_kernel_param data structure that we will see in the section “.init.setup Memory Section.”

Two-Pass Parsing

Because boot-time options used to be handled differently in previous kernel versions, and not all of them have been converted to the new model, the kernel handles both models. When the new infrastructure fails to recognize a keyword, it asks the obsolete infrastructure to handle it. If the obsolete infrastructure also fails, the keyword and value are passed on to the init process that will be invoked at the end of the init kernel thread via run_init_process The keyword and value are added either to the arg parameter list or to the envp environment variable list

  1. The first pass looks only for higher-priority options that must be handled early, which are identified by a special flag (early).
  2. The second pass takes care of all other options. Most of the options fall into this category. All options following the obsolete model are handled in this pass

Obsolete and new model options are placed into two different memory areas

_ _setup_start … _ _setup_end
We will see in a later section that this area is freed at the end of the boot phase: once the kernel has booted, these options are not needed anymore. The user cannot view or change them at runtime

__start_ _ _param … __stop_ _ _param
This area is not freed. Its content is exported to /sys, where the options are exposed to the user

Also note that all obsolete model options, regardless of whether they have the early flag set, are placed into the _ _setup_start … _ _setup_end memory area.

.init.setup Memory Section

The two inputs to the _ _setup macro we introduced in the previous section are placed into a data structure of type obs_kernel_param, defined in include/linux/init.h:
struct obs_kernel_param {
const char *str;
int (*setup_func)(char*);
int early;
};
str is the keyword, setup_func is the handler, and early is the flag we introduced in the section “Two-Pass Parsing.”

The _ _setup_param macro places all of the obs_kernel_params instances into a dedicated memory area. This is done mainly for two reasons:

  1. It is easier to walk through all of the instances—for instance, when doing a lookup based on the str keyword. We will see how the kernel uses the two pointers _ _setup_start and _ _setup_end, that point respectively to the start and end of the previously mentioned area, when doing a keyword lookup.
  2. The kernel can quickly free all of the data structures when they are not needed anymore. We will go back to this point in the section “Memory Optimizations.”

Use of Boot Options to Configure Network Devices

We already mentioned in the section “Registering a Keyword” that both the ether=and netdev= keywords are registered to use the same handler, netdev_boot_setup.
When this handler is invoked to process the input parameters (i.e., the string that follows the matching keyword), it stores the result into data structures of type netdev_boot_setup, defined in include/linux/netdevice.h. The handler and the data structure type happen to share the same name, so make sure you do not confuse the two.

struct netdev_boot_setup {
char name[IFNAMSIZ];
struct ifmap map;
};

struct ifmap
{
unsigned long mem_start;
unsigned long mem_end;
unsigned short base_addr;
unsigned char irq;
unsigned char dma;
unsigned char port;
/* 3 bytes spare */
};

The same keyword can be provided multiple times (for different devices) in the boottime string, as in the following example:
LILO: linux ether=5,0x260,eth0 ether=15,0x300,eth1

However, the maximum number of devices that can be configured at boot time with this mechanism is NETDEV_BOOT_SETUP_MAX, which is also the size of the static array dev_boot_setup used to store the configurations:
static struct netdev_boot_setup dev_boot_setup[NETDEV_BOOT_SETUP_MAX];
netdev_boot_setup is pretty simple: it extracts the input parameters from the string, fills in an ifmap structure, and adds the latter to the dev_boot_setup array with netdev_boot_setup_add.

At the end of the booting phase, the networking code can use the netdev_boot_setup_check function to check whether a given interface is associated with a boot-time configuration.The lookup on the array dev_boot_setup is based on the device name dev->name:

int netdev_boot_setup_check(struct net_device *dev){struct netdev_boot_setup *s = dev_boot_setup;int i;for (i = 0; i < NETDEV_BOOT_SETUP_MAX; i++) {if (s[i].name[0] != '\0' && s[i].name[0] != ' ' &&!strncmp(dev->name, s[i].name, strlen(s[i].name))) {dev->irq = s[i].map.irq;dev->base_addr = s[i].map.base_addr;dev->mem_start = s[i].map.mem_start;dev->mem_end = s[i].map.mem_end;return 1;}}return 0;}

Module Initialization Code

Every module must provide two special functions, called init_module and cleanup_module. The first one is called at module load time to initialize the module. The second one is invoked by the kernel when removing the module, to release any resources (memory included) that have been allocated for use by the module.

Optimized Macro-Based Tagging


Boot-Time Initialization Routines

Most initialization routines have two interesting properties:

  1. They need to be executed at boot time, when all the kernel components get initialized 
  2. They are not needed once they are executed

xxx_initcall Macros

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

  1. 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
  2. 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. 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 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).

Memory Optimizations

The module_init routines are executed only once when the associated module is
loaded. When the module is statically included in the kernel, the kernel can free
the module_init routine right at boot time, after it runs.
• The module_exit routines are never executed when the associated modules are
included statically in the kernel. In this case, therefore, there is no need to
include module_exit routines into the kernel image (i.e., the routines can be discarded
at link time).

0 0