Most of these are obvious, but let's state them here for completeness:
Interrupt service routines generally manipulate O/S data structures.
Thus they must (like system calls) be executed mutually exclusively. The
way they do this is by turning off interrupts (just like system calls do).
Remember this is done by storing a PS with interrupts disabled in the dispatch
table vectors.
Interrupts must not be disabled too long as we may miss other interrupts
(causing potential problems like loss of data from buffer overflow).
An ISR must never cause itself to block (by, for example, calling wait()
on a semaphore). The reason is simple: it is possible that the process
that was running at the time of the interrupt was the null process. Blocking
the null process would violate the rule that there must always be at least
one available process to run at any time.
It is possible that the ISR will cause a reschedule (say, for example,
by readying a process previously blocked on I/O). If this is the case,
the ISR had better be sure that any O/S data structures it was manipulating
are in a consistent state before the reschedule occurs. The reason is that
when the rescheduled process begins execution (which may be right away),
it will likely be executing with interrupts enabled. In that case another
interrupt may occur (and thus the ISR or a system call could execute again
manipulating the same O/S data structure).
On some architectures, the native code for handling interrupts must be preserved (i.e. PC BIOS calls). Unfortunately, great care must be taken on these architectures to properly handle multiple processes and rescheduling. Sometimes this means not even calling the BIOS calls, or setting the system up in such a way that rescheduling is prevented.