当前位置:文档之家› 中断处理程序下半部(软中断、tasklet、工作队列)

中断处理程序下半部(软中断、tasklet、工作队列)

中断处理程序下半部(软中断、tasklet、工作队列)
中断处理程序下半部(软中断、tasklet、工作队列)

中断处理程序下半部

1.下半部综述

1.1.使用下半部的目的

linux将中断处理程序分为上半部和下半部,目的是尽量减少上半部需要完成的工作,因为在上半部执行的时候,当前的中断线在所有处理器上都会被屏蔽。而且,如果一个处理程序是IRQF_DISABLED类型,它执行的时候会禁止所有本地中断。而缩短中断被屏蔽的时间对系统的响应能力和性能都至关重要。因此需要把一些工作放到下半部去做。

不仅是Linux,许多操作系统也把处理硬件中断的过程分为两个部分。上半部分简单快速,执行的时候禁止一些或全部中断。下半部分稍后执行,而且执行期间可以响应所有的中断。这种设计可使系统处于中断屏蔽状态的时间尽可能的短,以此来提高系统的响应能力。

1.2.Linux中的下半部发展

和上半部只能通过中断处理程序实现不同,下半部可以通过多种机制实现。最早的Linux只提供“bottom half”这种机制用于实现下半部。它提供了一个静态创建、由32个bottom halves组成的链表。上半部通过一个32位整数中的一个位来标识出哪个bottom half可以执行。每个BH都在全局范围内进行同步。即使分属于不同的处理器,也不允许任何两个bottom half同时执行。

不久,内核开发者们就引入了任务队列机制来实现工作的推后执行,并用它来代替BH 机制。内核为此定义了一组队列,其中每个队列都包含一个由等待调用的函数组成链表。根据其所处队列的位置,这些函数会在某个时刻执行。驱动程序可以把他们自己的下半部注册到合适的队列上去。

在2.3这个开发版中,引入了软中断和tasklet。软中断是一组静态定义的下半部接口,有32个,可以在所有处理器上同时执行——即使两个类型相同也可以。两个不同类型的

tasklet可以在不同的处理器上同时执行,但类型相同的tasklet不能同时执行。tasklet其实是一种在性能和易用性之间寻求平衡的产物。对于大部分下半部处理来说,用tasklet就足够了,像网络这样对性能要求非常高的情况下才需要使用软中断。可是,使用软中断需要特别小心,因为两个相同的软中断有可能同时被执行。此外,软中断还必须在编译期间就进行静态注册。与此相反,tasklet可以通过代码进行动态注册。

另外一个可以用于将工作推后执行的机制是内核定时器。内核定时器把操作推迟到某个确定的时间段之后执行。也就是说,当你必须保证在一个确定的时间段过去以后再执行时,你应该使用内核定时器。

工作队列是另外一种将工作推后执行的形式,工作队列可以把工作推后,交由一个内核线程去执行——这个下半部分总是会在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许重新调度甚至是睡眠。2.软中断和tasklet的关系

tasklet是利用软中断实现的一种下半部机制。tasklet是通过软中断实现的,所以tasklet本身也是软中断。

tasklet由两类软中断代表:HI_SOFTIRQ和TASKLET_SOFTIRQ。

2.1.软中断列表

2.2.注册tasklet

在kernel/softirq.c通过以下方式注册tasklet:

open_softirq(TASKLET_SOFTIRQ, tasklet_action);

open_softirq(HI_SOFTIRQ, tasklet_hi_action);

tasklet_action()和tasklet_hi_action()在kernel/softirq.c定义

2.3.触发软中断

一个注册的软中断必须在被标记后才会执行。这被称作触发软中断(raising the softirq)。通常,中断处理程序会在返回前标记它的软中断,使其在稍后被执行。在下列地方,待处理的软中断会被检查和执行:

●从一个硬件中断代码返回时

●在ksoftirqd内核线程中

●在那些显示检查和执行待处理的软中断的代码中,如网络子系统中

2.4.执行软中断

不管是用什么办法唤起软中断,它都要在do_softirq()中执行。该函数很简单,如果有待处理的软中断,do_softirq()会循环遍历每一个,调用他们的处理程序。下面是do_softirq()简化后的核心部分:

u32 pending;

pending = local_softirq_pending();

if (pending) {

struct softirq_action *h;

set_softirq_pending(0); //重设待处理的位图

h = softirq_vec;

do {

if (pending & 1)

h->action(h);

h++;

pending >>= 1;

} while (pending);

}

2.5.pending位图与softirq_vec数组的关系

下面是一个pending位图与softirq_vec数组的关系

u32 pending:

3.软中断

3.1.软件中断的数据结构

3.1.1.struct softirq_action

内核用softirq_action完成软中断的注册和激活等操作,它的定义如下:

struct softirq_action

{

void(*action)(struct softirq_action*);

};

非常简单,只有一个用于回调的函数指针。软件中断的资源是有限的,内核目前只实现了10种类型的软件中断,它们是:

enum

{

HI_SOFTIRQ=0,

TIMER_SOFTIRQ,

NET_TX_SOFTIRQ,

NET_RX_SOFTIRQ,

BLOCK_SOFTIRQ,

BLOCK_IOPOLL_SOFTIRQ,

TASKLET_SOFTIRQ,

SCHED_SOFTIRQ,

HRTIMER_SOFTIRQ,

RCU_SOFTIRQ,/*Preferable RCU should always be the last softirq*/

NR_SOFTIRQS

};

内核的开发者们不建议我们擅自增加软件中断的数量,如果需要新的软件中断,尽可能把它们实现为基于软件中断的tasklet形式。与上面的枚举值相对应,内核定义了一个softirq_action的结构数组,每种软中断对应数组中的一项:

static struct softirq_action softirq_vec[NR_SOFTIRQS]__cacheline_aligned_in_smp;

3.1.2.irq_cpustat_t

多个软中断可以同时在多个cpu运行,就算是同一种软中断,也有可能同时在多个cpu上运行。内核为每个cpu都管理着一个待决软中断变量(pending),它就是irq_cpustat_t:typedef struct{

unsigned int__softirq_pending;

}____cacheline_aligned irq_cpustat_t;

irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;

__softirq_pending字段中的每一个bit,对应着某一个软中断,某个bit被置位,说明有相应的软中断等待处理。

3.2.软中断的守护进程ksoftirqd

在cpu的热插拔阶段,内核为每个cpu创建了一个用于执行软件中断的守护进程ksoftirqd,同时定义了一个per_cpu变量用于保存每个守护进程的task_struct结构指针:DEFINE_PER_CPU(struct task_struct *, ksoftirqd);

大多数情况下,软中断都会在irq_exit阶段被执行,在irq_exit阶段没有处理完的软中

断才有可能会在守护进程中执行。

3.3.触发软中断

要触发一个软中断,只要调用api:raise_softirq即可,它的实现很简单,先是关闭本地cpu中断,然后调用:raise_softirq_irqoff

void raise_softirq(unsigned int nr)

{

unsigned long flags;

local_irq_save(flags);

raise_softirq_irqoff(nr);

local_irq_restore(flags);

}

再看看raise_softirq_irqoff:

inline void raise_softirq_irqoff(unsigned int nr)

{

__raise_softirq_irqoff(nr);

......

if (!in_interrupt())

wakeup_softirqd();

}

先是通过__raise_softirq_irqoff设置cpu的软中断pending标志位

(irq_stat[NR_CPUS]),然后通过in_interrupt判断现在是否在中断上下文中,或者软中断是否被禁止,如果都不成立,则唤醒软中断的守护进程,在守护进程中执行软中断的回调函数。否则什么也不做,软中断将会在中断的退出阶段被执行。

3.4.软中断的执行

基于上面所说,软中断的执行既可以守护进程中执行,也可以在中断的退出阶段执行。实际上,软中断更多的是在中断的退出阶段执行(irq_exit),以便达到更快的响应,加入

守护进程机制,只是担心一旦有大量的软中断等待执行,会使得内核过长地留在中断上下文中。

3.4.1.在irq_exit中执行

看看irq_exit的部分:

void irq_exit(void)

{

......

sub_preempt_count(IRQ_EXIT_OFFSET);

if (!in_interrupt() && local_softirq_pending())

invoke_softirq();

......

}

如果中断发生嵌套,in_interrupt()保证了只有在最外层的中断的irq_exit阶段,invoke_interrupt才会被调用,当然,local_softirq_pending也会实现判断当前cpu有无待决的软中断。代码最终会进入__do_softirq中,内核会保证调用__do_softirq时,本地cpu的中断处于关闭状态,进入__do_softirq:

asmlinkage void __do_softirq(void)

{

......

pending = local_softirq_pending();

__local_bh_disable((unsigned long)__builtin_return_address(0), SOFTIRQ_OFFSET);

restart:

/* Reset the pending bitmask before enabling irqs */

set_softirq_pending(0);

local_irq_enable();

h = softirq_vec;

do {

if (pending & 1) {

......

trace_softirq_entry(vec_nr); h->action(h);

trace_softirq_exit(vec_nr); ......

}

h++;

pending >>= 1;

} while (pending);

local_irq_disable();

pending = local_softirq_pending(); if (pending && --max_restart)

goto restart;

if (pending)

wakeup_softirqd();

lockdep_softirq_exit();

__local_bh_enable(SOFTIRQ_OFFSET); }

首先取出pending的状态;

禁止软中断,主要是为了防止和软中断守护进程发生竞争;

清除所有的软中断待决标志;

打开本地cpu中断;

循环执行待决软中断的回调函数;

如果循环完毕,发现新的软中断被触发,则重新启动循环,直到以下条件满足,才退出:没有新的软中断等待执行;循环已经达到最大的循环次数MAX_SOFTIRQ_RESTART,目前的设定值时10次;

如果经过MAX_SOFTIRQ_RESTART次循环后还未处理完,则激活守护进程,处理剩下的软中断;

退出前恢复软中断;

3.4.2.在ksoftirqd进程中执行

从前面几节的讨论我们可以看出,软中断也可能由ksoftirqd守护进程执行,这要发生在以下两种情况下:

在irq_exit中执行软中断,但是在经过MAX_SOFTIRQ_RESTART次循环后,软中断还未处理完,这种情况虽然极少发生,但毕竟有可能;

内核的其它代码主动调用raise_softirq,而这时正好不是在中断上下文中,守护进程将被唤醒;

守护进程最终也会调用__do_softirq执行软中断的回调,具体的代码位于

run_ksoftirqd函数中,内核会关闭抢占的情况下执行__do_softirq。

4.tasklet

因为内核已经定义好了10种软中断类型,并且不建议我们自行添加额外的软中断,所以对软中断的实现方式,我们主要是做一个简单的了解,对于驱动程序的开发者来说,无需实现自己的软中断。但是,对于某些情况下,我们不希望一些操作直接在中断的handler 中执行,但是又希望在稍后的时间里得到快速地处理,这就需要使用tasklet机制。tasklet 是建立在软中断上的一种延迟执行机制,它的实现基于TASKLET_SOFTIRQ和HI_SOFTIRQ

这两个软中断类型。

4.1.tasklet_struct

在软中断的初始化函数softirq_init的最后,内核注册了TASKLET_SOFTIRQ和

HI_SOFTIRQ这两个软中断:

void __init softirq_init(void)

{

......

open_softirq(TASKLET_SOFTIRQ, tasklet_action);

open_softirq(HI_SOFTIRQ, tasklet_hi_action);

}

内核用一个tasklet_struct来表示一个tasklet,它的定义如下:

struct tasklet_struct

{

struct tasklet_struct *next;

unsigned long state;

atomic_t count;

void (*func)(unsigned long);

unsigned long data;

};

next用于把同一个cpu的tasklet链接成一个链表,state用于表示该tasklet的当前状态,目前只是用了最低的两个bit,分别用于表示已经准备被调度执行和已经在另一个cpu 上执行:

enum

{

TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */

TASKLET_STATE_RUN /* Tasklet is running (SMP only) */

};

原子变量count用于tasklet对tasklet_disable和tasklet_enable的计数,count为0时表示允许tasklet执行,否则不允许执行,每次tasklet_disable时,该值加1,tasklet_enable时该值减1。func是tasklet被执行时的回调函数指针,data则用作回调函数func的参数。

4.2.初始化一个tasklet

有两种办法初始化一个tasklet,第一种是静态初始化,使用以下两个宏,这两个宏定义一个tasklet_struct结构,并用相应的参数对结构中的字段进行初始化:DECLARE_TASKLET(name, func, data);定义名字为name的tasklet,默认为enable 状态,也就是count字段等于0。

DECLARE_TASKLET_DISABLED(name, func, data);定义名字为name的tasklet,默认为enable状态,也就是count字段等于1。

第二个是动态初始化方法:先定义一个tasklet_struct,然后用tasklet_init函数进行初始化,该方法默认tasklet处于enable状态:

struct tasklet_struct tasklet_xxx;

......

tasklet_init(&tasklet_xxx, func, data);

4.3.tasklet的使用方法

使能和禁止tasklet,使用以下函数:

tasklet_disable() 通过给count字段加1来禁止一个tasklet,如果tasklet正在运行中,则等待运行完毕才返回(通过TASKLET_STATE_RUN标志)。

tasklet_disable_nosync() tasklet_disable的异步版本,它不会等待tasklet运行完毕。

tasklet_enable() 使能tasklet,只是简单地给count字段减1。

调度tasklet的执行,使用以下函数:

tasklet_schedule(struct tasklet_struct *t) 如果TASKLET_STATE_SCHED标志为0,则置位TASKLET_STATE_SCHED,然后把tasklet挂到该cpu等待执行的tasklet链表上,接着发出TASKLET_SOFTIRQ软件中断请求。

tasklet_hi_schedule(struct tasklet_struct *t) 效果同上,区别是它发出的是

HI_SOFTIRQ软件中断请求。

销毁tasklet,使用以下函数:

tasklet_kill(struct tasklet_struct *t) 如果tasklet处于TASKLET_STATE_SCHED状态,或者tasklet正在执行,则会等待tasklet执行完毕,然后清除TASKLET_STATE_SCHED 状态。

4.4.tasklet的内部执行机制

内核为每个cpu用定义了一个tasklet_head结构,用于管理每个cpu上的tasklet的调度和执行:

struct tasklet_head

{

struct tasklet_struct *head;

struct tasklet_struct **tail;

};

static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);

static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);

我们知道,tasklet是利用TASKLET_SOFTIRQ和HI_SOFTIRQ这两个软中断来实现的,两个软中断只是有优先级的差别,所以我们只讨论TASKLET_SOFTIRQ的实现,TASKLET_SOFTIRQ的中断回调函数是tasklet_action,我们看看它的代码:

static void tasklet_action(struct softirq_action *a)

{

struct tasklet_struct *list;

local_irq_disable();

list = __this_cpu_read(tasklet_vec.head);

__this_cpu_write(tasklet_vec.head, NULL);

__this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).h ead);

local_irq_enable();

while (list) {

struct tasklet_struct *t = list;

list = list->next;

if (tasklet_trylock(t)) {

if (!atomic_read(&t->count)) {

if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->sta te))

BUG();

t->func(t->data);

tasklet_unlock(t);

continue;

}

tasklet_unlock(t);

}

local_irq_disable();

t->next = NULL; /*重新添加到当前cpu的待处理tasklet链表上*/

*__this_cpu_read(tasklet_vec.tail) = t;

__this_cpu_write(tasklet_vec.tail, &(t->next));

__raise_softirq_irqoff(TASKLET_SOFTIRQ);

local_irq_enable();

}

}

解析如下:

关闭本地中断的前提下,移出当前cpu的待处理tasklet链表到一个临时链表后,清除当前cpu的tasklet链表,之所以这样处理,是为了处理当前tasklet链表的时候,允许新的tasklet被调度进待处理链表中。

遍历临时链表,用tasklet_trylock判断当前tasklet是否已经在其他cpu上运行,而且tasklet没有被禁止:

如果没有运行,也没有禁止,则清除TASKLET_STATE_SCHED状态位,执行tasklet 的回调函数。

如果已经在运行,或者被禁止,则把该tasklet重新添加到当前cpu的待处理tasklet 链表上,然后触发TASKLET_SOFTIRQ软中断,等待下一次软中断时再次执行。

通过以上的分析,我们需要注意的是,tasklet有以下几个特征:

同一个tasklet只能同时在一个cpu上执行,但不同的tasklet可以同时在不同的cpu

上执行;

一旦tasklet_schedule被调用,内核会保证tasklet一定会在某个cpu上执行一次;

如果tasklet_schedule被调用时,tasklet不是处于正在执行状态,则它只会执行一次;

如果tasklet_schedule被调用时,tasklet已经正在执行,则它会在稍后被调度再次被执行;

两个tasklet之间如果有资源冲突,应该要用自旋锁进行同步保护;

5.工作队列

5.1.工作队列

工作队列子系统是一个用于创建内核线程的接口,通过它创建的进程负责执行由内核其他部分排到队列里的任务。它创建的这些内核线程称为工作者线程。工作队列可以让你的驱动程序创建一个专门的工作者线程来处理需要推后的工作。不过,工作队列子系统提供了一个缺省的工作者线程来处理这些工作。因此,工作队列最基本的表现形式,就转变成了一个把需要推后执行的任务交给特定的通用线程的这样的一种接口。

缺省的工作者线程叫做events/n,这里n是处理器的编号;每个处理器对应的一个线程。缺省的工作者线程会从多个地方得到被推后的工作。许多内核驱动程序都把它们的下半部交给缺省的工作者线程去做。处理器密集型和性能要求严格的任务会因为拥有自己的工作者线程而获得好处。此时这么做有助于减轻缺省线程的负担,避免工作队列中其他需要完成的工作处于饥饿状态。

5.2.表示线程的数据结构

工作者线程用workqueue_struct结构表示:

struct workqueue_struct {

struct cpu_workqueue_struct cpu_wq[NR_CPUS];

struct list_head list;

const char *name;

int sinqlethread;

int freezeable;

int rt;

};

该结构内是一个由cpu_workqueue_struct结构体组成的数组,它定义在

kernel/workqueue.c 中,数组中的每一项对应系统中的一个处理器。由于系统中每个处理器对应一个工作者线程,所以对于给定的某台计算机来说,就是每个处理器,每个工作者线程对应一个这样的cpu_workqueue_struct结构体。

cpu_workqueue_struct是kernel/workqueue.c中的核心数据结构:

struct cpu_workqueue_struct {

spinlock_t lock;

struct list_head worklist;

wait_queue_head_t more_work;

struct work_struct *current_struct;

struct workqueue_struct *wq;

task_t *thread;

};

每个工作者线程类型关联一个自己的workqueue_struct。在该结构体里面,给每个线程分配一个cpu_workqueue_struct,因而也就是给每个处理器分配一个,因为每个处理器都有一个该类型的工作者线程。

5.3.表示工作的数据结构

所有的工作者线程都是用普通的内核线程实现的,他们都要执行worker_thread()函数。在它初始化完以后,这个函数执行一个死循环并开始休眠。当有操作被插入到队列里的时候,线程就会被唤醒,以便执行这些操作。当没有剩余的操作时,它又会继续休眠。

工作用中定义的work_struct 结构体表示:

struct work_struct {

atomic_long_t data;

struct list_head entry;

work_fun_t func;

};

这些结构体被连接成链表,在每个处理器上的每种类型的队列都对应这样的一个链表。比如,每个处理器上用于执行被推后的工作的那个通用线程就有一个这样的链表。当一个工作者线程被唤醒时,他会执行它的链表上的所有工作。工作被执行完毕,他就将相应的work_struct对象从链表上移去。当链表上不再有对象的时候,它就会继续休眠。

worker_thread()函数的核心流程,简化如下:

for (; ;) {

prepare_to_wait(&cwq->more_work, &wait, TASK_INTERRUPTIBLE);

if (lis_empty(&cwq->worklist))

schedule();

finish_wait(&cwq->more_work, &wait);

run_workqueue(cwq);

}

run_workqueue()的主要工作是:

while (!list_empty(&cwq->worklist)) {

struct work_struct *work;

work_func_t f;

void *data;

work = list_entry(cwq->worklist.next, struct work_struct, entry);

f = work->func;

list_del_init(cwq->worklist.next);

work_clear_pending(work);

f(work);

}

对于指定的一个类型,系统的每个CPU上都有一个该类的工作者线程。内核中有些部分可以根据需要来创建工作者线程,而在默认情况下内核只有event这一种类型的工作者线程。每个工作者线程都由一个cpu_workqueue_struct结构体来表示。而workqueue_struct结构体则表示给定类型的所有工作者线程。

5.4.创建推后的工作

在运行时通过指针创建一个工作:

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

这会动态地初始化一个由work指向的工作,处理函数为func,参数为data。

5.5.对工作进行调度

schedule_work(&work);

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

schedule_delayed_work(&work, delay);

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

5.6.刷新操作

排入队列的工作会在工作者线程下一次被唤醒的时候执行。有时,在继续下一步工作之前,你必须保证一些操作已经执行完毕了。这一点对模块来说很重要,在卸载之前,他就有可能需要调用下面的函数。而在内核的其他部分,为了防止竞争条件的出现,也可能需要确保不再有待处理的工作。

void flush_schedule_work(void);

函数会一直等待,直到队列中所有对象都被执行以后才返回。在等待所有待处理的工作执行的时候,该函数会进入休眠状态,所以只能在进程上下文中使用它。

该函数不会取消任何延迟执行的工作。也就是活,任何通过schedule_delayed_work()调度的工作,如果其延迟时间未结束,它并不会因为调用flush_schedule_work()而被刷新掉。取消延迟执行的工作应该调用:

int cancel_delayed_work(struct work_struct *work);

5.7.创建新的工作队列

struct workqueue_struct *create-workqueue(const char *name);

创建一个工作的时候无须考虑工作队列的类型。在创建之后,可以调用下面类型的函数。这些函数与schedule_work()以及schedule_delayed_work()相近,唯一的区别就在于他们针对给定的工作队列而不是缺省的events队列进行操作。

int queue_work(struct workqueue_struct *wq, struct work_struct *work);

int queue_delayed_work(struct workqueue_struct *wq, struct work_struct *work, unsigned long delay);

6.软中断源代码解析

irq_exit()

// do_IRQ 函数执行完硬件ISR 后退出时调用此函数。

void irq_exit(void)

{

account_system_vtime(current);

trace_hardirq_exit();

sub_preempt_count(IRQ_EXIT_OFFSET);

/*判断当前是否有硬件中断嵌套,并且是否有软中断在pending 状态,注意:这里只有两个条件同时满足时,才有可能调用do_softirq() 进入软中断。也就是说确认当前所有硬件中断处理完成,且有硬件中断安装了软中断处理时理时才会进入。*/

if (!in_interrupt() && local_softirq_pending())

invoke_softirq(); // 其实这里就是调用do_softirq() 执行preempt_enable_no_resched();

}

do_softirq()

#ifndef __ARCH_HAS_DO_SOFTIRQ

asmlinkage void do_softirq(void)

{

__u32 pending;

unsigned long flags;

/*这个函数判断,如果当前有硬件中断嵌套,或者有软中断正在执行时候,则马上返回。在这个入口判断主要是为了与ksoftirqd 互斥。*/

if (in_interrupt())

return;

local_irq_save(flags); // 关中断执行以下代码

pending = local_softirq_pending();

if (pending)

__do_softirq();

local_irq_restore(flags); // 开中断继续执行

}

__do_softirq()

#define MAX_SOFTIRQ_RESTART 10 // 最大软中断调用次数为10 次。

asmlinkage void __do_softirq(void)

{

// 软件中断处理结构,此结构中包括了ISR 中注册的回调函数。

struct softirq_action *h;

__u32 pending;

int max_restart = MAX_SOFTIRQ_RESTART;

int cpu;

pending = local_softirq_pending();

account_system_vtime(current);

/* 执行到这里要屏蔽其他软中断,这里也就证明了每个CPU 上同时运行的软中断只能有一个。*/

__local_bh_disable((unsigned long)__builtin_return_address(0));

trace_softirq_enter();

// 针对SMP 得到当前正在处理的CPU

cpu = smp_processor_id();

// 循环标志

restart:

//每次循环在允许硬件ISR 抢占前,首先重置软中断的标志位。

/* Reset the pending bitmask before enabling irqs */

set_softirq_pending(0);

/* 到这里才开中断运行,注意:以前运行状态一直是关中断运行,这时当前处理软中断才可能被硬件中断抢占。也就是说在进入软中断时不是一开始就会被硬件中断抢占。只有在这里以后的代码才可能被硬件中断抢占。*/

local_irq_enable();

/*这里要注意,以下代码运行时可以被硬件中断抢占,但这个硬件ISR 执行完成后,它的所注册的软中断无法马上运行,别忘了,现在虽是开硬件中断执行,但前面的

__local_bh_disable()函数屏蔽了软中断。所以这种环境下只能被硬件中断抢占,但这

个硬中断注册的软中断回调函数无法运行。要问为什么,那是因为__local_bh_disable() 函数设置了一个标志当作互斥量,而这个标志正是上面的irq_exit() 和do_softirq() 函数中的in_interrupt() 函数判断的条件之一,也就是说in_interrupt()函数不仅检测硬中断而且还判断了软中断。所以在这个环境下触发硬中断时注册的软中断,根本无法重新进入到这个函数中来,只能是做一个标志,等待下面的重复循环(最大MAX_SOFTIRQ_RESTART)才可能处理到这个时候触发的硬件中断所注册的软中断。*/

// 得到软中断向量表。

h = softirq_vec;

// 循环处理所有softirq 软中断注册函数。

单片机 实验2-外部中断程序设计-中断按键按下次数计数数码管显示-硬件和程序设计参考

硬件电路参考如下:

程序参考如下: #pragma sfr #pragma interrupt INTP0 LED_INTP0 /* 定义使用INTP0中断,中断函数名LED_INTP0*/ #pragma di /*禁止使用中断功能声明*/ #pragma ei /*允许使用中断功能声明*/ /*数码管编码数组*/ unsigned char LED_light[10]={0x30,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x3F}; unsigned char j=0; /*按键次数变量*/ void hdinit() /*硬件初始化*/ { PM1=0; /*P1口输出数码管字型码,所以设置为输出*/ PU1=0XFF; /*由于P1口直接驱动数码管显示,为增大驱动,设置为内部上拉*/ PM12.0=0; /*P12.0口线要作为中断多功能,设置为输出和内部上拉 */ PU12.0=1; PIF0=0; /*中断请求标志,没有中断请求*/ PMK0=0; /*中断屏蔽标志,允许中断*/ PPR0=1; /*中断优先级,低优先级*/ EGP.0=1; /*与EGN组合,上升沿有效*/ EGN.0=0; } void main (void) { DI(); /*首先做准备,禁止中断*/ IMS=0XCC; IXS=0X00; hdinit(); EI(); /*准备完成,允许中断*/ while(1) { /*啥也不干,就等待中断,仅是在这个实验中使用中断,实际不是这样/* } } __interrupt void LED_INTP0() /*中断函数*/ { P1= LED_light[j]; /*P1赋值,数码管显示相应数值*/ j++; /*按键次数加一*/ if(j==10) /*如果按键次数达到十次,按键计数归0*/ {j=0;} } 思考: 如果用两位数码管,从0—99循环计数又该怎样设计硬件和软件呢?

中断原理应用程序设计

第六章中断原理应用程序设计 6.1 中断系统的基本概念 CPU在处理某一事件A时,发生了另一事件B请求CPU迅速去处理(中断发生);CPU暂时中断当前的工作,转去处理事件B(中断响应和中断服务);待CPU将事件B处理完毕后,再回到原来事件A被中断的地方继续处理事件A(中断返回),这一过程称为中断。 中断源 引起CPU中断的根源,称为中断源。中断源向CPU提出的中断请求。CPU暂时中断原来的事务A,转去处理事件B。对事件B处理完毕后,再回到原来被中断的地方(即断点),称为中断返回。实现上述中断功能的部件称为中断系统(中断机构)。 MCS-51单片机提供了5个中断源,其中两个为外部中断请求源(P3.2)和(P3.3),两个片内定时器/计数器T0和T1的溢出请求中断源TF0(TCON的第5位)和TF1(TCON的第7位),1个片内串口发送或接收中断请求源TI(SCON的第1位)和RI(SCON的第0位)。 中断优先级 同一优先级中的中断申请不止一个时,则有中断优先权排队问题。同一优先级的中断优先权排队,由中断系统硬件确定的自然优先级形成,其排列如所示: 表6-4 MCS-51单片机中断源的自然优先级及入口地址

需要说明的是,为了便于用C语言编写单片机中断程序,C51编译器也支持51单片机的中断服务程序,而且用C语言编写中断服务程序,比用汇编语言方便的多。C语言编写中断服务函数的格式如下: 函数类型函数名(形式参数列表)[interrupt n] [using m] 其中,interrupt后面的n是中断编号,取值范围0~4,;using中的m表示使用的工作寄存器组号(如不声明,则默认用第0组)。 例如,定时器T0的中断函数可用如下方法编写: void Timer(void) interrupt 1 using 0 //定时器T0的中断服务函数,T0的中断编号为1,使用第0组工作寄存器 { ........//中断服务程序 } 6.2 中断系统的控制 定时器/计数器控制寄存器TCON TCON的功能是接收外部中断源(、)和定时器(T0、T1)送来的中断请求信号。字节地址为88H,可以进行位操作。表5-5列出了TCON的格式。 8FH 8EH 8DH 8CH 8BH 8AH 89H 88H TF1 TR1 TF0 TR0 IT0 IT1 IE0 IT0 表6-5 定时器/计数器控制寄存器TCON的格式

单片机_C语言函数_中断函数(中断服务程序)

单片机_C语言函数_中断函数(中断服务程序) 在开始写中断函数之前,我们来一起回顾一下,单片机的中断系统。 中断的意思(学习过微机原理与接口技术的同学,没学过单片机,也应该知道),我们在这里就不讲了,首先来回忆下中断系统涉及到哪些问题。 (1)中断源:中断请求信号的来源。(8051有3个内部中断源T0,T1,串行口,2个外部中断源INT0,INT1(这两个低电平有效,上面的那个横杠不知道怎么加上去))(2)中断响应与返回:CPU采集到中断请求信号,怎样转向特定的中断服务子程序,并在执行完之后返回被中断程序继续执行。期间涉及到CPU响应中断的条件,现场保护,现场恢复。 (3)优先级控制:中断优先级的控制就形成了中断嵌套(8051允许有两级的中断嵌套,优先权顺序为INT0,T0,INT1,T1,串行口),同一个优先级的中断,还存在优先权的高低。优先级是可以编程的,而优先权是固定的。 80C51的原则是①同优先级,先响应高优先权②低优先级能被高优先级中断③正在进行的中断不能被同一级的中断请求或低优先级的中断请求中断。 80C51的中断系统涉及到的中断控制有中断请求,中断允许,中断优先级控制 (1)3个内部中断源T0,T1,串行口,2个外部中断源INT0,INT1 (2)中断控制寄存器:定时和外中断控制寄存器TCON(包括T0、T1,INT0、INT1),串行控制寄存器SCON,中断允许寄存器IE,中断优先级寄存器IP 具体的是什么,包括哪些标志位,在这里不讲了,所有书上面都会讲。 在这里我们讲下注意的事项 (1)CPU响应中断后,TF0(T0中断标志位)和TF1由硬件自动清0。 (2)CPU响应中断后,在边沿触发方式下,IE0(外部中断INT0请求标志位)和IE1由硬件自动清零;在电平触发方式下,不能自动清楚IE0和IE1。所以在中断返回前必须撤出INT0和INT1引脚的低电平,否则就会出现一次中断被CPU多次响应。 (3)串口中断中,CPU响应中断后,TI(串行口发送中断请求标志位)和RI(接收中断请求标志位)必须由软件清零。 (4)单片机复位后,TCON,SCON给位清零。 C51语言允许用户自己写中断服务子程序(中断函数) 首先来了解程序的格式: void 函数名() interrupt m [using n] {} 关键字 interrupt m [using n] 表示这是一个中断函数 m为中断源的编号,有五个中断源,取值为0,1,2,3,4,中断编号会告诉编译器中断程序的入口地址,执行该程序时,这个地址会传个程序计数器PC,于是CPU开始从这里一条一条的执行程序指令。 n为单片机工作寄存器组(又称通用寄存器组)编号,共四组,取值为0,1,2,3 中断号中断源 0 外部中断0 1 定时器0 2 外部中断1 3 定时器1中断 4 串行口中断 (在上一篇文章中讲到的ROM前43个存储单元就是他们,这5个中断源的中断入口地址为: 这40个地址用来存放中断处理程序的地址单元,每一个类中断的存储单元只有8B,显然不

汇编语言中断程序设计

汇编语言程序设计实验报告 学院:计算机科学与技术专业:计算机科学与技术班级:计科131

LEA DX,FNAME MOV CX,0 ;语句1 INT 21H JC EXIT MOV FNUM,AX MOV BX,AX ;语句2 MOV CX,100 MOV AH,40H LEA DX ,BUF INT 21H MOV BX,FNUM MOV AH,3EH INT 21H EXIT: MOV AH,4CH INT 21H CODE ENDS END START 使用相应的文本编辑器建立文件,内容如上所示。 2.汇编并运行此程序后,在当前目录建立的文件名是什么其内容是什么 1>汇编: C:\masm> masm lab7; 2>连接: C:\masm> link lab7; 3>运行: C:\masm> lab7 3.若将语句1 改为mov cx,1,则运行情况与前面会有什么区别 4.若将语句1 改为mov cx,2,则运行结果同上会有什么不同并简要说明此语句的作用. 5.若将语句2 改为mov bx,1,则运行结果会有什么不同简要说明则语句的作用. 实验二:编写0 号中断的处理程序,使得在除法溢出发生时,在屏幕中间显示字符串“divide error!”,然后返回到DOS。源程序下: assume cs:code code segment start: mov ax,cs mov ds,ax

mov si,offset do mov ax,0 mov es,ax mov di,200h mov cx,offset doend-offset do ;安装中断例程cld rep movsb mov word ptr es:[0],200h mov word ptr es:[2],0 ;设置中断向量表 mov dx,0ffffh mov bx,1 ;测试一下 div bx mov ax,4c00h int 21h do:jmp short dostart db 'divide error!' dostart: mov ax,0 mov ds,ax mov si,202h mov ax,0b800h mov es,ax mov di,160*12+60 mov cx,13 s: mov al,ds:[si] mov ah,15 mov es:[di],ax inc si inc di inc di loop s mov ax,4c00h int 21h doend:nop code ends end start

定时器中断程序设计实验

实验一定时器/中断程序设计实验 一、实验目的 1、掌握定时器/中断的工作原理。 2、学习单片机定时器/中断的应用设计和调试 二、实验仪器和设备 1、普中科技单片机开发板; 2、Keil uVision4 程序开发平台; 3、PZ-ISP 普中自动下载软件。 三、实验原理 805l 单片机内部有两个 16 位可编程定时/计数器,记为 T0 和 Tl。8052 单片机内除了 T0 和 T1 之外,还有第三个 16 位的定时器/计数器,记为 T2。它们的工作方式可以由指令编程来设定,或作定时器用,或作外部脉冲计数器用。定时器 T0 由特殊功能寄存器 TL0 和 TH0 组成,定时器 Tl 由特殊功能寄存器 TLl 和 TH1 组成。定时器的工作方式由特殊功能寄存器 TMOD 编程决定,定时器的运行控制由特殊功能寄存器 TCON 编程控制。T0、T1 在作为定时器时,规定的定时时间到达,即产生一个定时器中断,CPU 转向中断处理程序,从而完成某种定时控制功能。T0、T1 用作计数器使用时也可以申请中断。作定时器使用时,时钟由单片机内部系统时钟提供;作计数器使用时,外部计数脉冲由 P3 口的 P3.4(或 P3.5)即 T0(或 T1)引脚输入。 方式控制寄存器 TMOD 的控制字格式如下: 低 4 位为 T0 的控制字,高 4 位为 T1 的控制字。GATE 为门控位,对定时器/计数器的启动起辅助控制作用。GATE=l 时,定时器/计数器的计数受外部引脚输入电平的控制。由由运行控制位 TRX (X=0,1)=1 和外中断引脚(0INT 或 1INT)上的高电平共同来启动定时器/计数器运行;GATE=0时。定时器/计数器的运行不受外部输入引脚的控制,仅由 TRX(X=0,1)=1 来启动定时器/计数器运行。 C/-T 为方式选择位。C/-T=0 为定时器方式,采用单片机内部振荡脉冲的 12 分频信号作为时钟计时脉冲,若采用 12MHz 的振荡器,则定时器的计数频率为 1MHZ,从定时器的计数值便可求得定时的时间。 C/-T=1 为计数器方式。采用外部引脚(T0 为 P3.4,Tl 为 P3.5)的输入脉冲作为计数脉冲,当 T0(或 T1)输入信号发生从高到低的负跳变时,计数器加 1。最高计数频率为单片机时钟频率的 1/24。 M1、M0 二位的状态确定了定时器的工作方式,详见表。

中断服务程序流程图

第一讲: 第六章I/O接口原理-接口、端口、编址 回顾:微机系统的层次结构,CPU、主机、接口电路及外部设备之间的结构关联,输入/输出的一般概念。 重点和纲要:微机系统主机与外部设备之间的数据传送,包括I/O端口的寻址方式,输入/输出的传送控制方式。 讲授内容: 6. 1 输入/输出数据的传输控制方式 一、输入/输出的一般概念 1.引言 输入/输出是微机系统与外部设备进行信息交换的过程。输入/输出设备称为外部设备,与存储器相比,外部设备有其本身的特点,存储器较为标准,而外部设备则比较复杂,性能的离散性比较大,不同的外部设备,其结构方式不同,有机械式、电动式、电子式等;输入/输出的信号类型也不相同,有数字信号,也有模拟信号;有电信号,也有非电信号;输入/输出信息的速率也相差很大。因此,CPU与外部设备之间的信息交换技术比较复杂。 CPU与外设之间的信息交换,是通过它们之间接口电路中的I/O端口来进行的,由于同一个外部设备与CPU之间所要传送的信息类型不同,方向不同,作用也不一样(例如数据信息、状态信息、控制信息、输入/输出等),所以接口电路中可以设置多个端口来分别处理这些不同的信息。 2.输入/输出端口的寻址方式 微机系统采用总线结构形式,即通过一组总线来连接组成系统的各个功能部件(包括CPU、内存、I/O端口),CPU、内存、I/O端口之间的信息交换都是通过总线来进行的,如何区分不同的内存单元和I/O端口,是输入/输出寻址方式所要讨论解决的问题。

根据微机系统的不同,输入/输出的寻址方式通常有两种形式:(1).存储器对应的输入、输出寻址方式 这种方式又称为存储器统一编址寻址方式或存储器映象寻址方式。 方法:把外设的一个端口与存储器的一个单元作同等对待,每一个I/O端口都有一个确定的端口地址,CPU与I/O端口之间的信息交换,与存储单元的读写过程一样,内存单元与I/O端口的不同,只在于它们具有不同的的地址。优点: ①CPU对I/O端口的读/写操作可以使用全部存储器的读/写操作指令,也可 以用对存储器的不同寻址方式来对I/O端口中的信息,直接进行算术、逻辑运算及循环、移位等操作。 ②内存与外设地址的分配,可以用统一的分布图。 ③不需要专门的输入、输出操作指令。 缺点: ①内存与I/O端口统一编址时,在地址总线根数一定的情况下,使系统中 实际可以直 接寻址的内存单元数减少。 ②一般情况下,系统中I/O端口数远小于内存单元数,所以在用直接寻址方 式来寻址这些端口时,要表示一个端口地址,必须用与表示内存单元地址相同的字节数,使得指令代码较长,相应地读/写执行时间也较长,这对提高系统的运行速度是不利的。 Mortorola公司的M6800CPU等均采用这种寻址I/O端口的方式。 3. CPU与外设之间所传送的信息类型 CPU与I/O端口之间所交换的信息,可以有下列几种类型: ①数据信息:包括数字量、模拟量、开关量等,可以输入、也可以输出 ②状态信息:这是I/O端口送给CPU的有关本端口所对应的外设当前状态 的信息。供CPU进行分析、判断、决策。 ③控制信息:这是CPU送给I/O端口的控制命令,使相应的外部设备完成 特定的操作。 数据信息、状态信息和控制信息是不同类型的信息,它们所起的作用也不一样。但在8086/8088微机系统中,这三种不同类型的信息的输入、输出过程是相同的。为了加以区分,可以使它们具有不同的端口地址,在端口地址相同的情况下,可以规定操作的顺序,或者在输入/输出的数据中设置特征位。

Windows 中断程序设计(一)

Windows 中断程序设计(一) 摘要该文探讨Windows3.1的中断机制,并结合DPMI接口给出一种中断程序设计方法,以越过系统和应用程序的消息队列,处理外部实时事件。 一、前言 Windows提供强大的功能以及友好的图形用户界面(GUI),使得它不仅广泛的用作管理事务型工作的支持平台,也被工业领域的工程人员所关注。但Windows3.1并非基于优先级来调度任务,无法立即响应外部事件中断,也就不能满足工业应用环境中实时事件处理和实时控制应用的要求。因此,如何在Windows环境中处理外部实时事件一直是技术人员尤其是实时领域工程人员所关注的问题。目前已有的方法大都采用内挂实时多任务内核的方式,如Windows下的实时控制软件包FLX等,而iRMX实时操作系统则把Windows3.1当作它的一个任务来运行。对于大型的工程项目,开发人员可采用购买实时软件然后集成方式。 对中小项目,从投资上考虑就不很经济。如何寻找一种简明的方法来处理外部实时事件依然显得很必要。 本文首先阐述Windwos的消息机制及中断机制,然后结合DPMI接口,给出一种保护模式下中断程序的设计方法,以处理外部实时事件。经实际运行结果表明,该方法具有简洁、实用、可靠的特点,并同样可运行于Win95。 二、Windows的消息机制 Windows是一消息驱动式系统,见图1。Windows消息提供了应用程序

与应用程序之间、应用程序与Windows系统之间进行通讯的手段。应用程序要实现的功能由消息来触发,并靠对消息的响应和处理来完成。Windows系统中有两种消息队列,一种是系统消息队列,另一种是应用程序消息队列。计算机的所有输入设备由Windows监控,当一个事件发生时,Windows先将输入的消息放入系统消息队列中,然后再将输入的消息拷贝到相应的应用程序队列中。应用程序中的消息循环从它的消息队列中检索每一个消息并发送给相应的窗口函数中。一个事件的发生,到达处理它的窗口函数必需经历上述过程。值得注意的是消息的非抢先性,即不论事件的急与缓,总是按到达的先后排队(一些系统消息除外),这就使得一些外部实时事件可能得不到及时的处理。 图1 三、Windows的保护模式及中断机制 1.Windows的保护模式 保护模式指的是线性地址由一个选择符间接生成的,该选择符指向描述表中的某一项;而实模式中则通过一个段/偏移量对来直接寻址。80386(486)CPU提供的保护模式能力包括一个64K的虚拟地址空间和一个4G的段尺寸。Windows3.1实现时有所差别,它支持标准模式和增强模式。标准模式针对286机器,不属本文探讨范围。增强模式是对386以上CPU而言,Windows正是使用保护模式来打破1M的屏障并且执行简单的内存保护。它使用选择器、描述器和描述器表控制访问指定内存的位置和段。描述器表包括全局描述器表、局部描述器表、中断描

计算机组成原理中断实验报告

北京建筑大学 2015/2016 学年第二学期 课程设计 课程名称计算机组成原理综合实验 设计题目微程序控制器设计与实现 系别电信学院计算机系 班级计141 学生姓名艾尼瓦尔·阿布力米提 学号 完成日期二〇一六年七月八日星期五 成绩 指导教师 (签名) 计算机组成综合实验任务书

指令执行流程图; ?5、利用上端软件,把所编写的微程序控制器内容写入实验台中控制器中。 ?6、利用单拍测试控制器与编程的要求是否一致。如果有错误重新修改后再写入控制器中。 7、编写一段测试程序,测试控制器运行是否正确。 实验目的 1.融合贯通计算机组成原理课程,加深对计算机系统各模块的工作原理及相互联系(寄存器堆、运算器、存储器、控制台、微程序控制器)。 2.理解并掌握微程序控制器的设计方法和实现原理,具备初步的独立设计能力;3.掌握较复杂微程序控制器的设计、调试等基本技能;提高综合运用所学理论知识独立分析和解决问题的能力。 实验电路 1. 微指令格式与微程序控制器电路 2.微程序控制器组成 仍然使用前面的CPU组成与机器指令执行实验的电路图,但本次实验加入中断系统。这是一个简单的中断系统模型,只支持单级中断、单个中断请求,有中断屏蔽功能,旨在说明最基本的原理。

中断屏蔽控制逻辑分别集成在2片GAL22V10(TIMER1 和TIMER2)中。其ABEL语言表达式如下: INTR1 := INTR; INTR1.CLK = CLK1; IE := CLR & INTS # CLR & IE & !INTC; IE.CLK= MF; INTQ = IE & INTR1; 其中,CLK1是TIMER1产生的时钟信号,它主要是作为W1—W4的时钟脉冲,这里作为INTR1的时钟信号,INTE的时钟信号是晶振产生的MF。INTS微指令位是INTS机器指令执行过程中从控制存储器读出的,INTC微指令位是INTC机器指令执行过程中从控制存储器读出的。INTE是中断允许标志,控制台有一个指示灯IE显示其状态,它为1时,允许中断,为0 时,禁止中断。当INTS = 1时,在下一个MF的上升沿IE变1,当INTC = 1时,在下一个MF的上升沿IE变0。CLR信号实际是控制台产生的复位信号CLR#。当CLR = 0时,在下一个CLK1的上升沿IE变0。当 CLR=1 且INTS = 0 且 INTC = 0时,IE保持不变。 INTR是外部中断源,接控制台按钮INTR。按一次INTR按钮,产生一个中断请求正脉冲INTR。INTR1是INTR经时钟CLK1同步后产生的,目的是保持INTR1与实验台的时序信号同步。INTR脉冲信号的上升沿代表有外部中断请求到达中断控制器。INTQ是中断屏蔽控制逻辑传递给CPU的中断信号,接到微程序控制器上。当收到INTR脉冲信号时,若中断允许位INTE=0,则中断被屏蔽,INTQ仍然为0;若INTE =1,则INTQ =1。

操作系统实验一中断处理

实习一中断处理 一、实习内容 模拟中断事件的处理。 二、实习目的 现代计算机系统的硬件部分都设有中断机构,它是实现多道程序设计的基础。中断机 构能发现中断事件,且当发现中断事件后迫使正在处理器上执行的进程暂时停止执行,而让操作系统的中断处理程序占有处理器去处理出现的中断事件。对不同的中断事件,由于它们的性质不同,所以操作系统应采用不同的处理。通过实习了解中断及中断处理程序的作用。本实习模拟“时钟中断事件”的处理,对其它中断事件的模拟处理,可根据各中断事件的性质确定处理原则,制定算法,然后依照本实习,自行设计。 三、实习题目 模拟时钟中断的产生及设计一个对时钟中断事件进行处理的模拟程序。 [提示]: (1) 计算机系统工作过程中,若出现中断事件,硬件就把它记录在中断寄存器中。中 断寄存器的每一位可与一个中断事件对应,当出现某中断事件后,对应的中断寄存器的某一位就被置成―1‖。 处理器每执行一条指令后,必须查中断寄存器,当中断寄存器内容不为―0‖时,说明有中断事件发生。硬件把中断寄存器内容以及现行程序的断点存在主存的固定单元,且让操作系统的中断处理程序占用处理器来处理出现的中断事件。操作系统分析保存在主存固定单元中的中断寄存器内容就可知道出现的中断事件的性质,从而作出相应的处理。 本实习中,用从键盘读入信息来模拟中断寄存器的作用,用计数器加1 来模拟处理器 执行了一条指令。每模拟一条指令执行后,从键盘读入信息且分析,当读入信息=0 时,表示无中断事件发生,继续执行指令;当读入信息=1 时,表示发生了时钟中断事件,转时钟中断处理程序。 (2)假定计算机系统有一时钟,它按电源频率(50Hz)产生中断请求信号,即每隔20 毫秒产生一次中断请求信号,称时钟中断信号,时钟中断的间隔时间(20 毫秒)称时钟单

中断处理程序设计

课程实验报告 课程名称:汇编语言程序设计 实验名称:实验四 实验时间: 2015-6-16,14:30-17:30 实验地点:南一楼804室 指导教师:李专 专业班级:学号: 姓名: 同组学生: 报告日期: 成绩: 计算机科学与技术学院

一、原创性声明 本人郑重声明:本报告的内容由本人独立完成,有关观点、方法、数据和文献等的引用已经在文中指出。除文中已经注明引用的内容外,本报告不包含任何其他个人或集体已经公开发表的作品或成果,不存在剽窃、抄袭行为。 特此声明! 学生签字: 日期: 二、评语与成绩评定 1.指导老师评语 2.实验成绩评定 实验完成质量得分(70分)(实验步骤清晰详细深入,实验记录真实完整等)报告撰写质量得分(30分) (报告规范、完整、通顺、 详实等) 总成绩(100分) 指导教师签字: 日期:

目录 1.实验目的 (1) 2.实验内容 (1) 2.1任务一 (1) 2.2任务二 (1) 2.3任务三 (2) 2.4任务四 (2) 3实验过程 (2) 3.1任务一 (2) 3.1.1实验要求 (2) 3.1.2实验结果 (2) 3.2任务二 (4) 3.2.1设计思想及存储分配 (4) 3.2.2程序框图 (5) 3.2.3源程序代码 (6) 3.2.4实验结果 (7) 3.3任务三 (7) 3.3.1源程序代码 (7) 3.3.2实验结果 (11) 3.4任务四 (12) 3.4.1源程序代码 (12) 3.4.2实验结果 (16) 4.实验体会 (16)

1.实验目的 (1) 掌握中断矢量表的概念 (2)掌握中断处理程序设计的技巧 (3)掌握简化段定义、函数调用伪指令 (4)了解Win32程序的编程方法及编译、链接方法 2.实验内容 2.1任务一 用三种方式获取中断类型码10H对应的中断处理程序的入口地址。 要求:(1) 直接运行调试工具(TD.EXE),观察中断矢量表中的信息; (2) 编写程序,用 DOS功能调用方式获取,观察相应的出口参数与(1) 中看到的结果是否相同(使用TD观看即可) (3) 编写程序,直接读取相应内存单元,观察读到的数据与(1)看到的结 果是否相同(使用TD观看即可)。 2.2任务二 编写一个中断服务程序并驻留内存,要求在程序返回DOS操作系统后,键盘的按键A变成了按键B、按键B变成了按键A。 提示:(1) 对于任何DOS程序,不管其采用什么方法获取按键,最后都是通过执行16H号软中断的0号和10H号功能调用来实现的。所以,你只需接 管16H号软中断的0号和10号功能调用并进行相应的处理; (2) 获得一个按键扫描码的方法:在TD中执行16H中断的0号和10H号 功能调用,按相应的键,观察AH中的内容。 资料:16H中断的0号和10H号功能 功能描述:从键盘读入字符 入口参数:AH = 00H——读键盘 = 10H——读扩展键盘 出口参数:AH =键盘的扫描码 AL =字符的ASCII码

51单片机串行口中断服务程序

51单片机串行口中断服务程序 ---------------------------------------------------------------------------- //串口中断服务程序,仅需做简单调用即可完成串口输入输出的处理 //编程:聂小猛。该资料来自“51单片机世界”https://www.doczj.com/doc/b317392250.html,/~dz2000,欢迎访问。 //出入均设有缓冲区,大小可任意设置。 //可供使用的函数名: //char getbyte(void);从接收缓冲区取一个byte,如不想等待则在调用前检测inbufsign是否为1。 //getline(char idata *line, unsigned char n); 获取一行数据回车结束,已处理backspce和delete,必须定义最大输入字符数 //putinbuf(uchar c);模拟接收到一个数据 //putbyte(char c);放入一个字节到发送缓冲区 //putbytes(unsigned char *outplace,j);放一串数据到发送缓冲区,自定义长度 //putstring(unsigned char code *puts);发送一个字符串到串口 //puthex(unsigned char c);发送一个字节的hex码,分成两个字节发。 //putchar(uchar c,uchar j);发送一个字节数据的asc码表达方式,需要定义小数点的位置 //putint(uint ui,uchar j);发送一个整型数据的asc码表达方式,需要定义小数点的位置 //CR;发送一个回车换行 //************************************************************************* #include //该头文件包括了51,52,80320的特殊寄存器,用在51,52上也可 #define uchar unsigned char #define uint unsigned int #define OLEN 64 /* size of serial transmission buffer */ idata unsigned char outbuf[OLEN]; /* storage for transmission buffer */ unsigned char idata *outlast=outbuf; //最后由中断传输出去的字节位置 unsigned char idata *putlast=outbuf; //最后放入发送缓冲区的字节位置 #define ILEN 2 /* size of serial receiving buffer */ idata unsigned char inbuf[ILEN]; unsigned char idata *inlast=inbuf; //最后由中断进入接收缓冲区的字节位置 unsigned char idata *getlast=inbuf; //最后取走的字节位置 bit outbufsign; //输出缓冲区非空标志有=1 bit inbufsign; //接收缓冲区非空标志有=1 bit inbufful; //输入缓冲区满标志满=1 #define CR putstring("\r\n") //CR=回车换行 //***************************** //放入一个字节到发送缓冲区 putbyte(char c) {uchar i,j; ES=0; /*暂停串行中断,以免数据比较时出错? */ if (outlast==putlast ) { i=(0-TH1); do{i--;j=36; do {j--;}while(j!=0);

单片机外部中断详解及程序

单片机外部中断详解及程序 单片机在自主运行的时候一般是在执行一个死循环程序,在没有外界干扰(输入信号)的时候它基本处于一个封闭状态。比如一个电子时钟,它会按时、分、秒的规律来自主运行并通过输出设备(如液晶显示屏)把时间显示出来。在不需要对它进行调校的时候它不需要外部干预,自主封闭地运行。如果这个时钟足够准确而又不掉电的话,它可能一直处于这种封闭运行状态。但事情往往不会如此简单,在时钟刚刚上电、或时钟需要重新校准、甚至时钟被带到了不同的时区的时候,就需要重新调校时钟,这时就要求时钟就必须具有调校功能。因此单片机系统往往又不会是一个单纯的封闭系统,它有些时候恰恰需要外部的干预,这也就是外部中断产生的根本原由。 实际上在第二个示例演示中,就已经举过有按键输入的例子了,只不过当时使用的方法并不是外部中断,而是用程序查询的方式。下面就用外部中断的方法来改写一下第二个示例中,通过按键来更改闪烁速度的例子(第二个例子)。电路结构和接线不变,仅把程序改为下面的形式。 #include ;

unsigned int t=500; //定义一个全局变量t,并设定初始值为500次 //===========延时子函数,在8MHz晶振时约 1ms============= void delay_ms(unsigned int k) { unsigned int i,j; for(i=0;i

定时中断T0服务程序参考框图

软件程序: ORG 0000H LJMP MAIN ORG 000BH LJMP PIT0 ORG 001BH LJMP PIT1 ORG 0100H MAIN: MOV SP,#FH ;设堆栈指针 MOV SCON,#00H ;设置串行口为方式0 MOV TMOD,#11H ;T0和T1初始化为方式1 MOV TH0, #3CH ;置时间常数,T0和T1定时100ms MOV TL0, #OB0H MOV TH1, #3CH MOV TL1, #0B0H MOV 50H, #96H ;T0中断次数计数单元 MOV 51H,#14H ;T1中断次数计数单元 MOV R1, #00H MOV R2, #00H MOV R0, #40H ;显示缓冲单元起始地址 DISP0:MOV @R0, #00H ;显示缓冲单元清零 INC R0 CJNE R0, #4CH,DISP0 MOV 44H,#01H ;设置通道号的显示缓冲单元 MOV 48H,#02H MOV R7,#40H ;置当前通道显示缓冲单元首址 MOV 53H,#40H SETB ETO ;开中断 SETB ET1 SETB EA SETB TR0 ;启动定时器 SETB TR1 LP: MOV R7, 53H ;调显示子程序 ACALL DISP AJMP JP 定时器TO中断服务程序 PIT0: MOV TH0, #3CH ;重置时间常数 MOV TL0, #OBOH DJNZ 50H,#96H PUSH ACC PUSH 03H ACALL WDXJ ;调温度巡检子程序 POP 03H POP ACC

DH0: RET1 定时器T1中断服务程序 PIT1: MOV TH1,#3CH ;重置时间常数 MOV TL0, #OBOH DJNZ 51H,DH1 ;计数20次即定时2S MOV 51H,#14H INC R2 CJNE R2,#03H,CNL0 ;根据R2中的内容确定显示缓冲区首址 MOV R2,#00H CNL0: CJNE R2,#00H,CNL1 MOV 53H,#40H SJMP DH1 CNL1: CJNE R2,#01H,CNL2 MOV 53H,#40H SJMP DH1 CNL2: MOV 53H,#48H DH1: RETI 显示子程序 DISP: CLR P3.7 ;输出锁存 MOV R3,#01H ;置显示字位码 MOV DPTR,#TAB DISP1:MOV A,R3 MOV SBUF,A ;字位码送串行口 JNB T1,$ ;等待串行转送结束 CLR T1 ;清串行中断标志 MOV A,R7 MOV R0,A MOV A,@RO ;取代显示的数据 MOVC A,@R0 ;查表求字段码 MOV SBUF, A ;字段码送串行口, JNB T1,$ ;等待串行中断标志 SETB P3.7 ;允许输出显示 ACALL DEL ;调延时子程序 MOV A,R3 JB ACC.3,DISP2 ;4位显示完否 RL A MOV R3,A INC R7 CLR P3.7 ;输出锁存 AJNP DISP1 DISP2:RET TAB : DB 3FH,06H,5BH,4FH,66H DB 6DH,7DH,07H,7FH,6FH DEL: PUSH 07H ;延时子程序

第5章 中断服务程序设计

第5章中断服务程序设计 中断服务程序(ISR)是嵌入式应用系统获取各种事件的基本手段,而“事件”是实时性问题的讨论基础和时间计算的起点。ISR的设计质量直接影响到系统的实时性指标和操作系统的工作效率。 只要没有关中断,中断服务程序可以中断任何任务的运行,可将中断服务程序可成比最高优先级(0级)还高的“任务”。 5.1中断优先级安排原则 中断源是系统及时获取异步事件的主要手段,其优先级安排原则如下: ●紧迫性:触发中断的事件允许耽误的时间越短,设定的中断优先级就越高。 ●关键性:触发中断的事件越关键(重要),设定的中断优先级就越高。 ●频繁性:触发中断的事件发生越频繁,设定的中断优先级就越高。 ●快捷性:ISR处理越快捷(耗时短),设定的中断优先级就越高。 中断服务程的功能应尽量简单,只要将获取的异步事件通信给关联任务,后续处理由关联任务完成。 5.2不受操作系统管理的中断服务程序 正常情况下,ISR应受操作系统的管理,因很多任务是靠ISR触发的。 但在两种情况下ISR不受操作系统管理:①没有必要;②操作系统没有对该ISR进行管理。 实时操作系统uC/OS-Ⅱ移植到ARM7体系的CPU上时,没有对FIQ进行处理,即FIQ 是不受操作系统管理的。 选用FIQ来响应实时性要求最高的高速采样操作是一个有效措施,保护现场的工作量很小(FIQ专有的8个寄存器不需要保护)。 在工程模板的系统启动文件Startup.s中,已经把汇编代码部分处理好,用户只需要用C 语言编写快速中断服务函数FIQ_Exception()即可,不需考虑保护现场和恢复现场的问题。 程序:Startup.s中队FIQ的处理 Reset ;异常向量表 LDR PC,ResetAddr ;跳转到复位入口地址 LDR PC,UndefinedAddr LDR PC,SWI_Addr ;跳转到软件中断入口地址 LDR PC,PrefetchAddr LDR PC,DataAbortAddr DCD 0xb9205f80 LDR PC,[PC,#-0xff0] ;跳转到向量中断入口地址(向量中断控制器) LDR PC,FIQ_Addr ;跳转到快速中断入口地址 ResetAddr DCD ResetInit UndefinedAddr DCD Undefined SWI_Addr DCD SoftwareInterrupt PrefetchAddr DCD PrefetchAbort Nouse DCD 0

PIC单片机中断程序的设计技巧

PIC单片机中断程序的设计技巧 所有的中档系列PIC单片机,PORTB端口最高的4个引脚(RB7~RB4)在设为输入模式时,当输入电平由高到低或由低到高发生变化时,可以让单片机产生中断。这就是通常所说的引脚状态变化中断。 在设计引脚中断程序时,有三个需要特别注意的地方。一是,在清除P0RTB中断标志位RBIF之前,必须安排一条必不可少的,以PORTB端口数据寄存器PORTB为源寄存器的读操作指令。放置这一指令的目的有时并不只是为了读取有用的数据,而是为了取消状态变化的硬件信号,以便顺利清除RBIF标志位,为下一次中断做好准备。二是,由于端口PORTB 是引脚电子变化中断,即无论引脚出现上升沿还是下降沿都会产生中断请求,所以必须处理好不需要的虚假中断。三是,一般都利用PIC单片机的引脚功能来检测按键,所以必须处理好按键消抖的问題。 2 引脚中断程序设计 在主程序里先设置有关的寄存器。 ◇设置TRISB寄存器,使RB7~RB4相关的引脚处于输入状态; ◇如果需要弱上拉,通过OPTION_REG的第7位设置; ◇RBIF=O; ◇RBIE=1; ◇GIF=1。 响应状态变化后的中断服务程序。 ◇检查RBIF是否为l,为l则是引脚变化引起的中断; ◇调用延时程序,延时20~30 ms,目的是为了按键去抖; ◇判断是引脚出现上升沿还是下降沿引起的中断; ◇调用按键处理程序; ◇读PORTB口的值,取消状态变化的硬件信号; ◇清除RBIF标志。 笔者认为上面程序设计最大的问题是在中断程序里调用延时程序。大家知道,中档PIC 单片机只有8层深度的硬件堆栈,在中断里调用于程序出现极易堆栈溢出的情况。另外,PIC单片机中断程序人口只有一个,在响应中断的请求时,PIC单片机就会自动把全局中断的使能位(INTCON的第7位GIF)清除,这样其他中断就暂时不能被响应(此时,如果别的中断发出的中断请求,标志位将一直保留着),直到这个中断程序退出后才会得到响应。这就要求我们设计中断程序的时候必须尽量短,避免调用子程序,更不要在中断里进行复杂的运算。 下面给出笔者设计程序时的思路。 当引脚状态变化引起中断时,在中断子程序里首先判断引起中断的原因是不是我们需要的变化引起的中断。如果是,不要在这里延时,而是设置一个标志位,接着清除中断标志,退出中断。中断程序如下: else if((RBIE&RBlF)==1){ //如果引脚变化引起中断 if(RB4==0){ //RB4上的按钮接地 key=1;//按键标志位置位 } RBIF=0;//清除引脚中断标志位 }

嵌入式实验4(中断处理程序设计)

北华航天工业学院 《嵌入式系统基础》课程实验报告 实验名称编号:实验4 中断处理程序设计 作者所在系部:计算机科学与工程系 作者所在专业:计算机科学与技术 作者所在班级:B09513 作者学号:20094051329 作者姓名:康建云 教师姓名:李建义 一、实验内容 1.本实验涵盖实验手册《ARM嵌入式系统设计及接口编程实验教程》中的实验9 中

断处理程序设计。 2.修改程序,使得当四个中断源中断时分别调用实验二跑马灯实验的实验内容第二项中编写的一个函数,即不同中断将控制四个跑马灯的闪烁顺序。 二、实验要求 1.了解ARM处理器中断处理过程。 2.掌握S3C2440下进行中断编程的方法,包括中断设置、中断服务子程序的编写。 3.理解实验手册中的实验9的实验程序。 4.编程实现实验内容中第2项任务。 5.撰写实验报告描述实现上述个要求的情况。 三、实验思路 在SinoSys-M3中,已经将EINT0、EINT1、EINT2、EINT19、EINT11作为外部中断源和开发板上位号为SW1、SW2、SW3、SW4的这四个小按键相连。在实验的过程中,在运行之后,按下开关板上这四个按钮,将触发处理器的四个外部中断,处理器转而去执行相应的中断服务程序,在中断服务程序中,向串口打印中断信息,并输出到开发主机的串口终端工具上。因为key=1、key=3、key=5、key=7分别对应SW1、SW2、SW3、SW4四个按钮。所以改程序时只需控制key值在不同值下的灯亮情况即可,修改程序实现跑马灯不同亮的次序并循环五次,所修改的程序如下: 四、实验程序 static void __irq Key_ISR(void) { int i; U8 key; if(rINTPND==BIT_EINT8_23) { ClearPending(BIT_EINT8_23); if(rEINTPEND&(1<<11)) { Uart_Printf("eint11\n"); rEINTPEND |= 1<< 11; } if(rEINTPEND&(1<<19)) { Uart_Printf("eint19\n"); rEINTPEND |= 1<< 19; }} if(rINTPND==BIT_EINT0) {//Uart_Printf("eint0\n"); ClearPending(BIT_EINT0); } if(rINTPND==BIT_EINT2) { Uart_Printf("eint2\n"); ClearPending(BIT_EINT2); } key=Key_Scan(); if(key==1)//从左到右依次亮 { for(i=0;i<5;i++){ rGPFDAT=rGPFD AT&0x0F|0xE0; Delay(1000); rGPFDAT=rGPFDAT&0x0F|0xD0; Delay(1000); rGPFDAT=rGPFDAT&0x0F|0xB0; Delay(1000); rGPFDAT=rGPFDAT&0x0F|0x70; Delay(2000); }} if(key==3) //从右到左依次亮 { for(i=0;i<5;i++){ rGPFDAT=rGPFD AT&0x0F|0x70; Delay(1000); rGPFDAT=rGPFDAT&0x0F|0xB0; Delay(1000); rGPFDAT=rGPFDAT&0x0F|0xD0; Delay(1000);

相关主题
文本预览
相关文档 最新文档