⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 中断.txt

📁 讲解linux内核 系统中断 部分经典讲义
💻 TXT
📖 第 1 页 / 共 5 页
字号:
while ((long)(jiffies - timer_jiffies) >= 0) {
        struct timer_list *timer;
        if (!tv1.index) {
                int n = 1;
                do {
                        cascade_timers(tvecs[n]);
                } while (tvecs[n]->index == 1 && ++n < NOOF_TVECS);
        }
        while ((timer = tv1.vec[tv1.index])) {
                void (*fn)(unsigned long) = timer->function;
                unsigned long data = timer->data;
                detach_timer(timer);
                timer->next = timer->prev = NULL;
                sti();
                fn(data);
                cli();
        }
        ++timer_jiffies;
        tv1.index = (tv1.index + 1) & TVR_MASK;
}
sti();
}
对run_timer_list函数的说明如下:
关中。
判断jiffies是否大等于timer_jiffies,若不是,goto 8。
判断tv1.index是否为0(即此时系统已经扫描过整个tv1的256个timer_list链表,又回到的第一个链表处,此时需重整TVECS结构),若是,置n为1;若不是,goto 
6。
调用cascade_timers()函数把TVECS[n]中由其index指定的那条链表上的timer放到TVECS[n-1]中来。注意:调用cascade_timers()函数后,index已经加1。
判断TVECS[n]->index是否为1,即原来为0。如果是(表明TVECS[n]上所有都已经扫描一遍,此时需对其后一级的TVECS[++n]调用cascade_timers()进行重整),把n加1,goto 
4。
执行tv1.vec上由tv1->index指定的那条链表上的所有timer的服务函数,并把该timer从链表中移走。在执行服务函数的过程中,允许中断。
timer_jiffies加1,tv1->index加1,若tv1->index等于256,则重新置为0,goto 2。
开中,返回。
Linux提供了两种定时器服务。一种早期的由timer_struct等结构描述,由run_old_times函数处理。另一种“新”的服务由timer_list等结构描述,由add_timer、del_timer、cascade_time和run_timer_list等函数处理。
早期的定时器服务利用如下数据结构:
struct timer_struct {
    unsigned long expires;  /*本定时器被唤醒的时刻 */
    void (*fn)(void);       /* 定时器唤醒后的处理函数 */
}
struct timer_struct timer_table[32];  /*最多可同时启用32个定时器 */
unsigned long timer_active;        /* 每位对应一定时器,置1表示启用 */
新的定时器服务依靠链表结构突破了32个的限制,利用如下的数据结构:
struct timer_list {
    struct timer_list *next;
    struct timer_list *prev;
    unsigned long expires;
    unsigned long data;          /* 用来存放当前进程的PCB块的指针,可作为参数传
    void (*function)(unsigned long);  给function */
}

表示上述数据结构的图示如下:

    在这里,顺便简单介绍一下旧的timer机制的运作情况。
    系统在每次调用函数do_bottom_half时,都会调用一次函数run_old_timers()。
函数run_old_timers()
该函数处理的很简单,只不过依次扫描timer_table中的32个定时器,若扫描到的定时器已经到期,并且已经被激活,则执行该timer的服务函数。
间隔定时器itimer
系统为每个进程提供了三个间隔定时器。当其中任意一个定时器到期时,就会发出一个信号给进程,同时,定时器重新开始运作。三种定时器描述如下:
ITIMER_REAL  真实时钟,到期时送出SIGALRM信号。
ITIMER_VIRTUAL  仅在进程运行时的计时,到期时送出SIGVTALRM信号。
ITIMER_PROF  
不仅在进程运行时计时,在系统为进程运作而运行时它也计时,与ITIMER_VIRTUAL对比,该定时器通常为那些在用户态和核心态空间运行的应用所花去的时间计时,到期时送出SIGPROF信号。
与itimer有关的数据结构定义如下:
struct timespec {
        long        tv_sec;                /* seconds */
        long        tv_nsec;        /* nanoseconds */
};
struct timeval {
        int        tv_sec;                /* seconds */
        int        tv_usec;        /* microseconds */
};
struct  itimerspec {
        struct  timespec it_interval;    /* timer period */
        struct  timespec it_value;       /* timer expiration */
};
struct        itimerval {
        struct        timeval it_interval;        /* timer interval */
        struct        timeval it_value;        /* current value */
};
这三种定时器在task_struct中定义:
struct task_struct {
    ……
    unsigned long timeout;
    unsigned long it_real_value,it_prof_value,it_virt_value;
    unsigned long it_real_incr,it_prof_incr,it_virt_incr;
    struct timer_list real_timer;
    ……
}
在进程创建时,系统把it_real_fn函数的入口地址赋给real_timer.function。(见sched.h)
我们小组分析了三个系统调用:sys_getitimer,sys_setitimer,sys_alarm。
在这三个系统调用中,需用到以下一些函数:
函数static int _getitimer(int which, struct itimerval *value)
该函数的运行过程大致如下:
根据传进的参数which按三种itimer分别处理:
若是ITIMER_REAL,则设置interval为current进程的it_real_incr,val设置为0;判断current进程的real_timer有否设置并挂入TVECS结构中,若有,设置val为current进程real_timer的expires,并把real_timer重新挂到TVECS结构中,接着把val与当前jiffies作比较,若小等于当前jiffies,则说明该real_timer已经到期,于是重新设置val为当前jiffies的值加1。最后把val减去当前jiffies的值,goto 
2。
若是ITIMER_VIRTUAL,则分别设置interval,val的值为current进程的it_virt_incr、it_virt_value,goto 
2。
若是ITIMER_PROF,则分别设置interval,val的值为current进程的it_prof_incr、it_prof_value,goto 2。
   (2)调用函数jiffiestotv把val,interval的jiffies值转换为timeval,返回0。
函数 int _setitimer(int which, struct itimerval *value, struct itimerval *ovalue)
该函数的运行过程大致如下:
调用函数tvtojiffies把value中的interval和value转换为jiffies i 和 j。
判断指针ovalue是否为空,若空,goto ;若不空,则把由which指定类型的itimer存入ovalue中,若存放不成功,goto 4;
根据which指定的itimer按三种类型分别处理:
若是ITIMER_REAL,则从TVECS结构中取出current进程的real_timer,并重新设置current进程的it_real_value和it_real_incr为j和i。若j等于0,goto 
4;若不等于0,则把当前jiffies的值加上定时器剩余时间j,得到触发时间。若i小于j,则表明I已经溢出,应该重新设为ULONG_MAX。最后把current进程的real_timer的expires设为i,把设置过的real_timer重新加入TVECS结构,goto 
4。
若是ITIMER_VIRTUAL,则设置current进程的it-_virt_value和it_virt_incr为j和i。
若是ITIMER_PROF,则设置current进程的it-_prof_value和it_prof_incr为j和i。
   (4)返回0。
函数verify_area(int type, const void *addr, unsigned long size)
该函数的主要功能是对以addr为始址的,长度为size的一块存储区是否有type类型的操作权利。
函数memcpy_tofs(to, from, n)
该函数的主要功能是从以from为始址的存储区中取出长度为n的一块数据放入以to为始址的存储区。
函数memcpy_fromfs(from, to, n)
该函数的主要功能是从以from为始址的存储区中取出长度为n的一块数据放入以to为始址的存储区。
函数memset((char*)&set_buffer, 0, sizeof(set_buffer))
该函数的主要功能是把set_buffer中的内容置为0,在这里,即把it_value和it_interval置为0。
现在,我简单介绍一下这三个系统调用:
系统调用sys_getitimer(int which, struct itimerval *value)
首先,若value为NULL,则返回-EFAULT,说明这是一个bad address。
其次,把which类型的itimer取出放入get_buffer。
再次,若存放成功,再确认对value的写权利。
最后,则把get_buffer中的itimer取出,拷入value。
系统调用sys_setitimer(int which, struct itimerval *value,struct itimerval *ovalue)
首先,判断value是否为NULL,若不是,则确认对value是否有读的权利,并把set_buffer中的数据拷入value;若value为NULL,则把set_buffer中的内容置为0,即把it_value和it_interval置为0。
其次,判断ovalue是否为NULL,若不是,则确认对ovalue是否有写的权利。
再次,调用函数_setitimer设置由which指定类型的itimer。
最后,调用函数memcpy_tofs把get_buffer中的数据拷入ovalue,返回。
系统调用sys_alarm(unsigned int seconds)
该系统调用重新设置进程的real_itimer,若seconds为0,则把原先的alarm定时器删掉。并且设interval为0,故只触发一次,并把旧的real_timer存入oldalarm,并返回oldalarm。
[目录]




from aka


[目录]




硬件中断

硬件中断
硬件中断概述
中断可以用下面的流程来表示:
中断产生源 --> 中断向量表 (idt) --> 中断入口 ( 一般简单处理后调用相应的函数) --->do_IRQ--> 后续处理(软中断等工作)
具体地说,处理过程如下:
中断信号由外部设备发送到中断芯片(模块)的引脚
中断芯片将引脚的信号转换成数字信号传给CPU,例如8259主芯片引脚0发送的是0x20
CPU接收中断后,到中断向量表IDT中找中断向量
根据存在中断向量中的数值找到向量入口
由向量入口跳转到一个统一的处理函数do_IRQ
在do_IRQ中可能会标注一些软中断,在执行完do_IRQ后执行这些软中断。
下面一一介绍。
8259芯片
本文主要参考周明德《微型计算机系统原理及应用》和billpan的相关帖子
1.中断产生过程
(1)如果IR引脚上有信号,会使中断请求寄存器(Interrupt Request Register,IRR)相应的位置位,比如图中, IR3, IR4, 
IR5上有信号,那么IRR的3,4,5为1
(2)如果这些IRR中有一个是允许的,也就是没有被屏蔽,那么就会通过INT向CPU发出中断请求信号。屏蔽是由中断屏蔽寄存器(Interrupt Mask 
Register,IMR)来控制的,比如图中位3被置1,也就是IRR位3的信号被屏蔽了。在图中,还有4,5的信号没有被屏蔽,所以,会向CPU发出请求信号。
(3)如果CPU处于开中断状态,那么在执行指令的最后一个周期,在INTA上做出回应,并且关中断.
(4)8259A收到回应后,将中断服务寄存器(In-Service Register)置位,而将相应的IRR复位:
8259芯片会比较IRR中的中断的优先级,如上图中,由于IMR中位3处于屏蔽状态,所以实际上只是比较IR4,I5,缺省情况下,IR0最高,依次往下,IR7最低(这种优先级可以被设置),所以上图中,ISR被设置为4.
(5)在CPU发出下一个INTA信号时,8259将中断号送到数据线上,从而能被CPU接收到,这里有个问题:比如在上图中,8259获得的是数4,但是CPU需要的是中断号(并不为4),从而可以到idt找相应的向量。所以有一个从ISR的信号到中断号的转换。在Linux的设置中,4对应的中断号是0x24.
(6)如果8259处于自动结束中断(Automatic End of Interrupt 
AEOI)状态,那么在刚才那个INTA信号结束前,8259的ISR复位(也就是清0),如果不处于这个状态,那么直到CPU发出EOI指令,它才会使得ISR复位。
2.一些相关专题
(1)从8259
在x86单CPU的机器上采用两个8259芯片,主芯片如上图所示,x86模式规定,从8259将它的INT脚与主8259的IR2相连,这样,如果从8259芯片的引脚IR8-IR15上有中断,那么会在INT上产生信号,主8259在IR2上产生了一个硬件信号,当它如上面的步骤处理后将IR2的中断传送给CPU,收到应答后,会通过CAS通知从8259芯片,从8259芯片将IRQ中断号送到数据线上,从而被CPU接收。
由此,我猜测它产生的所有中断在主8259上优先级为2,不知道对不对。
(2)关于屏蔽
从上面可以看出,屏蔽有两种方法,一种作用于CPU, 通过清除IF标记,使得CPU不去响应8259在INT上的请求。也就是所谓关中断。
另一种方法是,作用于8259,通过给它指令设置IMR,使得相应的IRR不参与ISR(见上面的(4)),被称为禁止(disable),反之,被称为允许(enable).
每次设置IMR只需要对端口0x21(主)或0xA1(从)输出一个字节即可,字节每位对应于IMR每位,例如:
outb(cached_21,0x21);
为了统一处理16个中断,Linux用一个16位cached_irq_mask变量来记录这16个中断的屏蔽情况:
static unsigned int cached_irq_mask = 0xffff;
为了分别对应于主从芯片的8位IMR,将这16位cached_irq_mask分成两个8位的变量:
#define __byte(x,y) (((unsigned char *)&(y))[x])
#define cached_21 (__byte(0,cached_irq_mask))
#define cached_A1 (__byte(1,cached_irq_mask))
在禁用某个irq的时候,调用下面的函数:
void disable_8259A_irq(unsigned int irq){
unsigned int mask = 1 << irq;
unsigned long flags;
spin_lock_irqsave(&i8259A_lock, flags);
cached_irq_mask |= mask;                /*-- 对这16位变量设置 */
if (irq & 8)                            /*-- 看是对主8259设置还是对从芯片设置 */
outb(cached_A1,0xA1);                   /*-- 对从8259芯片设置 */
else
outb(cached_21,0x21);                   /*-- 对主8259芯片设置 */
spin_unlock_irqrestore(&i8259A_lock, flags);
}

(3)关于中断号的输出

8259在ISR里保存的只是irq的ID,但是它告诉CPU的是中断向量ID,比如ISR保存时钟中断的ID 
0,但是在通知CPU却是中断号0x20.因此需要建立一个映射。在8259芯片产生的IRQ号必须是连续的,也就是如果irq0对应的是中断向量0x20,那么irq1对应的就是0x21,...
在i8259.c/init_8259A()中,进行设置:
outb_p(0x11, 0x20); /* ICW1: select 8259A-1 init */
outb_p(0x20 + 0, 0x21); /* ICW2: 8259A-1 IR0-7 mapped to 0x20-0x27 */
outb_p(0x04, 0x21); /* 8259A-1 (the master) has a slave on IR2 */

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -