日志标签 ‘中断’

内核任务

2012年5月31日

内核任务是指内核中执行的一切活动对象,每个内核任务都拥有一个独立的程序计数器、栈和一组寄存器。我们可以将Linux内核看作是不断对请求进行响应的服务器,这里的请求可能来自CPU上正在执行的进程,也可能是来自发出中断请求的外部设备。这个类比用来强调内核中各个任务之间并不是严格按照顺序执行的,而是采用交错执行的方式。本文简单说明内核中的任务分类,以及每种内核任务的特点。

内核线程

内核线程只运行在内核空间,它为内核完成一些周期性任务,比如用于执行工作队列的keventd线程,用于执行内存回收的kswapd线程以及用于将脏缓冲区中的内容刷新到磁盘上的pdflush线程等。内核线程与用户进程在某些方面比较类似,比如它们被内核中同一个进程调度器所调度、都通过do_fork()创建等。这主要是因为内核线程与进程都通过进程描述符task_struct来描述,我们可以将内核线程看作是运行在内核空间的进程。内核中使用thread来描述内核线程是因为它并不拥有用户空间,因此内核线程之间的切换是迅速的。虽然都使用了线程一词,但是应该和用户态的线程有所区分。

系统调用

用户态进程必须通过系统调用才能进入内核并获得内核提供的服务,比如访问硬件设备。系统调用是一种异常,它通过128号中断向量向内核发出一个明确的请求。系统一旦执行某个系统调用,就从用户态切换到内核态,接下来内核就代表发出系统调用的进程执行,执行完毕后又返回用户空间。由于系统调用是内核代表进程执行,因此它可以获得当前进程的信息,比如可以访问当前进程的描述符中的信息,而且系统调用的执行使用的是当前发出系统调用进程的时间片。

中断处理程序

根据Intel的说法,异步中断也被简称为中断,它是由硬件设备依照CPU时钟信号随机产生的。这类中断可以打断任何正在执行的内核任务。在响应一个特定中断的时候,内核会执行一个函数,该函数称为中断处理程序(Interrupt Handler)。如果一条中断线上共享了多个设备,那么每个设备将对应一个中断服务例程(Interrupt Server Routine,ISR)。当该某个中断发生时,内核会调用相应的中断处理程序。该中断处理程序首先在内核态堆栈中保存IRQ和寄存器的值,然后响应该中断,接着执行共享这个中断线上所有设备的中断服务例程,执行完毕后恢复之前被打断的内核任务执行现场。

异常处理程序

同步中断也被称为异常,它是由CPU控制单元产生的,只有在一条指令终止执行后CPU才会发出异常。内核为每一种异常提供了一个专门的异常处理程序。异常处理程序的执行过程与中断处理程序类似,它首先将大多数寄存器的值压入内核堆栈中,接着调用响应异常处理程序,最后从异常处理程序中返回并向产生异常的进程发出一个信号。

可延迟函数

中断随时可能发生,因此中断处理程序也就随时会被执行。为了能尽快恢复被中断的代码再次执行,中断处理程序必须快速运行完毕。但是,中断处理程序极有可能和硬件设备进行交互,它需要花一些时间和硬件进行数据交互,比如等待数据的到来,从外设拷贝数据到内存等。中断程序既要快速运行,又要完成大量工作,这两者显然存在矛盾。为了解决这个问题,内核将中断处理的过程分为上下两部分,上部分即中断处理程序,主要对中断请求进行快速响应;下半部分主要完成中断处理过程中对时间要求相对宽松的工作。下半部分包含三种机制:软中断(softirq,与上文中软件中断不同),tasklet以及工作队列。鉴于工作队列通过keventd内核线程来执行,因此将软中断和tasklet统称为可延迟函数。

内核中的动态定时器

2010年10月13日

内核中时间的相关概念

节拍率:系统定时器以某种频率触发时钟中断,这个频率就称为节拍率(tick rate)。节拍率是通过静态预处理定义的,被定义为HZ。对于x86体系结构来说,它的HZ为100。

节拍:两次时钟中断的间隔就称为节拍(tick)。可以看到,节拍等于节拍率分之一。

jiffies:全局变量jiffies用来记录系统自启动以来产生的节拍总数。通过jiffies/HZ就可获取系统自启动以来的秒数。

内核定时器

内核定时器,也称为动态定时器,是管理内核时间的基础,它是一种用来推迟执行程序的工具。前面中断的文章中我们说到内核将在稍候的时间执行下部分工作,具体是何时来执行就需要内核定时器。我们在使用关于定时器的相关函数时,先来看看内核是如何描述定时器的:

  10struct tvec_base;
  11
  12struct timer_list {
  13        /*
  14         * All fields that change during normal runtime grouped to the
  15         * same cacheline
  16         */
  17        struct list_head entry;//定时器链表入口
  18        unsigned long expires;//以jiffies为单位的定时值
  19        struct tvec_base *base;//
  20
  21        void (*function)(unsigned long);//定时器处理函数
  22        unsigned long data;//传给定时器处理函数的参数,无符号长整型
  23
  24        int slack;
  25
  26#ifdef CONFIG_TIMER_STATS
  27        void *start_site;
  28        char start_comm[16];
  29        int start_pid;
  30#endif
  31#ifdef CONFIG_LOCKDEP
  32        struct lockdep_map lockdep_map;
  33#endif
  34};

内核定时器使用起来也是相当简单的,基本的使用思路是:定义一个timer_list;初始化定时器,即对timer_list结构中的相关字段进行赋值;定义你自己的定时器处理函数;激活定时器;修改定时器,如果有必要的话;删除定时器。上述过程通过以下的API就可简单的完成。

秒字符驱动与内核定时器API

下面要分析的这个程序实现了一个秒字符设备驱动,程序整体的框架与之前我们分析的字符设备驱动相同。内核模块加载函数和卸载函数的功能和之前的字符设备驱动完全相同,因此接下来主要分析这个设备的操作函数。我们使用下面的结构来描述我们当前所讨论的秒字符设备。

struct second_dev
{
	struct cdev cdev;
	atomic_t counter;
	struct timer_list s_timer;
};

cdev即为内核中描述字符设备的结构体;counter用来记录秒数,它在测试程序中用来显示当前程序运行的时间。同时也是此设备驱动进行读写的数据;s_timer是为此设备所定义的定时器。每次定时一个HZ,也就是1秒,当定时到后,通过定时器处理函数对其进行修改,每次增加一个HZ。

接下来,我们一边分析驱动程序中的设备文件操作函数,一边学习内核定时器的API。

1.打开函数

秒字符设备在文件打开函数中实现对定时器的初始化以及赋值。

static int second_open(struct inode*inode,struct file*filp)
{
	//initializing the timer
	init_timer(&second_devp->s_timer);
	second_devp->s_timer.function=&second_timer_handler;
	second_devp->s_timer.expires=jiffies+HZ;
	add_timer(&second_devp->s_timer);//add a timer
	atomic_set(&second_devp->counter,0);//clear the counter
	return 0;
}

可以看到,内核定时器函数通过:init_timer(&t);来进行定时器的初始化,其中t为timer_list类型的指针。expires是用来设置定时器的定时值,单位是节拍数。我们可以看到初始定时值为一秒,即在当前时钟节拍数上加上一个节拍率。function则是上面提到的定时器处理函数。

另外,在open函数中,还通过add_timer(&t);来进行定时器的激活,也就是把当前这个定时器加入到内核定时器链表当中。由于我们在该设备对应的街头体中将counter设置为atomic_t(关于atomic.h头文件,本博客稍候会专门来分析,请持续关注~ e91 ),因此我们使用atomic_set函数对其进行初始化,其值设置为0。

2.读函数

static ssize_t second_read(struct file *filp,char __user *buf,size_t count,loff_t *ppos)
{
	int counter;

	counter=atomic_read(&second_devp->counter);
	if(put_user(counter,(int*)buf))
	{
		return -EFAULT;
	}
	else
	{
		return sizeof(unsigned int);
	}

}

这个读函数的意思是从内核中通过atomic_read函数读取counter,再通过put_user函数将其值返回至用户空间。

3.定时器处理函数

static void second_timer_handler(unsigned long arg)
{
	mod_timer(&second_devp->s_timer,jiffies+HZ);
	atomic_inc(&second_devp->counter);
	printk("Current jiffies is %ld\n",jiffies);
}

执行此处理函数时,说明先前定时器的时间已经到期。我们通过mod_timer(&t,jiffies+newdelay);来改变定时器的超时时间。这里我们每次增加一秒的时间。接着通过atomic_inc函数对counter加一,并且在内核中显示当前的节拍数。

4.文件释放函数

static int second_release(struct inode*inode,struct file*filp)
{
	del_timer(&second_devp->s_timer);
	return 0;
}

在文件释放函数中,通过del_timer(&t);就可以删除这个定时器

测试

1.make编译并将此模块插入内核:sudo insmod secondcdev.ko
2.查看/proc/devices文件中是否有主设备号为250的设备
3.创建该设备文件节点,使用命令:sudo mknod /dev/secondcdev  c  250  0
4.修改该设备文件权限:sudo chmod 777 /dev/secondcdev
5.编译测试文件:gcc -o test test.c
6.运行测试程序:./test;

关于测试程序代码如下:

#include < stdio.h >
#include < sys/stat.h >
#include < sys/types.h >
#include < fcntl.h >
#include < stdlib.h >
int main()
{
	int fd;
	int counter=0;
	int old_counter=0;

	//open the /dev/secondcdev device file
	if((fd=open("/dev/secondcdev",O_RDONLY))<0)
	{
		printf("Device open failed\n");
		return -1;
	}

	while(1)
	{
		read(fd,&counter,sizeof(unsigned int));//read the current second number
		if(counter!=old_counter)
		{
			printf("seconds after open /dev/second:%d\n",counter);
			old_counter=counter;
		}
	}

	return 0;

在测试程序中,首先创建一个设备文件;然后循环读取counter的值,如果前后两次读取的counter值不一样,那么就输出;由于我们在定时器处理函数中是每隔一秒修改一次定时器,因此测试程序将每个一秒输出一次counter值。可以看到,我们所编写的秒字符设备驱动使得数据(即counter)在内核空间和用户空间之间进行交互。

与中断有关的数据结构

2010年10月6日

Latest Update:2010/10/12

1.概述

通过前文,我们已经知道了中断通常由上下两部分组成。在上部分,也就是中断处理程序,完成中断请求的响应以及完成那些对时间要求紧迫的工作;而在下部分,通常完成那些被推后的工作,因为这部分工作对时间的要求相对宽松一些。通过了解上下两部分的工作情况,可以更好的理解中断这个概念。从下半部分执行机制来看——不管是tasklet还是工作队列——这些推后的工作总是在上半部分被调用,然后交给内核在适当的时间来完成。那么,中断上部分具体是如何工作的?内核对中断是如何处理的?

在开始分析之前,需要说明的是接下来分析的属于内核对外设产生的中断的处理情况,异常的处理过程在本文的最后会有简单解释。

所有的中断的处理程序在init_IRQ函数中都被初始化为interrupt[i]。interrupt数组中每一项均指向一个代码片段:

pushl $n-256
/*省略部分代码*/
jmp common_interrupt

该代码片段除了将中断向量号压入堆栈,还会跳到一个公共处理程序common_interrup:

//在linux/arch/x86/kernel/entry_32.S
 863 common_interrupt:
 864         addl $-0x80,(%esp)      /* Adjust vector into the [-256,-1] range *     /
 865         SAVE_ALL
 866         TRACE_IRQS_OFF
 867         movl %esp,%eax
 868         call do_IRQ
 869         jmp ret_from_intr

这段公共处理程序会将中断发生前的所有寄存器的值压入堆栈,也就是保存被中断任务的现场。然后调用do_IRQ函数,在do_IRQ函数中会调用(并非直接调用那么简单)到handle_IRQ_event函数,在此函数中会执行实际的中断服务例程。当中断服务例程执行完毕后,会返回到上面的那段汇编程序中,转入ret_from_intr代码段从中断返回。

在上述文字描述的基础上,可以通过下图进一步加深中断的处理过程:

从上面的概述中,我们可以很快定位我们未来要分析的函数:do_IRQ()和hand_IRQ_event();在分析上述函数之前,我们很有必要先分析一下关于中断的三个重要的数据结构。

2.struct irq_desc

在一开始我们分析中断的时候,谈及到了中断向量。在内核中,每个中断向量都有相应的有一个irq_desc结构体(稍早内核版本中为irq_desc_t)来描述一个中断向量(也就是中断源),具体代码如下:

  31struct irq_desc;
  32typedef void (*irq_flow_handler_t)(unsigned int irq,
  33                                            struct irq_desc *desc);

 175struct irq_desc {
 176        unsigned int            irq;
 177        struct timer_rand_state *timer_rand_state;
 178        unsigned int            *kstat_irqs;
 179#ifdef CONFIG_INTR_REMAP
 180        struct irq_2_iommu      *irq_2_iommu;
 181#endif
 182        irq_flow_handler_t      handle_irq;
 183        struct irq_chip         *chip;
 184        struct msi_desc         *msi_desc;
 185        void                    *handler_data;
 186        void                    *chip_data;
 187        struct irqaction        *action;
 188        unsigned int            status;
 189
 190        unsigned int            depth;
 191        unsigned int            wake_depth;
 192        unsigned int            irq_count;
 193        unsigned long           last_unhandled;
 194        unsigned int            irqs_unhandled;
 195        raw_spinlock_t          lock;
 196#ifdef CONFIG_SMP
 197        cpumask_var_t           affinity;
 198        const struct cpumask    *affinity_hint;
 199        unsigned int            node;
 200#ifdef CONFIG_GENERIC_PENDING_IRQ
 201        cpumask_var_t           pending_mask;
 202#endif
 203#endif
 204        atomic_t                threads_active;
 205        wait_queue_head_t       wait_for_threads;
 206#ifdef CONFIG_PROC_FS
 207        struct proc_dir_entry   *dir;
 208#endif
 209        const char              *name;
 210}

 216#ifndef CONFIG_SPARSE_IRQ
 217extern struct irq_desc irq_desc[NR_IRQS];
 218#endif

所有这样的描述符组织在一起形成irq_desc[NR_IRQS]数组。下面我们对上述结构体的部分字符进行解释;

irq:通过数据类型可知这便是这个描述符所对应的中断号;
handle_irq:指向该IRQ线的公共服务程序;
chip:它是一个struct irq_chip类型的指针,是中断控制器的描述符,与平台有关,下文有详细描述;
handler_data:用于handler_irq的参数;
chip_data:用于chip的参数;
action:一个struct irqaction类型的指针(下文有该结构的详细描述);它指向一个单链表,该单链表是由该中断线上所有中断服务程序(对应struct irqaction)所连接起来的;
status:描述中断线当前的状态;
depth:中断线被激活时,值为0;其值为正数时,表示被禁止的次数;
irq_count:记录该中断线发生中断的次数;
irqs_unhandled:该IRQ线上未处理中断发生的次数;
name: /proc/interrupts 中显示的中断名称;

3.struct irqaction

当多个设备共享一条IRQ线时,因为每个设备都要有各自的ISR。为了能够正确处理此条IRQ线上的中断处理程序(也就是区分每个设备),就需要我们使用irqaction结构体。在这个结构体中,会有专门的handler字段指向该设备的真正的ISR。共享同一条IRQ线上的多个这样的结构体会连接成了一个单链表,即所谓的中断请求队列。中断产生时,该IRQ线的中断请求队列上所有的ISR都会被依次调用,因此每个设备的ISR必须判断当前的中断是否是自己所属的设备产生的。irqaction结构具体的定义如下:

  98typedef irqreturn_t (*irq_handler_t)(int, void *);

 113struct irqaction {
 114        irq_handler_t handler;
 115        unsigned long flags;
 116        const char *name;
 117        void *dev_id;
 118        struct irqaction *next;
 119        int irq;
 120        struct proc_dir_entry *dir;
 121        irq_handler_t thread_fn;
 122        struct task_struct *thread;
 123        unsigned long thread_flags;
 124};

handler:指向一个具体的硬件设备的中断服务例程,可以从此指针的类型发现与前文我们所定义的中断处理函数声明相同;
flags:对应request_irq函数中所传递的第三个参数,可取IRQF_DISABLED、IRQF_SAMPLE_RANDOM和IRQF_SHARED其中之一;
name:对应于request_irq函数中所传递的第四个参数,可通过/proc/interrupts文件查看到;
next:指向下一个irqaction结构体;
dev_id:对应于request_irq函数中所传递的第五个参数,可取任意值,但必须唯一能够代表发出中断请求的设备,通常取描述该设备的结构体;
irq:中断号

如果一个IRQ线上有中断请求,那么内核将依次次调用在该中断线上注册的每一个中断服务程序,但是并不是所有中断服务程序都被执行。一般硬件设备都会提供一个状态寄存器,以便中断服务程序进行检查是否应该为这个硬件服务。也就是说在整个中断请求队列中,最多会有一个ISR被执行,也就是该ISR对应的那个设备产生了中断请求时;不过当该IRQ线上某个设备未找到匹配的ISR时,那这个中断就不会被处理。此时irq_desc结构中的irqs_unhandled字段就会加1。

4.struct irq_chip

struct irq_chip是一个中断控制器的描述符。通常不同的体系结构就有一套自己的中断处理方式。内核为了统一的处理中断,提供了底层的中断处理抽象接口,对于每个平台都需要实现底层的接口函数。这样对于上层的中断通用处理程序就无需任何改动。这样的结构体就好比一个插板,我们可以使用各种插头。至于插板内部是如何实现的,使用者并不需要过于关心。

比如经典的中断控制器是2片级联的8259A,那么得15个irq_desc描述符,每一个描述符的irq_chip都指向描述8259A的i8259A_irq_type变量(arch/alpha/kernel/irq_i8259.c):

 86struct irq_chip i8259a_irq_type = {
  87        .name           = "XT-PIC",
  88        .startup        = i8259a_startup_irq,
  89        .shutdown       = i8259a_disable_irq,
  90        .enable         = i8259a_enable_irq,
  91        .disable        = i8259a_disable_irq,
  92        .ack            = i8259a_mask_and_ack_irq,
  93        .end            = i8259a_end_irq,
  94};

这一点类似于VFS中所采用的原理:通过struct file_operations提供统一的接口,每种文件系统都必须具体实现这个结构体中所提供的接口。struct irq_chip具体代码如下:

111struct irq_chip {
112        const char      *name;
113        unsigned int    (*startup)(unsigned int irq);
114        void            (*shutdown)(unsigned int irq);
115        void            (*enable)(unsigned int irq);
116        void            (*disable)(unsigned int irq);
117
118        void            (*ack)(unsigned int irq);
119        void            (*mask)(unsigned int irq);
120        void            (*mask_ack)(unsigned int irq);
121        void            (*unmask)(unsigned int irq);
122        void            (*eoi)(unsigned int irq);
123
124        void            (*end)(unsigned int irq);
125        int             (*set_affinity)(unsigned int irq,
126                                        const struct cpumask *dest);
127        int             (*retrigger)(unsigned int irq);
128        int             (*set_type)(unsigned int irq, unsigned int flow_type);
129        int             (*set_wake)(unsigned int irq, unsigned int on);
130
131        void            (*bus_lock)(unsigned int irq);
132        void            (*bus_sync_unlock)(unsigned int irq);
133
134        /* Currently used only by UML, might disappear one day.*/
135#ifdef CONFIG_IRQ_RELEASE_METHOD
136        void            (*release)(unsigned int irq, void *dev_id);
137#endif
138        /*
139         * For compatibility, ->typename is copied into ->name.
140         * Will disappear.
141         */
142        const char      *typename;
143};

name:中断控制器的名字;
Startup:启动中断线;
Shutdown:关闭中断线;
Enable:允许中断;
Disable:禁止中断;

5.数据结构之间的关系

首先我们通过下图来了解上述三个数据结构的关系:

通过上述分析,我们可以大致的知道中断处理程序的调用过程:在do_IRQ函数中,通过对irq_desc结构体中handler_irq字段的引用,调用handler_irq所指向的公共服务程序;在这个公共服务程序中会调用hand_IRQ_event函数;在hand_IRQ_event函数中,通过对irqaction结构体中handler字段的引用最终调用我们所写的中断处理程序。

另外,通过上述分析,我们知道struct irq_chip描述了中断最底层的部分;而struct irqacton则描述最上层具体的中断处理函数;而与中断向量所对应的struct desc则类似一个中间层,将中断中的硬件相关的部分和软件相关的部分连接起来。

6.内核对异常的处理

与中断不同,各种异常都有固定的中断向量(0~31)以及固定的异常处理程序。因此,当异常发生时,将直接跳转到相应的服务程序中执行。

中断下半部-工作队列

2010年10月5日

本文包含那些内容?
工作队列和tasklet的区别;中断上下文;工作队列的使用;
本文适合那些人阅读?
想了解linuxer;学习驱动开发的beginner;学习内核模块编程beginner;其他super linux NBer;
参考书籍:
Linux内核设计与实现
Linux操作系统原理与应用

为什么还需要工作队列?


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

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

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

中断上下文


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

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

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

工作队列的使用


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

struct work_struct
{
	unsigned long pending;//这个工作是否正在等待处理
	struct list_head entry;//链接所有工作的链表,形成工作队列
	void (*func)(void *);//处理函数
	void *data;//传递给处理函数的参数
	void *wq_data;//内部使用数据
	struct timer_list timer;//延迟的工作队列所用到的定时器
};

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

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

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

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

INIT_WORK(struct work_struct *work,void(*func)(void*),void *data);

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

void work_handler(void *data)

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

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

schedule_work(&work);

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

中断下半部-tasklet

2010年10月4日

本文包含那些内容?
tasklet机制概述;tasklet机制的使用方法;一个阐述tasklet机制调用关系的举例。
本文适合那些人阅读?
想了解linuxer;学习驱动开发的beginner;学习内核模块编程beginner;其他super linux NBer;
参考书籍:
Linux内核设计与实现
Linux操作系统原理与应用


tasklet的实现


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

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

tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);
	unsigned long data;
};

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


使用tasklet


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

在<linux/interrupt.h>中的两个宏:

 464#define DECLARE_TASKLET(name, func, data) \
 465struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
 466
 467#define DECLARE_TASKLET_DISABLED(name, func, data) \
 468struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

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

 //在arch/x86/include/asm/atomic.h中
 15#define ATOMIC_INIT(i)  { (i) }
 //在linux/types.h中
 190typedef struct {
 191        int counter;
 192} atomic_t;

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

 470void tasklet_init(struct tasklet_struct *t,
 471                  void (*func)(unsigned long), unsigned long data)
 472{
 473        t->next = NULL;
 474        t->state = 0;
 475        atomic_set(&t->count, 0);
 476        t->func = func;
 477        t->data = data;
 478}

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

  //在arch/x86/include/asm/atomic.h中
  35static inline void atomic_set(atomic_t *v, int i)
  36{
  37        v->counter = i;
  38}

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

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

void tasklet_handler(unsigned long data);

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

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

tasklet_schedule(&my_tasklet)

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


一个举例


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

//define a argument of tasklet struct
static struct tasklet_struct mytasklet;

static void mytasklet_handler(unsigned long data)
{
	printk("This is tasklet handler..\n");
}

static irqreturn_t myirq_handler(int irq,void* dev)
{
	static int count=0;
	if(count<10)
	{
		printk("-----------%d start--------------------------\n",count+1);
            	printk("The interrupt handeler is working..\n");
             	printk("The most of interrupt work will be done by following tasklet..\n");
            	tasklet_init(&mytasklet,mytasklet_handler,0);
          	tasklet_schedule(&mytasklet);
             	printk("The top half has been done and bottom half will be processed..\n");
	}
	count++;
      	return IRQ_HANDLED;
}

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

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key