-
cpu一般在什么结束时响应中断请求_考研计算机 | CPU中断
2021-01-19 00:32:462021计算机考研暑期复习知识点:CPU中断CPU中断什么是中断允许触发器?它有何作用?...解:CPU响应I/O中断请求的条件和时间是:当中断允许状态为1(EINT=1),且至少有一个中断请求被查到,则在一条指令执行完...
2021计算机考研暑期复习知识点:CPU中断CPU中断什么是中断允许触发器?它有何作用?解:中断允许触发器是CPU中断系统中的一个部件,他起着开关中断的作用(即中断总开关,则中断屏蔽触发器可视为中断的分开关)。在什么条件和什么时间,CPU可以响应I/O的中断请求?解:CPU响应I/O中断请求的条件和时间是:当中断允许状态为1(EINT=1),且至少有一个中断请求被查到,则在一条指令执行完时,响应中断。某系统对输入数据进行取样处理,每抽取一个输入数据,CPU就要中断处理一次,将取样的数据存至存储器的缓冲区中,该中断处理需P秒。此外,缓冲区内每存储N个数据,主程序就要将其取出进行处理,这个处理需Q秒。试问该系统可以跟踪到每秒多少次中断请求?解:这是一道求中断饱和度的题,要注意主程序对数据的处理不是中断处理,因此Q秒不能算在中断次数内。N个数据所需的处理时间=P×N+Q秒平均每个数据所需处理时间= (P×N+Q) /N秒求倒数得:该系统跟踪到的每秒中断请求数=N/(P×N+Q)次。在程序中断方式中,磁盘申请中断的优先权高于打印机。当打印机正在进行打印时,磁盘申请中断请求。试问是否要将打印机输出停下来,等磁盘操作结束后,打印机输出才能继续进行?为什么?解:这是一道多重中断的题,由于磁盘中断的优先权高于打印机,因此应将打印机输出停下来,等磁盘操作结束后,打印机输出才能继续进行。因为打印机的速度比磁盘输入输出的速度慢,并且暂停打印不会造成数据丢失。CPU对DMA请求和中断请求的响应时间是否一样?为什么?解:CPU对DMA请求和中断请求的响应时间不一样,因为两种方式的交换速度相差很大,因此CPU必须以更短的时间间隔查询并响应DMA请求(一个存取周期末)。以上是小编为大家整理分享的“2021计算机考研暑期复习知识点:CPU中断”相关内容,希望对大家有帮助。更多考研福利关注“计算机考研研究院”公众号点击“资料下载”即可拥有!
☀22研友加:1071300584
☀21研友加:723214845
●考研计算机 | 数据结构—结构算法
●考研计算机 | 数据结构—研究内容
●考研计算机 | 数据结构—物理结构
●考研计算机 | 总线
确认过眼神你是我爱的人更多考研资讯 关注我们就对了
扫码关注我们
-
*32.什么是DMA方式?什么是中断方式?有什么区别?
2020-05-02 23:01:51DMA是l/O设备与主存之间由硬件组成的直接数据通路,主要用于高速I/O设备与主存之间的成组数据传送。(外设快、慢了没意义。然后数据多,少了也没多大...外设向cpu发出中断请求,cpu响应中断后进行数据传输。但是如...DMA是l/O设备与主存之间由硬件组成的直接数据通路,主要用于高速I/O设备与主存之间的成组数据传送。(外设快、慢了没意义。然后数据多,少了也没多大意义)
首先讲一下外设跟内存进行数据传输的几种方式:
1.程序传送。通过无条件或者if等条件语句使cpu主动进行数据传输,但是这种情况下要等待外设准备好,会浪费大量的等待时间。
2.中断方式。外设向cpu发出中断请求,cpu响应中断后进行数据传输。但是如果传输较多数据的情况下,cpu得一直花费时间在中断上,也会造成cpu利用率低。
3.dma方式。外设请求传输,dma向cpu发出总线控制请求,cpu把总线控制下发给DMA控制器。DMA利用总线进行数据的快速传输。传输完毕后把总线控制权交还给cpu。有点:快、能传输大量数据而不降低CPU速度。DMA方式和中断方式的区别:
1.DMA方式是硬件方式。中断是软件方式。
2.优先级:DMA方式比中断方式高。
3.DMA只占用cpu少部分时间,不浪费cpu资源。但是中断方式全程占有cpu。
4.中断方式能处理异常事件,但是DMA方式只能够传输数据。 -
中断和异常-中断的下半部处理机制
2019-08-19 16:30:31一般都是在中断请求关闭的条件下执行中断服务程序,以避免嵌套使中断控制复杂化。但是,中断是一个随机事件,随时会到来,如果关中断的时间太长,CPU不能响应其它的中断请求,会造成中断丢失。内核的目标是尽可能快...Linux并不是一次性把中断所要求的事情全部做完,而是分两部分来做,下面具体描述内核如何处理中断的下半部。
一、为什么把中断分为两部分来处理
一般都是在中断请求关闭的条件下执行中断服务程序,以避免嵌套使中断控制复杂化。但是,中断是一个随机事件,随时会到来,如果关中断的时间太长,CPU不能响应其它的中断请求,会造成中断丢失。内核的目标是尽可能快地处理完中断请求,把更多的处理向后推迟。例如,假设一个数据块已到达网线,当中断控制器接收到这个中断请求信号时,Linux内核只是简单地标志数据到来,然后让处理器恢复到它以前运行的状态,其余的处理稍后再进行。因此,内核把中断处理分两部分:上半部和下半部,上半部(中断服务程序)内核立即执行,下半部(就是一些内核函数)留着稍后处理,如图5.6所示。
首先,用一个快速的上半部来处理硬件发出的请求,它必须在一个新的中断产生之前终止。通常,除了在设备和一些内存缓冲区之间移动或传送数据,确定硬件是否处于健全的状态之外,这一部分的工作很少。
下半部运行时允许中断请求的,而上半部运行时是关中断的,这是二者之间的区别。
但内核到底什么时候执行下半部,以什么方式组织下半部?这是要讨论的下半部实现机制,这种机制在内核的演变过程中不断得到改进,在以前的内核中,这个机制叫做下半部,在2.4以后的版本中有新的发展和改进,改进的目标使下半部在多处理器上并发执行,有助于驱动程序的开发者进行驱动程序的开发,这种执行机制叫软中断机制。软中断中常用的小任务(Tasklet)机制及2.6版本内核中的工作队列机制。
二、小任务机制
小任务是指对要推迟执行的函数进行组织的一种机制。数据结构为tasklet_struct,每个结构代表一个独立的小任务,定义如下:
struct tasklet_struct
{
struct tasklet_struct *next; /* 指向链表中的下一个元素 */
unsigned long state; /* 小任务的状态 */
atomic_t count; /* 引用计数器 */
void (*func)(unsigned long); /* 要调用的函数 */
unsigned long data; /* 传递给函数的参数 */
};func域就是下半部中要推迟执行的函数,data是它唯一的参数。
state域的取值为TASKLET_STATE_SCHED或TASKLET_STATE_RUN。TASKLET_STATE_SCHED表示小任务已被调度,正准备投入运行,TASKLET_STATE_RUN表示小任务正在运行。TASKLET_STATE_RUN只有在多处理器系统上才使用,任何时候单处理器系统都清楚一个小任务是不是正在运行。
count域是小任务的引用计数器。如果它不为0,则小任务被禁止,不允许执行;只有当它为0,小任务才被激活,并且在被设置为挂起时,小任务才能够执行。
1、声明和使用小任务
大多数情况下,为了控制一个寻常的硬件设备,小任务机制是实现下半部的最佳选择。小任务可以动态创建,使用方便,执行起来比较快。
小任务可以静态创建,也可以动态创建。选择哪种方式取决于是到对小任务直接引用还是间接引用。如果静态创建一个小任务(也就是对它直接引用),可以使用如下两个宏中的任意一个:
#define DECLARE_TASKLET(name, func, data)
#define DECLARE_TASKLET_DISABLED(name, func, data)
这两个宏都能根据给定的名字静态创建一个tasklet_struct结构。当该小任务被调度后,给定的函数func被执行,其参数由data给出。这两个宏的区别在于引用计数器的初始值设置不同。DECLARE_TASKLET宏把创建的小任务的引用计数器设置为0,处于激活状态。
DECLARE_TASKLET_DISABLED宏把创建的小任务的引用计数器设置为1,处于禁止状态。例如:DECLARE_TASKLET(tasklet, tasklet_handler, dev);等价于:struct tasklet_struct tasklet = { NULL, 0, ATOMIC_INIT(0), tasklet_handler, dev };
这就创建一个名为tasklet的小任务,其处理程序为tasklet_handler,并且已被激活。当处理程序被调用时,dev会被传递给它。
2、编写自己的小任务处理程序
小任务处理程序必须符合如下函数类型:
void tasklet_handler(unsigned long data);
备注:
小任务不能睡眠,所以不能在小任务中使用信号量或者其它产生阻塞的函数。但是小任务运行时可以响应中断。
3、调度自己的小任务
通过调用tasklet_schedule()函数并传递给它相应的struct tasklet_struct指针,则该小任务就会被调度以适当的时候执行:
tasklet_schedule(&tasklet);/* 把tasklet标记为挂起 */
例如"test_and_set_bit(0, &var)","0" 不是要设置的值,而是表示var中第0位需要被设置为"1"。此函数返回相应比特位上一次被设置的值。
static inline void tasklet_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
int cpu = smp_processor_id();
unsigned long flags;local_irq_save(flags);
t->next = tasklet_vec[cpu].list;
tasklet_vec[cpu].list = t;
__cpu_raise_softirq(cpu, TASKLET_SOFTIRQ);
local_irq_restore(flags);
}
}在小任务被调度以后,只要有机会它就会尽可能早的运行。在它还没有得到运行机会之前,如果一个相同的小任务又被调度了,那么它仍然只会运行一次。
可以调用tasklet_disable()函数来禁止某个指定的小任务。如果该小任务当前正在执行,这个函数会等到它执行完毕再返回。调用tasklet_enable()函数可以激活一个小任务,如果希望把DECLARE_TASKLET_DISABLED()创建的小任务激活,也得调用这个函数,如:
tasklet_disable(&tasklet);/* 小任务现在被禁止,这个小任务不能运行 */
tasklet_enable(&tasklet);/* 小任务现在被激活 */
static inline void tasklet_disable(struct tasklet_struct *t)
{
tasklet_disable_nosync(t);
tasklet_unlock_wait(t);
}static inline void tasklet_enable(struct tasklet_struct *t)
{
atomic_dec(&t->count);
}也可以调用tasklet_kill()从挂起的队列中去掉一个小任务。该函数的参数是一个指向某个小任务的struct tasklet_struct的指针。在小任务重新调度它自身时,从挂起的队列中移去已调度的小任务会很有用。这个函数首先等待该小任务执行完毕,然后再将它移去。
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->func = func;
t->data = data;
t->state = 0;
atomic_set(&t->count, 0); /* 引用计数器设置为0,处于激活状态 */
}void tasklet_kill(struct tasklet_struct *t)
{
if (in_interrupt())
printk("Attempt to kill tasklet from interrupt\n");while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
current->state = TASK_RUNNING;
do {
current->policy |= SCHED_YIELD;
schedule();
} while (test_bit(TASKLET_STATE_SCHED, &t->state));
}
tasklet_unlock_wait(t);
clear_bit(TASKLET_STATE_SCHED, &t->state);
}4、小任务的简单应用
实验:编写一个内核模块并调用小任务的相关函数。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>static struct tasklet_struct tasklet;
static void tasklet_handler(unsigned long data)
{
printk(KERN_ALERT "tasklet_handler is running.\n");
}static int __init test_init(void)
{
printk(KERN_ALERT "test_init is running.\n");
tasklet_init(&tasklet, tasklet_handler, 0);
tasklet_schedule(&tasklet);
return 0;
}static void __exit test_exit(void)
{
tasklet_kill(&tasklet);
printk(KERN_ALERT "test_exit is running.\n");
}
module_init(test_init);
module_exit(test_exit);MODULE_LICENSE("GPL");
MODULE_VERSION("v1.0");
MODULE_AUTHOR("xz@vichip.com.cn");Makefile
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
#$(warning "11111111111111111111111)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
else
obj-m := test.o安装内核模块
sudo insmod test.ko
dmesg
[19077.755972] test_init is running.
[19077.756022] tasklet_handler is running.卸载内核模块
sudo rmmod test
dmesg
[19099.885167] test_exit running.
总结:所谓的小任务机制是为下半部函数的执行提供一种执行机制,也就是说,推迟处理的事情由tasklet_handler实现,什么时候执行,由小任务机制封装后交给内核去处理。
三、工作队列
工作队列是另外一种将工作推后执行的一种形式,工作队列把工作推后,交由一个内核线程执行,也就是说,这个下半部分可以在进程上下文中运行。最重要的就是工作队列允许被重新调度甚至是睡眠。
何时使用工作队列,何时使用小任务机制?
如果推后执行的任务需要睡眠,就选择工作队列。如果推后执行的任务不需要睡眠,就选择小任务机制。另外,如果需要用一个可以重新调度的实体来执行下半部的处理,也应该使用工作队列。工作队列是唯一能在进程上下文运行下半部实现的机制,也只有工作队列才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时或需要执行阻塞的I/O操作时,它非常有用。如果不需要用一个内核线程来推后执行工作,可以考虑使用小任务机制。
1、工作、工作队列和工作者线程
把推后执行的任务叫做工作,描述工作的数据结构为work_struct,这些工作以队列结构组成工作队列,工作队列数据结构为workqueue_struct,工作者线程负责执行工作队列中的工作。系统默认的工作者线程为events,自己也可以创建自己的工作者线程。
2、表示工作的数据结构
在linux/workqueue.h中定义work_struct结构:
typedef void (*work_func_t)(struct work_struct *work); /* 函数指针类型 */
struct work_struct {
atomic_long_t data;
struct list_head entry; /* 工作的链表 */
work_func_t func; /* 要执行的函数 */
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};这些结构被链接成链表。当一个工作者线程被唤醒时,工作者线程会执行其链表上的所有工作。工作被执行完毕,它将相应的struct work_struct对象从链表上移除。当链表上不再有对象时,工作者线程就会继续睡眠。
3、创建推后的工作
要使用工作队列,首先要做的是创建一些需要推后完成的工作。通过DECLARE_WORK宏在编译时静态创建struct work_struct该结构:
#define ATOMIC64_INIT(i) { (i) }
#define ATOMIC_LONG_INIT(i) ATOMIC64_INIT(i)
#define ATOMIC_INIT(i) { (i) }
#define ATOMIC_LONG_INIT(i) ATOMIC_INIT(i)
#define WORK_DATA_STATIC_INIT() \
ATOMIC_LONG_INIT((unsigned long)(WORK_STRUCT_NO_POOL | WORK_STRUCT_STATIC))#define __WORK_INITIALIZER(n, f) { \
.data = WORK_DATA_STATIC_INIT(), \
.entry = { &(n).entry, &(n).entry }, \
.func = (f), \
__WORK_INIT_LOCKDEP_MAP(#n, &(n)) \
}#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f)这样就静态地创建一个名为n,待执行函数为f的struct work_struct结构。
也可以在运行时动态地初始化一个由struct work_struct指向的工作。
/*
* initialize all of a work item in one go
*
* NOTE! No point in using "atomic_long_set()": using a direct
* assignment of the work data initializer allows the compiler
* to generate better code.
*/
#ifdef CONFIG_LOCKDEP
#define __INIT_WORK(_work, _func, _onstack) \
do { \
static struct lock_class_key __key; \
\
__init_work((_work), _onstack); \
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
lockdep_init_map(&(_work)->lockdep_map, "(work_completion)"#_work, &__key, 0); \
INIT_LIST_HEAD(&(_work)->entry); \
(_work)->func = (_func); \
} while (0)
#else
#define __INIT_WORK(_work, _func, _onstack) \
do { \
__init_work((_work), _onstack); \
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
INIT_LIST_HEAD(&(_work)->entry); \
(_work)->func = (_func); \
} while (0)
#endif#define INIT_WORK(_work, _func) \
__INIT_WORK((_work), (_func), 0)4、工作队列中待执行的函数
工作队列待执行的函数原型是:
typedef void (*work_func_t)(struct work_struct *work); /* 函数指针类型 work_func_t*/
这个函数由一个工作者线程执行,函数运行在进程上下文中。默认情况下,允许响应中断,并且不持有任何锁。如果需要,函数可以睡眠。注意:尽管该函数运行在进程上下文中,但它不能访问用户空间,因为内核线程在用户空间没有相关的内存映射。通常在系统调用发生时,内核代表用户空间的进程运行,此时它才能访问用户空间,只有在此时它才能映射用户空间的内存。
5、对工作进行调度
工作已被创建,可以调度工作。想要把给定工作的待处理函数提交给默认的events工作者线程,只需调用:
schedule_work(&work);
static inline bool schedule_work(struct work_struct *work)
{
return queue_work(system_wq, work);
}工作就被调度,一旦其所在的处理器上的工作者线程被唤醒,工作就被执行。有时并不希望工作马上就被执行,希望经过一段延迟之后再执行。这种情况下,可以调度工作在指定的时间执行:
schedule_delayed_work(&dwork, delay);
/**
* schedule_delayed_work - put work task in global workqueue after delay
* @dwork: job to be done
* @delay: number of jiffies to wait or 0 for immediate execution
*
* After waiting for a given time this puts a job in the kernel-global
* workqueue.
*/
static inline bool schedule_delayed_work(struct delayed_work *dwork,
unsigned long delay)
{
return queue_delayed_work(system_wq, dwork, delay);
}总结:这时,&dwork指向的work_struct直到delay指定的时钟节拍用完才会执行。
6、工作队列简单应用
编写一个内核模块调用工作队列的相关函数。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/workqueue.h>static struct work_struct work;
static struct workqueue_struct *queue = NULL;static void work_handler(struct work_struct *work)
{
printk(KERN_ALERT "work_handler is running.\n");
}static int __init test_init(void)
{
queue = create_singlethread_workqueue("helloworld"); /* 创建一个单线程的工作队列 */
if(!queue)
goto error;
INIT_WORK(&work, work_handler);
schedule_work(&work);
printk(KERN_ALERT "test_init is running.\n");
return 0;
error:
return -1;
}static void __exit test_exit(void)
{
destroy_workqueue(queue);
printk(KERN_ALERT "destroy_workqueue.\n");
}
module_init(test_init);
module_exit(test_exit);MODULE_LICENSE("GPL");
MODULE_VERSION("v1.0");
MODULE_AUTHOR("xz@vichip.com.cn");
Makefile
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
#$(warning "11111111111111111111111)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
else
obj-m := test.o -
为什么把中断分为两部分来处理
2015-01-10 17:19:22中断服务例程一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的中断请求,从而造成中断的丢失。因此,...图3.8 中断的分割
中断服务例程一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的中断请求,从而造成中断的丢失。因此,内核的目标就是尽可能快的处理完中断请求,尽其所能把更多的处理向后推迟。例如,假设一个数据块已经达到了网线,当中断控制器接受到这个中断请求信号时,Linux内核只是简单地标志数据到来了,然后让处理器恢复到它以前运行的状态,其余的处理稍后再进行(如把数据移入一个缓冲区,接受数据的进程就可以在缓冲区找到数据)。因此,内核把中断处理分为两部分:前半部分(top half)和后半部分(bottom half),前半部分内核立即执行,而后半部分留着稍后处理,如图3.8所示:
首先,一个快速的“前半部分”来处理硬件发出的请求,它必须在一个新的中断产生之前终止。通常地,除了在设备和一些内存缓冲区(如果你的设备用到了DMA,就不止这些)之间移动或传送数据,确定硬件是否处于健全的状态之外,这一部分做的工作很少。
然后,就让一些与中断处理相关的有限个函数作为 “后半部分”来运行:
· 允许一个普通的内核函数,而不仅仅是服务于中断的一个函数,能以后半部分的身份来运行。
· 允许几个内核函数合在一起作为一个后半部分来运行。
后半部分运行时是允许中断请求的,而前半部分运行时是关中断的,这是二者之间的主要区别。3.5.2 实现机制
Linux内核为将中断服务分为两部分提供了方便,并设立了相应的机制。在以前的内核中,这个机制就叫bottom half(简称bh),但在2.4版中有了新的发展和推广,叫做软中断(softirq)机制。
1.Bh机制
以前内核中的Bh机制设置了一个函数指针数组bh_base[],它把所有的后半部分都组织起来,其大小为32,数组中的每一项就是一个后半部分,即一个bh 函数。同时,又设置了两个32位无符号整数bh_active和bh_mask,每个无符号整数中的一位对应着bh_base[]中的一个元素,如图3.9所示:图3.9 bh机制示意图
在2.4以前的内核中,每次执行完do_IRQ()中的中断服务例程以后,以及每次系统调用结束之前,就在一个叫do_bottom_half()的函数中执行相应的bh函数。
在do_bottom_half()中对bh函数的执行是在关中断的情况下进行的,也就是说对bh的执行进行了严格的“串行化”,这种方式简化了bh的设计,这是因为,对单CPU来说,bh 函数的执行可以不嵌套;而对于多CPU来说,在同一时间内最多只允许一个CPU执行bh函数。
这种简化了的设计在一定程度上保证了从单CPU到多CPU SMP结构的平稳过渡,但随着时间的推移,就会发现这样的处理对于SMP的性能有不利的影响。因为,当系统中有很多个bh函数需要执行时,bh函数的“串行化”却只能使一个CPU执行一个bh函数,其它CPU即使空闲,也不能执行其它的bh函数。由此可以看出,bh函数的串行化是针对所有CPU的,根本发挥不出多CPU的优势。
那么,在新内核的设计中,是改进bh机制还是抛弃bh机制,建立一种新的机制?2.4选择了一种折中的办法,继续保留bh机制,另外增加一种或几种机制,并把它们纳入一个统一的框架中,这就是2.4内核中的软中断(softirq)机制。
2.软中断机制
软中断机制也是推迟内核函数的执行,然而,与bh函数严格地串行执行相比,软中断却在任何时候都不需要串行化。同一个软中断的两个实例完全有可能在两个CPU上同时运行。当然,在这种情况下,软中断必须是可重入的。软中断给网络部分带来的好处尤为突出,因为2.4内核中用两个软中断代替原来的一个NET_BH函数,这就使得在多处理机系统上软中断的执行更为高效。
3.Tasklet机制
另一个类似于bh的机制叫做tasklet。Tasklet建立在软中断之上,但与软中断的区别是,同一个tasklet只能运行在一个CPU上,而不同的tasklet可以同时运行在不同的CPU上。在这种情况下,tasklet就不需要是可重入的,因此,编写tasklet比编写一个软中断要容易。
Bh机制在2.4中依然存在,但不是作为一个单独的机制存在,而是建立在tasklet之上。因此,在2.4版中,设备驱动程序的开发者必须更新他们原来的驱动程序,用tasklet代替bh。3.5.3数据结构的定义
在具体介绍软中断处理机制之前,我们先介绍一下相关的数据结构,这些数据结构大部分都在/includee/linux/interrupt.h中
1.与软中断相关的数据结构
软中断本身是一种机制,同时也是一种基本框架。在这个框架中,既包含了bh机制,也包含了tasklet机制
(1) 内核定义的软中断enum
{
HI_SOFTIRQ=0,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
TASKLET_SOFTIRQ
};内核中用枚举类型定义了四种类型的软中断,其中NET_TX_SOFTIRQ和NET_RX_SOFTIRQ两个软中断是专为网络操作而设的,而HI_SOFTIRQ和TASKLET_SOFTIRQ是针对bh和tasklet而设的软中断。编码的作者在源码注释中曾提到,一般情况下,不要再分配新的软中断。
(2)软中断向量
struct softirq_action
{
void (*action)(struct softirq_action *);
void *data;
}
static struct softirq_action softirq_vec[32] __cacheline_aligned;从定义可以看出,内核定义了32个软中断向量,每个向量指向一个函数,但实际上,内核目前只定义了上面的四个软中断,而我们后面主要用到的为HI_SOFTIRQ和TASKLET_SOFTIRQ两个软中断。
(3)软中断控制/状态结构
softirq_vec[]是个全局量,系统中每个CPU所看到的是同一个数组。但是,每个CPU各有其自己的“软中断控制/状态”结构,这些数据结构形成一个以CPU编号为下标的数组irq_stat[](定义在include/i386/hardirq.h中)typedef struct {
unsigned int __softirq_pending;
unsigned int __local_irq_count;
unsigned int __local_bh_count;
unsigned int __syscall_count;
struct task_struct * __ksoftirqd_task; /* waitqueue is too large */
unsigned int __nmi_count; /* arch dependent */
} ____cacheline_aligned irq_cpustat_t;
irq_cpustat_t irq_stat[NR_CPUS];irq_stat[]数组也是一个全局量,但是各个CPU可以按其自身的编号访问相应的域。于是,内核定义了如下宏(在include/linux/irq_cpustat.h中):
#ifdef CONFIG_SMP#define __IRQ_STAT(cpu, member) (irq_stat[cpu].member)
#else
#define __IRQ_STAT(cpu, member) ((void)(cpu), irq_stat[0].member)
#endif
/* arch independent irq_stat fields */
#define softirq_pending(cpu) __IRQ_STAT((cpu), __softirq_pending)
#define local_irq_count(cpu) __IRQ_STAT((cpu), __local_irq_count)
#define local_bh_count(cpu) __IRQ_STAT((cpu), __local_bh_count)
#define syscall_count(cpu) __IRQ_STAT((cpu), __syscall_count)
#define ksoftirqd_task(cpu) __IRQ_STAT((cpu), __ksoftirqd_task)
/* arch dependent irq_stat fields */
#define nmi_count(cpu) __IRQ_STAT((cpu), __nmi_count) /* i386, ia64 */2.与tasklet相关的数据结构
与bh函数相比,tasklet是“多序”的bh函数。内核中用tasklet_task来定义一个tasklet:struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
从定义可以看出,tasklet_struct是一个链表结构,结构中的函数指针func指向其服务程序。内核中还定义了一个以CPU编号为下标的数组tasklet_vec[]和tasklet_hi_vec[]:
struct tasklet_head
{
struct tasklet_struct *list;
} __attribute__ ((__aligned__(SMP_CACHE_BYTES)));
extern struct tasklet_head tasklet_vec[NR_CPUS];
extern struct tasklet_head tasklet_hi_vec[NR_CPUS];
这两个数组都是tasklet_head结构数组,每个tasklet_head结构就是一个tasklet_struct结构的队列头。3.与bh相关的数据结构
前面我们提到,bh建立在tasklet之上,更具体地说,对一个bh的描述也是tasklet_struct结构,只不过执行机制有所不同。因为在不同的CPU上可以同时执行不同的tasklet,而任何时刻,即使在多个CPU上,也只能有一个bh函数执行。
(1) bh的类型
enum {
TIMER_BH = 0, /* 定时器 */
TQUEUE_BH, /* 周期性任务队列 */
DIGI_BH, /* DigiBoard PC/Xe */
SERIAL_BH, /* 串行接口 */
RISCOM8_BH, /* RISCom/8 */
SPECIALIX_BH, /* Specialix IO8+ */
AURORA_BH, /* Aurora多端口卡(SPARC)*/
ESP_BH, /* Hayes ESP 串行卡 */
SCSI_BH, /* SCSI接口*/
IMMEDIATE_BH, /* 立即任务队列*/
CYCLADES_BH, /* Cyclades Cyclom-Y 串行多端口 */
CM206_BH, /* CD-ROM Philips/LMS cm206磁盘 */
JS_BH, /* 游戏杆(PC IBM)*/
MACSERIAL_BH, /* Power Macintosh 的串行端口 */
ISICOM_BH /* MultiTech的ISI卡*/
};
在给出bh定义的同时,我们也给出了解释。从定义中可以看出,有些bh与硬件设备相关,但这些硬件设备未必装在系统中,或者仅仅是针对IBM PC兼容机之外的某些平台。
(2) bh的组织结构
在2.4以前的版本中,把所有的bh用一个bh_base[]数组组织在一起,数组的每个元素指向一个bh函数:
static void (*bh_base[32])(void);2.4版中保留了上面这种定义形式,但又定义了另外一种形式:
struct tasklet_struct bh_task_vec[32];
这也是一个有32个元素的数组,但数组的每个元素是一个tasklet_struct结构,数组的下标就是上面定义的枚举类型中的序号。3.5.4 软中断、bh及tasklet的初始化
1.Tasklet的初始化
Tasklet的初始化是由tasklet_ init()函数完成的:void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
{
t->next = NULL;
t->state = 0;
atomic_set(&t->count, 0);
t->func = func;
t->data = data;
}其中,atomic_set()为原子操作,它把t->count置为0。
2.软中断的初始化
首先通过open_softirq()函数打开软中断:void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;
}然后,通过softirq_init()函数对软中断进行初始化:
void __init softirq_init()
{
int i;
for (i=0; i<32; i++)
tasklet_init(bh_task_vec+i, bh_action, i);
open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}对于bh的32个tasklet_struct,调用tasklet_init以后,它们的函数指针func全部指向bh_action()函数,也就是建立了bh的执行机制,但具体的bh函数还没有与之挂勾,就像具体的中断服务例程还没有挂入中断服务队列一样。同样,调用open_softirq()以后,软中断TASKLET_SOFTIRQ的服务例程为tasklet_action(),而软中断HI_SOFTIRQ的服务例程为tasklet_hi_action()。
3.Bh的初始化
bh的初始化是由init_bh()完成的:void init_bh(int nr, void (*routine)(void))
{
bh_base[nr] = routine;
mb();
}这里调用的函数mb()与CPU中执行指令的流水线有关,我们对此不进行进一步讨论。下面看一下几个具体bh的初始化(在kernel/sched.c中):
init_bh(TIMER_BH,timer_bh);
init_bh(TUEUE_BH,tqueue_bh);
init_bh(IMMEDIATE_BH,immediate_bh);
初始化以后,bh_base[TIMER_BH]处理定时器队列timer_bh,每个时钟中断都会激活TIMER_BH,在第五章将会看到,这意味着大约每隔10ms这个队列运行一次。bh_base[TUEUE_BH]处理周期性的任务队列tqueue_bh,而bh_base[IMMEDIATE_BH]通常被驱动程序所调用,请求某个设备服务的内核函数可以链接到IMMEDIATE_BH所管理的队列immediate_bh中,在该队列中排队等待。3.5.5后半部分的执行
1.Bh的处理
当需要执行一个特定的bh函数(例如bh_base[TIMER_BH]())时,首先要提出请求,这是由mark_bh()函数完成的(在Interrupt.h中):static inline void mark_bh(int nr)
{
tasklet_hi_schedule(bh_task_vec+nr);
}从上面的介绍我们已经知道,bh_task_vec[]每个元素为tasklet_struct结构,函数的指针func指向bh_action()。
接下来,我们来看tasklet_hi_schedule()完成什么功能:static inline void tasklet_hi_schedule(struct tasklet_struct *t)
{
if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
int cpu = smp_processor_id();
unsigned long flags;
local_irq_save(flags);
t->next = tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list = t;
cpu_raise_softirq(cpu, HI_SOFTIRQ);
local_irq_restore(flags);
}其中smp_processor_id()返回当前进程所在的CPU号,然后以此为下标从tasklet_hi_vec[]中找到该CPU的队列头,把参数t所指向的tasklet_struct结构链入这个队列。由此可见,当某个bh函数被请求执行时,当前进程在哪个CPU上,就把这个bh函数“调度”到哪个CPU上执行。另一方面,tasklet_struct代表着将要对bh函数的一次执行,在同一时间内,只能把它链入一个队列中,而不可能同时出现在多个队列中。对同一个tasklet_struct结构,如果已经对其调用了tasklet_hi_schedule()函数,而尚未得到执行,就不允许再将其链入该队列,所以标志位TASKLET_STATE_SCHED就是保证这一点的。最后,通过cpu_raise_softirq()发出软中断请求,其中的参数HI_SOFTIRQ表示bh与HI_SOFTIRQ软中断对应。
软中断HI_SOFTIRQ的服务例程为tasklet_hi_action():static void tasklet_hi_action(struct softirq_action *a)
{
int cpu = smp_processor_id();
struct tasklet_struct *list;
local_irq_disable();
list = tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list = NULL; 临界区加锁
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->state))
BUG();
t->func(t->data);
tasklet_unlock(t);
continue;
}
tasklet_unlock(t);
}
local_irq_disable();
t->next = tasklet_hi_vec[cpu].list;
tasklet_hi_vec[cpu].list = t;
__cpu_raise_softirq(cpu, HI_SOFTIRQ);
local_irq_enable();
}
}这个函数除了加锁机制以外,读起来比较容易。其中要说明的是t->func(t->data)语句,这条语句实际上就是调用bh_action()函数:
/* BHs are serialized by spinlock global_bh_lock.
t is still possible to make synchronize_bh() as
spin_unlock_wait(&global_bh_lock). This operation is not used
by kernel now, so that this lock is not made private only
due to wait_on_irq().
It can be removed only after auditing all the BHs.
*/ spinlock_t global_bh_lock = SPIN_LOCK_UNLOCKED;
static void bh_action(unsigned long nr)
{
int cpu = smp_processor_id();
if (!spin_trylock(&global_bh_lock))
goto resched;
if (!hardirq_trylock(cpu))
goto resched_unlock;
if (bh_base[nr])
bh_base[nr]();
hardirq_endlock(cpu);
spin_unlock(&global_bh_lock);
return;
resched_unlock:
spin_unlock(&global_bh_lock);
resched:
mark_bh(nr);
}这里对bh函数的执行又设置了两道锁。一是hardirq_trylock(),这是防止从一个硬中断内部调用bh_action()。另一道锁是spin_trylock()。这把锁就是全局量global_bh_lock,只要有一个CPU在这个锁所锁住的临界区运行,别的CPU就不能进入这个区间,所以在任何时候最多只有一个CPU在执行bh函数。至于根据bh函数的编号执行相应的函数,那就比较容易理解了。
2.软中断的执行
内核每当在do_IRQ()中执行完一个中断请求队列中的中断服务例程以后,都要检查是否有软中断请求在等待执行。下面是do_IRQ()中的一条语句:if (softirq_pending(cpu))
do_softirq();
在检测到软中断请求以后,就要通过do_softirq()执行软中断服务例程,其代码在/kernel/softirq.c中:
smlinkage void do_softirq()
{
int cpu = smp_processor_id();
__u32 pending;
long flags;
__u32 mask;
if (in_interrupt())
return;
local_irq_save(flags);/*把eflags寄存器的内容保存在flags变量中*/
pending = softirq_pending(cpu);
if (pending) {
struct softirq_action *h;
mask = ~pending;
local_bh_disable();
estart:
/* Reset the pending bitmask before enabling irqs */
softirq_pending(cpu) = 0;
local_irq_enable(); /*开中断*/
h = softirq_vec;
do {
if (pending & 1)
h->action(h);
h++;
pending >>= 1;
} while (pending);
ocal_irq_disable(); / *关中断*/
pending = softirq_pending(cpu);
if (pending & mask) {
mask &= ~pending;
goto restart;
}
__local_bh_enable();
if (pending)
wakeup_softirqd(cpu);
}
local_irq_restore(flags); /*恢复eflags寄存器的内容*/
}从do_softirq()的代码可以看出,使CPU不能执行软中断服务例程的“关卡”只有一个,那就是in_interrupt(),这个宏限制了软中断服务例程既不能在一个硬中断服务例程内部执行,也不能在一个软中断服务例程内部执行(即嵌套)。但这个函数并没有对中断服务例程的执行进行“串行化”限制。这也就是说,不同的CPU可以同时进入对软中断服务例程的执行,每个CPU分别执行各自所请求的软中断服务。从这个意义上说,软中断服务例程的执行是“并发的”、多序的。但是,这些软中断服务例程的设计和实现必须十分小心,不能让它们相互干扰(例如通过共享的全局变量)。
http://www.eefocus.com/article/09-06/74823s.html
从前面对软中断数据结构的介绍可以知道,尽管内核最多可以处理32个软中断,但目前只定义了四个软中断。在对软中断进行初始化时,soft_Init()函数只初始化了两个软中断TASKLET_SOFTIRQ和HI_SOFTIRQ,这两个软中断对应的服务例程为tasklet_action()和tasklet_hi_action()。因此,do_softirq()中的do_while循环实际上是调用这两个函数。前面已经给出了tasklet_hi_action()的源代码,而tasklet_action()的代码与其基本一样,在此不再给出。
3.5.6 把bh移植到tasklet
在Linux2.2中,对中断的后半部分处理只提供了bh机制,而在2.4中新增加了两种机制:软中断和tasklet。通过上面的介绍我们知道,同一个软中断服务例程可以同时在不同的CPU上运行。为了提高SMP的性能,软中断现在主要用在网络子系统中。多个tasklet可以在多个不同的CPU上运行,但一个CPU一次只能处理一个tasklet。Bh由内核进行了串行化处理,也就是在SPM环境中,某一时刻,一个bh函数只能由一个CPU来执行。如果要把Linux2.2中的bh移植到2.4的tasklet,请按下面方法进行:
1.Linux2.4中对bh的处理
假设一个bh为FOO_BH(FOO表示任意一个),其处理函数为foo_bh,则
(1)处理函数的原型为: void foo_bh(void);
(2)通过init_bh(FOO_BH, foo_bh)函数对foo_bh进行初始化
(3)通过mark_bh(FOO_BH)函数提出对foo_bh()的执行请求。
2.把bh移植到tasklet
(1)处理函数的原型为:void foo_bh(unsigned long data);
(2)通过宏 DECLARE_TASKLET_DISABLED(foo_tasklet, foo_bh, 0) 或
struct tasklet_struct foo_tasklet;
tasklet_init(&foo_tasklet, foo_bh, 0);
tasklet_disable(&foo_tasklet);
对foo_tasklet进行初始化
(3)通过
tasklet_enable(&foo_tasklet);
tasklet_schedule(&foo_tasklet)
对foo_tas -
linux内核分析--为什么把中断分为上半部和下半步
2013-10-18 21:33:02中断服务例程一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的中断请求,从而造成中断的丢失。因此,... -
中断的下半部处理机制(1)小任务机制
2015-12-28 11:09:07中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的中断请求,从而造成中断的丢失。因此,... -
51单片机外部中断
2021-01-16 19:27:24中断请求 -> 中断响应 -> 中断处理 -> 中断返回 什么是中断系统 计算机执行某程序时,发生了紧急事件或有特殊请求,CPU暂停某程序的执行,转而去处理上述事件或请求,处理完毕后再重新执行某程序的过程叫做... -
ARM 中断--IRQ and FIQ配置--外部配置
2018-08-04 13:32:43ARM 中断--IRQ and FIQ配置--外部配置 ...ARM中中断分为两种:FIQ(fast interrupt request)快速中断请求,IRQ(interrupt requrest)一般的中断请求 为什么FIQ比IRQ的中断响应更快? 在ARM体系中,FIQ的优先级比IR... -
启动定时器t0的工作指令是_启动定时器 0 工作的指令是使 TCON 的 ( )_学小易找答案...
2020-12-20 19:01:41【单选题】微控制器响应中断时,保护现场的工作( )...【简答题】试述8051 MCU 响应中断的条件。【判断题】当串行口的 SM2=1 时,仅当收到的 RB8=1 时,数据接收的数据才会进入接收 SBUF 。( )【判断题】串口工作方式... -
计组(七)输入输出系统
2020-04-22 22:47:061、CPU响应中断应该具备哪些条件?...4. 满足以上三个条件,CPU在现行指令结束的最后一个状态周期响应中断。 2、中断响应优先级和中断处理优先级分别是指什么? 中断响应优先级是由硬件排队线路... -
考研复试—计算机组成第七章
2021-01-21 12:27:22具备上述三个条件时,CPU在现行指令结束的最后一个状态周期响应中断。 2、中断响应优先级和中断处理优先级分别指什么? 中断响应优先级是由硬件排队线路或中断查询程序的查询顺序决定的,不可动态改变;而中断处理... -
《微机原理与接口技术》习题解答7.doc
2020-08-17 07:40:25微机原理与接口技术习题解答 习题7 7.1 什么是中断常见的中断源有哪几类CPU响应中断的条件是什么 解答中断是指CPU在正常执行程序时由于内部/外部时间或程序的预先安排引起CPU暂时终止执行现行程序转而去执行请求CPU... -
进程间通信之-信号signal--linux内核剖析(九)
2016-03-27 13:22:21信号及信号来源什么是信号信号是UNIX和Linux系统响应某些条件而产生的一个事件,接收到该信号的进程会相应地采取一些行动。通常信号是由一个错误产生的。但它们还可以作为进程间通信或修改行为的一种方式,明确地由... -
专接本微机原理必备知识考点
2020-09-24 12:07:492.响应中断的四个条件: ①一条指令执行结束 ②CPU处于开中断状态 ③当前没有发生复位(RESET) 保持(HOLD) 内部中断和非屏蔽中断请求(NMI) ④若当前执行的指令是开中断(STI)和中断返回指令(IRET),则它们执行完... -
都是想要的考试题 速度下载
2010-05-24 10:11:059. 8086/8088在当前指令执行完且IF=1的情况下可以响应一个外部INTR中断请求。( ) 10. 8086被复位后,寄存器Flag、IP、CS的内容分别是0000 、0001 、FFFFH。 ( ) 四、汇编程序(共20分) 1. 试分析下面的程序段完成... -
寄雁传书谢不能——Linux中的信号
2018-06-17 22:05:44信号是软件中断,是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道... -
linux进程间的通信(C):信号
2013-02-24 22:47:03信号是UNIX和Linux系统响应某些条件而产生的一个事件。 接收到该信号的进程会相应地采取一些行动。 在软件层次上, 信号是对中断机制的一种模拟; 在实现原理上, 一个进程收到一个信号与处理器收到一个中断请求... -
微机课后题目答案 答案
2011-04-20 10:33:438086将类型码乘4后得到中断向量表的入口地址,从此地址开始读取4字节的中断处理程序的入口地址,8086从此地址开始执行程序,完成了INTR中断请求的响应过程。 18.什么是总线请求?8086在最小工作模式下,有关总线... -
学习Linux(37)信号
2020-07-25 18:39:18用于通知进程发生了异步事件,它是Linux系统响应某些条件而产生的一个事件,它是在软件层次上对中断机制的一种模拟,是一种异步通信方式,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。... -
linux 信号_学习Linux(37)信号
2020-11-26 10:55:50用于通知进程发生了异步事件,它是Linux系统响应某些条件而产生的一个事件,它是在软件层次上对中断机制的一种模拟,是一种异步通信方式,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。... -
你必须知道的495个C语言问题(高清版)
2010-03-31 16:24:091.4 新的64位机上的64位类型是什么样的? 3 指针声明 3 1.5 这样的声明有什么问题?char *p1, p2; 我在使用p2的时候报错了。 3 1.6 我想声明一个指针,并为它分配一些空间,但却不行。这样的代码有什么问题?... -
《你必须知道的495个C语言问题》
2010-03-20 16:41:181.4 新的64位机上的64位类型是什么样的? 3 指针声明 3 1.5 这样的声明有什么问题?char *p1, p2; 我在使用p2的时候报错了。 3 1.6 我想声明一个指针,并为它分配一些空间,但却不行。这样的代码有什么问题?... -
linux内核剖析(九)进程间通信之-信号signal
2018-05-04 16:41:00信号及信号来源 什么是信号 信号是UNIX和Linux系统响应某些条件而产生的一个事件,接收到该信号的进程...信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说... -
你必须知道的495个C语言问题
2015-10-16 14:14:281.4 新的64位机上的64位类型是什么样的? 指针声明 1.5 这样的声明有什么问题?char*p1,p2;我在使用p2的时候报错了。 1.6 我想声明一个指针,并为它分配一些空间,但却不行。这样的代码有什么问题?char*p;*p=... -
《计算机操作系统》期末复习指导
2009-12-30 10:57:55(2)产生死锁的四个必要条件是资源互斥使用、保持和等待、非剥夺性、循环等待。 (3)解决死锁的方法 一般有死锁的预防,即破坏产生死锁的四个必要条件中的一个或多个,使系统绝不会进入死锁状态;死锁... -
Java并发编程(学习笔记).xmind
2020-06-19 15:54:23(4)用户界面具备更短的响应时间:现代GUI框架中大都使用一个事件分发线程(类似于中断响应函数)来替代主事件循环,当用户界面用有事件发生时,在事件线程中将调用对应的事件处理函数(类似于中断处理函数) ... -
计算机组成原理测试题
2011-08-05 22:53:2310.外部设备提出中断请示的条件是 。 A.一个CPU周期结束 B. 外设工作完成和系统允许 C. CPU开放中断系统 D. 总线空闲 11.在3种集中式总线控制中,_______方式响应时间最快,_______方式对电路故障最敏感。 A.链式... -
PCI.EXPRESS系统体系结构标准教材.pdf
2013-02-17 16:21:169.2.3 生成msi中断请求的基础 9.2.4 中断处理程序处理时的存储器同步 9.2.5 中断延迟 9.2.6 一些规则、建议等等 9.3 传统的pci中断发送机制 9.3.1 背景知识——pci中断信令 9.3.2 虚拟intx信令 9.4 设备可以同时支持...