`
simohayha
  • 浏览: 1407401 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

linux内核组件初始化体系

阅读更多
先来看下内核初始化时调用的一些函数:





这里主要的初始化有三类:

1 boot比如grub,u-boot传递给内核的参数,内核的处理。这里是调用parse_args.
2 中断和时钟的初始化。
3 初始化的函数,这里主要是通过do_initcalls标记的驱动初始化函数。一般这里的初始化函数完成后,会调用free_init_mem释放掉这块的空间。

我们这里主要关注第1和第3类。

首先来看第一类的初始化,我们知道boot传递给内核的参数都是 "name_varibale=value"这种形式的,那么内核如何知道传递进来的参数该怎么去处理呢?

内核是通过__setup宏或者early_param宏来将参数与参数所处理的函数相关联起来的。我们接下来就来看这个宏以及相关的结构的定义:

#define __setup(str, fn)					\
	__setup_param(str, fn, fn, 0)

#define early_param(str, fn)					\
	__setup_param(str, fn, fn, 1)

#define __setup_param(str, unique_id, fn, early)			\
	static char __setup_str_##unique_id[] __initdata __aligned(1) = str; \
	static struct obs_kernel_param __setup_##unique_id	\
		__used __section(.init.setup)			\
		__attribute__((aligned((sizeof(long)))))	\
		= { __setup_str_##unique_id, fn, early }


看起来很复杂。 首先setup宏第一个参数是一个key。比如"netdev="这样的,而第二个参数是这个key对应的处理函数。这里要注意相同的handler能联系到不同的key。early_param和setup唯一不同的就是传递给__setup_param的最后一个参数,这个参数我么下面会说明

而接下来_setup_param定义了一个struct obs_kernel_param类型的结构,然后通过_section宏,使这个变量在链接的时候能够放置在段.init.setup(我们后面会详细介绍内核中的这些初始化段).

我们接下来来看struct obs_kernel_param结构:

struct obs_kernel_param {
	const char *str;
	int (*setup_func)(char *);
	int early;
};


前两个参数很简单,一个是key,一个是handler。最后一个参数其实也就是类似于优先级的一个flag,因为传递给内核的参数中,有一些需要比另外的一些更早的解析。(这也就是为什么early_param和setup传递的最后一个参数的不同的原因了。


接下来我们来看内核解析boot传递给内核的参数的步骤:

先看下面的图:



可以看到内核首先通过parse_early_param来解析优先级更高的,也就是需要被更早解析的命令行参数,然后通过parse_ares来解析一般的命令行参数.

下面这两个调用就是parse_early_param和parse_ares调用传递给parse_args的不同的参数.

parse_args("early options", tmp_cmdline, NULL, 0, do_early_param);
parse_args("Booting kernel", static_command_line, __start___param,
		   __stop___param - __start___param,
		   &unknown_bootoption);



我们先来看do_early_param函数:


static int __init do_early_param(char *param, char *val)
{
	struct obs_kernel_param *p;
///这里的__setup_start和_-setup_end分别是.init.setup段的起始和结束的地址。
	for (p = __setup_start; p < __setup_end; p++) {
///如果没有early标志则跳过。
		if ((p->early && strcmp(param, p->str) == 0) ||
		    (strcmp(param, "console") == 0 &&
		     strcmp(p->str, "earlycon") == 0)
		) {
///调用处理函数
			if (p->setup_func(val) != 0)
				printk(KERN_WARNING
				       "Malformed early option '%s'\n", param);
		}
	}
	/* We accept everything at this stage. */
	return 0;
}



接下来下来看下,一些内置模块的参数处理的问题。传递给模块的参数都是通过module_param来实现的:

#define module_param(name, type, perm)				\
	module_param_named(name, name, type, perm)

#define module_param_named(name, value, type, perm)			   \
	param_check_##type(name, &(value));				   \
	module_param_call(name, param_set_##type, param_get_##type, &value, perm); \
	__MODULE_PARM_TYPE(name, #type)

#define module_param_call(name, set, get, arg, perm)			      \
	__module_param_call(MODULE_PARAM_PREFIX, name, set, get, arg, perm)

#define __module_param_call(prefix, name, set, get, arg, perm)		\
	/* Default value instead of permissions? */			\
	static int __param_perm_check_##name __attribute__((unused)) =	\
	BUILD_BUG_ON_ZERO((perm) < 0 || (perm) > 0777 || ((perm) & 2));	\
	static const char __param_str_##name[] = prefix #name;		\
	static struct kernel_param __moduleparam_const __param_##name	\
	__used								\
    __attribute__ ((unused,__section__ ("__param"),aligned(sizeof(void *)))) \
	= { __param_str_##name, perm, set, get, { arg } }


这里也就是填充了 struct kernel_param的结构体,并将这个变量标记为__param段,以便于链接器将此变量装载到指定的段。

接下来我们来看struct kernel_param这个结构:

struct kernel_param {
	const char *name;
	unsigned int perm;
	param_set_fn set;///设置参数的函数,
	param_get_fn get;///读取参数的函数
	union {///传递给上面两个函数的参数。
		void *arg;
		const struct kparam_string *str;
		const struct kparam_array *arr;
	};
};




接下来来看parse_one函数,其中early和一般的pase都是通过这个函数来解析:

static int parse_one(char *param,
		     char *val,
		     struct kernel_param *params, 
		     unsigned num_params,
		     int (*handle_unknown)(char *param, char *val))
{
	unsigned int i;
///如果是early_param则直接跳过这步,而非early的,则要通过这步来设置一些内置模块的参数。
	/* Find parameter */
	for (i = 0; i < num_params; i++) {
		if (parameq(param, params[i].name)) {
			DEBUGP("They are equal!  Calling %p\n",
			       params[i].set);
///调用参数设置函数来设置对应的参数。
			return params[i].set(val, &params[i]);
		}
	}

	if (handle_unknown) {
		DEBUGP("Unknown argument: calling %p\n", handle_unknown);
		return handle_unknown(param, val);
	}

	DEBUGP("Unknown argument `%s'\n", param);
	return -ENOENT;
}


这里之所以把所有的内核初始化参数都放在一个段里,主要是为了初始化成功后,释放这些空间。


我们下来看一下内核编译完毕后的一些段的位置:




右边就是每个段定义的相关的宏。在__init_start和__init_end之间的段严格按照顺序进行初始化,比如core_initcall宏修饰的函数就要比arch_initcall宏定义的函数优先级高,所以就更早的调用。这个特性就可以使我们将一些优先级比较高的初始化函数放入相应比较高的段。。


这里要注意,每个init宏,基本都会有个对应的exit宏,比如__initial和__exit等等。。

内核中修饰的宏很多,我们下面只介绍一些常用的:

__devinit 用来标记一个设备的初始化函数,比如pci设备的probe函数,就是用这个宏来修饰。

__initcall这个已被废弃,现在实现为__devinit的别名。

剩下的宏需要去看内核的相关文档。。


最后还要注意几点:

1 当模块被静态的加入到内核中时,module_init标记的函数只会被执行一次,因此当初始化成功后,内核会释放掉这块的内存。

2 当模块静态假如内核,module_exit在链接时就会被删除掉。


这里的优化还有很多,比如热拔插如果不支持的话,很多设备的初始化函数都会在执行完一遍后,被丢弃掉。


这里可以看到,如果模块被静态的编译进内核时,内核所做的内存优化会更多,虽然损失了更多的灵活性。。




  • 大小: 24.9 KB
  • 大小: 25 KB
  • 大小: 28.7 KB
1
1
分享到:
评论

相关推荐

    linux内核分析----初始化

    在 start_kernel 函数中,首先会打印 Linux Banner 信息,然后开始初始化内核的各个组件,包括内存、硬件终端、调度等。 内存初始化是通过 setup_arch 函数来实现的,该函数将返回内核参数和内核可用的物理地址范围...

    趣谈Linux之第八章:内核的初始化部分

    Linux内核的初始化是整个操作系统的核心部分,它负责初始化各种系统组件,设置系统环境,准备系统的启动。在这篇文章中,我们将探索Linux内核的初始化过程,了解内核如何从实模式切换到保护模式,如何初始化项目管理...

    疯狂内核之——内核初始化

    - **KBuild体系**: Linux内核使用名为KBuild的Makefile体系进行编译。 - **内核目标**: KBuild通过定义特定的目标来组织编译过程,如`obj-m`用于模块,`obj-y`用于内核。 - **主机程序**: 主机程序是指在编译过程...

    深度:一文看懂Linux内核!Linux内核架构和工作原理详解

    理解Linux内核最好预备的知识点:懂C语言懂一点操作系统的知识熟悉少量相关算法懂计算机体系结构Linux内核的特点:结合了unix操作系统的一些基础概念Linux内核的任务:1.从技术层面讲,内核是硬件与软

    内核初始化

    - **转交控制权给内核**: 完成基本硬件初始化后,引导程序将控制权转交给Linux内核。 **Piggy**是一种用于构建内核映像的方法,它可以将一些必要的模块或者代码合并到内核映像中,以便在启动时能够快速加载并运行...

    Linux网络操作初始化过程

    在Linux内核中,网络栈的初始化是一个重要的过程,它确保了系统能够正确处理网络数据包的收发以及各种网络服务的运行。下面将详细阐述Linux网络操作初始化过程中的关键步骤,特别是网络操作核心函数指针的初始化。 ...

    最精简的linux内核说明,Linux内核入门必读

    - **内核体系结构**:本书第二章详细介绍了Linux内核的体系结构,包括内核模式、系统体系结构、中断机制、系统定时等方面。 - **内存管理**:探讨了Linux内核如何管理内存,包括内存分配、释放等机制。 - **进程控制...

    嵌入式Linux系统初始化分析.pdf

    在系统初始化阶段,Linux内核会加载驱动程序、安装文件系统、启动系统服务等。 在嵌入式Linux系统中,initrd/initramfs是系统初始化的关键组件。initrd/initramfs是一个临时的根文件系统,用于存储Linux内核和驱动...

    LINUX内核总结(PPT课件)(修改版)

    4. **虚拟文件系统(VFS)**:VFS是Linux内核的一个核心组件,它为各种不同的文件系统提供了一个统一的接口,使得应用程序无需关心底层具体的文件系统类型。VFS支持挂载、打开、读取、写入和关闭文件等操作。 5. **...

    Linux内核分析.pdf

    #### 四、Linux内核组件详解 - **知识点4:内存管理** - **虚拟内存**:通过虚拟内存技术,Linux可以将物理内存划分为多个页,当物理内存不足时,将不活跃的页面移至硬盘,以释放更多可用内存空间。 - **内存页**...

    linux 内核 socket相关的协议栈初始化

    ### Linux内核Socket相关协议栈初始化详解 #### 引言 Linux内核中关于socket相关的协议栈初始化是一项复杂而精细的任务。本文旨在深入探讨Linux内核中与socket相关的协议栈初始化过程,帮助读者理解其核心机制和...

    linux内核注释、设计与实现、深入linux内核构架

    这本书通常会更侧重于内核的架构和组件之间的关系,它会带领读者探索内核的层次结构,包括内核初始化、进程间通信、虚拟文件系统、网络堆栈等高级主题。深入理解这些内容可以帮助开发者优化系统性能,解决复杂的...

    Linux操作系统学习-内核初始化.pdf

    这些初始化工作都是 Linux 操作系统的关键组件,都是在 start_kernel() 函数中完成的。 本文对 Linux 操作系统的内核初始化进行了分析和介绍,涵盖了内核初始化的主要步骤和关键概念,为读者提供了一个系统的了解 ...

    linux内核解析笔记

    6. **初始化过程(init)**:在Linux启动时,内核加载完毕后会启动init进程,它是系统中的第一个进程,负责初始化系统环境,启动必要的服务,并根据配置启动其他进程。 在“linux内核解析笔记”中,可能会详细讲解...

    深度剖析linux内核

    总结来说,“深度剖析Linux内核”涵盖了Linux内核的多个关键领域,从启动流程到各个核心组件的运作机制。通过深入学习这些内容,开发者可以更好地理解和优化Linux系统的性能,为嵌入式系统或服务器环境提供稳定、...

    Linux内核源代码漫游

    - **其他关键组件初始化**: 如时钟管理、定时器、设备驱动等。 #### 四、内核构建与版本变化 随着Linux内核的发展,其构建方式也在不断变化。例如,从1.1.75版本开始,`boot`和`zBoot`目录被移到了`arch/i386/boot...

    Linux 内核完全剖析-基于0.12内核

    1. 系统引导过程:理解Linux系统从启动到内核初始化的整个过程,包括BIOS阶段、Bootloader阶段和内核加载过程。 2. 内核模块:内核模块是Linux内核的一项重要功能,它允许动态加载和卸载内核功能,使得内核能够根据...

Global site tag (gtag.js) - Google Analytics