精华内容
下载资源
问答
  • }由于对被中断系统调用处理方式的差异性,因此对应用程序来说,与被中断的系统调用相关的问题是:应用程序无法保证总是知道信号处理函数的注册方式,以及是否设置了SA_RESTART标志可移植的代码必须显式处理关键函数...

    favicon.ico摘要:

    amp;(errno==EINTR)){printf("readreturnfailed,errnoisEINTR

    ");}return0;}由于对被中断系统调用处理方式的差异性,因此对应用程序来说,与被中断的系统调用相关的问题是:应用程序无法保证总是知道信号处理函数的注册方式,以及是否设置了SA_RESTART标志可移植的代码必须显式处理关键函数的出错返回,当函数出错且err

    慢系统调用,指的是可能永远无法返回,从而使进程永远阻塞的系统调用,比如无客户连接时的accept、无输入时的read都属于慢速系统调用。

    在Linux中,当阻塞于某个慢系统调用的进程捕获一个信号,则该系统调用就会被中断,转而执行信号处理函数,这就是被中断的系统调用。

    然而,当信号处理函数返回时,有可能发生以下的情况:

    com.alibabadruid-spring-boot-star

    如果信号处理函数是用signal注册的,系统调用会自动重启,函数不会返回

    如果信号处理函数是用sigaction注册的

    默认情况下,系统调用不会自动重启,函数将返回失败,同时errno被置为EINTR

    只有中断信号的SA_RESTART标志有效时,系统调用才会自动重启

    下面我们编写代码,分别验证上述几种情形,其中系统调用选择read,中断信号选择SIGALRM,中断信号由alarm产生。

    urn;}intmain(){charbuf[10];intnread=0;structsigactionact;sigemptyset(&act.sa_mask);act.sa_handle

    使用signal

    datasource.master.type=com.alibaba.druid.pool.DruidDataSourcespring.shardingsphere.datasource.master

    #include

    #include

    #include

    #include

    void handler(int s)

    {

    printf("read is interrupt by signal handler

    ");

    return;

    }

    int main()

    {

    char buf[10];

    int nread = 0;

    signal(SIGALRM, handler);

    alarm(2);

    printf("read start

    ");

    nread = read(STDIN_FILENO, buf, sizeof(buf));

    printf("read return

    ");

    if ((nread < 0) && (errno == EINTR))

    {

    printf("read return failed, errno is EINTR

    ");

    }

    return 0;

    }

    638eea30-5962-4535-8d35-62df5359c7a4.jpg

    使用sigaction + 默认情况

    ding=utf-8spring.shardingsphere.datasource.master.username=rootspring.shardingsphere.datasource.mast

    #include

    #include

    #include

    #include

    void handler(int s)

    {

    printf("read is interrupt by signal handler

    ");

    return;

    }

    int main()

    {

    char buf[10];

    int nread = 0;

    struct sigaction act;

    sigemptyset(&act.sa_mask);

    act.sa_handler = handler;

    act.sa_flags = 0; //不给SIGALRM信号设置SA_RESTART标志,使用sigaction的默认处理方式

    //act.sa_flag |= SA_INTERRUPT; //SA_INTERRUPT是sigaction的默认处理方式,即不自动重启被中断的系统调用

    //实际上,不管act.sa_flags值为多少,只要不设置SA_RESTART,sigaction都是按SA_INTERRUPT处理的

    sigaction(SIGALRM, &act, NULL);

    alarm(2);

    printf("read start

    ");

    nread = read(STDIN_FILENO, buf, sizeof(buf));

    printf("read return

    ");

    if ((nread < 0) && (errno == EINTR))

    {

    printf("read return failed, errno is EINTR

    ");

    }

    return 0;

    }

    c5fd05c9-3246-4657-abd3-bfccf506ee22.jpg

    使用sigaction + 指定SA_RESTART标志

    igaction+指定SA_RESTART标志#include#include#include#inclu

    #include

    #include

    #include

    #include

    void handler(int s)

    {

    printf("read is interrupt by signal handler

    ");

    return;

    }

    int main()

    {

    char buf[10];

    int nread = 0;

    struct sigaction act;

    sigemptyset(&act.sa_mask);

    act.sa_handler = handler;

    act.sa_flags = 0;

    act.sa_flags |= SA_RESTART; //给SIGALRM信号设置SA_RESTART标志

    sigaction(SIGALRM, &act, NULL);

    alarm(2);

    printf("read start

    ");

    nread = read(STDIN_FILENO, buf, sizeof(buf));

    printf("read return

    ");

    if ((nread < 0) && (errno == EINTR))

    {

    printf("read return failed, errno is EINTR

    ");

    }

    return 0;

    }

    fc66a3f1-84ff-4d3c-96d9-76581620082f.jpg

    由于对被中断系统调用处理方式的差异性,因此对应用程序来说,与被中断的系统调用相关的问题是:

    -8<

    应用程序无法保证总是知道信号处理函数的注册方式,以及是否设置了SA_RESTART标志

    可移植的代码必须显式处理关键函数的出错返回,当函数出错且errno等于EINTR时,可以根据实际需求进行相应处理,比如重启该函数

    int nread = read(fd, buf, 1024);

    if (nread < 0)

    {

    if (errno == EINTR)

    {

    //read被中断,其实不应该算作失败,可以根据实际需求进行处理,比如重写调用read,也可以忽略它

    }

    else

    {

    //read真正的读错误

    }

    }

    展开全文
  • 中断系统调用

    2020-12-26 18:57:07
    为什么要采用中断系统。 维持系统可靠正常工作。1)程序员不能直接干预操纵机器,通过操作系统来实现。2)程序运行过程中,如果发生越界访问,应该由存储管理部件进行监测,一旦发生应向处理机发出中断请求,采取...

    中断

    为什么要采用中断系统。

    1. 维持系统可靠正常工作。1)程序员不能直接干预和操纵机器,通过操作系统来实现。2)程序运行过程中,如果发生越界访问,应该由存储管理部件进行监测,一旦发生应向处理机发出中断请求,采取保护措施。
    2. 满足实时处理要求。
    3. 提供故障线程处理手段。一旦故障或错误应立即发出中断请求,进行故障现场记录和隔离。

    中断的分类

    硬中断

    硬中断是由硬件产生的,比如磁盘、网卡、键盘、时钟等。每一个设备都有相关的驱动程序,如果设备使用中断,相应的驱动程序就注册一个中断处理程序。
    CPU本身有两条中断请求线:非屏蔽中断和可屏蔽中断线。当这两条线上收到中断请求信号而引起的中断则成为硬中断。

    展开全文
  • 中断异常和系统调用一、门二、中断中断向量表的初始化trap_initinit_IRQ中断请求队列的初始化中断的响应服务三、软中断与bottom half四、异常五、系统调用 中断分为外部中断和中断,外部中断由硬件产生,软件...

    中断分为外部中断和软中断,外部中断由硬件产生,软件无法预知外部中断发生的时机,软中断(陷阱trap)由软件触发,系统调用就是通过软中断的一种(INT 0x80)来实现的,异常通常是因为发生了类似于0作除数或者缺页异常这样的错误引发,异常的种类有cpu规定。

    x86 cpu支持256个不同的中断向量,在实模式中,内存开头的1K字节作为中断向量表,每个向量占4个字节,两个字节段地址两个字节的位移,这样就能组成相应服务程序的入口地址。

    保护模式下,为了实现模式切换和优先级的判断,中断向量表中的内容被扩展为门的概念,意思是当中断或者异常发生时,要首先穿过这道门才能到达相应服务程序。

    一、门

    x86 cpu一共有四种门:任务门(task gate)、中断门(interrupt gate)、陷阱门(trap gate)、调用门(call gate)。其中调用门不跟中断向量表关联。

    门的大小为64位,四种门的结构大同小异:

            16b       1b  2b  1b 1b   3b         8b         16b          16b
    +-----------------+-+-----+-+-+---------+----------+------------+-----------+
    |    位移的高16位   |P| DPL |0|D|  类型码  |   空闲   |   段选择码   | 位移低16位 |
    +-----------------+-+-----+-+-+---------+----------+------------+-----------+
    

    任务门只负责切换任务,不需要段内偏移,所以任务门的最高16位和最低16位空闲不用,对于其他三种门,这两部分共同指示了服务程序的入口地址。

    P标志位表示在内存

    DPL是优先级

    D标志位 1=32位,0=16位

    类型码:110中断门, 111陷阱门,100调用门,101任务门

    另外,通过中断门进入中断服务程序时,cpu会自动将中断关闭,也就是将EFLAGS中的IF标志位清0,防止中断的嵌套,而通过陷阱门进入服务程序则不会改变IF标志位。

    如果中断是由外部产生,或者cpu产生,则DPL优先级的检查被免除,可以直接进门。
    否则,要先用cpu的cpl与这个门的dpl相比较,cpl必须小于门的dpl才能过,也就是优先级要比门高才能越过这个门槛,进门以后有需要比较目标段的dpl与cpl进行比较,目标段dpl必须小于cpl,也就是说通过门进行的任务切换只能提高运行等级,不能降低等级。

    中断服务流程
    
    中断向量V ---->向量表IDT---->中断门--(段选择码)-->段描述表GDT
                             |段                  |段
                             |内                  |基
                             |偏                  |地
                             |移                  |址
                             |------------------->+------------>服务程序
    

    二、中断

    中断向量表的初始化

    linux内核在初始化阶段完成了对页面虚拟管理的初始化以后,便调用了trap_init和init_IRQ两个函数进行了中断机制的初始化。

    trap_init主要针对一些系统保留的中断向量的初始化,而init_IRQ则是用于外设的中断。

    trap_init

    void __init trap_init(void)
    {
    #ifdef CONFIG_EISA
    	if (isa_readl(0x0FFFD9) == 'E'+('I'<<8)+('S'<<16)+('A'<<24))
    		EISA_bus = 1;
    #endif
    
    	set_trap_gate(0,&divide_error);
    	set_trap_gate(1,&debug);
    	set_intr_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,&spurious_interrupt_bug);
    	set_trap_gate(16,&coprocessor_error);
    	set_trap_gate(17,&alignment_check);
    	set_trap_gate(18,&machine_check);
    	set_trap_gate(19,&simd_coprocessor_error);
    
    	set_system_gate(SYSCALL_VECTOR,&system_call);
    
    	/*
    	 * default LDT is a single-entry callgate to lcall7 for iBCS
    	 * and a callgate to lcall27 for Solaris/x86 binaries
    	 */
    	set_call_gate(&default_ldt[0],lcall7);
    	set_call_gate(&default_ldt[4],lcall27);
    
    	/*
    	 * Should be a barrier for any external CPU state.
    	 */
    	cpu_init();
    
    #ifdef CONFIG_X86_VISWS_APIC
    	superio_init();
    	lithium_init();
    	cobalt_init();
    #endif
    }
    

    程序设置了中断向量表的前19个cpu预留的异常表项,然后是系统调用向量,SYSCALL_VECTOR在include/asm-i386/hw_irq.h中定义为0x80

    #define SYSCALL_VECTOR		0x80
    

    可以看到,向量表项的初始化使用了四个函数set_trap_gate、set_intr_gate、set_system_gate、以及set_call_gate,其中调用门linux本身并不使用,只是为了兼容在支持调用门的系统上编译的应用程序而设置了两个调用门。

    void set_intr_gate(unsigned int n, void *addr)
    {
    	_set_gate(idt_table+n,14,0,addr);
    }
    
    static void __init set_trap_gate(unsigned int n, void *addr)
    {
    	_set_gate(idt_table+n,15,0,addr);
    }
    
    static void __init set_system_gate(unsigned int n, void *addr)
    {
    	_set_gate(idt_table+n,15,3,addr);
    }
    

    这些函数统统调用_set_gate实现,只是第二三个参数不同,这两个参数分别对应了中断门中的D标志位加类型码type和优先级dpl。

    参数 |  D   |    类型码   | 
    14  |   1   |    110    | intr
    15  |   1   |    111    | trap
    

    中断的dpl一律设置为最高等级0,因为当中断是由外部或cpu异常产生的,中断门的dpl被忽略不计可以自然过门,而用户程序尝试使用INT 2这样的指令来进入不可屏蔽中断的服务程序时,由于用户空间的运行级别为3,由用户软件产生的中断就会被拒之门外。

    系统调用的dpl设置为3是为了用户空间能够使用系统调用进入系统空间。

    #define _set_gate(gate_addr,type,dpl,addr) \
    do { \
      int __d0, __d1; \
      __asm__ __volatile__ ("movw %%dx,%%ax\n\t" \
    	"movw %4,%%dx\n\t" \
    	"movl %%eax,%0\n\t" \
    	"movl %%edx,%1" \
    	:"=m" (*((long *) (gate_addr))), \
    	 "=m" (*(1+(long *) (gate_addr))), "=&a" (__d0), "=&d" (__d1) \
    	:"i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
    	 "3" ((char *) (addr)),"2" (__KERNEL_CS << 16)); \
    } while (0)
    

    _set_gate将addr的高16位放在门的最高16位(48-63位),低16位放在门的最低16位(0-15位)
    将代码段地址放在了段选择码的位置(16-31位)
    P标志位+dpl+D标志位+类型码放在(40-47位)刚好填充了中断门的所有内容。

    init_IRQ

    void __init init_IRQ(void)
    {
    	int i;
    
    #ifndef CONFIG_X86_VISWS_APIC
    	init_ISA_irqs();
    #else
    	init_VISWS_APIC_irqs();
    #endif
    	/*
    	 * Cover the whole vector space, no vector can escape
    	 * us. (some of these will be overridden and become
    	 * 'special' SMP interrupts)
    	 */
    	for (i = 0; i < NR_IRQS; i++) {
    		int vector = FIRST_EXTERNAL_VECTOR + i;
    		if (vector != SYSCALL_VECTOR) 
    			set_intr_gate(vector, interrupt[i]);
    	}
    
    #ifdef CONFIG_SMP
    	...
    #endif	
    
    #ifdef CONFIG_X86_LOCAL_APIC
    ...
    #endif
    
    	/*
    	 * Set the clock to HZ Hz, we already have a valid
    	 * vector now:
    	 */
    	outb_p(0x34,0x43);		/* binary, mode 2, LSB/MSB, ch 0 */
    	outb_p(LATCH & 0xff , 0x40);	/* LSB */
    	outb(LATCH >> 8 , 0x40);	/* MSB */
    
    #ifndef CONFIG_VISWS
    	setup_irq(2, &irq2);
    #endif
    
    	/*
    	 * External FPU? Set up irq13 if so, for
    	 * original braindamaged IBM FERR coupling.
    	 */
    	if (boot_cpu_data.hard_math && !cpu_has_fpu)
    		setup_irq(13, &irq13);
    }
    

    init_IRQ的主要内容就是一个循环设置了所有外部中断的中断向量表项,还有对系统时钟的初始化(outb那三行),这时候中断向量中并没有实际的服务程序,所以即使打开并产生了时钟中断,也只是在common_interrupt中空跑一趟。

    我们看到循环中从FIRST_EXTERNAL_VECTOR开始初始化除SYSCALL_VECTOR以外的NR_IRQS-1个外部中断,并且将他们的服务程序注册成interrupt[i]。

    /*
     * 16 8259A IRQ's, 208 potential APIC interrupt sources.
     * Right now the APIC is mostly only used for SMP.
     * 256 vectors is an architectural limit. (we can have
     * more than 256 devices theoretically, but they will
     * have to use shared interrupts)
     * Since vectors 0x00-0x1f are used/reserved for the CPU,
     * the usable vector space is 0x20-0xff (224 vectors)
     */
    #define NR_IRQS 224
    
    /*
     * IDT vectors usable for external interrupt sources start
     * at 0x20:
     */
    #define FIRST_EXTERNAL_VECTOR	0x20
    
    #define SYSCALL_VECTOR		0x80
    

    i386支持256个中断向量,0x20以前的向量有cpu预留使用,所以外部中断使用0x20-0xff的224个中断向量,还有0x80作为系统调用专用的中断号自然要去掉。

    interrupt[i]是一组函数指针,定义在arch/i386/kernel/i8259.c

    #define IRQ(x,y) \
    	IRQ##x##y##_interrupt
    
    #define IRQLIST_16(x) \
    	IRQ(x,0), IRQ(x,1), IRQ(x,2), IRQ(x,3), \
    	IRQ(x,4), IRQ(x,5), IRQ(x,6), IRQ(x,7), \
    	IRQ(x,8), IRQ(x,9), IRQ(x,a), IRQ(x,b), \
    	IRQ(x,c), IRQ(x,d), IRQ(x,e), IRQ(x,f)
    
    void (*interrupt[NR_IRQS])(void) = {
    	IRQLIST_16(0x0),
    
    #ifdef CONFIG_X86_IO_APIC
    			 IRQLIST_16(0x1), IRQLIST_16(0x2), IRQLIST_16(0x3),
    	IRQLIST_16(0x4), IRQLIST_16(0x5), IRQLIST_16(0x6), IRQLIST_16(0x7),
    	IRQLIST_16(0x8), IRQLIST_16(0x9), IRQLIST_16(0xa), IRQLIST_16(0xb),
    	IRQLIST_16(0xc), IRQLIST_16(0xd)
    #endif
    };
    

    最终,interrupt[i]被展开为IRQ0x00_interrupt~IRQ0x0f_interrupt等函数指针,而这些函数指针同样定义在这个文件

    #define BI(x,y) \
    	BUILD_IRQ(x##y)
    
    #define BUILD_16_IRQS(x) \
    	BI(x,0) BI(x,1) BI(x,2) BI(x,3) \
    	BI(x,4) BI(x,5) BI(x,6) BI(x,7) \
    	BI(x,8) BI(x,9) BI(x,a) BI(x,b) \
    	BI(x,c) BI(x,d) BI(x,e) BI(x,f)
    
    /*
     * ISA PIC or low IO-APIC triggered (INTA-cycle or APIC) interrupts:
     * (these are usually mapped to vectors 0x20-0x2f)
     */
    BUILD_16_IRQS(0x0)
    

    BUILD_IRQ()定义在include/asm-i386/hw_irq.h,是一段汇编代码

    #define BUILD_IRQ(nr) \
    asmlinkage void IRQ_NAME(nr); \
    __asm__( \
    "\n"__ALIGN_STR"\n" \
    SYMBOL_NAME_STR(IRQ) #nr "_interrupt:\n\t" \
    	"pushl $"#nr"-256\n\t" \
    	"jmp common_interrupt");
    

    nr的内容位0x00~0x0f,这段汇编翻译一下就是IRQ0x00_interrupt到IRQ0x0f_interrupt这16个标签都先将一个nr-256的数字压栈然后跳转到common_interrupt执行,这个地方之所以要减去一个256是为了区分系统调用的调用号,系统调用发生时,堆栈中的这个位置存放对应的系统调用号,而系统调用与中断共用与中断服务会共用一部分子程序,所以需要进行区分。

    common_interrupt由arch/i386/kernel/i8259.c中的BUILD_COMMON_IRQ()定义,最终实现在include/asm-i386/hw_irq.h

    #define BUILD_COMMON_IRQ() \
    asmlinkage void call_do_IRQ(void); \
    __asm__( \
    	"\n" __ALIGN_STR"\n" \
    	"common_interrupt:\n\t" \
    	SAVE_ALL \
    	"pushl $ret_from_intr\n\t" \
    	SYMBOL_NAME_STR(call_do_IRQ)":\n\t" \
    	"jmp "SYMBOL_NAME_STR(do_IRQ));
    

    common_interrupt重要的操作是一个SAVE_ALL宏定义,用作我们提到中断时常说的保护现场,其次就是将一个ret_from_intr的标签地址压栈,这样就能使得下面即将跳转执行的do_IRQ返回时,直接返回到ret_from_intr处继续执行。

    首先SAVE_ALL把中断发生前所有寄存器的内容都保存到栈中,然后将段寄存器DS和ES用于指向内核的__KERNEL_DS。

    #define SAVE_ALL \
    	"cld\n\t" \
    	"pushl %es\n\t" \
    	"pushl %ds\n\t" \
    	"pushl %eax\n\t" \
    	"pushl %ebp\n\t" \
    	"pushl %edi\n\t" \
    	"pushl %esi\n\t" \
    	"pushl %edx\n\t" \
    	"pushl %ecx\n\t" \
    	"pushl %ebx\n\t" \
    	"movl $" STR(__KERNEL_DS) ",%edx\n\t" \
    	"movl %edx,%ds\n\t" \
    	"movl %edx,%es\n\t"
    

    如此,当前堆栈中的内容如下:

    +---------------------+
    |      用户堆栈的SS     |----------
    +---------------------+         |--->用户堆栈指针
    |      用户堆栈的SP     |----------
    +---------------------+
    |        EFLAGS       |
    +---------------------+
    |      用户空间的cs     |-------
    +---------------------+      |---->返回地址
    |         EIP         |-------
    +---------------------+
    |      中断号-256      |
    +---------------------+
    |          ES         |
    +---------------------+
    |          DS         |
    +---------------------+
    |         EAX         |
    +---------------------+
    |         EBP         |
    +---------------------+
    |         EDI         |
    +---------------------+
    |         ESI         |
    +---------------------+
    |         EDX         |
    +---------------------+
    |         ECX         |
    +---------------------+
    |         EBX         |
    +---------------------+<-----------系统堆栈指针
    

    然后jmp到do_IRQ,该函数定义在arch/i386/kernel/irq.c中

    asmlinkage unsigned int do_IRQ(struct pt_regs regs)
    {	
    ...
    }
    

    首先参数,struct pt_regs regs:

    /* this struct defines the way the registers are stored on the 
       stack during a system call. */
    
    struct pt_regs {
    	long ebx;
    	long ecx;
    	long edx;
    	long esi;
    	long edi;
    	long ebp;
    	long eax;
    	int  xds;
    	int  xes;
    	long orig_eax;
    	long eip;
    	int  xcs;
    	long eflags;
    	long esp;
    	int  xss;
    };
    

    所以我们之前主动将所有寄存器压栈的原因就是为了向do_IRQ传参,从ret_from_intr入栈开始,我们完整的模拟了一次c语言函数调用栈。do_IRQ的内容主要是找到并调用链接在中断请求队列中的服务程序,这部分内容在中断的响应和服务中细看。

    中断请求队列的初始化

    在中断向量表的初始化函数init_IRQ中,第一件事情起始是在init_ISA_irqs中初始化irq_desc数组:

    void __init init_IRQ(void)
    {
    	int i;
    
    #ifndef CONFIG_X86_VISWS_APIC
    	init_ISA_irqs();
    #else
    	init_VISWS_APIC_irqs();
    #endif
    
    void __init init_ISA_irqs (void)
    {
    	int i;
    
    	init_8259A(0);
    
    	for (i = 0; i < NR_IRQS; i++) {
    		irq_desc[i].status = IRQ_DISABLED;
    		irq_desc[i].action = 0;
    		irq_desc[i].depth = 1;
    
    		if (i < 16) {
    			/*
    			 * 16 old-style INTA-cycle interrupts:
    			 */
    			irq_desc[i].handler = &i8259A_irq_type;
    		} else {
    			/*
    			 * 'high' PCI IRQs filled in on demand
    			 */
    			irq_desc[i].handler = &no_irq_type;
    		}
    	}
    }
    

    首先初始化8259中断控制器,然后初始化irq_desc结构数组:

    /*
     * Interrupt controller descriptor. This is all we need
     * to describe about the low-level hardware. 
     */
    struct hw_interrupt_type {
    	const char * typename;
    	unsigned int (*startup)(unsigned int irq);
    	void (*shutdown)(unsigned int irq);
    	void (*enable)(unsigned int irq);
    	void (*disable)(unsigned int irq);
    	void (*ack)(unsigned int irq);
    	void (*end)(unsigned int irq);
    	void (*set_affinity)(unsigned int irq, unsigned long mask);
    };
    
    typedef struct hw_interrupt_type  hw_irq_controller;
    
    /*
     * This is the "IRQ descriptor", which contains various information
     * about the irq, including what kind of hardware handling it has,
     * whether it is disabled etc etc.
     *
     * Pad this out to 32 bytes for cache and indexing reasons.
     */
    typedef struct {
    	unsigned int status;		/* IRQ status */
    	hw_irq_controller *handler;
    	struct irqaction *action;	/* IRQ action list */
    	unsigned int depth;		/* nested irq disables */
    	spinlock_t lock;
    } ____cacheline_aligned irq_desc_t;
    

    系统为每个中断向量提供了一个队列,使得中断向量的共用成为可能,irq_desc的每一个元素就是一个队列的头部控制结构。

    首先中断源将自己的服务程序挂到相应的队列中,当中断发生时,先执行中断向量的总服务程序,然后根据中断源和设备号在其所属队列中找到特定的服务程序。

    因为每个中断向量可以服务一个队列的中断源,所以每个中断向量我们成为一个通道,每个通道下有一个队列action可以服务n个中断源,而handler则作为这个通道的通用的控制结构,其内容一般取决于中断控制器。

    通常前16个中断请求通道由中断控制器8259控制,所以将控制器对应的控制函数结构赋值给前16个通道的控制结构,其函数实现都是与控制器的交互,而外部中断对应的通道则赋值为空。

    static struct hw_interrupt_type i8259A_irq_type = {
    	"XT-PIC",
    	startup_8259A_irq,
    	shutdown_8259A_irq,
    	enable_8259A_irq,
    	disable_8259A_irq,
    	mask_and_ack_8259A,
    	end_8259A_irq,
    	NULL
    };
    static void enable_none(unsigned int irq) { }
    static unsigned int startup_none(unsigned int irq) { return 0; }
    static void disable_none(unsigned int irq) { }
    static void ack_none(unsigned int irq)
    struct hw_interrupt_type no_irq_type = {
    	"none",
    	startup_none,
    	shutdown_none,
    	enable_none,
    	disable_none,
    	ack_none,
    	end_none
    };
    

    具体中断服务程序描述结构struct irqaction在include/linux/interrupt中定义:

    struct irqaction {
    	void (*handler)(int, void *, struct pt_regs *);
    	unsigned long flags;
    	unsigned long mask;
    	const char *name;
    	void *dev_id;
    	struct irqaction *next;
    };
    

    其中最为重要的handler是服务程序的函数指针,dev_id表示具体设备,next将这个通道的所有服务程序链接成队列。

    在idt表初始化完成之初,整个中断服务程序队列都是空的,需要具体设备初始化程序将其中断注册到中断请求队列。

    int request_irq(unsigned int irq, 
    		void (*handler)(int, void *, struct pt_regs *),
    		unsigned long irqflags, 
    		const char * devname,
    		void *dev_id)
    {
    	int retval;
    	struct irqaction * action;
    
    #if 1
    	/*
    	 * Sanity-check: shared interrupts should REALLY pass in
    	 * a real dev-ID, otherwise we'll have trouble later trying
    	 * to figure out which interrupt is which (messes up the
    	 * interrupt freeing logic etc).
    	 */
    	if (irqflags & SA_SHIRQ) {
    		if (!dev_id)
    			printk("Bad boy: %s (at 0x%x) called us without a dev_id!\n", devname, (&irq)[-1]);
    	}
    #endif
    
    	if (irq >= NR_IRQS)
    		return -EINVAL;
    	if (!handler)
    		return -EINVAL;
    
    	action = (struct irqaction *)
    			kmalloc(sizeof(struct irqaction), GFP_KERNEL);
    	if (!action)
    		return -ENOMEM;
    
    	action->handler = handler;
    	action->flags = irqflags;
    	action->mask = 0;
    	action->name = devname;
    	action->next = NULL;
    	action->dev_id = dev_id;
    
    	retval = setup_irq(irq, action);
    	if (retval)
    		kfree(action);
    	return retval;
    }
    

    首先irq表示要使用哪个中断通道,SA_SHIRQ表示允许其他中断源共用此中断通道,这个时候就需要一个非零的中断号进行区分,如果irqflags & SA_SHIRQ == 0则次队列只能有一个中断服务程序,后面的setup_irq中将看到这部分实现。

    然后申请一个action,使用参数中的函数指针,设备名设备号等信息初始化这个action,并在setup_irq中将action链接入这个中断的请求队列。

    /* this was setup_x86_irq but it seems pretty generic */
    int setup_irq(unsigned int irq, struct irqaction * new)
    {
    	int shared = 0;
    	unsigned long flags;
    	struct irqaction *old, **p;
    	irq_desc_t *desc = irq_desc + irq;
    
    	/*
    	 * Some drivers like serial.c use request_irq() heavily,
    	 * so we have to be careful not to interfere with a
    	 * running system.
    	 */
    	if (new->flags & SA_SAMPLE_RANDOM) {
    		/*
    		 * This function might sleep, we want to call it first,
    		 * outside of the atomic block.
    		 * Yes, this might clear the entropy pool if the wrong
    		 * driver is attempted to be loaded, without actually
    		 * installing a new handler, but is this really a problem,
    		 * only the sysadmin is able to do this.
    		 */
    		rand_initialize_irq(irq);
    	}
    
    	/*
    	 * The following block of code has to be executed atomically
    	 */
    	spin_lock_irqsave(&desc->lock,flags);
    	p = &desc->action;
    	if ((old = *p) != NULL) {
    		/* Can't share interrupts unless both agree to */
    		if (!(old->flags & new->flags & SA_SHIRQ)) {
    			spin_unlock_irqrestore(&desc->lock,flags);
    			return -EBUSY;
    		}
    
    		/* add new interrupt at end of irq queue */
    		do {
    			p = &old->next;
    			old = *p;
    		} while (old);
    		shared = 1;
    	}
    
    	*p = new;
    
    	if (!shared) {
    		desc->depth = 0;
    		desc->status &= ~(IRQ_DISABLED | IRQ_AUTODETECT | IRQ_WAITING);
    		desc->handler->startup(irq);
    	}
    	spin_unlock_irqrestore(&desc->lock,flags);
    
    	register_irq_proc(irq);
    	return 0;
    }
    

    首先desc = irq_desc + irq;找到目标中断通道,然后p = &desc->action指向该通道的中断请求队列,然后需要满足队列的第一个action和当前的action都同意共享这个中断通道,才能将它链入,否则返回失败。

    shared = 0表示这是第一个尝试链入队列的action,这是需要一些初始化动作,包括要调用本通道的startup函数。

    中断的响应和服务

    前面初始化向量表的最后提到,当中断发生是,会先根据中断向量找到IDT中的中断门,通过门后,使用门中的服务程序地址跳转到对应地址,外部中断统一执行common_interrupt,然后通过汇编调用到do_IRQ中开始正经的中断服务程序。

    asmlinkage unsigned int do_IRQ(struct pt_regs regs)
    {	
    	/* 
    	 * We ack quickly, we don't want the irq controller
    	 * thinking we're snobs just because some other CPU has
    	 * disabled global interrupts (we have already done the
    	 * INT_ACK cycles, it's too late to try to pretend to the
    	 * controller that we aren't taking the interrupt).
    	 *
    	 * 0 return value means that this irq is already being
    	 * handled by some other CPU. (or is disabled)
    	 */
    	int irq = regs.orig_eax & 0xff; /* high bits used in ret_from_ code  */
    	int cpu = smp_processor_id();
    	irq_desc_t *desc = irq_desc + irq;
    	struct irqaction * action;
    	unsigned int status;
    
    	kstat.irqs[cpu][irq]++;
    	spin_lock(&desc->lock);
    	desc->handler->ack(irq);
    	/*
    	   REPLAY is when Linux resends an IRQ that was dropped earlier
    	   WAITING is used by probe to mark irqs that are being tested
    	   */
    	status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);
    	status |= IRQ_PENDING; /* we _want_ to handle it */
    
    	/*
    	 * If the IRQ is disabled for whatever reason, we cannot
    	 * use the action we have.
    	 */
    	action = NULL;
    	if (!(status & (IRQ_DISABLED | IRQ_INPROGRESS))) {
    		action = desc->action;
    		status &= ~IRQ_PENDING; /* we commit to handling */
    		status |= IRQ_INPROGRESS; /* we are handling it */
    	}
    	desc->status = status;
    
    	/*
    	 * If there is no IRQ handler or it was disabled, exit early.
    	   Since we set PENDING, if another processor is handling
    	   a different instance of this same irq, the other processor
    	   will take care of it.
    	 */
    	if (!action)
    		goto out;
    
    	/*
    	 * Edge triggered interrupts need to remember
    	 * pending events.
    	 * This applies to any hw interrupts that allow a second
    	 * instance of the same irq to arrive while we are in do_IRQ
    	 * or in the handler. But the code here only handles the _second_
    	 * instance of the irq, not the third or fourth. So it is mostly
    	 * useful for irq hardware that does not mask cleanly in an
    	 * SMP environment.
    	 */
    	for (;;) {
    		spin_unlock(&desc->lock);
    		handle_IRQ_event(irq, &regs, action);
    		spin_lock(&desc->lock);
    		
    		if (!(desc->status & IRQ_PENDING))
    			break;
    		desc->status &= ~IRQ_PENDING;
    	}
    	desc->status &= ~IRQ_INPROGRESS;
    out:
    	/*
    	 * The ->end() handler has to deal with interrupts which got
    	 * disabled while the handler was running.
    	 */
    	desc->handler->end(irq);
    	spin_unlock(&desc->lock);
    
    	if (softirq_active(cpu) & softirq_mask(cpu))
    		do_softirq();
    	return 1;
    }
    

    首先参数中的regs.orig_eax位中断向量减256,相当于从0开始数第8位到最高位取反,&上0xff又恢复为中断向量,作为irq_desc数组的下标。desc->status为当前通道的状态,如果通道没有打开或者正在执行中断服务又或者当前通道的请求队列为空,那么action=NULL,无法响应中断。否则将通道状态设置为执行中,调用handle_IRQ_event:

    int handle_IRQ_event(unsigned int irq, struct pt_regs * regs, struct irqaction * action)
    {
    	int status;
    	int cpu = smp_processor_id();
    
    	irq_enter(cpu, irq);
    
    	status = 1;	/* Force the "do bottom halves" bit */
    
    	if (!(action->flags & SA_INTERRUPT))
    		__sti();
    
    	do {
    		status |= action->flags;
    		action->handler(irq, action->dev_id, regs);
    		action = action->next;
    	} while (action);
    	if (status & SA_SAMPLE_RANDOM)
    		add_interrupt_randomness(irq);
    	__cli();
    
    	irq_exit(cpu, irq);
    
    	return status;
    }
    

    SA_INTERRUPT标志表明该中断服务程序需要在开启中断的条件下执行,中断在穿过中断门的时候会自动改变EFLAGS中的IF标志位,关闭中断,那么在中断服务程序较长,操作较为复杂的情况,如果不打开中断的话,就会发生丢失中断的情况。

    紧接着就是循环执行队列中的每一个服务程序。

    返回do_IRQ有一个判断if (softirq_active(cpu) & softirq_mask(cpu)),这是linux设置的一个成为bottom half的中断处理机制,也就是将中断处理程序分成了两部分,其一是必须立即执行,一般是在中断关闭的情况下完成,其二是比较耗时的,不适合在关闭中断情况下完成的操作,这部分内容会在软中断中有较详细的分析,这里暂不讨论。

    如前所述,从do_IRQ返回后,将返回到ret_from_intr处执行,这个标签被定义在arch/i386/kernel/entry.S中:

    ENTRY(ret_from_intr)
    	GET_CURRENT(%ebx)
    	movl EFLAGS(%esp),%eax		# mix EFLAGS and CS
    	movb CS(%esp),%al
    	testl $(VM_MASK | 3),%eax	# return to VM86 mode or non-supervisor?
    	jne ret_with_reschedule
    	jmp restore_all
    

    GET_CURRENT获取当前进程的task_struct并存放在ebx中。

    #define GET_CURRENT(reg) \
    	movl $-8192, reg; \
    	andl %esp, reg
    

    -8192翻译成16进制0xffffe000,所以上面宏定义可以翻译成将esp的低13位(8K)清零返回,而task_struct结构存放在8K堆栈空间的底部,esp是当前的栈顶指针,所以返回值为当前的栈底也是task_struct的地址。

    判断CS中的dpl如果是0跳转到restore_all,不是0就是3则跳转到ret_with_reschedule

    ret_with_reschedule:
    	cmpl $0,need_resched(%ebx)
    	jne reschedule
    	cmpl $0,sigpending(%ebx)
    	jne signal_return
    restore_all:
    	RESTORE_ALL
    
    	ALIGN
    signal_return:
    	sti				# we can get here from an interrupt handler
    	testl $(VM_MASK),EFLAGS(%esp)
    	movl %esp,%eax
    	jne v86_signal_return
    	xorl %edx,%edx
    	call SYMBOL_NAME(do_signal)
    	jmp restore_all
    

    首先判断是否需要进行调度,由于前面将ebx设置为task_struct的地址,所以need_resched(%ebx)就是task_struct中need_resched的值,sigpending(%ebx)同理

    /*
     * these are offsets into the task-struct.
     */
    state		=  0
    flags		=  4
    sigpending	=  8
    addr_limit	= 12
    exec_domain	= 16
    need_resched	= 20
    tsk_ptrace	= 24
    processor	= 52
    

    need_resched非0表示需要调度,跳转到reschedule:

    reschedule:
    	call SYMBOL_NAME(schedule)    # test
    	jmp ret_from_sys_call
    

    如果sigpending非0则说明有信号需要处理:

    signal_return:
    	sti				# we can get here from an interrupt handler
    	testl $(VM_MASK),EFLAGS(%esp)
    	movl %esp,%eax
    	jne v86_signal_return
    	xorl %edx,%edx
    	call SYMBOL_NAME(do_signal)
    	jmp restore_all
    

    这两个场景均需要调用相应的函数去进行调度或者信号处理,处理结束后也都会跳转到restore_all继续执行,这里处理过程设计进程调度和进程间通信的内容,暂时不做讨论,直接看restore_all:

    restore_all:
    	RESTORE_ALL
    
    #define RESTORE_ALL	\
    	popl %ebx;	\
    	popl %ecx;	\
    	popl %edx;	\
    	popl %esi;	\
    	popl %edi;	\
    	popl %ebp;	\
    	popl %eax;	\
    1:	popl %ds;	\
    2:	popl %es;	\
    	addl $4,%esp;	\
    3:	iret;		\
    

    这里与进入中断处理之前的SAVE_ALL相对应,将栈中保存的中断前寄存器内容依次弹出,最后esp加4是为了跳过中断请求号-256,随后iret返回到中断服务前的堆栈中。

    三、软中断与bottom half

    中断服务进程一般在中断关闭的状态下运行,但是因为过长的关闭中断的时间会导致cpu不能及时响应其他的中断请求,所以内核允许中断服务程序挂入中断请求队列时设置SA_INTERRUPT标志为0,使中断可以在开中断的条件下执行。而中断服务程序执行过程中完全打开中断又有中断嵌套,所以内核往往将中断服务过程分为两个部分。

    开头的部分执行那些必须在不受干扰的情况下原子的完成一些关键性操作,而后半部分则可以执行那些耗时的部分,甚至可以将多次中断服务中的相关部分一起处理,这部分就是bottom half。

    bh函数的处理严格的串行话,也就是不同的cpu同时只能执行一个bh函数,并且bh函数不可嵌套。但从单cpu结构到多cpu smp结构的过渡后,多个bh函数应该可以在不同的cpu上同时执行,于是在保留bh机制的基础上,增加了一种软中断机制,并将他们统一在一套框架之内。

    这里的软中断机制指“硬中断服务程序”对内核的中断。

    首先软中断的初始化在start_kernel中执行,其定义softirq_init在kernel/softirq.c中:

    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);
    }
    

    首先初始化一个包含32个元素的结构数组,每个元素描述一个bh函数,其结构定义:

    struct tasklet_struct
    {
    	struct tasklet_struct *next;
    	unsigned long state;
    	atomic_t count;
    	void (*func)(unsigned long);
    	unsigned long data;
    };
    

    一个tasklet_struct结构代表着一个tasklet,其中的func就指向其服务程序,tasklet_init完成了结构的初始化。

    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);
    }
    

    结合softirq_init中调用时的参数,可以发现每个tasklet的func都指向了bh_action,这些tasklet都要依托在一种softirq中,softirq的初始化通过open_softirq来完成。

    void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
    {
    	unsigned long flags;
    	int i;
    
    	spin_lock_irqsave(&softirq_mask_lock, flags);
    	softirq_vec[nr].data = data;
    	softirq_vec[nr].action = action;
    
    	for (i=0; i<NR_CPUS; i++)
    		softirq_mask(i) |= (1<<nr);
    	spin_unlock_irqrestore(&softirq_mask_lock, flags);
    }
    

    softirq_vec是一个全局数组,数组中的每一个元素表述一种softirq,每个sorftirq有自己的处理函数action和他的私有数据data

    struct softirq_action
    {
    	void	(*action)(struct softirq_action *);
    	void	*data;
    };
    

    softirq的种类定义在include/linux/interrupt.h

    enum
    {
    	HI_SOFTIRQ=0,
    	NET_TX_SOFTIRQ,
    	NET_RX_SOFTIRQ,
    	TASKLET_SOFTIRQ
    };
    

    NET_TX_SOFTIRQ和NET_RX_SOFTIRQ是转为网络设计的,所以可以看到softirq_init中只初始化了HI_SOFTIRQ和TASKLET_SOFTIRQ两种软中断。

    初始化的最后对系统中的每个cpu执行了softirq_mask(i) |= (1<<nr),也就是将对应cpu的__softirq_mask中对应软中断种类的那一位置1,表示允许响应该类性的软中断。

    #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_active(cpu)	__IRQ_STAT((cpu), __softirq_active)
    #define softirq_mask(cpu)	__IRQ_STAT((cpu), __softirq_mask)
    
    /* entry.S is sensitive to the offsets of these fields */
    typedef struct {
    	unsigned int __softirq_active;
    	unsigned int __softirq_mask;
    	unsigned int __local_irq_count;
    	unsigned int __local_bh_count;
    	unsigned int __syscall_count;
    	unsigned int __nmi_count;	/* arch dependent */
    } ____cacheline_aligned irq_cpustat_t;
    

    __softirq_mask是一个整数,每一位对应一种软中断,__IRQ_STAT宏定义从一个全局的描述cpu状态的数组irq_stat中找到对应的cpu并访问该cpu结构中的对应成员。

    相对应的__softirq_active也是一个整数,对__softirq_active的某一位置位,表示发起这个软中断。

    每一种软中断又有一组全局的链表,用来管理每个cpu上待处理的软中断服务程序:

    struct tasklet_head
    {
    	struct tasklet_struct *list;
    } __attribute__ ((__aligned__(SMP_CACHE_BYTES)));
    
    struct tasklet_head tasklet_vec[NR_CPUS] __cacheline_aligned;
    ...
    struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned;
    

    当中断服务程序检测到当前cpu的__softirq_active以及__softirq_mask同时不为0时,就会到相应的链表中遍历执行事先注册好的所有软中断的action。

    asmlinkage unsigned int do_IRQ(struct pt_regs regs)
    {	
    ...
    	if (softirq_active(cpu) & softirq_mask(cpu))
    		do_softirq();
    	return 1;
    }
    
    asmlinkage void do_softirq()
    {
    	int cpu = smp_processor_id();
    	__u32 active, mask;
    
    	if (in_interrupt())
    		return;
    
    	local_bh_disable();
    
    	local_irq_disable();
    	mask = softirq_mask(cpu);
    	active = softirq_active(cpu) & mask;
    
    	if (active) {
    		struct softirq_action *h;
    
    restart:
    		/* Reset active bitmask before enabling irqs */
    		softirq_active(cpu) &= ~active;
    
    		local_irq_enable();
    
    		h = softirq_vec;
    		mask &= ~active;
    
    		do {
    			if (active & 1)
    				h->action(h);
    			h++;
    			active >>= 1;
    		} while (active);
    
    		local_irq_disable();
    
    		active = softirq_active(cpu);
    		if ((active &= mask) != 0)
    			goto retry;
    	}
    
    	local_bh_enable();
    
    	/* Leave with locally disabled hard irqs. It is critical to close
    	 * window for infinite recursion, while we help local bh count,
    	 * it protected us. Now we are defenceless.
    	 */
    	return;
    
    retry:
    	goto restart;
    }
    

    首先确保当前cpu没有在硬中断服务和软中断服务中,然后设置cpu状态结构进入软中断服务状态,通过active和mask确认要执行的软中断有哪些(if(active)时为1的那些位),然后循环的执行这些软中断的action。

    就HI_SOFTIRQ来说,其action函数被定义为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 != NULL) {
    		struct tasklet_struct *t = list;
    
    		list = list->next;
    
    		if (tasklet_trylock(t)) {
    			if (atomic_read(&t->count) == 0) {
    				clear_bit(TASKLET_STATE_SCHED, &t->state);
    
    				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();
    	}
    }
    

    可以看到函数中获取了当前cpu的tasklet_hi_vec的队列头,local_irq_enable使能本cpu中断,然后遍历整个队列,依次执行队列成员tasklet中的服务程序func。

    而这些队列成员则是在我们需要执行一个bh函数时,调用mark_bh链接入对应的队列的

    static inline void mark_bh(int nr)
    {
    	tasklet_hi_schedule(bh_task_vec+nr);
    }
    

    实际的链入工作由tasklet_hi_schedule完成,不过对于bh函数而言,他们的tasklet结构被统一管理在bh_task_vec中,bh_task_vec在softirq_init中初始化,并统一将所有的服务函数均设置为bh_action。

    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);
    	}
    }
    

    当一个tasklet被链入某个队列但没有得到执行的时候是不允许再链接入其他队列的,这样就保证同一时间,只有一个相同的tasklet会被执行。TASKLET_STATE_SCHED就是为了保证这一点。
    最后__cpu_raise_softirq将当前cpu的active中的HI_SOFTIRQ位置1,正式发出软中断申请。

    如前所述,所有的bh函数都被注册为bh_action,在do_IRQ–>do_softirq–>tasklet_hi_action中以参数data循环调用bh_action,data在初始化tasklet时被设置为bh_task_vec的脚标:

    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_action中以bh_task_vec下标nr为下标调用bh_base函数指针数组中的第nr个函数,就真正进入到了bh函数中执行了。

    bh_base的内容在早在sched_init中就通过init_bh设置好了

    void __init sched_init(void)
    {
    ......
    	init_bh(TIMER_BH, timer_bh);
    	init_bh(TQUEUE_BH, tqueue_bh);
    	init_bh(IMMEDIATE_BH, immediate_bh);
    ......
    }
    
    void init_bh(int nr, void (*routine)(void))
    {
    	bh_base[nr] = routine;
    	mb();
    }
    

    目前内核中已定义的bh函数类型如下

    enum {
    	TIMER_BH = 0,
    	TQUEUE_BH,
    	DIGI_BH,
    	SERIAL_BH,
    	RISCOM8_BH,
    	SPECIALIX_BH,
    	AURORA_BH,
    	ESP_BH,
    	SCSI_BH,
    	IMMEDIATE_BH,
    	CYCLADES_BH,
    	CM206_BH,
    	JS_BH,
    	MACSERIAL_BH,
    	ISICOM_BH
    };
    

    具体的服务程序则由根据不同的中断源来指定,比如这里的TIMER_BH的软中断服务函数timer_bh。

    四、异常

    异常都有cpu为其预留的专用的中断向量,所以初始化时都直接将服务程序的入口地址直接写入了,以page_fault为例

    void __init trap_init(void)
    {
    ...
    	set_trap_gate(14,&page_fault);
    ...
    }
    

    入口page_fault定义在arch/asm-i386/kernel/entry.S中:

    ENTRY(page_fault)
    	pushl $ SYMBOL_NAME(do_page_fault)
    	jmp error_code
    

    do_page_fault为服务的入口地址,将其入栈准备跳转到其地址,而error_code为异常通用的程序入口,所有异常都要先执行error_code处的代码才进入各自的服务程序。

    有些异常会产生错误码,在产生异常时会同时将错误码压栈,而有些则不会,为了统一处理流程,在不产生错误码的异常入口处添加一个0,虚补一个错误码,例如:

    ENTRY(machine_check)
    	pushl $0
    	pushl $ SYMBOL_NAME(do_machine_check)
    	jmp error_code
    
    error_code:
    	pushl %ds
    	pushl %eax
    	xorl %eax,%eax
    	pushl %ebp
    	pushl %edi
    	pushl %esi
    	pushl %edx
    	decl %eax			# eax = -1
    	pushl %ecx
    	pushl %ebx
    	cld
    	movl %es,%ecx
    	movl ORIG_EAX(%esp), %esi	# get the error code
    	movl ES(%esp), %edi		# get the function address
    	movl %eax, ORIG_EAX(%esp)
    	movl %ecx, ES(%esp)
    	movl %esp,%edx
    	pushl %esi			# push the error code
    	pushl %edx			# push the pt_regs pointer
    	movl $(__KERNEL_DS),%edx
    	movl %edx,%ds
    	movl %edx,%es
    	GET_CURRENT(%ebx)
    	call *%edi
    	addl $8,%esp
    	jmp ret_from_exception
    

    error_code处没有像common_interrupt一样调用SAVE_ALL,原因在于除了错误码与中断请求号的不同外,异常处理的入口地址也被写入了堆栈。
    所以error_code在将一干寄存器入栈以后又将入口地址page_fault取出到edi中,将原来的位置写入es,并将错误码和当前栈顶指针作为参数压入栈中,才call edi调用page_fault,这样堆栈中的内容就与中断响应时的堆栈完全一样了,在返回时就可以用restore_all来恢复现场了。

    ret_from_exception:
    #ifdef CONFIG_SMP
    	GET_CURRENT(%ebx)
    	movl processor(%ebx),%eax
    	shll $CONFIG_X86_L1_CACHE_SHIFT,%eax
    	movl SYMBOL_NAME(irq_stat)(,%eax),%ecx		# softirq_active
    	testl SYMBOL_NAME(irq_stat)+4(,%eax),%ecx	# softirq_mask
    #else
    	movl SYMBOL_NAME(irq_stat),%ecx		# softirq_active
    	testl SYMBOL_NAME(irq_stat)+4,%ecx	# softirq_mask
    #endif
    	jne   handle_softirq
    
    ENTRY(ret_from_intr)
    	GET_CURRENT(%ebx)
    	movl EFLAGS(%esp),%eax		# mix EFLAGS and CS
    	movb CS(%esp),%al
    	testl $(VM_MASK | 3),%eax	# return to VM86 mode or non-supervisor?
    	jne ret_with_reschedule
    	jmp restore_all
    

    do_page_fault返回时判断是否有软中断需要处理,没有的话就继续执行与中断相应返回时完全一样的ret_from_intr。

    五、系统调用

    用户空间程序通过INT 0x80进入系统空间的通道称为系统调用。系统调用的通道即0x80号中断门的初始化前面已经有介绍。

    void __init trap_init(void)
    {
    ......
    	set_system_gate(SYSCALL_VECTOR,&system_call);
    ......
    }
    
    static void __init set_system_gate(unsigned int n, void *addr)
    {
    	_set_gate(idt_table+n,15,3,addr);
    }
    

    由于中断门中的dpl设置为3,所以用户空间的程序可以穿过系统调用门进入内核空间。

    以系统调用sethostname为例,其函数原型如下

    int sethostname(const char *name, size_t len);
    

    在用户空间中sethostname是一个库函数(/usr/lib/libc.a),实际的系统调用就是在这个函数中发起的,通过对libc.a的反汇编可得到sethostname的汇编代码如下(objdump -d /usr/lib/libc.a):

    sethostname.o:	file format elf32-i386
    
    Disassembly of section .text:
    
    00000000 <sethostname>:
    	0: 89 da				movl 	%eax, edx
    	2: 8b 4c 24 08 			movl 	0x8(%esp,1), %ecx
    	6: 8b 5c 24 04			movl 	0x4(%esp,1), %ebx
    	a: b8 4a 00 00 00		movl 	$0x4a, %eax
    	f: cd 80				int 	$0x80
       11: 89 d3				movl	%edx, %ebx
       13: 3d 01 f0 ff ff		cmpl	$0xfffff001, %eax
       18: 0f 83 fc ff ff		jae 	1a <sethostname+0x1a>
       1d: ff
       1a: R_386_PC32			__syscall_error
       1e: c3					ret
    

    当我们调用并执行到sethostname的时候,堆栈中栈顶的内容就是函数的两个参数。movl 0x8(%esp,1), %ecx,取esp+8的内容存入ecx(len),紧接着将name存入ebx,这里是利用两个通用寄存器向系统调用传参,因为系统调用会经过中断门并进入系统空间,并且因为运行等级发生了变化(3->0),会切换运行堆栈,所以不能像普通的函数调用那样进行传参。

    而系统调用号则通过eax传递。
    int $0x80发起一次中断,经过idtr寻址中断门,段选择码索引段描述项,然后由段描述项中的基地址与中断门中的段内入口地址功能寻址到系统调用的入口system_call。

    system_call定义在arch/asm-i386/kernel/entry.S中:

    ENTRY(system_call)
    	pushl %eax			# save orig_eax
    	SAVE_ALL
    	GET_CURRENT(%ebx)
    	cmpl $(NR_syscalls),%eax
    	jae badsys
    	testb $0x02,tsk_ptrace(%ebx)	# PT_TRACESYS
    	jne tracesys
    	call *SYMBOL_NAME(sys_call_table)(,%eax,4)
    	movl %eax,EAX(%esp)		# save the return value
    

    首先push %eax,将系统调用号入栈,然后将包括含有参数的ebx和ecx的所有寄存器入栈,此时栈中的内容如下:

    EBX		= 0x00 ----第一个参数name指针
    ECX		= 0x04 ---- 第二个参数len
    EDX		= 0x08
    ESI		= 0x0C
    EDI		= 0x10
    EBP		= 0x14
    EAX		= 0x18
    DS		= 0x1C
    ES		= 0x20
    ORIG_EAX	= 0x24 ---- 系统调用号
    EIP		= 0x28  
    CS		= 0x2C
    EFLAGS		= 0x30
    OLDESP		= 0x34
    OLDSS		= 0x38
    

    SAVE_ALL如此安排寄存器的入栈顺序,刚好使得系统调用的两个参数在栈顶位置,如此就可以不做任何变动,顺利地将参数传递给即将调用的系统调用处理函数了。

    cmpl $(NR_syscalls),%eax判断系统调用号是否支持,然后call SYMBOL_NAME(sys_call_table)(,%eax,4),直接到sys_call_table += 4eax处调用事先定义好的系统支持的系统调用。

    sys_call_table是一个全局的函数指针数组,定义了系统支持的所有系统调用的处理函数,在arch/asm-i386/kernel/entry.S中:

    ENTRY(sys_call_table)
    ....
    	.long SYMBOL_NAME(sys_sigpending)
    	.long SYMBOL_NAME(sys_sethostname)
    	.long SYMBOL_NAME(sys_setrlimit)	/* 75 */
    ....
    

    实际的sethostname函数实现在kernel/sys.c中:

    asmlinkage long sys_sethostname(char *name, int len)
    {
    	int errno;
    
    	if (!capable(CAP_SYS_ADMIN))
    		return -EPERM;
    	if (len < 0 || len > __NEW_UTS_LEN)
    		return -EINVAL;
    	down_write(&uts_sem);
    	errno = -EFAULT;
    	if (!copy_from_user(system_utsname.nodename, name, len)) {
    		system_utsname.nodename[len] = 0;
    		errno = 0;
    	}
    	up_write(&uts_sem);
    	return errno;
    }
    

    首先用capable(CAP_SYS_ADMIN)判断当前进程是否拥有特权,因为只有特权用户才能调用这个函数。

    为防止处理过程被打断,将对system_utsname.nodename的操作放在了受信号量保护的临界区中,这样,当有另外的进程试图进入这个函数时,就会在down_write(&uts_sem)的时候发现前面有人正在操作了。

    具体操作内容只有一个copy_from_user来拷贝用户空间的内容到系统空间。

    #define copy_from_user(to,from,n)			\
    	(__builtin_constant_p(n) ?			\
    	 __constant_copy_from_user((to),(from),(n)) :	\
    	 __generic_copy_from_user((to),(from),(n)))
    

    __builtin_constant_p是一个gcc内置函数,判断n是否是一个编译时常数,是返回1,不是返回0。

    我们直接看一般情况下的操作__generic_copy_from_user:

    arch/i386/lib/usercopy.c
    unsigned long
    __generic_copy_from_user(void *to, const void *from, unsigned long n)
    {
    	if (access_ok(VERIFY_READ, from, n))
    		__copy_user_zeroing(to,from,n);
    	return n;
    }
    
    #define access_ok(type,addr,size) ( (__range_ok(addr,size) == 0) && \
    			 ((type) == VERIFY_READ || boot_cpu_data.wp_works_ok || \
    			 segment_eq(get_fs(),KERNEL_DS) || \
    			  __verify_write((void *)(addr),(size))))
    
    #endif
    

    在检查了from和n的合法性以后调用__copy_user_zeroing(to,from,n)

    #define __copy_user_zeroing(to,from,size)				\
    do {									\
    	int __d0, __d1;							\
    	__asm__ __volatile__(						\
    		"0:	rep; movsl\n"					\
    		"	movl %3,%0\n"					\
    		"1:	rep; movsb\n"					\
    		"2:\n"							\
    		".section .fixup,\"ax\"\n"				\
    		"3:	lea 0(%3,%0,4),%0\n"				\
    		"4:	pushl %0\n"					\
    		"	pushl %%eax\n"					\
    		"	xorl %%eax,%%eax\n"				\
    		"	rep; stosb\n"					\
    		"	popl %%eax\n"					\
    		"	popl %0\n"					\
    		"	jmp 2b\n"					\
    		".previous\n"						\
    		".section __ex_table,\"a\"\n"				\
    		"	.align 4\n"					\
    		"	.long 0b,3b\n"					\
    		"	.long 1b,4b\n"					\
    		".previous"						\
    		: "=&c"(size), "=&D" (__d0), "=&S" (__d1)		\
    		: "r"(size & 3), "0"(size / 4), "1"(to), "2"(from)	\
    		: "memory");						\
    } while (0)
    

    正常情况下的操作只有前三行和最后三行,rep; movsl进行成串的move,ecx位计数器,esi为源指针,edi为目标指针。先按4字节进行,然后对剩余部分,不超过3个字节的内容按字节进行move。
    这段代码等价于下面的c语言代码:

    r = size & 3;
    size /= 4;
    while (size --) *((int *) to) ++ = *((int *) from) ++;
    while (r --) *((char *) to) ++ = *((char *) from) ++;
    

    至于从.section .fixup到.section __ex_table中间的内容,是因为name指针由用户空间提供,这就存在了从name开始len这么长的这段空间没有物理页面的映射,而发生缺页异常。

    缺页异常中如果通过find_vma发现没有这块虚存区间,就转入bad_area,如果发生错误的进程在系统空间中,就要找一下异常表中是否有相应的修复方法。

    no_context:
    	/* Are we prepared to handle this kernel fault?  */
    	if ((fixup = search_exception_table(regs->eip)) != 0) {
    		regs->eip = fixup;
    		return;
    	}
    

    这个修复方法由产生错误的地方提供,也就是说我们写程序的时候就需要识别到这里有可能出现这样的问题,并提供相应的解决办法,使缺页异常返回时能够继续执行,而不是放任不管,导致缺页异常返回时依然会缺页异常,导致万劫不复。

    __copy_user_zeroing中的.section __ex_table提供了一个异常表,表中说明了两个可能出现异常的地方(0,1),修复方法分别在0—3,1—4。

    从标号3开始,将目的地址剩余部分设置为0,并将未完成的size返回。

    所以copy_from_user的返回值0表示成功,非0则表示失败,失败的话,sys_sethostname将返回错误码errno = -EFAULT

    返回到system_call

    	call *SYMBOL_NAME(sys_call_table)(,%eax,4)
    	movl %eax,EAX(%esp)		# save the return value
    ENTRY(ret_from_sys_call)
    #ifdef CONFIG_SMP
    	movl processor(%ebx),%eax
    	shll $CONFIG_X86_L1_CACHE_SHIFT,%eax
    	movl SYMBOL_NAME(irq_stat)(,%eax),%ecx		# softirq_active
    	testl SYMBOL_NAME(irq_stat)+4(,%eax),%ecx	# softirq_mask
    #else
    	movl SYMBOL_NAME(irq_stat),%ecx		# softirq_active
    	testl SYMBOL_NAME(irq_stat)+4,%ecx	# softirq_mask
    #endif
    	jne   handle_softirq
    	
    ret_with_reschedule:
    	cmpl $0,need_resched(%ebx)
    	jne reschedule
    	cmpl $0,sigpending(%ebx)
    	jne signal_return
    restore_all:
    	RESTORE_ALL
    
    	ALIGN
    

    将返回值errno保存到堆栈,然后判断当前是否有软中断需要处理,最后,restore_all返回用户空间。

    用户空间中若发现返回值为负数,则将返回值写入全局变量errno作为错误码,并将eax的值置为-1表示系统调用失败。

    展开全文
  • 2.1操作系统的启动 一、操作系统的启动 1、CPU,I/O,内存 三者通过总线连接 2、DISK(硬盘):存放OS BIOS(基本输入输出系统):基本I/O处理系统。开机时让计算机开始检测计算机的外设,之后才能加在相应的软件...

    2.1操作系统的启动

    一、操作系统的启动
    1、CPU,I/O,内存 三者通过总线连接
    2、DISK(硬盘):存放OS
    BIOS(基本输入输出系统):基本I/O处理系统。开机时让计算机开始检测计算机的外设,之后才能加在相应的软件开始执行
    Bootloader(引导程序):加载OS。能够让OS从硬盘放到内存中,从而让CPU可以执行操作系统
    3、电脑通电时,段寄存器(CS)和指令寄存器(IP)能够确定一个内存地址,例如:CS:IP=0xf000:fff0
    4、POST(加电自检):寻找显卡和BIOS。检查自身的设备是否能够正常工作
    在这里插入图片描述
    在这里插入图片描述

    二、操作系统与设备和程序交互
    1、系统调用(来源于应用程序):应用程序主动向操作系统发出服务请求
    2、异常(来源于不良的应用程序):非法指令或者其他坏的处理状态(如:内存出错)
    3、中断(来源于外设):来自不同的硬件设备的计时器和网络的中断

    三、为何应用系统不能直接访问外设而是要通过操作系统?
    操作系统是一个特殊的软件,它具有对整个计算机系统的控制权。
    -在计算机运行中,内核是被信任的第三方(安全角度)
    -只有内核可以执行特权指令
    -为了方便应用程序(屏蔽底层的device,给上层应用程序提供简单的接口,使得应用程序变得通用、可移植)
    在这里插入图片描述

    四、中断、异常、系统调用的特点以及差异
    1、源头
    -中断:外设(键盘、鼠标、网卡等,可产生各种各样的事件)
    -异常:应用程序意想不到的行为(并不是应用程序主动的请求,而是操作系统去应对一些意外事件的支持,如:恶意程序,应用程序需要的资源未得到满足)
    -系统调用:应用程序请求操作提供服务(系统主动要求,如:打开/关闭/读写文件,发送网络包)
    2、处理时间
    -中断:异步
    -异常:同步
    -系统调用:异步或同步(发出请求后返回时间可能是异步或同步)
    3、响应
    -中断:持续,对用户应用程序是透明的,看不到的
    -异常:杀死或者重新执行意想不到的应用程序指令
    -系统调用:等待和持续(等待服务完成后继续执行,不会重复执行产生系统调用这条指令)

    2.2操作系统的中断、异常和系统调用

    一、中断和异常处理机制
    -中断是外设事件
    -异常是内部CPU的事件
    -中断和异常迫使CPU访问一些被中断和异常服务访问的功能

    中断和异常都有一个硬件的处理过程和软件的处理过程,合在一起就能构成中断和异常的具体服务
    要判断中断或异常时具体是哪一个过程在服务,将中断和异常进行编号,每一个编号有一个对应的起始地址。
    这些中断号会构成一个表,当发生中断或者是异常的时候,只需要去查找这个表,就可以容易查找到对应的地址
    在打断之后就要对中断的服务进行保护,即保存和恢复机制,之后系统就能在中断后继续运行

    二、中断的处理过程
    分两部分来完成(硬件和软件)

    硬件(外设):设置中断标记【CPU初始化】
    1、将内部、外部时间设置中断标记
    2、中断事件的ID

    软件(操作系统):
    1、保存当前处理状态
    2、中断服务程序处理
    3、清楚中断标记
    4、恢复之前保存的处理状态

    三、异常的处理过程(异常也会有一个编号)
    -保存现场
    -异常处理:
    1、杀死产生异常的程序
    2、重新执行异常指令
    -恢复现场

    四、系统调用
    程序访问主要是通过高层次的API接口而不是直接进行系统调用
    在这里插入图片描述
    操作系统是如何实现系统调用?
    这些API定义了可以提供哪些系统调用
    1、通常情况下,与每个系统调用相关的序号。
    系统调用接口根据这些序号来维护表的索引
    2、系统调用接口调用内核态中预期的系统调用。
    并返回系统调用的状态和其他任何返回值
    3、用户不需要知道系统调用是如何实现的。
    1) 只需要获取API和了解新系统将什么作为返回结果
    2) 操作系统接口的细节大部分都隐藏在API中
    3) 通过运行程序的库来管理(用包含编译器的库来创建函数集)

    两个概念:
    1、用户态:
    应用程序在执行的过程中,CPU所处于的一个特权级的状态,其特权级特别低,不能访问某些特殊的机器指令和I/O

    2、内核态:
    操作系统运行过程中CPU所处于的一个状态,CPU可以执行任何的一条特权指令和I/O,可以完全的控制这个计算机系统

    PS: 当一个应用程序需要一个系统调用时,会完成从用户态到内核态的转化,从而使控制权从应用程序移到操作系统。操作系统就是可以对系统调用识别来完成具体的服务。

    函数调用和系统调用的区别:
    1、当应用程序发出函数调用时,它是在一个栈空间中完成参数的传递、参数的返回
    2、在系统调用的执行过程中,应用程序和内核都有各自的堆栈。这意味着当应用程序发出系统调用时,当它切换到内核态去执行时,他需要切换堆栈,同时还需要完成特权级的转换(从用户态到内核态的转换),这都会需要一定的开销

    PS: 执行系统调用比执行函数调用的开销大很多,但是会换来安全和可靠

    跨越操作系统边界的开销:
    一、
    1、在执行时间上的开销超过程序调用
    2、开销:
    a.建立中断/异常/系统调用号与对应服务例程映射关系的初始化开销
    b.建立内核堆栈
    c.验证参数
    d.内核态映射到用户态的地址空间更新页面映射权限
    e.内核态独立地址空间TLB

    二、
    1、建立中断、异常、系统调用号与对应服务例程映射关系的初始化开销,并且会有一个映射的表,需要对这个表进行维护。
    2、操作系统有自己堆栈,需要对这个堆栈进行维护有消耗。(当然,应用程序也有自己的堆栈)
    3、操作系统不信任应用程序,会对参数进行检查,会有一个时间上的开销。
    4、数据的内存拷贝,从内核空间到用户空间,会有一个拷贝的开销。

    展开全文
  • 功能分类 操作系统(Operation System,OS)是指控制管理整个计算机系统的硬件软件资源,并合理地组织调度计算机的工作资源的分配,以提供给用户其他...功能:命令接口(直接使用),程序接口(系统调用),G
  • 可异步同步,对于返回值,同步会等待值返回再执行下一步,异步发出系统调用请求后,紧接着就会执行下一步操作。 异常 (Exception): 应用程序产生的,在执行过程中发生非法的指令,破坏其他程序的处理状态。...
  • ** 操作系统第3次理论课作业(时钟中断-访管中断-系统功能...虽然说实际编程时调用的是库函数,但真正的“操作系统-应用程序”的接口是系统功能调用(原题为系统调用)。原语只是系统功能的一个子集,说法不够准确。 2.
  • 注:很多人习惯把 Linux、Windows、MacOS 的“小黑框”中使用的命令也称为“指令”,其实这是“交互式命令接口”,注意与本节的“指令”区别开。 本节中的“指令”指二进制机器指令 (二)内核程序 v.s. 应用程序 ...
  • 中断系统调用

    2021-08-31 14:06:38
    中断系统如何接收中断源的中断请求,因机器而算,一般由中断逻辑线路中断寄存器实现 2.中断响应: (1)中断信号的扫描结构 它在每条指令执行周期的最后时刻扫描中断寄存器 (2)若无中断信号 处理器就执行下一条...
  • 用户态转换为内核态是通过中断实现的,并且中断是用户态进入核心态的唯一途径 核心态转换为用户态:通过执行一个特权指令,将程序状态字寄存器(PSW)的标志位设置为用户态来实现的 两种程序 内核
  • 一、启动 1.计算机体系结构概述 计算机组成: cpu,内存,io 操作系统存放位置: 硬盘 BIOS:基本I/O处理系统,计算机启动时加载的第一个程序,在内存中,...二、 中断、异常和系统调用 1. 背景 2. 中断、...
  • 中断系统调用

    2021-05-02 21:17:05
    概念:如果一个进程正在调用一个系统调用,比如正在调用read函数,如果这个时候来了一个信号,那么当信号处理函数执行完毕以后,该系统调用是重新执行还是直接返回错误呢?这要进行分类,如下所示: 来几个范例验证...
  • 文章目录操作系统的启动中断和异常处理机制 操作系统的启动 DISK:存放os,bootloader程序 bootloader:一段执行程序,负责将os放入内存,硬盘第一个扇区,512字节 BIOS:基本IO处理系统,负责检测各种外设是否正常 ...
  • 中断和异常 定义 中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。 中断是为了实现多道程序并发引入的...
  • 操作系统(Operating System,OS)是控制管理整个计算机系统的硬件软件资源,并合理的组织调度计算机的工作资源的分配,以提供给用户其它软件方便的接口环境,它是计算机系统中最基本的系统软件。...
  • 中断”会让CPU由用户态变为内核态,使操作系统重新夺回对CPU的控制权。并且“中断”是操作系统内核夺回CPU使用权的唯一途径 内核态——>用户态: 执行一条特权指令——修改PSW的标志位为“用户态” , 这个动作...
  • 中断装置中断处理程序统称为中断系统中断系统是计算机的重要组成部分。实时控制、故障自动处理、计算机与外围设备间的数据传送往往采用中断系统中断系统的应用大大提高了计算机效率。不同的计算机其硬件结构...
  • 我们假定博客的读者已经具备了计算机系统结构方面的基础知识,所以本系列博客对中断以及异常(exception)处理的原理机制不作深入的介绍。缺乏这方面基础的读者不妨先阅读一些微处理器方面的材料。不过,我们也并...
  • 因此,很容易通过导出符号表来找到系统调用表的地址,因此给攻击程序提供了方便的策略。因此,为了安全,在2.6内核版本中,系统调用表不再被导出。下面给出在2.6内核代码中获得系统调用表,实现系统劫持的过程。...
  • 2.1 操作系统的启动 CPU, I/O , 内存通过总线连接 DISK中有Bootloader(512字节)OS,Bootloader的作用是将OS加载到内存。...2.2 操作系统的中断、异常和系统调用(1)—基本概念 操作系统与外设程序交互:OS
  • 中断函数是硬件或者操作系统自动调用的。。 也就是说只要满足触发条件,就会自动调用中断函数(此时主函数是停止的)。 当中断函数执行完毕,又返回主函数继续执行主函数。 然后这样不断的循环,反正只要是触发中断...
  • 微型计算机原理及应用学习笔记DOS系统功能调用和ROMBIOS中断调用作者:山东自考来源:山东自考点击数:更新时间:2013-12-5 10:58:43一、系统功能调用(一)概述DOS为程序设计者提供了许多可直接调用的功能子程序,...
  • 目录 一、操作系统的启动 二、中断/异常/系统调用 三者的区别 ...操作系统外设产生中断应用程序产生异常和系统调用中断:来自不同的硬件设备的计时器网络的中断,来源于外设。 异常:非
  • 切换模式并不会发生进程上下文切换,因为用户内核都有自己独立的堆栈 每个进程都有两个堆栈:用户空间堆栈,内核空间堆栈 在这个过程中就发生了 CPU 上下文切换,整个过程是这样的: 1、保存 CPU 寄存器...
  • LINUX系统调用原理-既应用层如何调用内核层函数之软件中断SWI:software interrupt 软件中断ARMLinux系统利用SWI指令来从用户空间进入内核空间,还是先让我们了解下这个SWI指令吧。SWI指令用于产生软件中断,从而...
  • 2、调用子程序:是指调用子程序的指令,包括调用指令(转子指令)返回指令(返主指令)。二、特点不同1、调用中断服务程序:当中央处理器正在处理内部数据时,外界发生了紧急情况,要求CPU暂停当前的工作转去处理这个...
  • 中断 系统调用

    2021-05-06 15:48:56
    中断 系统调用 中断 异常 系统调用 源头 外部硬件 内核被动产生 主动触发 处理时机 异步 同步 同步/异步 响应 打断执行 对用户透明(感知不到) 杀死/重新执行 等待完成继续执行 OOT-1620287319707)] ...
  • 1、两过程定义与作用子程序是微机基本程序结构中的1种,基本程序结构包括顺序(简单)、分支(判断)、循环、子程序查表等5种。子程序是一组可以公用的指令序列,只要给出子程序的入口地址就能从主程序转入子程序。子...
  • 中断和异常 1. 中断的概念作用 中断产生背景:解决串行执行程序,系统资源利用率低的问题。为解决该问题,发明了操作系统,发生中断意味着需要操作系统介入管理。 2. 中断的分类 内中断(也称”异常“) 外中断 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 382,109
精华内容 152,843
关键字:

中断和系统调用的区别

友情链接: ManagerSystem.rar