linux中断编程&.amp

   1.中断可以随时的打断处理机对其他程序的执行,如果被打断的代码对系统很重要,那么此时中断处理程序的执行时间应该是越短越好。

  2.通过上文我们知道,中断处理程序正在执行时,会屏蔽同条中断线上的中断请求;而更严重的是,如果设置了IRQF_DISABLED,那么该中断服务程序执行是会屏蔽所有其他的中断请求。那么此时应该让中断处理程序执行的越快越好。

  上面的几个例子都要求中断服务程序的执行时间越短越好。一般的,中断处理程序会在上半部分执行。而事实上,几乎所有的情况,上半部分就只执行中断处理程序。因此,我们可以这样认为:一个完整的中断处理流程是由中断处理程序和下半部分共同完成的。

  这样划分是有一定原因的,因为我们必须有一个快速、异步而且简单的处理程序专门来负责对硬件的中断请求做出快速响应,与此同时也要完成那些对时间要求很严格的操作。而那些对时间要求相对宽松,其他的剩余工作则会在稍候的任意时间执行,也就是在所谓的下半部分去执行。

  总之,这样划分一个中断处理过程主要是希望减少中断处理程序的工作量(当然了,理想情况是将全部工作都抛给下半段。但是中断处理程序至少应该完成对中断请求的相应。),因为在它运行期间至少会使得同级的中断请求被屏蔽,这些都直接关系到整个系统的响应能力和性能。而在下半段执行期间,则会允许响应所有的中断。

  和上半段只能通过中断处理程序实现不同的是,下半部可以通过多种机制来完成:小任务(tasklet),工作队列,软中断。在本博客后续的文章当中你会看到,不管是那种机制,它们均为下半部提供了一种执行机制,比上半部灵活多了。至于何时执行,则由内核负责。

  以上是上下部分划分的基本概述,通过tasklet和工作队列机制,你可以更深刻的理解下部分的执行。

  tasklet的实现

  tasklet(小任务)机制是中断处理下半部分最常用的一种方法,其使用也是非常简单的。正如在前文中你所知道的那样,一个使用tasklet的中断程序首先会通过执行中断处理程序来快速完成上半部分的工作,接着通过调用tasklet使得下半部分的工作得以完成。可以看到,下半部分被上半部分所调用,至于下半部分何时执行则属于内核的工作。对应到我们此刻所说的tasklet就是,在中断处理程序中,除了完成对中断的响应等工作,还要调用tasklet,如下图示。


  tasklet由tasklet_struct结构体来表示,每一个这样的结构体就表示一个tasklet。在中可以看到如下的定义:

  1tasklet_struct

  2{

  3structtasklet_struct *next;

  4unsigned long state;

  5atomic_t count;

  6void(*func)(unsignedlong);

  7unsigned long data;

  8};

  在这个结构体中,第一个成员代表链表中的下一个tasklet。第二个变量代表此刻tasklet的状态,一般为TASKLET_STATE_SCHED,表示此tasklet已被调度且正准备运行;此变量还可取TASKLET_STATE_RUN,表示正在运行,但只用在多处理器的情况下。count成员是一个引用计数器,只有当其值为0时候,tasklet才会被激活;否则被禁止,不能被执行。而接下来的func变量很明显是一个函数指针,它指向tasklet处理函数,这个处理函数的唯一参数为data。

  使用tasklet

  在使用tasklet前,必须首先创建一个tasklet_struct类型的变量。通常有两种方法:静态创建和动态创建。这样官方的说法仍然使我们不能理解这两种创建到底是怎么一回事。不够透过源码来分析倒是可以搞明白。

  在中的两个宏:

  1464#define DECLARE_TASKLET(name, func, data)

  2465struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

  3466

  4467#define DECLARE_TASKLET_DISABLED(name, func, data)

  5468struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

  就是我们进行静态创建tasklet的两种方法。通过第一个宏创建的tasklet处于激活状态,再通过调度函数被挂起尽而被内核执行;而通过第二个宏创建的tasklet处于禁止状态。从两个宏的定义可以看到,所谓的静态创建就是直接定义个一个名为name的tasklet_struct类型的变量,并将宏中各个参数相应的赋值给这个name变量的各个成员。注意,两个宏在功能上差异就在于对name变量count成员的赋值上,具体原因在第一部分已经说明。也许你对ATOMIC_INIT这样的初始化方式感到疑惑,那么看完定义后,你就会一目了然:

  1//在arch/x86/include/asm/atomic.h中

  215#define ATOMIC_INIT(i) { (i) }

  3//在linux/types.h中

  4190typedef struct{

  5191 intcounter;

  6192} atomic_t;

  与静态创建相对的是动态创建,通过给tasklet_init函数传递一个事先定义的指针,来动态创建一个tasklet。这个函数源码如下。

  1470void tasklet_init(structtasklet_struct *t,

  2471 void(*func)(unsignedlong), unsignedlongdata)

  3472{

  4473 t->next = NULL;

  5474 t->state = 0;

  6475 atomic_set(&t->count, 0);

  7476 t->func = func;

  8477 t->data = data;

  9478}

  相信你在阅读上面的代码是基本上没有什么难以理解的地方,不过这里还是要特别说明一下atomic_set函数:

  1//在arch/x86/include/asm/atomic.h中

  235static inlinevoidatomic_set(atomic_t *v,inti)

  336{

  437 v->counter = i;

  538}

  首先tasklet_init当中,将&t->count传递给了此函数。也就是说将atomic_t类型的成员count的地址传递给了atomic_set函数。而我们在此函数中却要为count变量中的成员counter赋值。如果说我们当前要使用i,那么应该是如下的引用方法:t-》count.i。明白了吗?

  ok,通过上述两种方法就可以创建一个tasklet了。同时,你应该注意到不管是上述那种创建方式都有func参数。透过上述分析的源码,我们可以看到func参数是一个函数指针,它指向的是这样的一个函数:

  1void tasklet_handler(unsignedlongdata);

  如同上半部分的中断处理程序一样,这个函数需要我们自己来实现。

  创建好之后,我们还要通过如下的方法对tasklet进行调度:

  1tasklet_schedule(&my_tasklet)

  通过此函数的调用,我们的tasklet就会被挂起,等待机会被执行

  一个举例

  在此只分析上下两部分的调用关系,完整代码在这里查看。

  01//define a argument of tasklet struct

  02static struct tasklet_struct mytasklet;

  03

  04static void mytasklet_handler(unsigned longdata)

  05{

  06printk("This is tasklet handler..n");

  07}

  08

  09static irqreturn_t myirq_handler(int irq,void* dev)

  10{

  11staticintcount=0;

  12if(count<10)

  13{

  14printk("-----------%d start--------------------------n",count+1);

  15printk("The interrupt handeler is working..n");

  16printk("The most of interrupt work will be done by following tasklet..n");

  17tasklet_init(&mytasklet,mytasklet_handler,0);

  18tasklet_schedule(&mytasklet);

  19printk("The top half has been done and bottom half will be processed..n");

  20}

  21count++;

  22returnIRQ_HANDLED;

  23}

  从代码中可以看到,在上半部中通过调用tasklet,使得对时间要求宽松的那部分中断程序推后执行。

  为什么还需要工作队列?

  工作队列(work queue)是另外一种将中断的部分工作推后的一种方式,它可以实现一些tasklet不能实现的工作,比如工作队列机制可以睡眠。这种差异的本质原因是,在工作队列机制中,将推后的工作交给一个称之为工作者线程(worker thread)的内核线程去完成(单核下一般会交给默认的线程events/0)。因此,在该机制中,当内核在执行中断的剩余工作时就处在进程上下文(process context)中。也就是说由工作队列所执行的中断代码会表现出进程的一些特性,最典型的就是可以重新调度甚至睡眠。

  对于tasklet机制(中断处理程序也是如此),内核在执行时处于中断上下文(interrupt context)中。而中断上下文与进程毫无瓜葛,所以在中断上下文中就不能睡眠。

  因此,选择tasklet还是工作队列来完成下半部分应该不难选择。当推后的那部分中断程序需要睡眠时,工作队列毫无疑问是你的最佳选择;否则,还是用tasklet吧。

  中断上下文

  在了解中断上下文时,先来回顾另一个熟悉概念:进程上下文(这个中文翻译真的不是很好理解,用“环境”比它好很多)。一般的进程运行在用户态,如果这个进程进行了系统调用,那么此时用户空间中的程序就进入了内核空间,并且称内核代表该进程运行于内核空间中。由于用户空间和内核空间具有不同的地址映射,并且用户空间的进程要传递很多变量、参数给内核,内核也要保存用户进程的一些寄存器、变量等,以便系统调用结束后回到用户空间继续执行。这样就产生了进程上下文。

  所谓的进程上下文,就是一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容。当内核需要切换到另一个进程时(上下文切换),它需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态继续执行。上述所说的工作队列所要做的工作都交给工作者线程来处理,因此它可以表现出进程的一些特性,比如说可以睡眠等。

  对于中断而言,是硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理,中断上下文就可以理解为硬件传递过来的这些参数和内核需要保存的一些环境,主要是被中断的进程的环境。因此处于中断上下文的tasklet不会有睡眠这样的特性。

  工作队列的使用

  内核中通过下述结构体来表示一个具体的工作:

  1struct work_struct

  2{

  3unsigned long pending;//这个工作是否正在等待处理

  4structlist_head entry;//链接所有工作的链表,形成工作队列

  5void(*func)(void*);//处理函数

  6void*data;//传递给处理函数的参数

  7void*wq_data;//内部使用数据

  8structtimer_list timer;//延迟的工作队列所用到的定时器

  9};

  而这些工作(结构体)链接成的链表就是所谓的工作队列。工作者线程会在被唤醒时执行链表上的所有工作,当一个工作被执行完毕后,相应的work_struct结构体也会被删除。当这个工作链表上没有工作时,工作线程就会休眠。

  通过如下宏可以创建一个要推后的完成的工作:

  1DECLARE_WORK(name,void(*func)(void*),void*data);

  也可以通过下述宏动态创建一个工作:

  1INIT_WORK(structwork_struct *work,void(*func)(void*),void*data);

  与tasklet类似,每个工作都有具体的工作队列处理函数,原型如下:

  1void work_handler(void*data)

  将工作队列机制对应到具体的中断程序中,即那些被推后的工作将会在func所指向的那个工作队列处理函数中被执行。

  实现了工作队列处理函数后,就需要schedule_work函数对这个工作进行调度,就像这样:

  1schedule_work(&work);

  这样work会马上就被调度,一旦工作线程被唤醒,这个工作就会被执行(因为其所在工作队列会被执行)。

  (PS;在前面很多篇文章中从理论的角度分析了中断机制的处理流程,分为上下两部完成中断处理,其实就是为了满足各个条件才分两步的。用实际的例子讲述怎么使用下半部 http://oss.org.cn/kernel-book/ch03/3.3.3.htm)

时间: 2024-07-28 17:48:43

linux中断编程&.amp的相关文章

linux中断--内核中断编程

Linux中断内核编程 前言 在前面分析了中断的基本原理后,就可以写一个内核中断程序来体验以下,也可以借此程序继续深入来了解内核中断的执行过程 一.内核中断程序: 我们还是来看一看成程序: 在看程序之前,要熟悉如何进行模块编程,和了解module_pararm()的用法.如果不熟悉的话请大家看,module_param()的学习和Linux内核模块编程,在此不作解释. 1.程序interrupt.c [c-sharp] view plaincopy 1 /* 2 *file name :inte

Linux中断内核编程

转自:http://blog.csdn.net/tigerjb/article/details/6069516 在前面分析了中断的基本原理后,就可以写一个内核中断程序来体验以下,也可以借此程序继续深入来了解内核中断的执行过程 一.内核中断程序 : 我们还是来看一看成程序: 在看程序之前,要熟悉如何进行模块编程,和了解module_pararm()的用法.如果不熟悉的话请大家看,module_param()的学习 和Linux内核模块编程,在此不作解释. 1.程序interrupt.c [c-sh

linux系统编程之信号(五) 实时信号与sigqueue函数

一.sigqueue函数 功能:新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction()配合使用. 原型:int sigqueue(pid_t pid, int sig, const union sigval value); 参数: sigqueue的第一个参数是指定接收信号的进程id,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值. 返回值:成功返回0,失败返回-1 typede

linux系统编程之信号(二) 信号发送函数及不同精度的睡眠

一.kill, raise, killpg 函数 int kill(pid_t pid, int sig); int raise(int sig); int killpg(int pgrp, int sig); kill命令是调用kill函数实现的,kill函数可以给一个指定的进程或进程组发送指定的信号,其中kill 函数的pid 参数取值不同表示不同含义,具体可man 一下.raise函数可以给当前进程发送指定的信号(自己给自己发信号).killpg 函数可以给进程组发生信号.这三个函数都是成

linux中断--中断嵌套&amp;amp;中断请求丢失

  关于中断嵌套: 在linux内核里,如果驱动在申请注册中断的时候没有特别的指定,do_irq在做中断响应的时候,是开启中断的,如果在驱动的中断处理函数正在执行的过程中,出现同一设备的中断或者不同设备的中断,这时候新的中断会被立即处理,还是被pending,等当前中断处理完成后,再做处理. 在2.4和2.6内核里,关于这一块是否有什么不同. 一般申请中断的时候都允许开中断,即不使用SA_INTERRUPT标志.如果允许共享则加上 SA_SHIRQ,如果可以为内核熵池提供熵值(譬如你写的驱动是i

Linux网络编程入门

(一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍 客户端和服务端          网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户端         在网络程序中,如果一个程序主动和外面的程序通信,那么我们把这个程序称为客户端程序. 比如我们使用ftp程序从另外一         个地方获取文件的时候,是我们的ftp程序主动同外面进行通信(获取文件), 所以这个地方我们的ftp程序就是客户端程序.  服务端        

linux socket编程初认识

  学习是分享和合作式的! 转载请注明出处:http://blog.csdn.net/wdzxl198/article/details/10472999: 直接进入主题:           socket起源于Unix,而Unix/Linux基本哲学之一就是"一切皆文件",都可以用"打开open –> 读写write/read –> 关闭close"模式来操作.我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket

《Linux系统编程(第2版)》——第1章 入门和基本概念 1.1 系统编程

第1章 入门和基本概念 摆在你面前的是一本关于系统编程的书,你将在本书中学习到编写系统软件的相关技术和技巧.系统软件运行在系统的底层,与内核和系统核心库进行交互.常见的系统软件包括Shell.文本编辑器.编译器.调试器.核心工具(GNU Core Utilities)以及系统守护进程.此外,网络服务.Web服务和数据库也属于系统软件的范畴.这些程序都是基于内核和C库实现的,可以称为"纯"系统软件.相对地,其他软件(如高级GUI应用),很少和底层直接交互.有些程序员一直在编写系统软件,而

Linux 内核编程基本功之内核同步与互斥锁mutex

Linux 内核编程基本功之内核同步与互斥锁mutex 作者 digoal 日期 2016-11-07 标签 PostgreSQL , 同步流复制 , mutex , Linux 背景 在使用PostgreSQL实现同步流复制时,在主节点发现有大量的mutex,导致了写并发被限制. 本文为转载文章 http://blog.csdn.net/cug_fish_2009/article/details/6126414 Pro-II.内核同步与互斥锁 1.理解互斥锁? 互斥锁的使用也是保持内核临界区的