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

概述

内核的初始化过程过程中,与网络相关的工作如下所示:

内核引导时执行start_kernel,start_kernel结束之前会调用rest_init,rest_init初始化内核线程init(在Linux3-12中为kernel_init)。

asmlinkage void __init start_kernel(void)
{
    ...
    parse_early_param();//间接调用parse_args
    parse_args(...); //处理内核引导程序(boot loader)在引导期间传给内核的参数,
    ...
    init_IRQ();   //初始化硬件中断
    tick_init();
    init_timers(); //定时器用于支持后续初始化工作的运行。
    hrtimers_init();
    softirq_init(); //初始化软件中断
    ...
    rest_init();   //这里会通过kernel_thread函数调用内核线程init(kernel_init).
}
static init __ref kernel_init(void *unused)
{
    kernel_init_freeable();   //Linux-3.12 在这里调用do_basic_setup
    free_initmem();  //用于释放已经不再需要的内存。
    .....
    run_init_process(execute_command)
    ...
}
static void __init do_basic_setup(void)
{
    cpuset_init_smp();
    usermodehelper_init();
    shmem_init();
    driver_init();
    init_irq_proc();
    do_ctors();
    usermodehelper_enable();
    do_initcalls();  //初始化内核子系统和内建的设备驱动
    random_int_secret_init();
}

设备的注册和初始化

一个设备要能够正常工作,他就必须被内核所识别,并且与正确的驱动关联起来。设备驱动程序以私有结构体的形式保存了驱动本设备所需要的所有信息,并且与其他和本设备有交互的组件相互影响。

设备的注册和初始化一部分由内核完成,一部分由设备驱动程序完成。

设备的初始化包括了硬件初始化、软件初始化、功能初始化三部分:硬件初始化:由设备驱动程序和通用总线层完成,有时也需要用户提供一些参数。主要任务是将硬件功能配置成IRQ和I/O地址,以便能够跟内核相互作用。软件初始化:设备使用之前必须关注当前配置或启用的网络协议,
一般需要用户提供诸如ip地址之类的参数。功能初始化: Linux内核提供了一系列的网络选项,有些网络选项对每一个设备都需要进行单独配置(如实现Qos的子系统),这些配置决定了数据包进入队列和离开设备的出口队列的方式 。

NIC初始化目标

网络设备在Linux中都是以net_device实例进行初始化的,本节先不讨论这个,本节主要介绍设备驱动程序如何分配/建立设备与内核通信所需要的资源。IRQ线:NIC必须分派一个IRQ,用于在必要时唤起内核的注意(虚拟设备不需要分配IRQ,因为它的工作都是在内部实现。)/proc/interrupts文件可用于观察当前中断线分派状态。I/O端口和内存注册: 驱动程序会将设备的一块内存映射到系统内存,使得驱动程序的读写操作能够通过系统内存直接进行。注册和释放操作分别由request_region和
release_region进行。

设备与内核之间的交互

几乎所有设备与内核的交互都是通过以下两种方式:

内核的轮询:

内核定时检查设备的状态,判断设备是否有什么请求。

设备驱动的中断请求:

设备驱动发送硬件信号引起内核注意

内核轮询在其他文章会介绍,本文主要介绍硬件中断中与网络有关的概念。

硬件中断

每一个中断都会运行一个中断处理程序,这些中断响应程序都是设备驱动为设备量身定做的。一般而言,当设备注册一个NIC时,它首先会请求并分配一个IRQ,然后要为IRQ注册(如果设备被卸载了,则需要注销)一个IRQ响应程序。相应的内核代码在kernel/irq/manage.c和arch/XXX/kernel/irq.c。(其中XXX为处理器架构)

int request_threaded_irq(unsigned int irq, irq_handler_t handler,
             irq_handler_t thread_fn, unsigned long irqflags,
             const char *devname, void *dev_id)
void free_irq(unsigned int irq, void *dev_id)

注意:irq的注册和释放函数都带有参数dev_id。因为IRQ是可以共享的,因此需要IRQ number和dev_id共同来唯一表示中断。

另,在注册IRQ时,必须保证IRQ还未有设备请求,除非所有设备都支持IRQ共享。

内核接收到一个中断信号时,会通过IRQ number调用关联的中断响应程序。IRQ number与中断响应程序以表的形式保存。由于多个设备可能共享IRQ的关系,IRQ number与中断响应程序的关系可能是一对多的。

中断类型:接收到数据帧、帧传输失败、DMA传输已成功完成、设备已经有足够内存来创建新的传输会话(可用NIC可用内存达到一定数值<一般为设备MTU>时产生一个中断)

为了防止内核在设备内存不足时多次提交传输请求,设备驱动可以关闭内核出口队列,待到资源足够是才重启。下面是一个范例:

static netdev_tx_t
el3_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
    ……
    netif_stop_queue (dev);
    ……
    dev->trans_start = jiffies;
    if (inw(ioaddr + TX_FREE) > 1536)
        netif_start_queue(dev);
    else
        /* Interrupt us when the FIFO has room for max-sized packet. */
        outw(SetTxThreshold + 1536, ioaddr + EL3_CMD);
    ……
}

IRQ共享:IRQ线是很有限的资源,为了让一个系统能支持更多的设备,只能让多个设备共享IRQ线。IRQ共享的机制是这样的,内核收到中断请求,然后调用所有与该中断相关联的响应例程,然后有各个响应例程自行判断过滤是否对这个中断进行处理。(注意,IRQ与响应程序是一对多的,发生一个IRQ,哪些响应程序要处理,哪些不需要不是有内核去判断,而是各个中断响应程序自己判断,内核则是调用所有的响应程序。)

  IRQ与IRQ响应程序的组织:用全局的vector:irq_desc来组织,irq_desc包含所有IRQ,每个IRQ对应自己的链表,链表中是该IRQ关联的所有响应程序。只有IRQ共享时,IRQ链表的节点才会超过一个。整个组织如下图:

初始化选项

所有的系统内建组件以及作为模块加载的设备都能通过用户的输入参数调整所实现的功能、重写其默认值,或者在引导前后有不同的值。

模块选项:(module_param系列的宏)

模块加载时可以定义。如果是内建的组件,由于在引导期间无法配置,可以通过sys/进行运行时配置。

引导期间内核选项:(__setup系列宏)

引导期间提供。用于可以内建到内核的模块

设备处理层的初始化

网络代码初始化有以下重要的部分:流量控制,每个CPU输入队列初始化。这些初始化工作在引导期间由net_dev_init完成:

static int __init net_dev_init(void)
subsys_initcall(net_dev_init);   

用户空间辅助程序

  • /sbin/modprobe 在内核需要加载某个模块时调用,判断内核传递的模块是不是/etc/modprobe.conf文件中定义的别名
  • /sbin/hotplug 在内核检测到一个新设备插入或拔出系统时调用,它的任务是根据设备标识加载正确的驱动

  1. 以模块方式加载

    kmod模块加载器允许内核组件通过调用request_module请求加载某个模块

    举个例子;如果系统管理员使用ifconfig配置某个网卡,但这个网卡驱动还没有加载,如eth0,内核就会给/sbin/modprobe发送一个请求,让它加载名称为

    eth0的模块。如果/etc/modprobe.conf中包含“alias eth0 xxx”的字符,/sbin/modprobe就会尝试加载xxx.ko模块。

    module_param 宏定义在引入sysfs后可以通过文件来访问得到模块参数

    模块选项有三项 , 第一项参数名称,第二项参数类型,第三项表示参数作为文件在sys文件系统中所有的权限。

    每个模块都会在sys/modules下生成对应的目录,通过目录下的文件可以获取模块参数。

  2. pnp热插拔

    hotplug允许内核检测热插拔设备的插入和拔出并通知用户进程(/sbin/hotplug),用户进程根据这些通知来加载相应的驱动

    在编译内核时,会在kernel目录下生成modules.pcimap和modules.usbmap两个文件,这两个文件分别包含了内核所支持设备的pci id和usb id,文件中还包

    含于每个设备的id相对应的内核模块名称,当用户进程收到内核关于pnp的通知后,会使用这个文件来查找正确的设备驱动

虚拟设备

虚拟设备一般也使用net_device结构体进行实例化(也有一些例外,如别名接口设备)。

虚拟设备一般会有用户空间配置工具来对其进行配置。尤其是无法使用ifconfig来进行配置的高级字段。

虚拟设备一般会有一个/proc接口目录,其内容详细程度取决于虚拟设备的设计。

虚拟设备与这是设备的对应关系不是一一对应的。这就导致了虚拟设备可能需要进行流量控制的配置。

虚拟设备的流量是简介从真实物理设备获得的,因而不需要分配IRQ、IO端口和IO内存。

虚拟设备与其他真实物理设备一样,能对特殊的事件通知做出相应的反应。

/proc调整

时间: 2024-08-01 10:43:43

深入理解Linux网络技术内幕——网络设备初始化的相关文章

深入理解Linux网络技术内幕——虚拟设备初始化小结

概述 虚拟设备是建立在一个或者多个真实设备上的抽象.虚拟设备和真实设备的对应关系时多对多的,但是并不是所有的组合都能被定义,或者被内核所支持. 下面列举一些比较常见虚拟设备: Bonding //绑定,虚拟设备绑定物理设备,使其如同单一设备 802.1Q //IEEE标准,用于建立VLAN Bridging //网桥的虚拟代表 Aliasing interfaces //别名接口,用以让一个Ethernet真实接口有多个虚拟接口 True equalizer (TEQL) //普通均衡器,是一个

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

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

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

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

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

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

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

引导期间的内核选项 Linux允许用户把内核配置选项传给引导记录,再有引导记录传给内核,以便对内核进行调整. start_kernel中调用两次parse_args,用于引导期间配置用户输入数据. parse_param是一个函数,用于解析输入的内核配置选项的参数字符串.字符串的格式为:name_variable=value.寻址特定关键字,并调用对应的函数. 注册关键字: 用宏进行定义: __setup(string, fn) string表示关键字,fn表示关联的处理函数.__setup的宏

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

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

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

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

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

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

深入理解Linux网络技术内幕——PCI层和网络接口卡

概述 内核的PCI子系统(即PCI层)提供了不同设备一些通用的功能,以便简化各种设备驱动程序. PCI层重要结构体如下: pci_device_id 设备标识,根据PCI标志定义的ID,而不是Linux本地的. pci_dev 类似于网络设备的net_device.每个PCI会被分配一个net_dev实例. pci_driver PCI层和设备驱动程序之间的接口.主要由一些函数指针组成.如下所示: struct pci_driver { struct list_head node; char *