Chinaunix首页 | 论坛 | 博客
  • 博客访问: 52307
  • 博文数量: 17
  • 博客积分: 1410
  • 博客等级: 上尉
  • 技术积分: 230
  • 用 户 组: 普通用户
  • 注册时间: 2007-09-16 11:21
文章分类

全部博文(17)

文章存档

2013年(3)

2010年(11)

2009年(1)

2008年(2)

我的朋友

分类: LINUX

2008-12-22 01:41:27

硬件相关部分
Bootloader中的中断部分
Linux中断策略
  • 硬中断
  • 软中断
驱动程序中的中断处理

中断硬件:
同其他大多数的单片机一样,2410有相应的异常响应入口(中断只是异常响应的一部分,2410提供了两个中断入口),且入口从地址0开始,每个异常响应分配了4字节,这样当然放不下响应代码,只能放置一个跳转地址,以跳转到响应代码处。
Bootloader中的中断部分:
具体分析参阅bootloader分析部分。
Linux中断策略

硬件相关部分
S3C2410是异常及中断系统的设计基于ARM9核的,其中断的硬件部分可以参阅2410异常及中断系统相关章节。这里不予多述。这里我们只需了解:
ARM中断是异常的一部分
S3C2410中,出现中断触发(如有按键按下)时,内部硬件保证PC指针跳转到相应的中断入口处。
S3C2410的异常入口从地址0处开始,每4个字节提供一个异常入口。其中,有一个普通中断入口,一个快速中断入口。由于每个异常入口只保留4个字节,所以只能放置一条跳转指令,跳转到中断服务程序处。

从内核初始化过程中有关于中断的初始化,具体分析见源码中的相关注释,需要注意的几个地方:
1、__trap_init((void *)vectors_base())函数是一段汇编代码,R0中存放参数vectors_base(),vectors_base()是是异常向量的入口地址,不同的设计有不同的值,但在本源代码中,vectors_base()=0,函数中有一段代码是把.LCvectors 处的一段跳转代码拷到vectors_base()指示的起始位置,即地址0处,这好象冲掉了bootloader中地址0处的代码,即进入linux后,不再是bootloader设置的跳转地址。当然,对面2410来说,这种改动是可以的,因为2410中0-4K是SRAM,当然也不用当心bootloader的代码会被改掉,因为它是在FLASH中的,只是机器启动时被映射到地址0。如果单片机的地址0处是不可读写的,我想vectors_base()不能为0,否则,代码无法复制过去,此时vectors_base()应设置到RAM地址段去。当然,机器重启的时候,2410还是先把FLASH中的bootloader 映射到地址0处,此时地址0还是bootloader中的跳转代码。
2、__trap_init((void *)vectors_base())第二个复制是把­­__stubs_start 至__stubs_ends处的代码复制到 vectors_base()+0x200处,即0x200处。这一段好象是中断响应代码入口。
软中断
软中断出现的原因:
软中断是利用硬件中断的概念,用软件方式进行模拟,实现宏观上的异步执行效果。很多情况下,软中断和"信号"有些类似,同时,软中断又是和硬中断相对应的,"硬中断是外部设备对CPU的中断","软中断通常是硬中断服务程序对内核的中断","信号则是由内核(或其他进程)对某个进程的中断"(《Linux内核源代码情景分析》第三章)。软中断的一种典型应用就是所谓的"下半部"(bottom half),它的得名来自于将硬件中断处理分离成"上半部"和"下半部"两个阶段的机制:上半部在屏蔽中断的上下文中运行,用于完成关键性的处理动作;而下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。bottom half的应用也是激励内核发展出目前的软中断机制的原因,因此,我们先从bottom half的实现开始。
从bottom half的演变过程来理解LINUX它的运作机制也许是个好办法:
最初的bottom half
在Linux内核中,bottom half通常用"bh"表示,最初用于在特权级较低的上下文中完成中断服务的非关键耗时动作,现在也用于一切可在低优先级的上下文中执行的异步动作。最早的bottom half实现是借用中断向量表的方式,定义了一个函数指针数组(static void (*bh_base[32])(void);        /* kernel/softirq.c */)来放置软中断响应函数的地址。
需要明白地方:
底半的软中断响应程序在哪定义
响应程序如何同软中断函数指针数组挂钩
在硬中断部分中断服务程序处,如何告诉内核已有底半部分代码需响应。
内核用什么方式让底半代码得到执行?
任务队列(task queue)
很明显,原来的设计有一个明显的缺陷:32个函数指针只能挂上32个响应函数,现在的外部硬件越来越多。得有一个办法进行扩充,task queue就是为了解决这个问题的。
实现思路:原来的32个函数指针,每个指针只能指向一个函数,且此函数只完成一个特定的任务(类似串口中断任务的下半部),这样们只能用软中断处理32个不同的任务。相对现在的应用来说,当然不够了,为此,设计LINUX的天才们又构思出一种新的方式:改动原来每个函数指针指向的函数的工作任务,让它不再只是实现某一特定任务,而是依次执行一个任务队列中的所有任务,至于这个任务队列的长短,由你定,这样一来,我们可处理的任务可就远不止32个那么多了,一句话,由你定。
好了,我们再来看内核代码的构造:
            task queue是在系统队列数据结构的基础上建成的,以下即为task queue的数据结构,定义在include/linux/tqueue.h中:
struct tq_struct {
        struct list_head list;          /* 链表结构 */
        unsigned long sync;             /* 初识为0,入队时原子的置1,以避免重复入队 */
        void (*routine)(void *);        /* 激活时调用的函数 */
        void *data;                     /* routine(data) */
};
typedef struct list_head task_queue;
在使用时,按照下列步骤进行:
DECLARE_TASK_QUEUE(my_tqueue); /* 定义一个my_tqueue,实际上就是一个以tq_struct为元素的list_head队列 */
说明并定义一个tq_struct变量my_task;
queue_task(&my_task,&my_tqueue); /* 将my_task注册到my_tqueue中 */
run_task_queue(&my_tqueue); /* 在适当的时候手工启动my_tqueue */
大多数情况下,都没有必要调用DECLARE_TASK_QUEUE()定义自己的task queue,因为系统已经预定义了三个task queue:
tq_timer,由时钟中断服务程序启动;
tq_immediate,在中断返回前以及schedule()函数中启动;
tq_disk,内存管理模块内部使用。
一般使用tq_immediate就可以完成大多数异步任务了。
    run_task_queue(task_queue *list)函数可用于启动list中挂接的所有task,可以手动调用,也可以挂接在上面提到的bottom half向量表中启动。以run_task_queue()作为bh_base[nr]的函数指针,实际上就是扩充了每个bottom half的函数句柄数,而对于系统预定义的tq_timer和tq_immediate的确是分别挂接在TQUEUE_BH和IMMEDIATE_BH上(注意,TIMER_BH没有如此使用,但TQUEUE_BH也是在do_timer()中启动的),从而可以用于扩充bottom half的个数。此时,不需要手工调用run_task_queue()(这原本就不合适),而只需调用mark_bh(IMMEDIATE_BH),让bottom half机制在合适的时候调度它。
小注:struct head_list的定义:struct list_head {struct list_head *next, *prev;}

驱动程序中的中断处理

阅读(639) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:人机界面中的LCD控制驱动与接口设计

给主人留下些什么吧!~~