中断——中断描述符表的定义和初始化(二) (基于3.16-rc4),符表3.16-rc4
中断——中断描述符表的定义和初始化(二) (基于3.16-rc4),符表3.16-rc4
上篇博文对中断描述符表(IDT)中异常和非屏蔽中断部分的初始化做了说明,这篇文章将分析中断部分的初始化。
在上篇博文中,可以看到,内核在setup_once汇编片段中,对中断和异常部分做了初步的初始化,用early_idt_handlers函数的地址来初始化异常门描述符,用ignore_int函数地址来初始化剩下的中断门描述符。接着,内核在trap_init函数中对IDT做了进一步的初始化,用有效的异常处理程序来初始化中断向量号为0-31的描述符。细心的你应该可以发现,在这一步初始化过程中,仅仅对异常和非屏蔽中断做了初始化(也就是中断向量号前32个),并没有对后256-32=224个中断门描述符初始化,也就是说后244个中断门描述符依然指向的是ignore_int这个无用的函数。下面将分析中断门描述符的最终初始化。首先介绍下interrupt全局数组,该数组中装有了所有的中断处理程序,如下所示:(arch/x86/kernel/entrt_32.S)
1 .section .init.rodata,"a" 2 ENTRY(interrupt) 3 .section .entry.text, "ax" 4 .p2align 5 5 .p2align CONFIG_X86_L1_CACHE_SHIFT 6 ENTRY(irq_entries_start) 7 RING0_INT_FRAME 8 vector=FIRST_EXTERNAL_VECTOR 9 .rept (NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7 10 .balign 32 11 .rept 7 12 .if vector < NR_VECTORS 13 .if vector <> FIRST_EXTERNAL_VECTOR 14 CFI_ADJUST_CFA_OFFSET -4 15 .endif 16 1: pushl_cfi $(~vector+0x80) /* Note: always in signed byte range */ 17 .if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6 18 jmp 2f 19 .endif 20 .previous 21 .long 1b 22 .section .entry.text, "ax" 23 vector=vector+1 24 .endif 25 .endr 26 2: jmp common_interrupt 27 .endr 28 END(irq_entries_start) 29 30 .previous 31 END(interrupt) 32 .previous
这段代码定义了一个interrupt全局数组,如果不懂ATT汇编的话,这段代码看起来非常吃力。下面笔者粗略分析下这段代码,第1行声明了一个数据段,第2行给这个数据段起了个名字,叫做‘inerrupt’,第3行又声明了一个代码段,该代码段被包在了前边的数据段当中,从第6行可看出这个代码段名字叫做‘irq_entries_start’。接着4-5行说明了代码段对齐的方式,接下来第7行给vector进行赋值,vector=32,实际上,interrup这个数组中存放的全是外部中断,没有异常,异常初始化已经在trap_init函数中完成了,而外部中断的向量号从32开始,所以vector赋值32。接下来在第9,11行大家可以看到出现了伪指令.rept,这个伪指令是循环的意思,你可以把它当成for循环去理解,指令后边的数字是循环次数。这个伪指令实际上告诉编译器要把后边的内容在内存中复制若干次。第9行的(NR_VECTORS-FIRST_EXTERNAL_VECTOR+6)/7值为32,要求其后的内容被循环复制32次。因此第9行和第11行合起来,就相当于一个双重for循环,总共循环32*7=224次,这刚好就是外部中断向量号的数量,每执行一次内部循环,就将一个外部中断处理程序放入了一个数组元素中。接着第20行出现了.previous伪指令。该指令的意思是返回到上一个段中,在这里就是要返回到interrupt数据段中,第21行,在interrupt数据段中定义了一个long型数据,值为标号1,标号1实际上就是第16行代码的地址。接着第22行又回到了当前代码段中,让vector自加1,然后第25行进入内重循环的下一次循环。第26行,标号2的这个指令夹在了内外两重循环之间,说明每执行7次内循环就要将jmp common_interrupt复制一次。然后第27行进入外重循环的下一次循环。总共执行32*7次循环后,这段代码就结束了。通过使用.preivous伪指令,最终实际上就定义了两个数组,一个是interrput数组,该数组的每个元素均为.long 1b(第21行),另外一个数组是irq_entries_start,该数组每个元素中放入了若干条汇编指令(16-18行,26行)。这就是.previous的用处,每在irq_entries_start数组中初始化完一个元素,立马返回到interrupt数组中定义一个指向irq_entries_start数组中刚初始化过的元素的指针(.long 1b),作为interrupt数组的元素。最终interrput数组中存放了224个指针(每个中断处理程序的地址),分别指向了irq_entries_start数组中的对应元素。irq_entries_start数组每个元素存放的是几条汇编指令(这些汇编指令就是中断处理程序的开头公共部分)。而且,通过第26行,可以看到,irq_entries_start数组每个元素都包含jmp common_interrupt指令,跳入到一段公共的代码中。
上边的工作,内核只是把所有的外部中断处理程序用两个数组管理起来了,接下来,就要在IDT(中断描述符表)中初始化所有外部中断的门描述符。代码如下:(arch/x86/kernel/irqinit.c)
1 void __init native_init_IRQ(void) 2 { 3 int i; 4 5 /* Execute any quirks before the call gates are initialised: */ 6 x86_init.irqs.pre_vector_init(); 7 8 apic_intr_init(); 9 10 /* 11 * Cover the whole vector space, no vector can escape 12 * us. (some of these will be overridden and become 13 * 'special' SMP interrupts) 14 */ 15 i = FIRST_EXTERNAL_VECTOR; 16 for_each_clear_bit_from(i, used_vectors, NR_VECTORS) { 17 /* IA32_SYSCALL_VECTOR could be used in trap_init already. */ 18 set_intr_gate(i, interrupt[i - FIRST_EXTERNAL_VECTOR]); 19 } 20 21 if (!acpi_ioapic && !of_ioapic) 22 setup_irq(2, &irq2); 23 24 #ifdef CONFIG_X86_32 25 irq_ctx_init(smp_processor_id()); 26 #endif 27 }
从16-19行,可以看出,用interrupt数组中存放的所有中断处理程序地址来初始化IDT的中断门描述符。set_intr_gate函数上篇博文已经分析过来,这里不再分析。
至此,所有IDT中的异常和中断门描述符就初始化完成了。
/*时间:2011年6月16日
*@作者:风之风信子
*@功能:定义一个3*4的数组,使用下标、地址和指针三种方式输出其中的元素。
*/
#include <iostream>
using namespace std;
/*为了你研究方便,我按照函数编写的。如果你愿意可以加到主函数里。*/
void output_name(int a[][4]);
void output_address(int a[][4]);
void output_pointer(int a[][4]);
void main()
{
int array[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
output_name(array);
output_address(array);
output_pointer(array);
}
void output_name(int a[][4])/*注意形参的形式。*/
{
int i,j;
cout<<"按照下标输出的结果是"<<endl;
for (i=0;i<3;i++)
{
for (j=0;j<4;j++)
{
cout<<a[i][j]<<" ";
}
}
cout<<" "<<endl;
}
void output_address(int a[][4])
{
int i,j;
cout<<"按照地址输出的结果是"<<endl;
for (i=0;i<3;i++)
{
for (j=0;j<4;j++)
{
cout<<*(*(a+i)+j)<<" ";/*仔细看好地址的格式,*(a+i)和a[i]是等价的,而a[i][j]和*(*(a+i)+j)是等价的,一定弄清楚!*/
}
}
cout<<" "<<endl;
}
void output_pointer(int a[][4])
{
int (*p)[4],i,j;
p=a;
cout<<"按照指针输出的结果是"<<endl;
for (i=0;i<3;i++)
{
for (j=0;j<4;j++)
{
cout<<*(*(p+i)+j)<<" ";/*仔细看好这里p的意义。*/
}
}
cout<<" "<<endl;
}...余下全文>>
使用Windows操作系统的人有时会遇到这样的错误信息:
「“0X????????”指令引用的“0x00000000”内存,该内存不能为“read”或“written”」,然后应用程序被关闭。
如果去请教一些「高手」,得到的回答往往是「Windows就是这样不稳定」之类的义愤和不屑。其实,这个错误并不一定是Windows不稳定造成的。本文就来简单分析这种错误的一般原因。
一、应用程序没有检查内存分配失败
程序需要一块内存用以储存数据时,就需要使用操作系统提供的「功能函数」来申请,如果内存分配成功,函数就会将所新开辟的内存区地址返回给应用程序,应用程序就可以通过这个地址使用这块内存。这就是「动态内存分配」,内存地址也就是编程中的「光标」。内存不是永远都招之即来、用之不尽的,有时候内存分配也会失败。当分配失败时系统函数会返回一个0值,这时返回值「0」已不表示新启用的游标,而是系统向应用程序发出的一个通知,告知出现了错误。作为应用程序,在每一次申请内存后都应该检查返回值是否为0,如果是,则意味着出现了故障,应该采取一些措施挽救,这就增强了程序的「健壮性」。若应用程序没有检查这个错误,它就会按照「思维惯性」认为这个值是给它分配的可用游标,继续在之后的执行中使用这块内存。真正的0地址内存区储存的是计算机系统中最重要的「中断描述符表」,绝对不允许应用程序使用。在没有保护机制的操作系统下(如DOS),写数据到这个地址会导致立即当机,而在健壮的操作系统中,如Windows等,这个操作会马上被系统的保护机制捕获,其结果就是由操作系统强行关闭出错的应用程序,以防止其错误扩大。这时候,就会出现上述的「写内存」错误,并指出被引用的内存地址为「0x00000000」。内存分配失败故障的原因很多,内存不够、系统函数的版本不匹配等都可能有影响。因此,这种分配失败多见于操作系统使用很长时间后,安装了多种应用程序(包括无意中「安装」的病毒程序),更改了大量的系统参数和系统档案之后。
二、应用程序由于自身BUG引用了不正常的内存光标
在使用动态分配的应用程序中,有时会有这样的情况出现:程序试突读写一块「应该可用」的内存,但不知为什么,这个预料中可用的光标已经失效了。有可能是「忘记了」向操作系统要求分配,也可能是程序自己在某个时候已经注销了这块内存而「没有留意」等等。注销了的内存被系统回收,其访问权已经不属于该应用程序,因此读写操作也同样会触发系统的保护机制,企图「违法」的程序唯一的下场就是被操作终止执行,回收全部资源。计算机世界的法律还是要比人类有效和严厉得多啊!像这样的情况都属于程序自身的BUG,你往往可在特定的操作顺序下重现错误。无效光标不一定总是0,因此错误提示中的内存地址也不一定为「0x00000000」,而是其它随机数字。如果系统经常有所提到的错误提示,下面的建议可能会有说明 :1.检视系统中是否有木马或病毒。这类程序为了控制系统往往不负责任地修改系统,
从而导致操作系统异常。平常应加强信息安全意识,对来源不明的可执行程序绝不好奇。
2.更新操作系统,让操作系统的安装程序重新拷贝正确版本的系统档案、修正系统参数。
有时候操作系统本身也会有BUG,要注意安装官方发行的升级程序。
3.试用新版本的应用程序。Mode:
将虚拟内存撤换
答案:
目前为止是肯定的,也就是如在下次冷天到来时亦没再发生,就代表这是主因
追加:
如果你用 Ghost 恢复 OS 后建议 删除WINDOWS\PREFETCH目录下所有*.PF文件因为需让windows重新收集程序的物理地址
有些应用程序错......余下全文>>
评论暂时关闭