您的位置:首页 > 运维架构 > Linux

linux内核中断和时钟初步整理及其问题解答

2012-11-11 21:45 281 查看
Linux的中断和时钟

对于linux学习中,一般是中断和时钟一起学习。在linux内核中,时钟的处理也是采用中断的方式,内核软件的定时也是最终要依赖于时钟,时钟要依赖于中断,所以中断是首先要学习的。

中断服务程序的执行是不需要存在于进程上下文的,所以这也要求中断是越短越好,也就是说我们不希望打扰原来的程序很长时间, 要尽快返回刚才的中断位置继续执行原来的程序。

中断源分类是很多的:1 可屏蔽中断,不屏蔽中断。2 向量中断和非向量中断。这俩个是根据不同分来方法分的。

学习linux中断,我想最主要还是学习linux中断处理架构。

从 linux1.x版本开始,中断处理程序从概念上被分为上半部分top half和下半部分bottom half。在中断发生时上半部分的处理 过程立即执行,因为它是完全屏蔽中断的,所以要快,否则其它的中断就得不到及时的处理。但是下半部分(如果有的话)几乎做了中断处理程序所有的事情,可以 推迟执行。内核把上半部分和下半部分作为独立的函数来处理,上半部分的功能就是“登记中断”,决定其相关的下半部分是否需要执行。需要立即执行的部分必须 位于上半部分,而可以推迟的部分可能属于下半部分。下半部分的任务就是执行与中断处理密切相关但上半部分本身不执行的工作,如查看设备以获得产生中断的时 间信息,并根据这些信息(一般通过读设备上的寄存器得来)进行相应的处理。从这里我们可以看出下半部分其实是上半部分引起的,例如当打印机端口产生一个中 断时,其中断处理程序会立即执行相关的上半部分,上半部分就会产生一个软中断(下半部分的一种)并送到操作系统内核里,这样内核就会根据这个软中断唤醒睡 眠的打印机任务队列中的处理进程。



上下部分图解

很多同学要问:为何要分为这两部分,让其中一部分去做,或者合并不是很好吗?

其实分为两部分的原因很简单,就是最快速有效的发现并执行中断。一般我们要求的是中断越快处理完毕越好,但是总是需要排队处理中断,这样上半部分就可以处理一些紧急事件,上半部分的剩余工作则会在稍候的任意时间执行,也就是在所谓的下半部分去执行。 总之,这样划分一个中断处理过程主要是希望减少中断处理程序的工作量(当然了,理想情况是将全部工作都抛给下半段。但是中断处理程序至少应该完成对中断请求的相应),因为在它运行期间至少会使得同级的中断请求被屏蔽,这些都直接关系到整个系统的响应能力和性能。而在下半段执行期间,则会允许响应所有的中断。 一句话就是说在性能和反应中断上做到综合处理。

对于上半部分和下半部分之间的划分没有严格的规则,靠驱动程序开发人员自己的编程习惯来划分,但是还是有一些习惯供参考:

Ⅰ.如果该任务对时间比较敏感,将其放在上半部中执行。

Ⅱ.如果该任务和硬件相关,一般放在上半部中执行。

Ⅲ.如果该任务要保证不被其他中断打断,放在上半部中执行(因为这是系统关中断)。

Ⅳ.其他不太紧急的任务,般考虑在下半部执行。

在上半部分中,中能采用的中断的形式处理程序。在下半部分中,可以采用tasklet,工作队列和软中断。

申请中断,释放中断,使能和屏蔽中断这里就不做介绍了,其实就是几个函数,只要记住就OK了。这里着重处理下下半部分的处理机制。

Work queue 工作队列

工作队列(work queue)是另外一种将工作推后执行的形式,它和前面讨论的tasklet有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。

那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。

1 工作、工作队列和工作者线程

我们把推后执行的任务叫做工作(work),描述它的数据结构为work_struct,这些工作以队列结构组织成工作队列(workqueue),其数据结构为workqueue_struct,而工作线程就是负责执行工作队列中的工作。系统默认的工作者线程为events,自己也可以创建自己的工作者线程。

2 表示工作的数据结构

工作用<linux/workqueue.h>中定义的work_struct结构表示:

struct work_struct{

unsigned long pending;

struct list_head entry;

void (*func) (void *);

void *data;

void *wq_data;

struct timer_list timer;

};

这些结构被连接成链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的work_struct对象从链表上移去。当链表上不再有对象的时候,它就会继续休眠。

3. 创建推后的工作

要使用工作队列,首先要做的是创建一些需要推后完成的工作。可以通过 DECLARE_WORK在编译时静态地建该结构:

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

这样就会静态地创建一个名为name,待执行函数为func,参数为data的work_struct结构。

同样,也可以在运行时通过指针创建一个工作:

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

这会动态地初始化一个由work指向的工作。

4. 工作队列中待执行的函数

工作队列待执行的函数原型是:

void work_handler(void *data)

这个函数会由一个工作者线程执行,因此,函数会运行在进程上下文中。默认情况下,允许响应中断,并且不持有任何锁。如果需要,函数可以睡眠。需要注意的是,尽管该函数运行在进程上下文中,但它不能访问用户空间,因为内核线程在用户空间没有相关的内存映射。通常在系统调用发生时,内核会代表用户空间的进程运行,此时它才能访问用户空间,也只有在此时它才会映射用户空间的内存。

5. 对工作进行调度

现在工作已经被创建,我们可以调度它了。想要把给定工作的待处理函数提交给缺省的events工作线程,只需调用

schedule_work(&work);

work马上就会被调度,一旦其所在的处理器上的工作者线程被唤醒,它就会被执行。

有时候并不希望工作马上就被执行,而是希望它经过一段延迟以后再执行。在这种情况下,可以调度它在指定的时间执行:

schedule_delayed_work(&work, delay);

这时,&work指向的work_struct直到delay指定的时钟节拍用完以后才会执行。

6. 工作队列的简单应用

cpp code

#include <linux/module.h>

#include <linux/init.h>

#include <linux/workqueue.h>

static struct workqueue_struct *queue = NULL;

static struct work_struct work;

static void work_handler(struct work_struct *data) //执行函数

{

printk(KERN_ALERT "work handler function./n");

}

static int __init test_init(void) //初始化函数

{

queue = create_singlethread_workqueue("helloworld");

if (!queue)

goto err;

INIT_WORK(&work, work_handler);

schedule_work(&work);

return 0;

err:

return -1;

}

static void __exit test_exit(void) //退出函数

{

destroy_workqueue(queue);

}

MODULE_LICENSE("GPL");

module_init(test_init);

module_exit(test_exit);

这个比较简单,写工作队列程序的时候,可以按照《linux设备驱动开发详解(宋宝华)》中的模板写。

request_irq(...,void *dev_id)中的dev_id问题

一般我们会认为,在多个中断共享的情况下,如果有中断到来,是不是根据dev_id直接就可以找到中断的设备。其实不是这样的,在中断到来的时候,内核会遍历共享此中断的所有中断处理函数,中断函数的上半部分会通过硬件寄存器中的信息对照传入得的dev_id参数判断,是的话则返回IRQ_HANDLED,否则返回IRQ_NONE。识别是哪个设备中断是由中断处理函数来检测的。

linux 内核定时器编程

-----------使用定时器的步骤--------------

struct timer_list my_timer_list;//定义一个定时器,可以把它放在你的设备结构中 struct{定义一个定时器}

init_timer(&my_timer_list);//初始化一个定时器

my_timer_list.expire=jiffies+HZ;//定时器1s后运行服务程序

my_timer_list.function=timer_function;//定时器服务函数

add_timer(&my_timer_list);//添加定时器

void timer_function(unsigned long)//写定时器服务函数

del_timer(&my_timer_list);//当定时器不再需要时删除定时器

del_timer_sync(&my_timer_list);//基本和del_timer一样,比较适合在多核处理器使用,一般推荐使用del_timer_sync

具体的应用程序,可以参照我上一篇转载的文章,哪个程序不错,简明完全。

提供一个下载地址,学习linux书籍资料比较全面700M:http://download.csdn.net/detail/jnwangcan/4506459
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: