深入理解Linux网络技术内幕——内核基础架构和组件初始化

引导期间的内核选项

Linux允许用户把内核配置选项传给引导记录,再有引导记录传给内核,以便对内核进行调整。

start_kernel中调用两次parse_args,用于引导期间配置用户输入数据。

parse_param是一个函数,用于解析输入的内核配置选项的参数字符串。字符串的格式为:name_variable=value。寻址特定关键字,并调用对应的函数。

注册关键字:

用宏进行定义:

__setup(string, fn)

string表示关键字,fn表示关联的处理函数。__setup的宏定义分为两种情况

#ifndef MODULE
#define early_param(str, fn)                        __setup_param(str, fn, fn, 0)
#else /* MODULE */
#define early_param(str, fn)
#endif

即一块代码编译为模块时,__setup被忽略了。

看两个例子:

//net/core/dev.c
_setup("netdev=", netdev_boot_setup);
//net/ethernet/eth.c
__setup("ether=", netdev_boot_setup);   

上面的例子也可以看出,不同的关键字可以关联到同一处理函数中。

start_kernel中调用两次parse_args原因在于:内核引导选项分为两类:

默认选项:

大多数选项属于这一类,由__setup定义,由parse_args的第一次调用处理、

初期选项:

这些选项必须必其它选项更早处理。early_param(而非__setup)进行处理。early_param与__setup区别在于early_param会设置一个标识,以便内核识别。

内核是通过__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 }
    early_param和__setup唯一不同的就是传递给__setup_param的最后一个参数early。_setup_param定义了一个struct obs_kernel_param类型的结构,然后通过_section宏,使这个变量在链接的时候能够放置在段.init.setup。下文有.init.setup的介绍。
    struct obs_kernel_param结构如下:
struct obs_kernel_param {
    const char *str;
    int (*setup_func)(char *);
    int early;
}; 

str表示key,setup_func表示handler。early类似于优先级的一个flag,因为传递给内核的参数中,有一些需要比另外的一些更早的解析。 参考下面两边分析一节:

两遍分析:

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);

在start_kernel函数中第二次调用parse_args函数时,会检查有module_param宏填入的模块选项,这些选项会存储在kernel_param数据结构中,module_param宏会确保这些数据结构都会被存放在特定的内存区块中(__param),有指针__start___param

和__stop___param限定。

__setup_start……__setup_end:这个区域会在引导阶段结束时释放掉,用户在运行期间不能看到或修改这些选项;

__start___param……__stop___param:这个区域不会被释放,其内容会被输出到/sys文件系统,这些选项可以显示给用户。

.init.setup 内存区

有__setup()和early_param()宏定义的关键字会被放入__setup_start……__setup_end区域,只是early_param宏定义的关键字会被设置early标记。

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

模块初始化代码:

每一个模块都要提供两个函数called init_module and cleanup_module。由下面两个宏指定:

module_init(XXX);
module_exit(XXX);

当前模块设计模型:

static char version[] _ _devinitdata = DRV_NAME " ... ";
static struct vortex_chip_info {
...
} vortex_info_tbl[] _ _devinitdata = {
{"3c590 Vortex 10Mbps",
... ... ...
}
static int _ _init vortex_init (void)
{
...
}
static void _ _exit vortex_cleanup (void)
{
...
}
module_init(vortex_init);
module_exit(vortex_cleanup);

基于宏标签的优化:

Linux使用许多宏来对一些函数和数据结构设定特殊的属性。这些宏大多定义在include/linux/init.h.中。而有些宏会通知链接器把具有相同属性的代码或数据结构放到特定专用的内存区域 。

右侧为宏的名字。

时间: 2024-10-12 05:38:18

深入理解Linux网络技术内幕——内核基础架构和组件初始化的相关文章

深入理解Linux网络技术内幕——IPv4 报文的传输发送

报文传输,指的是报文离开本机,发往其他系统的过程. 传输可以由L4层协议发起,也可以由报文转发发起. 在深入理解Linux网络技术内幕--IPv4 报文的接收(转发与本地传递)一文中,我们可以看到,报文转发最后会调用dst_output与邻居子系统进行交互,然后传给设备驱动程序. 这里,我们从L4层协议发起的传输,最后也会经历这一过程(调用dst_output).本文讨论的是L4层协议发起的传输,在IPv4协议处理(IP层)中的一些环节. 大蓝图 我们先看下传输环节的大蓝图,以便对传输这一过程有

深入理解Linux网络技术内幕——设备的注册与初始化(二)

设备注册于设备除名 设备注册与设备除名一般有 register_netdev和unregister_netdev完成.这两个是包裹函数,负责上锁,真正起作用的是其调用的register_netdevice和unregister_netdevice.参见:net/core/dev.c. 下图描述了设备注册过程中的一些状态变化 状态的改变会用到UNINITIALIZED和REGISTERED之间的状态REGISTERING.这些进程有netdev_run_todo进行.参照"切割操作:netdev_

深入理解Linux网络技术内幕——用户空间与内核空间交互

概述: 内核空间与用户空间经常需要进行交互.举个例子:当用户空间使用一些配置命令如ifconfig或route时,内核处理程序就要响应这些处理请求. 用户空间与内核有多种交互方式,最常用的有以下四种:通过/proc虚拟文件系统,通过/sys虚拟文件系统,通过ioctl系统调用,通过Netlink socket. 其中编写程序时最常使用ioctl,这四种方式中有两种是通过虚拟文件系统. procfs 与 sysctl procfs挂载/proc  sysctl挂载在/proc/sys(与后面介绍的

深入理解Linux网络技术内幕——Notification内核通知表链

为什么要有内核通知表链: Linux由多个相互依赖的子系统组成.其中一些子系统可能需要对其他子系统的一些事件感兴趣.这样子系统之间需要一些通信机制来实现这一功能. 在接触Notification Chain之前,我们可能想到通过轮询来实现,事件发生时,子系统轮询所有其他的子系统,看看有没有对这一事件感兴趣的,有没有需要执行的子函数. If (subsystem_X_enabled) { do_something_1 } if (subsystem_Y_enabled) { do_somethin

《深入理解Linux网络技术内幕》阅读笔记 --- 路由

一.Linux内核中路由相关的主要数据结构 struct fib_result:对路由表查找后返回该结构,它的内容并不是简单的包含下一跳信息,而且包含其他特性,例如策略路由所需的更多参数. struct fib_rule:表示由策略路由在路由流量时选择路由表的规则 struct fib_node:一条路由表项.例如,该数据结构用于存储由route add或ip route add命令添加一条路由时生成的信息. struct fn_zone:一个zone表示子网掩码长度相同的一组路由 struct

深入理解Linux网络技术内幕——设备的注册于初始化(一)

副标题:设备注册相关的基本结构的原理框架 设备注册与删除时间 设备在下列两种情况下进行注册: 1)加载NIC驱动时 2)插入热插拔设备时 这里NIC与热插拔设备有些不同.a.对于非热插拔NIC来说,NIC的注册是伴随着其驱动的发生的,而NIC可以内建到内核,也可以作为模块载入,如果内建入内核,则NIC设备和初始化均发生在引导时,如果NIC作为模块加载,则NIC的注册和驱动初始化均发生在模块加载时.b. 对于热插拔NIC设备来说,其驱动已经加载,因此设备的注册发生在插入设备,内核通知关联驱动时.

深入理解Linux网络技术内幕——中断与网络驱动程序

接收到帧时通知驱动程序 在网络环境中,设备(网卡)接收到一个数据帧时,需要通知驱动程序进行处理.有一下几种通知机制: 轮询: 内核不断检查设备是否有话要说.(比较耗资源,但在一些情况下却是最佳方法) 中断: 特定事件发生时,设备驱动程序代表内核指示设备产生硬件中断,内核中断其它活动满足设备的需要.多数网络驱动程序使用中断. 中断期间处理多帧: 中断被通知,且驱动程序执行.然后保持帧的接收(载入),直到输入队列达到指定的数目.或者一直做下去知道队列清空.或者经过指定时间. 定时器驱动的中断事件 驱

深入理解Linux网络技术内幕——帧的接收与传输

帧的接收 NAPI与netif_rx(非NAPI) Linux内核获取网络帧到达通知的方式有两中:中断和轮询.(中断值设备向内核发出中断,轮询指linux内核主动轮询设备) 在早起的linux内核中,网络帧主要以中断的方式通知linux内核帧的到达.这是非NAPI方式. 现在的操作系统中,linux内核使用NAPI方式, 获取帧到达的消息.NAPI混合使用了中断和轮询. netif_rx(非NAPI): 每一个帧接收完毕时,设备向内核发送一个中断.(在低流量负载的情况下,这种方式对比轮询优势明显

深入理解Linux网络技术内幕——协议处理函数

网络帧在进入网络层时,需要区分不同的网络协议进行处理,这就需要涉及协议处理函数. 首先我们从驱动接收到一个数据帧,分析数据帧在协议栈中自下而上的传输流程. 设备驱动程序在接收到一个数据帧时,会将其保存在一个sk_buff缓冲区数据结构,并对其进行初始化. struct sk_buff { ...... __be16 protocol:16; ...... } 在这个缓冲区结构体中,有一个protocol字段,用于标识网络层的协议. 我们知道网络帧在设备驱动程序中处理后,设备驱动程序会调用neti