接收到帧时通知驱动程序

    在网络环境中。设备(网卡)接收到一个数据帧时,须要通知驱动程序进行处理。

有一下几种通知机制:

轮询:
    内核不断检查设备是否有话要说。(比較耗资源,但在一些情况下却是最佳方法)

中断:
    特定事件发生时,设备驱动程序代表内核指示设备产生硬件中断,内核中断其他活动满足设备的须要。

多数网络驱动程序使用中断。

中断期间处理多帧:
    中断被通知。且驱动程序运行。

然后保持帧的接收(加载),直到输入队列达到指定的数目、或者一直做下去知道队列清空、或者经过指定时间。

定时器驱动的中断事件
    驱动程序指定设备定期产生中断事件(驱动程序主动。而不是设备主动,与前面的中断不同)。然后处理函数处理上次驱动以来到达的帧。

这样的机制会导致帧处理的延时。比方指定时间为100ms。而帧可能在第0ms、第50ms、也可能在第100ms刚好到达,平均延时为50ms。

组合机制
    低流量负载下使用中断

    高流量负载下使用定时器驱动的中断

中断的优缺点:
    中断在低流量负载下是非常好的选择。但在高流量负载情况下,因为没接收到一个帧就进行一次中断,非常easy让CPU在处理中断上浪费时间,甚至崩溃。

    负责接收帧的代码,分为两部分(实际上为中断的上半部函数、下半部函数)。上半部函数将帧复制到输出队列,并运行其它一些不可抢占的工作。下半部函数的内容则是内核处理输入队列中的帧(将帧传给详细的协议处理)。因为上半部函数能够抢占下半部函数的运行,在高流量负载下,就有可能上半部函数一直运行,而下半部函数被搁置。而导致输入队列溢出,系统崩溃。


中断处理函数

为什么有下半部函数

    简单的说,下半部函数之所以存在是由于中断是不可抢占的。而我们假设花太多时间去处理一个中断,则可能导致其它中断迟迟不能运行。为此。我们将中断处理程序分为上半部函数和下半部函数。上半部函数主要运行中断处理程序中不可抢占的内容(如把帧从设备复制到输入队列)。下半部函数运行可被抢占的内容(如帧的详细给各自协议的处理)。
    上半部函数独占CPU资源运行,下半部函数运行时能够被其它中断抢占CPU资源。有了下半部函数后,中断处理程序的模型例如以下:
    1)设备发送信号给CPU,通知有中断事件
    2)CPU关中断,运行上半部函数
    3)上半部函数运行
    4)上半部函数运行完成。CPU开中断。并运行下半部函数
    上半部函数处理的主要内容包含:
    a)把内核稍后要处理的中断事件的全部信息保存到RAM
    b)设置标识,一边内核之后知道须要处理该中断。及怎样处理
    c)开中断,

下半部函数解决方式

    内核提供多种下半部函数的解决方式,主要有旧式下半部、微任务、软IRQ三种。

不同的解决方式的区别主要在于执行环境及并发与上锁。

    1)旧式下半部: 不论什么时刻,仅仅有一个旧式下半部函数能够运行(无论多少个CPU)
    2)微任务:        不论什么时刻。每一个CPU,仅仅有一个微任务实例能够运行.(多数情况下的选择)
    3)软IRQ:        不论什么时刻,一个CPU的每一个软IRQ仅仅有一个实例能够执行。(收发帧等须要及时响应的的网络任务的选择
/***********************Linux-2.6.32************************************/
//include/linux/hardirq.h
in_irq() //CPU正服务于硬件中断时,返回True
in_softirq() //CPU正服务于软件中断时,返回True
in_interrupt() //CPU正在服务于一个硬件中断或软件中断。或抢占功能关闭时,返回True //arch/x86/include/asm/hardirq.h
local_softirq_pending() //本地CPU至少有一个IRQ出于未决状态时,返回True //include/linux/interrupt.h
__raise_softirq_irqoff() //设置与软IRQ相关联的标识,将IRQ标记为未决
raise_softirq_irqoff() //__raise_softirq_irqoff包裹函数,当in_interrupt为False时,唤醒ksoftirqd
raise_softirq() //包裹raise_softirq_irqoff,调用raise_softirq_irqoff前先关中断 //kernel/softirq.c
__local_bh_enable() //开启本地CPU的下半部
local_bh_enable() //假设有不论什么软IRQ未决,且in_interrupt返回False,则invoke_softirq
local_bh_disable() //关闭CPU下半部 //include/linux/irqflags.h
local_irq_enable() //开启本地CPU中断功能
local_irq_disable() //关闭本地CPU中断功能
local_irq_save() //先把本地CPU中断状态保存,再予以关闭
local_irq_restore() //恢复本地CPU之前的中断状态,恢复local_irq_save保存的中断信息 //include/linux/spinlock.h
spin_lock_bh() //取得回旋锁。关闭下半部及抢占功能
spin_unlock_bh() //释放回旋锁,重新启动下半部抢占功能


抢占功能

    Linux2.5之后的内核实现了全然抢占(preemptitle)的功能。(即内核本身也能够被抢占)。可是有些时候。内核运行的任务不希望被抢占,(比方正在服务于硬件)这时就须要关闭抢占功能。以下是几个与抢占功能的管理相关的函数。

//inculde/linux/preempt.h
preempt_disable() //为当前任务关闭抢占功能。可反复调用,递增引用计数器
preempt_enable() //抢占功能再度开启,(须要先检查引用计数器是否为0)
preempt_enable_no_resch() //递减引用计数器,仅仅有引用计数器为0时,抢占功能才干再度开启
preempt_check_resched() //由preempt_enable调用,检查引用计数器是否为0. // arch/x86/include/asm/thread_info.h
struct thread_info {
……
int preempt_count; /* 0 => preemptable,
<0 => BUG */ //抢占计数器,指定进程能否被抢占
……
};


下半部函数

   下半部函数的基础构架有下面几个部分:
1)分类:把下半部函数分成适当类型
2)关联:注冊(登记)下半部函数类型及其处理函数间的关联关系
3)调度:为下半部函数进行调度,以准备运行
4)通知:通知内核BH的存在

旧式下半部函数(linux-2.2曾经)

    旧式下半部函数模型(如linux-2.2版本号)把下半部分为非常多种类型。例如以下:
enum {
TIMER_BH = 0,
CONSOLE_BH,
TQUEUE_BH,
DIGI_BH,
SERIAL_BH,
RISCOM8_BH,
SPECIALIX_BH,
AURORA_BH,
ESP_BH,
NET_BH, //网络下半部
SCSI_BH,
IMMEDIATE_BH,
KEYBOARD_BH,
CYCLADES_BH,
CM206_BH,
JS_BH,
MACSERIAL_BH,
ISICOM_BH
};

各个类型及其处理函数用init_bh()关联。如网络下半部在net_dev_init中关联
_ _initfunc(int net_dev_init(void))
{
... ... ...
init_bh(NET_BH, net_bh);
... ... ...
}

中断处理函数要触发下半部函数时,就使用mark_bh在全局位图bh_active设置标志位
extern inline void mark_bh(int nr)
{
set_bit(nr, &bh_active);
};

如网络设备接收到一个帧时,就调用netif_rx通知内核,将帧复制到输入队列backlog,然后标记NET_BH下半部标识:
skb_queue_tail(&backlog, skb);
mark_bh(NET_BH);
return

引入软IRQ

    linux-2.4版本号以后的linux内核引入了软IRQ。

(软IRQ能够视为IRQ的多线程版本号)

    新式软IRQ有下面几种类型(linux-2.4仅仅有六种。后面又发展了):
//include/linux/interrupt.h
enum
{
HI_SOFTIRQ=0, //高优先级微任务
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ, //网络软IRQ
NET_RX_SOFTIRQ, //网络软IRQ
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ, //低优先级微任务软IRQ
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ NR_SOFTIRQS
};
    一种软IRQ在一个CPU上仅仅能由一个实例在执行。
    为此,每种软IRQ类型维护一个softnet_data类型的数组,数组的大小为CPU的数目。而每一个CPU相应该类型的软IRQ维护一个 softnet_data的数据结构。
/*
* Incoming packets are placed on per-cpu queues so that
* no locking is needed.
*/
struct softnet_data
{
struct Qdisc *output_queue; //qdisc是queueing discipline的简写。也就是排队规则,即qos.这里也就是输出帧的控制。
struct sk_buff_head input_pkt_queue; //当输入帧被驱动取得之前,就保存在这个队列里,(不适用与napi驱动,napi有自己的私有队列)
struct list_head poll_list; //表示有输入帧待处理的设备链表。
struct sk_buff *completion_queue; //表示已经成功被传递出的帧的链表。 struct napi_struct backlog; //用来兼容非napi的驱动。
};

初始化在net_dev_init中
static int __init net_dev_init(void)
{
......
for_each_possible_cpu(i) {
struct softnet_data *queue; queue = &per_cpu(softnet_data, i);
skb_queue_head_init(&queue->input_pkt_queue);
queue->completion_queue = NULL;
INIT_LIST_HEAD(&queue->poll_list); queue->backlog.poll = process_backlog;
queue->backlog.weight = weight_p;
queue->backlog.gro_list = NULL;
queue->backlog.gro_count = 0;
}
......
}

软IRQ的注冊于调度机制

    软IRQ的注冊与调度机制与旧式模型类似,仅仅是函数不一样。

    相应init_bh(),软IRQ使用spen_softirq()对软IRQ类型与其关联函数的关系进行注冊。

// kernel/softirq.c
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}

软IRQ通过下列函数在本地CPU上进行调度,准备运行:
__raise_softirq_irqoff()  //设置与软IRQ相关联的标识,将IRQ标记为未决
raise_softirq_irqoff() //__raise_softirq_irqoff包裹函数,当in_interrupt为False时,唤醒ksoftirqd
raise_softirq() //包裹raise_softirq_irqoff,调用raise_softirq_irqoff前先关中断
软IRQ详细的运行參考其它博文
do_IRQ
schecule
do_softirq
參考其它博文


微任务

    微任务是建立在软IRQ的基础之上的。相应软IRQ的HI_SOFTIRQ(高优先级微任务)和TASKLET_SOFTIRQ(普通优先级微任务)。
    每一个CPU有两份tasklet_struct表。一份相应HI_SOFTIRQ,一份相应TASKLET_SOFTIRQ。
/*
* Tasklets
*/
struct tasklet_head
{
struct tasklet_struct *head;
struct tasklet_struct **tail;
}; static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

微任务有一些特征(与旧式下半部函数的差别)
1)微任务不限数目,可是base_bh的每一位标识仅仅限于一种类型的下半部函数
2)微任务提供两种等级的优先级
3)不同微任务能够再不同CPU上同事执行
4)微任务相对于旧式下半部来说是动态的,不须要静态地在XXX_BH或XXX_SOFTIRQ枚举列表中静态声明
struct tasklet_struct
{
struct tasklet_struct *next; //把关联到同一个CPU的结构链接起来
unsigned long state; //位图标识,其可能的取值由TASKLET_STATE_XXX枚举
atomic_t count; //计数器,0表示微任务被关闭,不可运行。非0表示微任务已经开启
void (*func)(unsigned long); //要运行的函数
unsigned long data; //上面函数的參数
}; enum
{
TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */
TASKLET_STATE_RUN /* Tasklet is running (SMP only) */
};



深入理解Linux网络技术内幕——中断与网络驱动程序的更多相关文章

  1. 深入理解linux网络技术内幕读书笔记(三)--用户空间与内核的接口

    Table of Contents 1 概论 1.1 procfs (/proc 文件系统) 1.1.1 编程接口 1.2 sysctl (/proc/sys目录) 1.2.1 编程接口 1.3 sy ...

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

    概述    内核的初始化过程过程中,与网络相关的工作如下所示:     内核引导时执行start_kernel,start_kernel结束之前会调用rest_init,rest_init初始化内核线 ...

  3. 深入理解linux网络技术内幕读书笔记(九)--中断与网络驱动程序

    Table of Contents 1 接收到帧时通知驱动程序 1.1 轮询 1.2 中断 2 中断处理程序 3 抢占功能 4 下半部函数 4.1 内核2.4版本以后的下半部函数: 引入软IRQ 5 ...

  4. 深入理解linux网络技术内幕读书笔记(十)--帧的接收

    Table of Contents 1 概述 1.1 帧接收的中断处理 2 设备的开启与关闭 3 队列 4 通知内核帧已接收:NAPI和netif_rx 4.1 NAPI简介 4.1.1 NAPI优点 ...

  5. 《深入理解Linux网络技术内幕》阅读笔记 --- 路由基本概念

    一.路由的基本概念 1.一条路由就是一组参数,这些参数存储了往一个给定目的地转发流量所需的信息,而一条路由所需的最少的参数集合为:(1)目的网络,(2)出口设备,(3)下一跳网关 2.路由中的相关术语 ...

  6. 深入理解linux网络技术内幕读书笔记(五)--网络设备初始化

    Table of Contents 1 简介 2 系统初始化概论 2.1 引导期间选项 2.2 中断和定时器 2.3 初始化函数 3 设备注册和初始化 3.1 硬件初始化 3.2 软件初始化 3.3 ...

  7. 深入理解linux网络技术内幕读书笔记(六)--PCI层与网络接口卡

    Table of Contents 1 本章涉及的数据结构 1.1 pci_device_id结构 1.2 pci_dev结构 1.3 pci_driver结构 2 PCI NIC设备驱动程序的注册 ...

  8. 深入理解linux网络技术内幕读书笔记(四)--通知链

    Table of Contents 1 概述 2 定义链 3 链注册 4 链上的通知事件 5 网络子系统的通知链 5.1 包裹函数 5.2 范例 6 测试实例 概述 [注意] 通知链只在内核子系统之间 ...

  9. 深入理解linux网络技术内幕读书笔记(二)--关键数据结构

    Table of Contents 1 套接字缓冲区: sk_buff结构 1.1 网络选项及内核结构 1.2 结构说明及操作函数 2 net_device结构 2.1 MTU 2.2 结构说明及操作 ...

随机推荐

  1. C语言的本质(1)——计算机与二进制

    人类的计数方式通常是"逢十进一",称为十进制(Decimal),大概因为人有十个手指,所以十进制是最自然的计数方式,各民族的文字中都有十个数字,而阿拉伯数字0-9是目前最广泛采用的 ...

  2. Egret的若干局限

    Egret是个好东西,整套workflow用下来,特别顺手,对于移动端游戏的开发来说,选择Egret无疑是个不二的选择. 当然,小学语文课上老师就教过一种写作手法,欲扬先抑,笔者今天倒过来,来说说Eg ...

  3. 解决Easyui1.3.3 IE8兼容性问题

    事先声明:项目在Firefox和Chrome上完美运行,在MSIE9.MSIE10上基本没问题,但是放在MSIE8上面运行问题就出来了.登录系统后,系统页面跳动,导致系统无法使用:我使用的是Easyu ...

  4. linux学习之(三)-文件操作命令

    创建一个空文件: touch  文件名 例:touch   tom 查看: 查看一个文件的内容命令cat 文件名 例:cat tom   注:cat命令并不能显示文件的所有信息,但屏幕显示的 行数是有 ...

  5. wxpython 树形控件全选和取消全选

    #encoding:utf-8 import wx import wx.lib.agw.customtreectrl as CT class MyFrame(wx.Frame): def __init ...

  6. NBA工资帽

    工资帽(Salary cap):在NBA,工资帽是最著名工资限制条款. 每年的"工资帽"是依据NBA前一年的总收入,然后取这个总收入的48%作为NBA球队工资总额. 再拿这48%的 ...

  7. java于23设计模式

    详情请参阅23设计模式 版权声明:本文博主原创文章,博客,未经同意不得转载.

  8. UITabBarController 笔记(二) ViewController中加UITabBarController

    新建一个简单视图iOS工程,在ViewController的viewDidLoad中代码如下 - (void)viewDidLoad { [super viewDidLoad]; // Do any ...

  9. Android API Level在11前后及16之后时Notification的不同用法

    作为刚入门Android的小白,最近在按照郭大神的<第一行代码>在练习,在用到Notification时遇到了一些问题,网上资料比较零散,我这里做了一个总结分析给各位,若有错误,恳请指正~ ...

  10. 汇编语言学习系列 for循环实现

    假如汇编语言要实现如下C语言的功能,编译环境Ubuntu14.04(32位). #include<stdio.h> int fact_for(int n) { int i; ; ; i & ...