Exception 陷阱指令导致的硬件或软件中断或异常?
警察说 陷阱指令 当程序发出陷阱指令时 处理器生成软件陷阱异常。典型的程序 当程序需要用户维护时,发出软件陷阱 操作系统。操作系统的常规异常处理程序 系统确定陷阱的原因并做出适当的响应 然而,当我之前问到这一问题时,答案是这是一个软件中断: 对于不同的体系结构,异常和中断之间的差异似乎也略有不同,因此可以有4种组合(?)硬件、软件、异常和中断Exception 陷阱指令导致的硬件或软件中断或异常?,exception,assembly,interrupt-handling,nios,assembly-trap,Exception,Assembly,Interrupt Handling,Nios,Assembly Trap,警察说 陷阱指令 当程序发出陷阱指令时 处理器生成软件陷阱异常。典型的程序 当程序需要用户维护时,发出软件陷阱 操作系统。操作系统的常规异常处理程序 系统确定陷阱的原因并做出适当的响应 然而,当我之前问到这一问题时,答案是这是一个软件中断: 对于不同的体系结构,异常和中断之间的差异似乎也略有不同,因此可以有4种组合(?)硬件、软件、异常和中断 # The label alt_main is defined in this file. # There is a call to this labe
# The label alt_main is defined in this file.
# There is a call to this label in the Altera-supplied startup code for Nios II.
# At label alt_main, interrupts and handlers are initialized; thereafter,
# the label main is called, starting the main program.
################################################################
#
# Definitions for important devices and addresses in this system.
#
# Uart_0 at 0x860
.equ de2_uart_0_base,0x860
# Timer_1 at 0x920, interrupt index 10 (mask 2^10 = 0x400)
.equ de2_timer_1_base,0x920
.equ de2_timer_1_intmask,0x400
# Timeout value for 0,1 ms tick-count interval (CHANGED in every version)
.equ de2_timer_1_timeout_value,4999
# Required tick count per time-slice, meaning
# the number of timer-interrupts before a thread-switch is performed
.equ oslab_ticks_per_timeslice,100
# Interrupt address at 0x800020
.equ de2_nios2_interrupt_address,0x800020
#
# End of device-address definitions
#
################################################################
################################################################
#
# Definition of variables for keeping system time etcetera.
#
.data
.align 2
.global oslab_internal_globaltime
oslab_internal_globaltime: .word 0
# Definition of variable for remembering the number of
# timer-interrupts since the last thread-switch
.data
.align 2
.global oslab_internal_tickcount
oslab_internal_tickcount: .word 0
# Definition of system (interrupt) stack, sp, and gp
.data
.align 2
oslab_internal_gp: .word 0
oslab_internal_sp: .word 0
oslab_system_stack: .fill 256,1,0
oslab_system_stacktop:
# Definition of the end-of-timeslice message.
oslab_internal_yield_message:
.asciz "\n#### Thread yielded after using %d tick%c."
#
# End of system-time variable definitions.
#
################################################################
################################################################
#
# Interrupt handling code.
#
# Stub for interrupt handler
.text
oslab_internal_stub:
movia et,oslab_exception_handler
jmp et
# The interrupt handler
oslab_exception_handler:
# Check source of exception, following the procedure
# described in the Nios II Processor Reference Handbook.
rdctl et,estatus # Check ESTATUS
andi et,et,1 # Test EPIE
beq et,r0,oslab_exception_was_not_an_interrupt
rdctl et,ipending # Check IPENDING
beq et,r0,oslab_exception_was_not_an_interrupt
# If control comes here, we have established that the
# exception was caused by an interrupt.
# Subtract 4 from ea, so that the interrupted instruction
# will be re-run when we return.
subi ea,ea,4
# Check the source of the interrupt.
# Possible source No. 1: Timer_1 (currently the only source).
rdctl et,ipending
andi et,et,de2_timer_1_intmask
bne et,r0,oslab_timer_1_interrupt
# If control comes here, we have an interrupt from an unknown source.
# This condition is IGNORED in this version of OSLAB.
eret
oslab_exception_was_not_an_interrupt:
# Test if the interrupted instruction was a TRAP
subi sp,sp,4 # PUSH r8 (instruction 1)
stw r8,0(sp) # PUSH r8 (instruction 2)
movia r8,0x003b683a # binary code for TRAP
ldw et,-4(ea) # Load interrupted instruction
cmpeq et,et,r8 # Compare to binary code for TRAP
# Result from comparison is now in et.
ldw r8,0(sp) # POP r8 (instruction 1)
addi sp,sp,4 # POP r8 (instruction 2)
# Use the comparison result in et as branch condition.
# The value in et will also be used later, to tell if the
# exception was a trap or an interrupt.
bne et,r0,oslab_trap_handler
# If control comes here, we have an exception which was not a TRAP.
# This should not normally happen.
# However, someone writing programs for the OSLAB micro-operating system
# could perhaps use unimplemented instructions. To catch unimplemented
# instructions, we insert a BREAK instruction here. This will stop execution
# unless the program is run through the debugger.
break 0
eret
oslab_timer_1_interrupt:
# Acknowledge the timer_1 interrupt.
movia et,de2_timer_1_base
stw r0,0(et)
# Save contents of R8, to get a free register for
# temporary values.
subi sp,sp,4
stw r8,0(sp) # PUSH r8
# Increase system clock.
movia r8,oslab_internal_globaltime
ldw et,0(r8)
addi et,et,1
stw et,0(r8)
# Increase tick counter.
movia r8,oslab_internal_tickcount
ldw et,0(r8)
addi et,et,1
stw et,0(r8)
# Restore original contents of R8.
ldw r8,0(sp) # POP r8
addi sp,sp,4
# Check value of tick counter,
# against the required number of ticks per time-slice.
# Note: oslab_ticks_per_timeslice is an assembler constant,
# and not a variable. Hence, no load/store-instructions here.
subi et,et,oslab_ticks_per_timeslice
# If the result from the subtraction is zero (or perhaps positive),
# then it is time to switch threads.
bge et,r0,oslab_time_to_switch
# If we fall-through here, then we have had one of those many
# timer interrupts on which we should not switch threads.
# Return to caller.
eret
oslab_time_to_switch:
# This code will now fall-through into the TRAP handler
# which performs a context switch.
#
# We will print out a message for each timer interrupt.
# To be able to tell that we had a timer interrupt, and not
# a TRAP, we set et to zero.
movi et,0
oslab_trap_handler:
# Save registers r1 through r23, plus fp, gp, ra and ea
.set noat # R1 is used here.
subi sp,sp,108 # Make room for all registers.
stw r1, 4(sp) # R1 is saved in slot 1, not slot 0.
stw r2, 8(sp)
stw r3,12(sp)
stw r4,16(sp)
stw r5,20(sp)
stw r6,24(sp)
stw r7,28(sp)
stw r8,32(sp)
stw r9,36(sp)
stw r10,40(sp)
stw r11,44(sp)
stw r12,48(sp)
stw r13,52(sp)
stw r14,56(sp)
stw r15,60(sp)
stw r16,64(sp)
stw r17,68(sp)
stw r18,72(sp)
stw r19,76(sp)
stw r20,80(sp)
stw r21,84(sp)
stw r22,88(sp)
stw r23,92(sp)
stw r26,96(sp)
stw r28,100(sp)
stw r31,104(sp)
stw ea,0(sp) # Special case, saved in slot 0.
mov r4,sp # Copy stack pointer to param1 register
movia sp,oslab_system_stacktop # Use system stack instead
# Test et to see if this was a timeout event or a TRAP.
beq et,r0,oslab_not_a_trap
# If this was a trap event, we fall through here.
# Our simplified printf is used to print a message,
# saying that the previous thread yielded parts of its time-slice.
################################################################
#
# The following code prints a nice message. Nothing more.
# This code saves and restores all registers it uses.
# You can safely ignore the following code, up to
# (but NOT including) the label oslab_not_a_trap.
#
subi sp,sp,4 # Contents of r4 must be preserved.
stw r4,0(sp) # PUSH r4.
movia r4,oslab_internal_yield_message
movia r5,oslab_internal_tickcount
ldw r5,0(r5)
movi r6,0 # Gold-plating: check if 1 tick or several ticks.
subi et,r5,1 # Do not print the s if only 1 tick.
beq et,r0,oslab_no_plural_ticks
movi r6,'s' # If 0 ticks, or 2 or more ticks, print the s.
oslab_no_plural_ticks:
call printf
ldw r4,0(sp) # POP r4
addi sp,sp,4
#
# This comment marks the end of the code for printing a nice message.
# Now comes other code, which is potentially much more interesting.
#
################################################################
# Move on to thread-switch code.
oslab_not_a_trap:
# Clear tick counter, since we are going to switch threads.
movia et,oslab_internal_tickcount
stw r0,0(et)
# Now it is time to execute the thread-switch code.
# We use the more general callr, rather than call.
movia et,oslab_internal_threadswitch
callr et # Call thread switch routine written in C
mov sp,r2 # Copy return value to stack pointer
# Yes, the system stack pointer is lost,
# but who cares? We will not need it any more.
# restore registers
ldw r1, 4(sp)
ldw r2, 8(sp)
ldw r3,12(sp)
ldw r4,16(sp)
ldw r5,20(sp)
ldw r6,24(sp)
ldw r7,28(sp)
ldw r8,32(sp)
ldw r9,36(sp)
ldw r10,40(sp)
ldw r11,44(sp)
ldw r12,48(sp)
ldw r13,52(sp)
ldw r14,56(sp)
ldw r15,60(sp)
ldw r16,64(sp)
ldw r17,68(sp)
ldw r18,72(sp)
ldw r19,76(sp)
ldw r20,80(sp)
ldw r21,84(sp)
ldw r22,88(sp)
ldw r23,92(sp)
ldw r26,96(sp)
ldw r28,100(sp)
ldw r31,104(sp)
ldw ea,0(sp) # Special case
addi sp,sp,108
eret # Return from exception
#
# End of exception handling code.
#
################################################################
################################################################
#
# Startup code.
#
# When the system is started, Altera-supplied code initializes the
# Nios II CPU and cache memories, and then calls alt_main.
#
.global alt_main
alt_main:
wrctl status,r0 # Disable interrupts. status is register 0
wrctl ienable,r0 # Clear all bits in IENABLE. ienable is internal interrupt-enable bits
# Now copy the stub.
movia r8,oslab_internal_stub
movia r9,de2_nios2_interrupt_address
ldw r10,0(r8)
stw r10,0(r9)
ldw r10,4(r8)
stw r10,4(r9)
ldw r10,8(r8)
stw r10,8(r9)
# Initialize timer_1.
movia r8,de2_timer_1_base
movia r9,de2_timer_1_timeout_value
srli r10,r9,16
stw r10,12(r8) # Write periodh
andi r10,r9,0xffff
stw r10,8(r8) # Write periodl
movi r10,7 # Continuous, interrupt on timeout, and start
stw r10,4(r8)
# Initialize CPU for interrupts from timer_1.
movi r10,de2_timer_1_intmask
wrctl ienable,r10
movi r10,1
wrctl status,r10
# Call to main. Do not jump, main is a subroutine,
# and may execute a ret instruction.
subi sp,sp,4
stw ra,0(sp) # PUSH r31
movia r8,main
callr r8
ldw ra,0(sp) # POP r31
addi sp,sp,4
# If main returns, we will return directly to the routine
# that called us (that called alt_main).
ret
#
# End of startup code.
#
################################################################
################################################################
#
# Helper functions for initialization and thread handling.
#
.text
.align 2
.global oslab_internal_get_gp
oslab_internal_get_gp:
mov r2,gp
ret
.global oslab_begin_critical_region
oslab_begin_critical_region:
wrctl status,r0
ret
.global oslab_end_critical_region
oslab_end_critical_region:
movi r8,1
wrctl status,r8
ret
.global oslab_get_internal_globaltime
oslab_get_internal_globaltime:
movia r2,oslab_internal_globaltime
ldw r2,0(r2)
ret
.global oslab_get_internal_tickcount
oslab_get_internal_tickcount:
movia r2,oslab_internal_tickcount
ldw r2,0(r2)
ret
.global oslab_yield
oslab_yield:
trap
ret
#
# End of helper functions.
#
################################################################
#
# ********************************************************
# *** You don't have to study the code below this line ***
# ********************************************************
#
################################################################
#
# A simplified printf() replacement.
# Implements the following conversions: %c, %d, %s and %x.
# No format-width specifications are allowed,
# for example "%08x" is not implemented.
# Up to four arguments are accepted, i.e. the format string
# and three more. Any extra arguments are silently ignored.
#
# The printf() replacement relies on routines
# out_char_uart_0, out_hex_uart_0,
# out_number_uart_0 and out_string_uart_0
# in file oslab_lowlevel_c.c
#
# We need the macros PUSH and POP - definitions follow.
# PUSH reg - push a single register on the stack
.macro PUSH reg
subi sp,sp,4 # reserve space on stack
stw \reg,0(sp) # store register
.endm
# POP reg - pop a single register from the stack
.macro POP reg
ldw \reg,0(sp) # fetch top of stack contents
addi sp,sp,4 # return previously reserved space
.endm
.text
.global printf
printf:
PUSH ra # PUSH return address register r31.
PUSH r16 # R16 will point into format string.
PUSH r17 # R17 will contain the argument number.
PUSH r18 # R18 will contain a copy of r5.
PUSH r19 # R19 will contain a copy of r6.
PUSH r20 # R20 will contain a copy of r7.
mov r16,r4 # Get format string argument
movi r17,0 # Clear argument number.
mov r18,r5 # Copy r5 to safe place.
mov r19,r6 # Copy r6 to safe place.
mov r20,r7 # Copy r7 to safe place.
asm_printf_loop:
ldb r4,0(r16) # Get a byte of format string.
addi r16,r16,1 # Point to next byte
# End of format string is marked by a zero-byte.
beq r4,r0,asm_printf_end
cmpeqi r9,r4,92 # Check for backslash escape.
bne r9,r0,asm_printf_backslash
cmpeqi r9,r4,'%' # Check for percent-sign escape.
bne r9,r0,asm_printf_percentsign
asm_printf_doprint:
# No escapes present, just print the character.
movia r8,out_char_uart_0
callr r8
br asm_printf_loop
asm_printf_backslash:
# Preload address to out_char_uart_0 into r8.
movia r8,out_char_uart_0
ldb r4,0(r16) # Get byte after backslash
addi r16,r16,1 # Increase byte count.
# Having a backslash at the end of the format string
# is illegal, but must not crash our printf code.
beq r4,r0,asm_printf_end
cmpeqi r9,r4,'n' # Newline
beq r9,r0,asm_printf_backslash_not_newline
movi r4,10 # Newline
callr r8
br asm_printf_loop
asm_printf_backslash_not_newline:
cmpeqi r9,r4,'r' # Return
beq r9,r0,asm_printf_backslash_not_return
movi r4,13 # Return
callr r8
br asm_printf_loop
asm_printf_backslash_not_return:
# Unknown character after backslash - ignore.
br asm_printf_loop
asm_printf_percentsign:
addi r17,r17,1 # Increase argument count.
cmpgei r8,r17,4 # Check against maximum argument count.
# If maximum argument count exceeded, print format string.
bne r8,r0,asm_printf_doprint
cmpeqi r9,r17,1 # Is argument number equal to 1?
beq r9,r0,asm_printf_not_r5 # beq jumps if cmpeqi false
mov r4,r18 # If yes, get argument from saved copy of r5.
br asm_printf_do_conversion
asm_printf_not_r5:
cmpeqi r9,r17,2 # Is argument number equal to 2?
beq r9,r0,asm_printf_not_r6 # beq jumps if cmpeqi false
mov r4,r19 # If yes, get argument from saved copy of r6.
br asm_printf_do_conversion
asm_printf_not_r6:
cmpeqi r9,r17,3 # Is argument number equal to 3?
beq r9,r0,asm_printf_not_r7 # beq jumps if cmpeqi false
mov r4,r20 # If yes, get argument from saved copy of r7.
br asm_printf_do_conversion
asm_printf_not_r7:
# This should not be possible.
# If this strange error happens, print format string.
br asm_printf_doprint
asm_printf_do_conversion:
ldb r8,0(r16) # Get byte after percent-sign.
addi r16,r16,1 # Increase byte count.
cmpeqi r9,r8,'x' # Check for %x (hexadecimal).
beq r9,r0,asm_printf_not_x
movia r8,out_hex_uart_0
callr r8
br asm_printf_loop
asm_printf_not_x:
cmpeqi r9,r8,'d' # Check for %d (decimal).
beq r9,r0,asm_printf_not_d
movia r8,out_number_uart_0
callr r8
br asm_printf_loop
asm_printf_not_d:
cmpeqi r9,r8,'c' # Check for %c (character).
beq r9,r0,asm_printf_not_c
# Print character argument.
br asm_printf_doprint
asm_printf_not_c:
cmpeqi r9,r8,'s' # Check for %s (string).
beq r9,r0,asm_printf_not_s
movia r8,out_string_uart_0
callr r8
br asm_printf_loop
asm_printf_not_s:
asm_printf_unknown:
# We do not know what to do with other formats.
# Print the format string text.
movi r4,'%'
movia r8,out_char_uart_0
callr r8
ldb r4,-1(r16)
br asm_printf_doprint
asm_printf_end:
POP r20
POP r19
POP r18
POP r17
POP r16
POP ra
ret
#
# End of simplified printf() replacement code.
#
################################################################
.end
现在我正在研究一个小系统的这个组件,我认为我可以自己学习单个指令,但我正在寻求帮助来理解更大的图景,为什么一个事件就是一个软件异常而不是一个硬件异常,一个硬件中断,一个软件中断
# The label alt_main is defined in this file.
# There is a call to this label in the Altera-supplied startup code for Nios II.
# At label alt_main, interrupts and handlers are initialized; thereafter,
# the label main is called, starting the main program.
################################################################
#
# Definitions for important devices and addresses in this system.
#
# Uart_0 at 0x860
.equ de2_uart_0_base,0x860
# Timer_1 at 0x920, interrupt index 10 (mask 2^10 = 0x400)
.equ de2_timer_1_base,0x920
.equ de2_timer_1_intmask,0x400
# Timeout value for 0,1 ms tick-count interval (CHANGED in every version)
.equ de2_timer_1_timeout_value,4999
# Required tick count per time-slice, meaning
# the number of timer-interrupts before a thread-switch is performed
.equ oslab_ticks_per_timeslice,100
# Interrupt address at 0x800020
.equ de2_nios2_interrupt_address,0x800020
#
# End of device-address definitions
#
################################################################
################################################################
#
# Definition of variables for keeping system time etcetera.
#
.data
.align 2
.global oslab_internal_globaltime
oslab_internal_globaltime: .word 0
# Definition of variable for remembering the number of
# timer-interrupts since the last thread-switch
.data
.align 2
.global oslab_internal_tickcount
oslab_internal_tickcount: .word 0
# Definition of system (interrupt) stack, sp, and gp
.data
.align 2
oslab_internal_gp: .word 0
oslab_internal_sp: .word 0
oslab_system_stack: .fill 256,1,0
oslab_system_stacktop:
# Definition of the end-of-timeslice message.
oslab_internal_yield_message:
.asciz "\n#### Thread yielded after using %d tick%c."
#
# End of system-time variable definitions.
#
################################################################
################################################################
#
# Interrupt handling code.
#
# Stub for interrupt handler
.text
oslab_internal_stub:
movia et,oslab_exception_handler
jmp et
# The interrupt handler
oslab_exception_handler:
# Check source of exception, following the procedure
# described in the Nios II Processor Reference Handbook.
rdctl et,estatus # Check ESTATUS
andi et,et,1 # Test EPIE
beq et,r0,oslab_exception_was_not_an_interrupt
rdctl et,ipending # Check IPENDING
beq et,r0,oslab_exception_was_not_an_interrupt
# If control comes here, we have established that the
# exception was caused by an interrupt.
# Subtract 4 from ea, so that the interrupted instruction
# will be re-run when we return.
subi ea,ea,4
# Check the source of the interrupt.
# Possible source No. 1: Timer_1 (currently the only source).
rdctl et,ipending
andi et,et,de2_timer_1_intmask
bne et,r0,oslab_timer_1_interrupt
# If control comes here, we have an interrupt from an unknown source.
# This condition is IGNORED in this version of OSLAB.
eret
oslab_exception_was_not_an_interrupt:
# Test if the interrupted instruction was a TRAP
subi sp,sp,4 # PUSH r8 (instruction 1)
stw r8,0(sp) # PUSH r8 (instruction 2)
movia r8,0x003b683a # binary code for TRAP
ldw et,-4(ea) # Load interrupted instruction
cmpeq et,et,r8 # Compare to binary code for TRAP
# Result from comparison is now in et.
ldw r8,0(sp) # POP r8 (instruction 1)
addi sp,sp,4 # POP r8 (instruction 2)
# Use the comparison result in et as branch condition.
# The value in et will also be used later, to tell if the
# exception was a trap or an interrupt.
bne et,r0,oslab_trap_handler
# If control comes here, we have an exception which was not a TRAP.
# This should not normally happen.
# However, someone writing programs for the OSLAB micro-operating system
# could perhaps use unimplemented instructions. To catch unimplemented
# instructions, we insert a BREAK instruction here. This will stop execution
# unless the program is run through the debugger.
break 0
eret
oslab_timer_1_interrupt:
# Acknowledge the timer_1 interrupt.
movia et,de2_timer_1_base
stw r0,0(et)
# Save contents of R8, to get a free register for
# temporary values.
subi sp,sp,4
stw r8,0(sp) # PUSH r8
# Increase system clock.
movia r8,oslab_internal_globaltime
ldw et,0(r8)
addi et,et,1
stw et,0(r8)
# Increase tick counter.
movia r8,oslab_internal_tickcount
ldw et,0(r8)
addi et,et,1
stw et,0(r8)
# Restore original contents of R8.
ldw r8,0(sp) # POP r8
addi sp,sp,4
# Check value of tick counter,
# against the required number of ticks per time-slice.
# Note: oslab_ticks_per_timeslice is an assembler constant,
# and not a variable. Hence, no load/store-instructions here.
subi et,et,oslab_ticks_per_timeslice
# If the result from the subtraction is zero (or perhaps positive),
# then it is time to switch threads.
bge et,r0,oslab_time_to_switch
# If we fall-through here, then we have had one of those many
# timer interrupts on which we should not switch threads.
# Return to caller.
eret
oslab_time_to_switch:
# This code will now fall-through into the TRAP handler
# which performs a context switch.
#
# We will print out a message for each timer interrupt.
# To be able to tell that we had a timer interrupt, and not
# a TRAP, we set et to zero.
movi et,0
oslab_trap_handler:
# Save registers r1 through r23, plus fp, gp, ra and ea
.set noat # R1 is used here.
subi sp,sp,108 # Make room for all registers.
stw r1, 4(sp) # R1 is saved in slot 1, not slot 0.
stw r2, 8(sp)
stw r3,12(sp)
stw r4,16(sp)
stw r5,20(sp)
stw r6,24(sp)
stw r7,28(sp)
stw r8,32(sp)
stw r9,36(sp)
stw r10,40(sp)
stw r11,44(sp)
stw r12,48(sp)
stw r13,52(sp)
stw r14,56(sp)
stw r15,60(sp)
stw r16,64(sp)
stw r17,68(sp)
stw r18,72(sp)
stw r19,76(sp)
stw r20,80(sp)
stw r21,84(sp)
stw r22,88(sp)
stw r23,92(sp)
stw r26,96(sp)
stw r28,100(sp)
stw r31,104(sp)
stw ea,0(sp) # Special case, saved in slot 0.
mov r4,sp # Copy stack pointer to param1 register
movia sp,oslab_system_stacktop # Use system stack instead
# Test et to see if this was a timeout event or a TRAP.
beq et,r0,oslab_not_a_trap
# If this was a trap event, we fall through here.
# Our simplified printf is used to print a message,
# saying that the previous thread yielded parts of its time-slice.
################################################################
#
# The following code prints a nice message. Nothing more.
# This code saves and restores all registers it uses.
# You can safely ignore the following code, up to
# (but NOT including) the label oslab_not_a_trap.
#
subi sp,sp,4 # Contents of r4 must be preserved.
stw r4,0(sp) # PUSH r4.
movia r4,oslab_internal_yield_message
movia r5,oslab_internal_tickcount
ldw r5,0(r5)
movi r6,0 # Gold-plating: check if 1 tick or several ticks.
subi et,r5,1 # Do not print the s if only 1 tick.
beq et,r0,oslab_no_plural_ticks
movi r6,'s' # If 0 ticks, or 2 or more ticks, print the s.
oslab_no_plural_ticks:
call printf
ldw r4,0(sp) # POP r4
addi sp,sp,4
#
# This comment marks the end of the code for printing a nice message.
# Now comes other code, which is potentially much more interesting.
#
################################################################
# Move on to thread-switch code.
oslab_not_a_trap:
# Clear tick counter, since we are going to switch threads.
movia et,oslab_internal_tickcount
stw r0,0(et)
# Now it is time to execute the thread-switch code.
# We use the more general callr, rather than call.
movia et,oslab_internal_threadswitch
callr et # Call thread switch routine written in C
mov sp,r2 # Copy return value to stack pointer
# Yes, the system stack pointer is lost,
# but who cares? We will not need it any more.
# restore registers
ldw r1, 4(sp)
ldw r2, 8(sp)
ldw r3,12(sp)
ldw r4,16(sp)
ldw r5,20(sp)
ldw r6,24(sp)
ldw r7,28(sp)
ldw r8,32(sp)
ldw r9,36(sp)
ldw r10,40(sp)
ldw r11,44(sp)
ldw r12,48(sp)
ldw r13,52(sp)
ldw r14,56(sp)
ldw r15,60(sp)
ldw r16,64(sp)
ldw r17,68(sp)
ldw r18,72(sp)
ldw r19,76(sp)
ldw r20,80(sp)
ldw r21,84(sp)
ldw r22,88(sp)
ldw r23,92(sp)
ldw r26,96(sp)
ldw r28,100(sp)
ldw r31,104(sp)
ldw ea,0(sp) # Special case
addi sp,sp,108
eret # Return from exception
#
# End of exception handling code.
#
################################################################
################################################################
#
# Startup code.
#
# When the system is started, Altera-supplied code initializes the
# Nios II CPU and cache memories, and then calls alt_main.
#
.global alt_main
alt_main:
wrctl status,r0 # Disable interrupts. status is register 0
wrctl ienable,r0 # Clear all bits in IENABLE. ienable is internal interrupt-enable bits
# Now copy the stub.
movia r8,oslab_internal_stub
movia r9,de2_nios2_interrupt_address
ldw r10,0(r8)
stw r10,0(r9)
ldw r10,4(r8)
stw r10,4(r9)
ldw r10,8(r8)
stw r10,8(r9)
# Initialize timer_1.
movia r8,de2_timer_1_base
movia r9,de2_timer_1_timeout_value
srli r10,r9,16
stw r10,12(r8) # Write periodh
andi r10,r9,0xffff
stw r10,8(r8) # Write periodl
movi r10,7 # Continuous, interrupt on timeout, and start
stw r10,4(r8)
# Initialize CPU for interrupts from timer_1.
movi r10,de2_timer_1_intmask
wrctl ienable,r10
movi r10,1
wrctl status,r10
# Call to main. Do not jump, main is a subroutine,
# and may execute a ret instruction.
subi sp,sp,4
stw ra,0(sp) # PUSH r31
movia r8,main
callr r8
ldw ra,0(sp) # POP r31
addi sp,sp,4
# If main returns, we will return directly to the routine
# that called us (that called alt_main).
ret
#
# End of startup code.
#
################################################################
################################################################
#
# Helper functions for initialization and thread handling.
#
.text
.align 2
.global oslab_internal_get_gp
oslab_internal_get_gp:
mov r2,gp
ret
.global oslab_begin_critical_region
oslab_begin_critical_region:
wrctl status,r0
ret
.global oslab_end_critical_region
oslab_end_critical_region:
movi r8,1
wrctl status,r8
ret
.global oslab_get_internal_globaltime
oslab_get_internal_globaltime:
movia r2,oslab_internal_globaltime
ldw r2,0(r2)
ret
.global oslab_get_internal_tickcount
oslab_get_internal_tickcount:
movia r2,oslab_internal_tickcount
ldw r2,0(r2)
ret
.global oslab_yield
oslab_yield:
trap
ret
#
# End of helper functions.
#
################################################################
#
# ********************************************************
# *** You don't have to study the code below this line ***
# ********************************************************
#
################################################################
#
# A simplified printf() replacement.
# Implements the following conversions: %c, %d, %s and %x.
# No format-width specifications are allowed,
# for example "%08x" is not implemented.
# Up to four arguments are accepted, i.e. the format string
# and three more. Any extra arguments are silently ignored.
#
# The printf() replacement relies on routines
# out_char_uart_0, out_hex_uart_0,
# out_number_uart_0 and out_string_uart_0
# in file oslab_lowlevel_c.c
#
# We need the macros PUSH and POP - definitions follow.
# PUSH reg - push a single register on the stack
.macro PUSH reg
subi sp,sp,4 # reserve space on stack
stw \reg,0(sp) # store register
.endm
# POP reg - pop a single register from the stack
.macro POP reg
ldw \reg,0(sp) # fetch top of stack contents
addi sp,sp,4 # return previously reserved space
.endm
.text
.global printf
printf:
PUSH ra # PUSH return address register r31.
PUSH r16 # R16 will point into format string.
PUSH r17 # R17 will contain the argument number.
PUSH r18 # R18 will contain a copy of r5.
PUSH r19 # R19 will contain a copy of r6.
PUSH r20 # R20 will contain a copy of r7.
mov r16,r4 # Get format string argument
movi r17,0 # Clear argument number.
mov r18,r5 # Copy r5 to safe place.
mov r19,r6 # Copy r6 to safe place.
mov r20,r7 # Copy r7 to safe place.
asm_printf_loop:
ldb r4,0(r16) # Get a byte of format string.
addi r16,r16,1 # Point to next byte
# End of format string is marked by a zero-byte.
beq r4,r0,asm_printf_end
cmpeqi r9,r4,92 # Check for backslash escape.
bne r9,r0,asm_printf_backslash
cmpeqi r9,r4,'%' # Check for percent-sign escape.
bne r9,r0,asm_printf_percentsign
asm_printf_doprint:
# No escapes present, just print the character.
movia r8,out_char_uart_0
callr r8
br asm_printf_loop
asm_printf_backslash:
# Preload address to out_char_uart_0 into r8.
movia r8,out_char_uart_0
ldb r4,0(r16) # Get byte after backslash
addi r16,r16,1 # Increase byte count.
# Having a backslash at the end of the format string
# is illegal, but must not crash our printf code.
beq r4,r0,asm_printf_end
cmpeqi r9,r4,'n' # Newline
beq r9,r0,asm_printf_backslash_not_newline
movi r4,10 # Newline
callr r8
br asm_printf_loop
asm_printf_backslash_not_newline:
cmpeqi r9,r4,'r' # Return
beq r9,r0,asm_printf_backslash_not_return
movi r4,13 # Return
callr r8
br asm_printf_loop
asm_printf_backslash_not_return:
# Unknown character after backslash - ignore.
br asm_printf_loop
asm_printf_percentsign:
addi r17,r17,1 # Increase argument count.
cmpgei r8,r17,4 # Check against maximum argument count.
# If maximum argument count exceeded, print format string.
bne r8,r0,asm_printf_doprint
cmpeqi r9,r17,1 # Is argument number equal to 1?
beq r9,r0,asm_printf_not_r5 # beq jumps if cmpeqi false
mov r4,r18 # If yes, get argument from saved copy of r5.
br asm_printf_do_conversion
asm_printf_not_r5:
cmpeqi r9,r17,2 # Is argument number equal to 2?
beq r9,r0,asm_printf_not_r6 # beq jumps if cmpeqi false
mov r4,r19 # If yes, get argument from saved copy of r6.
br asm_printf_do_conversion
asm_printf_not_r6:
cmpeqi r9,r17,3 # Is argument number equal to 3?
beq r9,r0,asm_printf_not_r7 # beq jumps if cmpeqi false
mov r4,r20 # If yes, get argument from saved copy of r7.
br asm_printf_do_conversion
asm_printf_not_r7:
# This should not be possible.
# If this strange error happens, print format string.
br asm_printf_doprint
asm_printf_do_conversion:
ldb r8,0(r16) # Get byte after percent-sign.
addi r16,r16,1 # Increase byte count.
cmpeqi r9,r8,'x' # Check for %x (hexadecimal).
beq r9,r0,asm_printf_not_x
movia r8,out_hex_uart_0
callr r8
br asm_printf_loop
asm_printf_not_x:
cmpeqi r9,r8,'d' # Check for %d (decimal).
beq r9,r0,asm_printf_not_d
movia r8,out_number_uart_0
callr r8
br asm_printf_loop
asm_printf_not_d:
cmpeqi r9,r8,'c' # Check for %c (character).
beq r9,r0,asm_printf_not_c
# Print character argument.
br asm_printf_doprint
asm_printf_not_c:
cmpeqi r9,r8,'s' # Check for %s (string).
beq r9,r0,asm_printf_not_s
movia r8,out_string_uart_0
callr r8
br asm_printf_loop
asm_printf_not_s:
asm_printf_unknown:
# We do not know what to do with other formats.
# Print the format string text.
movi r4,'%'
movia r8,out_char_uart_0
callr r8
ldb r4,-1(r16)
br asm_printf_doprint
asm_printf_end:
POP r20
POP r19
POP r18
POP r17
POP r16
POP ra
ret
#
# End of simplified printf() replacement code.
#
################################################################
.end
让我们先谈谈“语境”;那么陷阱就很容易理解了
当您编写传统的汇编代码时,最终会用对代码的该部分重要的值填充各个寄存器
子例程调用是一种特殊指令,通常通过直接指定子例程的地址来明确指定要调用的子例程的位置。当该代码调用子程序时,子程序调用本身会将程序计数器保存在众所周知的位置,以便子程序在完成时可以将PC设置为保存的PC,从而将控制权返回到调用点
子例程调用点处已填充的寄存器在子例程完成后包含这些相同的值通常是有用的。子例程本身可能会损坏其中的一些或大部分,并且通常不知道在调用点什么是重要的。为了保留已填充的寄存器,程序员可能会在调用指令之前插入指令,以将这些寄存器保存在安全位置,并在程序员调用之后插入指令,以恢复保存的寄存器。有时,甚至控制处理器状态的条件代码或其他模式位(在x86上,“方向”位就是其中之一)也需要保存并还原
所有要保存的信息都是子程序调用点处计算的“上下文”
保存的PC和其他寄存器的存储位置通常位于由机器管理的下推堆栈上,或者仅由(但有用的)编程约定管理。在没有堆栈或没有这种约定的机器上,这些位置可以简单地由程序员选择。重要的是在某处留出空间来存储“上下文”,并从中恢复上下文
陷阱现在很容易理解。trap指令(或所谓的“软件中断”)工作就像子程序调用一样,但是目标位置没有直接编码到陷阱中。陷阱指令类似于调用指令,因为有一个操作码,可能还有一个操作数字段来区分陷阱。程序员可以在其代码中放置这样的陷阱,以使陷阱例程(不管它是什么,通常是一个OS服务函数)运行。因此,就像子程序调用一样,它可以将PC保存在安全的地方。可以坚持让程序员在trap调用之前/之后决定保存/恢复哪些寄存器/上下文。然而,陷阱例程通常是由程序员构建的,与编写陷阱调用的程序员无关,并且通常执行复杂的任务,供大量用户使用,因此方便性是关键。因此,这项工作通常由陷阱例程完成,该例程保存所有寄存器、条件代码和模式位(“完整上下文”),执行其工作(修改保存的完整上下文以显示任何结果),恢复完整上下文,并将控制权传递回调用程序;因此,用户程序员不必方便地完成这项工作。
这是如此有用和常见,以至于许多处理器都为陷阱提供了特殊支持,这会导致保存上下文的关键部分(其余部分由陷阱例程完成)
所以,陷阱很容易使用:只需在可能对服务的子例程调用进行编码的地方对其进行编码,它们的效果就会发生。(通常陷阱例程提供程序必须在硬件中的某个位置设置特殊向量,陷阱指令会自动使用这些向量来查找陷阱例程。这在用户程序启动之前就已经完成了)
陷阱(注意:不是指令!)是由于程序条件而发生的动作。例如,如果用户程序除以零,CPU可能会强制对特殊例程进行隐含的陷阱调用,以处理除以零的操作。(它可能修补结果,或中止程序,或执行任何其他认为有用的操作)。这样的陷阱只适用于陷阱指令;它选择的特定矢量通常由特定的陷阱条件决定。请注意,陷阱与处理器看到的指令流同步发生,如程序员提供的。还请注意,对于陷阱,无法方便地建立用户级上下文保存和恢复,特别是在陷阱种类繁多的情况下。因此,对于此类陷阱,上下文保存/恢复实际上总是由陷阱例程完成
(硬件)中断是一个异步事件。它作为陷阱在编程指令流的任意位置实现。通过保存完整上下文,中断的陷阱例程(称为“中断例程”)可以处理异步条件(为磁盘提供服务等),恢复完整上下文,并且用户代码可以像从未发生中断一样继续
所以,陷阱基本上只是保存和恢复上下文的子例程调用。问题是,何时会发生,如何发生