分类: LINUX
2012-11-23 19:29:59
内核内置组件以及作为模块加载的组件都可以通过输入参数设置模块的功能,或在系统引导前后为不同的值。
模块参数(module_param系列的宏):当加载模块时可以提供这些定义选项,但如组件编译在内核中则不能在内核引导期间提供这些选项的值。但通过/sys文件系统可以在运行期间对参数进行配置。
引导内核参数(__setup系列的宏):引导加载程序引导期间可以提供这些定义参数,来由编译在内核中的模块而不能编译成独立加载模块的内核组件使用。
不同的模块在模块加载期间定义相同名称的参数,不会有名称冲突的风险,但若在内核引导期间传递参数,则必须确保这些模块之间没有名称冲突的问题。
在drivers/block/loop.c文件中的使用实例:
/*编译为模块动态加载*/
static int max_loop;
module_param(max_loop, int, 0);
MODULE_PARM_DESC(max_loop, "Maximum number of loop devices");
/*编译在内核中*/
#ifndef MODULE
static int __init max_loop_setup(char *str)
{
max_loop = simple_strtol(str, NULL, 0);
return 1;
}
__setup("max_loop=", max_loop_setup);
#endif
module_param宏的三个参数中,第一个是使用的参数名称,第二个是参数类型,第三个是表示参数作为文件输出到/sys时,该文件的权限,如drivers/net/sis900.c文件中:
在/sys的目录下可以看到:
每个模块在/sys/modules中都被分派一个目录,子目录/paramenters中的每个文件就是该模块所输出的每个参数。
也可以赋予这些文件写权限,但需要通过sys文件系统API来获取参数修改通知。
设备处理层初始化:net_dev_init
流量控制和各个CPU入口队列等网络初始化的重要部分,在引导期间由定义在net/core/dev.c中的net_dev_init完成:
/*
* Initialize the DEV module. At boot time this walks the device list and
* unhooks any devices that fail to initialise (normally hardware not
* present) and leaves us with a valid list of present and active devices.
*
*/
/*
* This is called single threaded during boot, so no need
* to take the rtnl semaphore.
*/
static int __init net_dev_init(void)
subsys_initcall(net_dev_init);
subsys_initcall宏会确保net_dev_init函数在任何NIC设备驱动程序自行注册之前先执行。
net_dev_init函数的主要完成工作是:
static int __init net_dev_init(void)
{
int i, rc = -ENOMEM;
BUG_ON(!dev_boot_phase);
/*内核支持/proc文件系统时,有些文件会通过dev_proc_init和dev_mcast_init添加到/proc中*/
if (dev_proc_init())
goto out;
/*向sysfs注册为net类,这样会创建/sys/class/net目录,在该目录下每个已注册的网络设备都会有一个子目录,包含很多文件*/
if (netdev_kobject_init())
goto out;
INIT_LIST_HEAD(&ptype_all);
/*协议处理例程向量ptypes_base初始化,用于分离入口流量的多路合并传输*/
for (i = 0; i < PTYPE_HASH_SIZE; i++)
INIT_LIST_HEAD(&ptype_base[i]);
if (register_pernet_subsys(&netdev_net_ops))
goto out;
/*
* Initialise the packet receive queues.
*/
/*对应于各个CPU所的,由接收和发送队列软件中断所使用的数据结构被初始化*/
for_each_possible_cpu(i) {
struct softnet_data *sd = &per_cpu(softnet_data, i);
memset(sd, 0, sizeof(*sd));
skb_queue_head_init(&sd->input_pkt_queue);
skb_queue_head_init(&sd->process_queue);
sd->completion_queue = NULL;
INIT_LIST_HEAD(&sd->poll_list);
sd->output_queue = NULL;
sd->output_queue_tailp = &sd->output_queue;
#ifdef CONFIG_RPS
sd->csd.func = rps_trigger_softirq;
sd->csd.info = sd;
sd->csd.flags = 0;
sd->cpu = i;
#endif
sd->backlog.poll = process_backlog;
sd->backlog.weight = weight_p;
sd->backlog.gro_list = NULL;
sd->backlog.gro_count = 0;
}
dev_boot_phase = 0;
/* The loopback device is special if any other network devices
* is present in a network namespace the loopback device must
* be present. Since we now dynamically allocate and free the
* loopback device ensure this invariant is maintained by
* keeping the loopback device as the first device on the
* list of network devices. Ensuring the loopback devices
* is the first device that appears and the last network device
* that disappears.
*/
if (register_pernet_device(&loopback_net_ops))
goto out;
if (register_pernet_device(&default_device_ops))
goto out;
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
/*将一个回调处理函数注册到发出关于CPU热插拔事件的通知链,回调函数是dev_cpu_callback,目前唯一处理的事件时CPU停止事件,但接收到此消息时,CPU入口队列中的缓冲区出列,然后传给netif_rx函数*/
hotcpu_notifier(dev_cpu_callback, 0);
/*协议无关的目的地址缓存初始化*/
dst_init();
dev_mcast_init();
rc = 0;
out:
return rc;
}
subsys_initcall宏要确保net_dev_init在任何设备驱动程序注册其设备之前执行,是通过引入一次标记的旧式机制。全局变量dev_boot_phase作为布尔标记,用来记住net_dev_init是否已被执行,其初始值为1,然后由net_dev_init设置为0清除,每次register_netdevice被一个设备驱动程序调用时,都会检查dev_boot_phase的值。如果该标记被设置为函数尚未执行,则执行net_dev_init。
但这种机制已经不再使用,因为如果关键的设备驱动程序的例程已经被正确标记,register_netdevice就不会再net_dev_init之前调用。但为了发现错误的标记或有BUG的代码,net_dev_init仍然在现在代码中清除dev_boot_phase的值,而register_netdevice会使用BUG_ON宏确保当dev_boot_phase设置时,出现错误提示。
int register_netdevice(struct net_device *dev)
{
……
BUG_ON(dev_boot_phase);
ASSERT_RTNL();
……
}
用户空间辅助程序
在某些情况下,内核也需要调用用户空间应用程序以处理事件。其中有两个程序特别重要:
内核提供函数call_usermodehelper来执行这类用户空间辅助程序,并允许调用者通过arg[]传递一些自变量,并通过env[]传递一些环境变量给应用程序。如arg[0]表示要调用的用户空间程序路径和名称,arg[1]用于通知程序该使用什么配置脚本。
static inline int
call_usermodehelper(char *path, char **argv, char **envp, enum umh_wait wait)
{
return call_usermodehelper_fns(path, argv, envp, wait,
NULL, NULL, NULL);
}
下图显示了内核函数request_module和kobject_hotplug通过函数call_usermodehelper如何调用/sbin/modprobe和/sbin/hotplug的。
kernel/kmod.c是内核模块加载程序,用于内核组件请求加载一个模块,内核提供了很多这种函数如request_module,此函数用要加载的模块名称初始化arg[1],/sbin/modprobe使用配置文件/etc/modprobe.conf去做一些加载前的处理事件,如判断需要加载的模块名称是否为其他模块的别名等。
如:管理员使用命令ifconfig配置驱动程序尚未加载的网卡时,如ifconfig eth0,内核向/sbin/modprobe发出一个请求,以加载名称“eth0”的模块,若/etc/modprobe.conf包含一个“alias eth0 3c59x”条目,则/sbin/modprobe会尝试加载模块3c59x.ko
热插拔
Linux内核引入热插拔是为了实现PnP即插即用功能,这项功能让内核发现可热插拔设备的插入和拔出,然后通知用户程序使其在必要时加载相关联的驱动程序,或应用相关联的配置。
Linux系统会在引导期间执行一组脚本对接口设备做初始化,包括网络设备在内。这些脚本的语法、名字以及位置会随之不同的Linux发行套件而不同,一般在/etc/rc.d目录下的每个执行等级下都有一个目录。因此,对于引导期间已存在的设备的通知信息会被忽略,因为这些脚本最终都会配置相关联的设备。
在编译内核模块时,目标文件默认都放在/lib/modules/kernel_version/目录下,其中存在两个文件modules.pcimap和modules.usbmap,在这两个文件猪分别包含了内核所支持设备的PCI ID和USB ID。这些文件还包含了相关联的内核模块的引用,当用户空间辅助程序接收到一个可热插拔设备正在插入的通知信息时,就会使用这些文件找出正确的设备驱动程序。
modules.xxxmap文件的数据来自于设备驱动程序所提供的ID向量,如PCI设备驱动程序都会对一个pci_device_id实例做初始化,其内容会被加入到modules.pcimap文件中。
struct pci_device_id {
__u32 vendor, device; /* Vendor and device ID or PCI_ANY_ID*/
__u32 subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */
__u32 class, class_mask; /* (class,subclass,prog-if) triplet */
kernel_ulong_t driver_data; /* Data private to the driver */
};
hotplug默认的用户空间辅助程序是脚本/sbin/hotplug,该脚本是Hotplug套件的一部分,这个套件可以通过默认目录/etc/hotplug和/etc/hotplug.d/中的文件进行配置。
内核会调用kobject_hotplug函数响应一个设备的插入和删除以及其他事件,kobject_hotplug将arg[0]的初值设置为/sbin/hotplug,arg[1]设置为要使用的代理程序。/sbin/hotplug是一个简单脚本,把事件的处理委托给arg[1]指定的另一个脚本处理。如,当一块NIC添加到系统中或从系统删除时,kobject_hotplug会将arg[1]的初值设为net,使得/sbin/hotplug去执行net.agent程序。
为了能够配置设备,需要先加载相关的设备驱动程序。如添加一块PCMCIA Ethernet卡,可能引起调用/sbin/hotplug好几次。