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

📄 中断.txt

📁 讲解linux内核 系统中断 部分经典讲义
💻 TXT
📖 第 1 页 / 共 5 页
字号:
        "call "SYMBOL_NAME_STR(do_IRQ)"\n\t" \
        "addl $8,%esp\n\t" \
        "cli\n\t" \
        UNBLK_##chip(mask) \
        "decl "SYMBOL_NAME_STR(intr_count)"\n\t" \
        "incl "SYMBOL_NAME_STR(syscall_count)"\n\t" \
        "jmp ret_from_sys_call\n");
    其中nr为中断请求类型,取值0~15。在irq.c中通过语句BUILD_TIMER_IRQ(first, 0, 
0x01)调用该宏,在执行宏的过程中处理时钟中断响应程序do_irq()。
    
函数do_irq()的第一个参数是中断请求队列序号,时钟中断请求传进来的该参数是0。于是程序根据参数0找到请求队列irq_action[0],逐个处理该队列上handler所指的时钟中断请求的服务函数。由于已经指定时钟中断请求的服务函数是timer_interrupt,在函数timer_interrupt中,立即调用do_timer()函数。
    
函数do_timer()把jiffies和lost_ticks加1,接着就执行mark_bh(TIMER_BH)函数,把bottom_half中时钟队列对应的位置位,表示该队列处于激活状态。在做完这些动作后,程序从函数do_irq()中返回,继续执行以后的汇编代码。于是,程序在执行语句jmp 
ret_from_sys_call后,跳到指定的位置处继续执行。
代码段jmp ret_from_sys_call及其相关的代码段如下:
        ALIGN
        .globl ret_from_sys_call
ret_from_sys_call:
        cmpl $0,SYMBOL_NAME(intr_count)
        jne 2f
9:        movl SYMBOL_NAME(bh_mask),%eax
        andl SYMBOL_NAME(bh_active),%eax
        jne handle_bottom_half
#ifdef __SMP__
        cmpb $(NO_PROC_ID), SYMBOL_NAME(saved_active_kernel_processor)
        jne 2f
#endif
        movl EFLAGS(%esp),%eax                # check VM86 flag: CS/SS are
        testl $(VM_MASK),%eax                # different then
        jne 1f
        cmpw $(KERNEL_CS),CS(%esp)        # was old code segment supervisor ?
        je 2f
1:        sti
        orl $(IF_MASK),%eax                # these just try to make sure
        andl $~NT_MASK,%eax                # the program doesn't do anything
        movl %eax,EFLAGS(%esp)                # stupid
        cmpl $0,SYMBOL_NAME(need_resched)
        jne reschedule
#ifdef __SMP__
        GET_PROCESSOR_OFFSET(%eax)
        movl SYMBOL_NAME(current_set)(,%eax), %eax
#else
        movl SYMBOL_NAME(current_set),%eax
#endif
        cmpl SYMBOL_NAME(task),%eax        # task[0] cannot have signals
        je 2f
        movl blocked(%eax),%ecx
        movl %ecx,%ebx                        # save blocked in %ebx for signal 
handling
        notl %ecx
        andl signal(%eax),%ecx
        jne signal_return
2:        RESTORE_ALL
ALIGN
signal_return:
        movl %esp,%ecx
        pushl %ecx
        testl $(VM_MASK),EFLAGS(%ecx)
        jne v86_signal_return
        pushl %ebx
        call SYMBOL_NAME(do_signal)
        popl %ebx
        popl %ebx
        RESTORE_ALL
ALIGN
v86_signal_return:
        call SYMBOL_NAME(save_v86_state)
        movl %eax,%esp
        pushl %eax
        pushl %ebx
        call SYMBOL_NAME(do_signal)
        popl %ebx
        popl %ebx
        RESTORE_ALL
  handle_bottom_half:
incl SYMBOL_NAME(intr_count)
call SYMBOL_NAME(do_bottom_half)
decl SYMBOL_NAME(intr_count)
jmp 9f
ALIGN
reschedule:
pushl $ret_from_sys_call
  jmp SYMBOL_NAME(schedule)    # test
另外,一些与时钟中断及bottom half机制有关的数据结构介绍如下:
#define        HZ        100
unsigned long volatile jiffies=0;
系统每隔10ms自动把它加1,它是核心系统计时的单位。
enum {
        TIMER_BH = 0,
        CONSOLE_BH,
        TQUEUE_BH,
        DIGI_BH,
        SERIAL_BH,
        RISCOM8_BH,
SPECIALIX_BH,
        BAYCOM_BH,
        NET_BH,
        IMMEDIATE_BH,
        KEYBOARD_BH,
        CYCLADES_BH,
        CM206_BH
};
现在只定义了13个bottom half队列,将来可扩充到32个队列。
unsigned long intr_count = 0;
相当于信号量的作用。只有其等于0,才可以do_bottom_half。
int bh_mask_count[32];
用来计算bottom half队列被屏蔽的次数。只有某队列的bh_mask_count数为0,才能enable该队列。
unsigned long bh_active = 0;
bh_active是32位长整数,每一位表示一个bottom 
half队列,该位置1,表示该队列处于激活状态,随时准备在CPU认为合适的时候执行该队列的服务,置0则相反。
unsigned long bh_mask = 0;
bh_mask也是32位长整数,每一位对应一个bottom half队列,该位置1,表示该队列可用,并把处理函数的入口地址赋给bh_base,置0则相反。
void (*bh_base[32])(void);
bottom half服务函数入口地址数组。定时器处理函数拥有最高的优先级,它的地址存放在bh_base[0],总是最先执行它所指向的函数。
我们注意到,在IRQ#_interrupt和fast_IRQ#_interrupt中断函数处理返回前,都通过语句jmp 
ret_from_sys_call,跳到系统调用的返回处(见irq.h),如果bottom half队列不为空,则在那里做类似:
           if (bh_active & bh_mask) {
                            intr_count = 1;
                            do_bottom_half();
                            intr_count = 0;
                    }(该判断的汇编代码见Entry.S)
的判断,调用do_bottom_half()函数。
在CPU调度时,通过schedule函数执行上述的判断,再调用do_bottom_half()函数。
总而言之,在下列三种时机:
CPU调度时
系统调用返回前
中断处理返回前
都会作判断调用do_bottom_half函数。Do_bottom_half函数依次扫描32个队列,找出需要服务的队列,执行服务后把对应该队列的bh_active的相应位置0。由于bh_active标志中TIMER_BH对应的bit为1,因而系统根据服务函数入口地址数组bh_base找到函数timer_bh()的入口地址,并马上执行该函数,在函数timer_bh中,调用函数run_timer_list()和函数run_old_timers()函数,定时执行服务。
TVECS结构及其实现
有关TVECS结构的一些数据结构定义如下:
#define TVN_BITS 6
#define TVR_BITS 8
#define TVN_SIZE (1 << TVN_BITS)
#define TVR_SIZE (1 << TVR_BITS)
#define TVN_MASK (TVN_SIZE - 1)
#define TVR_MASK (TVR_SIZE - 1)
#define SLOW_BUT_DEBUGGING_TIMERS 0
struct timer_vec {
        int index;
        struct timer_list *vec[TVN_SIZE];
};
struct timer_vec_root {
        int index;
        struct timer_list *vec[TVR_SIZE];
};
static struct timer_vec tv5 = { 0 };
static struct timer_vec tv4 = { 0 };
static struct timer_vec tv3 = { 0 };
static struct timer_vec tv2 = { 0 };
static struct timer_vec_root tv1 = { 0 };
static struct timer_vec * const tvecs[] = {
        (struct timer_vec *)&tv1, &tv2, &tv3, &tv4, &tv5
};
#define NOOF_TVECS (sizeof(tvecs) / sizeof(tvecs[0]))
static unsigned long timer_jiffies = 0;
TVECS结构是一个元素个数为5的数组,分别指向tv1,tv2,tv3,tv4,tv5的地址。其中,tv1是结构timer_vec_root的变量,它有一个index域和有256个元素的指针数组,该数组的每个元素都是一条类型为timer_list的链表。其余四个元素都是结构timer_vec的变量,它们各有一个index域和64个元素的指针数组,这些数组的每个元素也都是一条链表。
函数internal_add_timer(struct timer_list *timer)
函数代码如下:
static inline void internal_add_timer(struct timer_list *timer)
{
        /*
        * must be cli-ed when calling this
        */
        unsigned long expires = timer->expires;
        unsigned long idx = expires - timer_jiffies;
        if (idx < TVR_SIZE) {
                int i = expires & TVR_MASK;
                insert_timer(timer, tv1.vec, i);
        } else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
                int i = (expires >> TVR_BITS) & TVN_MASK;
                insert_timer(timer, tv2.vec, i);
        } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
                int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
                insert_timer(timer, tv3.vec, i);
        } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
                int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
                insert_timer(timer, tv4.vec, i);
        } else if (expires < timer_jiffies) {
                /* can happen if you add a timer with expires == jiffies,
                * or you set a timer to go off in the past
                */
                insert_timer(timer, tv1.vec, tv1.index);
        } else if (idx < 0xffffffffUL) {
                int i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
                insert_timer(timer, tv5.vec, i);
        } else {
                /* Can only get here on architectures with 64-bit jiffies */
                timer->next = timer->prev = timer;
        }
}
   expires

在调用该函数之前,必须关中。对该函数的说明如下:
取出要加进TVECS的timer的激发时间(expires),算出expires与timer_jiffies的差值idx,用来决定该插到哪个队列中去。
若idx小于2^8,则取expires的第0位到第7位的值I,把timer加到tv1.vec中第I个链表的第一个表项之前。
若idx小于2^14,则取expires的第8位到第13位的值I,把timer加到tv2.vec中第I个链表的第一个表项之前。
若idx小于2^20,则取expires的第14位到第19位的值I,把timer加到tv3.vec中第I个链表的第一个表项之前。
若idx小于2^26,则取expires的第20位到第25位的值I,把timer加到tv4.vec中第I个链表的第一个表项之前。
若expires小于timer_jiffies,即idx小于0,则表明该timer到期,应该把timer放入tv1.vec中tv1.index指定的链表的第一个表项之前。
若idx小于2^32,则取expires的第26位到第32位的值I,把timer加到tv5.vec中第I个链表的第一个表项之前。
若idx大等于2^32,该情况只有在64位的机器上才有可能发生,在这种情况下,不把timer加入TVECS结构。
函数cascade_timers(struct timer_vec *tv)
该函数只是把tv->index指定的那条链表上的所有timer调用internal_add_timer()函数进行重新调整,这些timer将放入TVECS结构中比原来位置往前移一级,比如说,tv4上的timer将放到tv3上去,tv2上的timer将放到tv1上。这种前移是由run_timer_list函数里调用cascade_timers函数的时机来保证的。然后把该条链表置空,tv->index加1,若tv->index等于64,则重新置为0。
函数run_timer_list()
函数代码如下:
static inline void run_timer_list(void)
{
cli();

⌨️ 快捷键说明

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