Linux0.11-内核中断体系

在哪里用到中断?

1.硬件的中断响应 ---> 内核驱动中的中断
2.系统调用的函数响应(sys call) --->系统调用
3.自定义中断 --->软件的软中断模式
4.信号中断 (kill -signalnum)
5.系统的异常和错误 ---> 系统异常处理获取 了解系统异常的作用

1. Linux的中断机制

1.1 分类:

硬件中断和软件中断
硬中断:由电脑主机的8259A类似的硬件中断控制芯片发出的中断
AR中断控制器发出的中断
软中断:异常第一类:CPU自行保留的中断
系统调用异常

1.2 代码结构:

asm.s -> trap.c
system_call.s -> fork.c signal.c exit.c sys.c

2. 中断的工作流程:

2.1 Linux 中中断的工作流程

1 将所有寄存器的值入栈(切换CPU模式)
SS EFLAGS(标志位寄存器) ESP CS EIP

2 将异常码入栈(中断号:哪个设备产生的中断,如果发生错误能找到谁发出的中断处理)
他这里异常码是中断号,中断处理过程中的c代码要靠这个号找到中断处理函数

3 将当前的函数返回值进行入栈(为了在中断执行后能够找到在哪里中断,能够复原)

4 调出 对应的中断服务函数

5 寄存器复原

在这里插入图片描述

 

                                    中断前的处理过程,中断的回复过程     中断服务程序的实现

硬件中断的处理过程                   asm.s                                    trap.c
软件及系统调用的处理过程 system call.s                             fork.c signal .c exit.c sys.c

3. 中断的代码实现过程

类似于函数调用过程 :从指令角度掌握函数调用堆栈详细过程_~怎么回事啊~的博客-CSDN博客_堆栈的操作过程

        EIP寄存器,用来存储CPU要读取指令的地址,CPU通过EIP寄存器读取即将要执行的指令。每次CPU执行完相应的汇编指令之后,EIP寄存器的值就会增加。

        CS(code segment)代码段地址寄存器,存放代码段的起始地址

        DS(data segment)数据段地址寄存器,存放数据段的起始地址

        SS(stack segment)堆栈段地址寄存器,存放堆栈段的起始地址(每个进程都有自己的栈空间)

        ES(extra segment)附加段地址寄存器,存放附加段的起始地址

   asm.s     

_divide_error:
	push dword ptr _do_divide_error ;// 首先把将要调用的函数地址入栈。这段程序的出错号为0。
no_error_code: ;// 这里是无出错号处理的入口处,见下面push 0 ;// "error code" ;// 将出错码入栈。
	xchg [esp],eax ;// _do_divide_error 的地址 -> eax中的值和esp中的值交换(esp现在是_do_divide_error 的地址),eax 被交换入栈。

     // 1 入栈寄存器
	push ebx  
	push ecx
	push edx
	push edi
	push esi
	push ebp
	push ds ;// !!16 位的段寄存器入栈后也要占用4 个字节。
	push es
	push fs

    // 2 入栈错误码
	push 0 ;// "error code" ;// 将出错码入栈。

    // 3 入栈原调用返回地址处堆栈指针位置
	lea edx,[esp+44] ;// 取原调用返回地址处堆栈指针位置,并压入堆栈。
	push edx
	mov edx,10h ;// 内核代码数据段选择符。
	mov ds,dx
	mov es,dx
	mov fs,dx

    //4 调用中断服务函数
	call eax ;// 调用C 函数do_divide_error()。
	add esp,8 ;// 让堆栈指针重新指向寄存器fs 入栈处。



    //5 出栈
	pop fs
	pop es
	pop ds
	pop ebp
	pop esi
	pop edi
	pop edx
	pop ecx
	pop ebx
	pop eax ;// 弹出原来eax 中的内容。
	iretd

        这就是asm.s里面的一串代码,也就是中断的处理过程,可以看到第三行是no_error_code,是无错误码的,在asm.s下面的代码还有一串error_code是有错误码的,而代码也正如上面说的,而这里因为没有错误码,压入的错误码是0,然后会调用中断服务函数,也就是它call eax;而在第四行代码可以看到,它其实是将eax和esp里的内容做了一个交换,所以它其实是调用了最开始压入的函数地址,也就是_do_divide_error函数。

no_error_code 代码在trap.c中:

//asm.s调用的第一个函数在这里
void do_divide_error(long esp, long error_code)
{
	die("divide error",esp,error_code);
}
//esp_ptr 段指针
//nr 出错的段号
//总的来说die函数就是用来打印错误信息
static void die(char * str,long esp_ptr,long nr)
{
	long * esp = (long *) esp_ptr;
	int i;
	//以下基本在打印栈信息
	printk("%s: %04x\n\r",str,nr&0xffff);
	printk("EIP:\t%04x:%p\nEFLAGS:\t%p\nESP:\t%04x:%p\n",
		esp[1],esp[0],esp[2],esp[4],esp[3]);
	printk("fs: %04x\n",_fs());
	printk("base: %p, limit: %p\n",get_base(current->ldt[1]),get_limit(0x17));
	if (esp[4] == 0x17) {
		printk("Stack: ");
		for (i=0;i<4;i++)
			printk("%p ",get_seg_long(0x17,i+(long *)esp[3]));
		printk("\n");
	}
	str(i);
	printk("Pid: %d, process nr: %d\n\r",current->pid,0xffff & i);
	for(i=0;i<10;i++)
		printk("%02x ",0xff & get_seg_byte(esp[1],(i+(char *)esp[0])));
	printk("\n\r");
	do_exit(11);//退出中断		/* play segment exception */
}

 

asm.s中下面还有其他的的异常处理:

// 都是先将异常处理函数  do_xxx 入栈,然后调用no_error_code 和上面的过程一致
_debug://这是一个中断
	pushl $_do_int3		# _do_debug
	jmp no_error_code//跳转

_nmi:
	pushl $_do_nmi
	jmp no_error_code

_int3:
	pushl $_do_int3
	jmp no_error_code

_overflow:
	pushl $_do_overflow
	jmp no_error_code

_bounds:
	pushl $_do_bounds
	jmp no_error_code

_invalid_op:
	pushl $_do_invalid_op
	jmp no_error_code

trap_init

//中断的初始化函数
//set_trap_gate 优先级较低 只能由用户程序来调用
//set_system_gate 优先级很高 能由系统和用户所有的程序调用
void trap_init(void)
{
	int i;

	set_trap_gate(0,&divide_error);//如果被除数是0就会产生这个中断
	set_trap_gate(1,&debug);//单步调试的时候调用这个中断
	set_trap_gate(2,&nmi);
	set_system_gate(3,&int3);	/* int3-5 can be called from all */
	set_system_gate(4,&overflow);
	set_system_gate(5,&bounds);
	set_trap_gate(6,&invalid_op);
	set_trap_gate(7,&device_not_available);
	set_trap_gate(8,&double_fault);
	set_trap_gate(9,&coprocessor_segment_overrun);
	set_trap_gate(10,&invalid_TSS);
	set_trap_gate(11,&segment_not_present);
	set_trap_gate(12,&stack_segment);
	set_trap_gate(13,&general_protection);
	set_trap_gate(14,&page_fault);
	set_trap_gate(15,&reserved);
	set_trap_gate(16,&coprocessor_error);
	for (i=17;i<48;i++)
		set_trap_gate(i,&reserved);
	set_trap_gate(45,&irq13);
	outb_p(inb_p(0x21)&0xfb,0x21);
	outb(inb_p(0xA1)&0xdf,0xA1);
	set_trap_gate(39,&parallel_interrupt);
}


//idt就是 Interrupt Descriptor Table 中断描述表,这里就是往表里填中断号和中断程序的地址
#define set_trap_gate(n,addr) \
	_set_gate(&idt[n],15,0,addr)


#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
	"movw %0,%%dx\n\t" \
	"movl %%eax,%1\n\t" \
	"movl %%edx,%2" \
	: \
	: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
	"o" (*((char *) (gate_addr))), \
	"o" (*(4+(char *) (gate_addr))), \
	"d" ((char *) (addr)),"a" (0x00080000))

4. 系统调用system_call

        系统调用表在sys_call_table中,所有的系统调用C函数放到了一个统一的sys_call_table
中,比如open、write等等

include\linux\sys.h

//定义系统调用的sys_call_table
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid,sys_setregid };

linux-自定义进程通信方式_~怎么回事啊~的博客-CSDN博客

有关于0.11 system_call的调用过程 

在kernel\system_call.s中

_system_call:
	cmpl $nr_system_calls-1,%eax
	ja bad_sys_call

    // 1 压栈相关寄存器
	push %ds
	push %es
	push %fs
	pushl %edx
	pushl %ecx		# push %ebx,%ecx,%edx as parameters
	pushl %ebx		# to the system call
	movl $0x10,%edx		# set up ds,es to kernel space
	mov %dx,%ds
	mov %dx,%es
	movl $0x17,%edx		# fs points to local data space
	mov %dx,%fs
    
    //调用 系统调用表 eax中存放系统调用号
	call _sys_call_table(,%eax,4)
	pushl %eax
	movl _current,%eax
	cmpl $0,state(%eax)		# state
	jne reschedule
	cmpl $0,counter(%eax)		# counter
	je reschedule

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值