第一部分移步传送门召唤!!:http://www.cnblogs.com/lenomirei/p/5562086.html

上回说了Linux内核实现中断会把中断分为两部分进行处理,上回讲了上部分,这回讲下部分的设计思路

  • 下半部的实现机制
    • 软中断
    • tasklet:是通过软中断实现的,但和软中断有所不同
    • 工作队列

讲上面几个实现机制之前先讲一个古老的方法,现在版本的内核虽然已经不再食用了,但是思想还在继续使用

最早的Linux只提供了“bottom half”这种机制实现下半部分,被称为BH,实现简单粗暴,设置一个全局变量(32位整数),表示一个32个节点的链表队列,哪位设置为1证明哪个bottom half就可以执行了。

  • 软中断

第一个先将软中断实现下半部分机制,要想将这个机制,必须得先说明软中断的实现方式,软中断实在编译期间静态分配的,kernel/softirq.c中定义了一个包含有32个结构体的数组static struct softirq_action softirq_vec[NR_SOFTIRQS],并且有一个对应的32位整数u32 pending,用来表示每个软中断的状态(不要嫌少,一般根本用不了那么多,一般9个10个就够用了,为什么这么少?很少有用软中断处理下半部分的,能用tasklet的地方绝不会使用软中断)

把软中断放进刚才说的32个长度的结构体数组中就完成了软中断的注册,想要执行软中断必须先标记注册好的软中断,这个过程被称为触发软中断,通常,中断处理程序(就是上半部分)会在返回之前标记它的软中断,所以不必担心,然后在合适的时刻就会执行该软中断

合适的时刻:1.从一个硬件中断代码处返回时;2.在ksoftirqd内核线程中;3.在那些显示检查和执行待处理的软中断的代码中;

不管是上面哪个时刻,软中断最终都是会被执行的,调用do_softirq()该函数会循环遍历(循环检查pending的每一个位,所以循环最多只能执行32次)

  • tasklet

因为takslet是使用软中断实现的,所以tasklet本身就是个软中断,我们是通过tasklet来实现下半部的机制的,所以在处理方式上和软中断十分的相似,tasklet由tasklet结构体表示,每一个结构体单独代表一个tasklet,它的定义如下

 struct tasklet_struct
{
stauct tasklet_struct *next;//链表中的下一个节点
unsigned long state;//tasklet的状态
atomic_t count;//引用计数器
void (*func)(unsigned long);//tasklet处理函数
unsigned long data;//给tasklet处理函数的参数
};

其中tasklet的状态一共只有三种:0,TASKLET_STATE_SCHED,TASKLET_STATE_RUN,只能在这三种之间取值,0表示啥也没有等待调度,SCHED表示已经调度,RUN表示该tasklet正在运行。

已经被调度的tasklet结构体存放在两种单处理器数据结构当中,分别是tasklet_vec(普通优先级的tasklet)和tasklet_hi_vec(高优先级的tasklet),几乎没区别,只是优先级不一样,调度的步骤如下

  1. 检查tasklet的状态是否为TASKLET_STATE_SCHED,如果是,就证明不需要调度了,直接返回
  2. 调用_tasklet_schedule()函数进行调度
  3. 保存中断状态,禁止本地中断,防止数据被其他中断拿去更改
  4. 头插加入链表,就刚才说的那两个优先级不同的链表
  5. 唤起tasklet中断(封装好的软中断)
  6. 恢复中断并返

运行的步骤如下:

  1. 禁止中断,检测两个链表里面有没有东西
  2. 把当前处理器的该链表设置为NULL(意思就是我要把链表里的东西全弄完,先置成NULL)
  3. 允许相应中断
  4. 循环遍历tasklet链表上的每一个节点
  5. 如果是多处理器系统,查看节点状态如果是RUN就证明在其他处理器上运行中,直接跳到下一个节点(因为同一时间里,相同类型的tasklet只有一个能执行)
  6. 如果当前节点的状态不是RUN,就设置成RUN,以防其他处理器调用
  7. 检查count是不是0(看看别人是否正在占用)如果不是0则被禁止,跳到下一个挂起的tasklet去
  8. 安全确保,开始执行
  9. 一直循环,直到没有tasklet了(因为我们把链表置为NULL了,必须把拿出来的东西处理完)

其实tasklet给人的感觉就是一个对软中断的封装的简单接口而已。。

每个处理器都有一组辅助处理软中断(当然也就包括tasklet)的内核线程,那么什么时候执行这些软中断呢,上面在软中断部分也阐述了,但是这样有个问题,那就是软中断如果继续调软中断,就会不停的执行软中断。。这样在处理器负载很严重的时候就不太好了,会导致用户空间进程饥饿,还有一种方案,那就是并不立即处理软中断,而是等待一段时间,但是在处理器比较闲的时候这么做很显然不太好,因为完全可以立即执行你却让处理器闲着。作为改进,当大量软中断出现的时候,内核会唤醒一组内核线程来处理这些负载,关键来了,这些带着软中断的线程的优先级会被设置到最低的优先级上(nice值取最高为19),这样的会在处理器比较忙的时候,这些软中断不会跟用户空间进程争夺处理器资源,而且最终一定会被执行,处理器空闲的时候也可以直接得到运行。

  • 工作队列

工作队列是另外一种比较新的将工作推后的形式,和之前的两种处理方式不同,它会把工作交给一个内核线程去执行,这就意!味!着!是由进程上下文来处理了!就可以睡眠了!!(中断是不允许睡眠的)所以很简单就可以在这两种方法之间做出选择。

每一个处理器都有一个对应的工作者线程

 struct workqueue_struct
{
struct cpu_work_queue_struct cpu_wq[NR_CPUS];
struct list_head list;
const char *name;
int sinqlethread;
int freezeable;
int rt;
};

 struct cpu_workqueue_struct
{
spinlck_t lock;//锁保护这种结构
struct list_head worklist;//工作列表
wait_queue_head_t more_work;
struct work_struct *current_struct;
struct workqueue_struct *wq;//关联工作队列结构
task_t *thread;//关联线程
};

表示工作的数据结构

 struct work_struct
{
atomic_long_t data;
struct list_head entry;
work_func_t func;
};

这些工作的结构体被连城链表,当链表上的所有工作都做完了之后,线程就会休眠

实现方式也很简单,

  1. 线程首先把自己设置为休眠状态(只是设置,并没有立即进入休眠)并把自己加入等待队列
  2. 如果工作链表是空的,就用schedule()调度函数进入睡眠状态
  3. 如果链表中有对象,线程就不会睡眠了,就把自己的状态改为TASK_RUNNING,然后从等待队列中出来
  4. 如果链表非空,执行那些被退后的下半部分应该干的工作(就是循环一直找。。。)

来个结构图

  • 下半部机制的选择

这三种看上去都不错,那么应该怎么选择呢

如果你对共享有很高的要求,虽然比较麻烦,但还是使用软中断吧,因为可以各种操作(虽然保障这些很麻烦)

如果你不是对共享有那么高的要求,推荐使用tasklet,因为两种同类型的tasklet不能同时并行

如果你想在进程上下文中解决下半部分的问题,使用工作队列吧,当然如果你想睡眠,你也没得选了

* 全剧终*

Linux内核实现中断和中断处理(二)的更多相关文章

  1. Linux内核实现中断和中断处理(一)

    Linux实现中断处理 内核是怎么知道应用程序要调用系统调用的呢?或者说应用程序怎么通知系统内核自己需要执行一个系统调用,这是通过软中断实现的,通过引发一个异常来促使系统切换到内核态去执行异常处理程序 ...

  2. 再思linux内核在中断路径内不能睡眠/调度的原因(2010)【转】

    转自:http://blog.csdn.net/maray/article/details/5770889 Linux内核中断路径中不能睡眠,为什么? 这里就行了很深入的讨论,值得一看:http:// ...

  3. 深入理解Linux内核-中断和异常

    Linux内核代码查看 http://androidxref.com/ 中断:被定义位一个事件,它能改变处理器执行指令的顺序.它对应硬件(CPU.其他硬件设备)电路产生的电信号. 同步中断:指令执行时 ...

  4. Linux内核分析-分析system_call中断处理过程

    姓名:江军 ID:fuchen1994 分析system_call中断处理过程 使用gdb跟踪分析一个系统调用内核函数(您上周选择那一个系统调用),系统调用列表参见http://codelab.shi ...

  5. 理解Linux内核之中断控制

    乍一看下边的Linux内核代码,貌似L3389有bug,于是我就绕有兴趣地阅读了一下local_irq_save/local_irq_restore的源代码. /* linux-4.14.12/mm/ ...

  6. Linux内核启动流程分析(二)【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-3380544.html S3C2410 Linux 2.6.35.7启动分析(第二阶段) 接着上面的分析,第 ...

  7. Linux 内核PCI 中断

    对于中断, PCI 是容易处理的. 在 Linux 启动时, 计算机的固件已经分配一个唯一的中 断号给设备, 并且驱动只需要使用它. 中断号被存储于配置寄存器 60 (PCI_INTERRUPT_LI ...

  8. Linux内核分析——分析system_call中断处理过程

    万子惠 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 我选择的是get ...

  9. Linux内核ROP姿势详解(二)

    /* 很棒的文章,在freebuf上发现了这篇文章上部分的翻译,但作者貌似弃坑了,顺手把下半部分也翻译了,原文见文尾链接 --by JDchen */ 介绍 在文章第一部分,我们演示了如何找到有用的R ...

随机推荐

  1. 59. Spiral Matrix && Spiral Matrix II

    Spiral Matrix Given a matrix of m x n elements (m rows, n columns), return all elements of the matri ...

  2. AX 2012 EP服务器配置多个环境

    AX 2012 如何在一台服务器配置不同环境的EP站点 安装完EP后,修改对应站点的web.config文件,指定需要连接的客户端配置文件路径即可,如下图: ` ``````````````````` ...

  3. 一千行MySQL学习笔记

    以下为本人当年初学MySQL时做的笔记,也从那时起没再更新过,但还是囊括了基本的知识点,有时还翻出来查查.是不是干货,就看亲们了~ 如果哪天笔记有更新了,我还是会更新该文章滴,其实笔记已经放到了Git ...

  4. [转载:]Fortran 二进制文件读写

    一些朋友总是咨询关于二进制文件的读写和转化.这里就我自己的理解说一说. 一).一般问题 二进制文件与我们通常使用的文本文件储存方式有根本的不同.这样的不同很难用言语表达,自己亲自看一看,理解起来会容易 ...

  5. NHibernate系列文章二:创建NHibernate工程

    摘要 这篇文章介绍了如何创建一个简单的使用NHibernate的控制台应用程序,包括使用NuGet.简单的配置.单表映射.对NHibernate配置文件添加智能提示.使用ISessionFactory ...

  6. 第十六章 综合实例——《跟我学Shiro》

    简单的实体关系图 简单数据字典 用户(sys_user) 名称 类型 长度 描述 id bigint 编号 主键 username varchar 100 用户名 password varchar 1 ...

  7. Failed to load the JNI shared library jvm.dll

    jdk和使用的ide版本不符合,换一个版本的jdk或者换版本的ide

  8. Setup Apache + PHP + MySql on Windows 10

    The below steps recorded my experiences to setup the Apache + PHP + MySql on my Windows 10. 1. Downl ...

  9. MongoDB-MMS使用总结

    环境:阿里云 系统:ubuntu 12.04 数据库:MongoDB shell version: 2.0.4 登录MMS,注册相应用户 根据文档开始安装:Install the Monitoring ...

  10. Python 之Django

    1.安装Django(下载慢的时候用MaxVPN) pip3 install django 2.Django处理流程 新建project(mysite),执行dj(mysite),然后在console ...