深入理解Linux网络技术内幕 第5章 网络设备初始化

内核初始化

当内核引导时,会执行start_kernel函数,start_kernel函数对一些子系统做初始化,start_kernel函数终止前调用init内核线程,由其负责后续的初始化。

  • 引导期间选项
    start_kernel函数里调用两次parse_args,一次是通过parse_early_param间接调用。
    这两个用于处理引导加载程序(BOOTLOAD,LILO或GRUB)传递给内核的配置参数。
	parse_early_param();
	after_dashes = parse_args("Booting kernel",
				  static_command_line, __start___param,
				  __stop___param - __start___param,
				  -1, -1, NULL, &unknown_bootoption);
  • 中断和定时器
    硬中断和软中断分别在init_IRQ和softirq_init函数中初始化。
	init_IRQ();
	softirq_init();
  • 初始化函数
    内核子系统内建的设备驱动程序由do_initcalls初始化。

  • run_init_process
    run_init_process函数运行第一个进程,其PID为1,一直运行到系统关机。正常情况下运行的程序是init。也可以在内核启动时通过init=在内核启动参数中指定另一个程序。

if (ramdisk_execute_command) {
		ret = run_init_process(ramdisk_execute_command);
		if (!ret)
			return 0;
		pr_err("Failed to execute %s (error %d)\n",
		       ramdisk_execute_command, ret);
	}

	/*
	 * We try each of these until one succeeds.
	 *
	 * The Bourne shell can be used instead of init if we are
	 * trying to recover a really broken machine.
	 */
	if (execute_command) {
		ret = run_init_process(execute_command);
		if (!ret)
			return 0;
		panic("Requested init %s failed (error %d).",
		      execute_command, ret);
	}
	if (!try_to_run_init_process("/sbin/init") ||
	    !try_to_run_init_process("/etc/init") ||
	    !try_to_run_init_process("/bin/init") ||
	    !try_to_run_init_process("/bin/sh"))
		return 0;

	panic("No working init found.  Try passing init= option to kernel. "
	      "See Linux Documentation/admin-guide/init.rst for guidance.");

设备注册和初始化

一个设备可用必须关联到正确的驱动程序。驱动程序把驱动设备所需的所有信息存储在私有数据结构中,然后与其他需要此设备的内核组件交互。
初始化几个阶段:

  • 硬件初始化:由设备驱动程序和总线(PCI或USB)合作完成。
  • 软件初始化
  • 功能初始化

NIC初始化

Linux内核中,每个网络设备都有一个net_device数据结构标识,这个结构的部分字段由驱动程序初始化,部分由内核初始化。驱动程序分配建立设备/内核通信所需的资源:

  • IRQ
    NIC必须被分配一个IRQ。虚拟设备不需要分配IRQ,比如环路设备。
  • IO端口和内存注册
    驱动程序将其设备的内存区域(比如设备寄存器)映射到系统内存内,使得驱动程序的读写操作可以通过系统内存地址直接进行。IO端口和内存的注册使用request_regiion进行。

设备与内核交互

设备与内核交互方式有:

  • 轮询
  • 中断

硬件中断

每个中断事件都会运行一个中断处理程序,当设备驱动程序注册一个NIC时,会请求分配一个IRQ,然后用两个依赖CPU体系结构的函数为给定的IRQ注册或删除处理程序。
两个程序是
request_irq该函数首先确保请求的中断是一个有效的中断,而且还没有分配给另外一个设备,除非两个设备可以共享IRQ。


static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev)
{
	return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

当内核接收到中断通知时,会使用IRQ编号找到该驱动的处理函数并执行。使用IRQ号找到处理函数会用到一张全局表,该表存储有IRQ编号和处理函数之间的对应关系。

中断类型

NIC中断有以下几种:

  • 接收数据报文
  • 传输事变
  • DMA传输完成
    给定一个传输报文,使用同步传输时,当报文数据复制到NIC的内存准备传输时,驱动程序就会将缓冲区释放掉。使用DMA时,是异步传输时,缓冲区的释放必须在NIC发出明确的中断事件后。
  • 设备有足够内存时
    当网卡的出口队列没有足够空间保存一个最大尺寸的帧时,NIC驱动程序会停止出口队列关闭传输。当内存有足够的内存时,使用中断通知开启。

中断共享

Linux系统允许IRQ共享,一组设备共享一个IRQ时,所有这些设备的驱动程序都必须有能力处理共享的IRQ,换言之,当一个设备注册IRQ时必须明确说明是否支持IRQ。
IRQ处理函数存在一个表中,每个IRQ对应一个处理函数的链表。当某个IRQ支持共享时,该IRQ号对应的链表中会有多个元素。

设备处理层初始化

流量控制和每个CPU报文入口队列是网络协议栈的重要部分,这些功能由引导期间由net_dev_init完成。
该函数主要做以下工作

  • 初始化两个网络软中断和网络软中断所使用的各个CPU数据结构。
  • 使用dev_proc_init函数初始化procfs
  • 使用netdev_kobject_init函数初始化sysfs
  • 初始化协议处理向量ptype_base,用于分离入口流量的多路合并传输。
/*
 *	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)
{
	int i, rc = -ENOMEM;

	BUG_ON(!dev_boot_phase);

	if (dev_proc_init())
		goto out;

	if (netdev_kobject_init())
		goto out;

	INIT_LIST_HEAD(&ptype_all);
	for (i = 0; i < PTYPE_HASH_SIZE; i++)
		INIT_LIST_HEAD(&ptype_base[i]);

	INIT_LIST_HEAD(&offload_base);

	if (register_pernet_subsys(&netdev_net_ops))
		goto out;

	/*
	 *	Initialise the packet receive queues.
	 */

	for_each_possible_cpu(i) {
		struct work_struct *flush = per_cpu_ptr(&flush_works, i);
		struct softnet_data *sd = &per_cpu(softnet_data, i);

		INIT_WORK(flush, flush_backlog);

		skb_queue_head_init(&sd->input_pkt_queue);
		skb_queue_head_init(&sd->process_queue);
#ifdef CONFIG_XFRM_OFFLOAD
		skb_queue_head_init(&sd->xfrm_backlog);
#endif
		INIT_LIST_HEAD(&sd->poll_list);
		sd->output_queue_tailp = &sd->output_queue;
#ifdef CONFIG_RPS
		sd->csd.func = rps_trigger_softirq;
		sd->csd.info = sd;
		sd->cpu = i;
#endif

		init_gro_hash(&sd->backlog);
		sd->backlog.poll = process_backlog;
		sd->backlog.weight = weight_p;
	}

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

	rc = cpuhp_setup_state_nocalls(CPUHP_NET_DEV_DEAD, "net/dev:dead",
				       NULL, dev_cpu_dead);
	WARN_ON(rc < 0);
	rc = 0;
out:
	return rc;
}

热插拔

Linux引入热插拔是为了实现PnP(plug and play,即插即用)功能,这项功能让内核侦测可热插拔设备的插入和删除。然后通知用户应用程序,提供足够的细节,使其在必要时加载相关联的驱动程序。
编译内核模块时,目标文件放在/lib/modules/kernel_version/目录下,在这个目录下存在两个文件:
modules.pcimap和modules.usbmap。这些文件包含内核支持设备的PCI ID和USB ID,这些文件还包括了相关联内核模块的引用,当用户空间辅助程序接收到一个可热插拔设备插入的信息时,使用这些文件找到正确的驱动程序。

hotplug

/sbin/hotplug脚本是默认的用户空间辅助程序。内核调用kobject_hotplug函数相应一个设备的插入和删除。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值