Process information when interrupted: stack or process control block - process

I am studying an Operating System course and we have this chapter about Processes. In this chapter we define the Process Control Block, which keeps the information about a process such as the program counter, content of registers, state, priority and so on. In this chapter it says that when the processor switches to another process (by interrupt), information will be saved in this process control block (PC, registers,...). In another chapter (1.4 Interrupts) it says when a process gets interrupted the PSW, PC and registers get put on the stack and when processor retakes control of this process it takes it from the stack.
It seems to be there are 2 different explanations here for what happens when an interrupt occurs. Do they both happen simultaneously or what? Can anyone explain this to me?
Thanks in advance
Sander

Think of an interrupt as a function call with the difference that it stores more information on the stack and occurs at any time breaking into the normal flow of the program instructions. So, if interrupt handler function decides just to return from the interrupt call, the state is restored from the stack.
Otherwise, if inside the interrupt call, OS decides to preempt the current user process, it saves all the process state to PCB and switch the stack to another process.
BTW, switching to another process can happen not only by interrupt but during any normal call to OS kernel API (syscall).

Related

Switching context inside an ISR on Cortex-M

I'm trying to write a barebones round-robin scheduler for the Cortex-M using the CodeSourcery GCC toolchain. My scheduler uses the SysTick to fire an interrupt after the expiry of a time slice and the context switching takes place inside the ISR. To keep things simple, I am using only the main stack pointer (MSP) for everything.
I am stuck in determining how to handle loading the new context on the Cortex-M3. According to the Cortex-M3 Technical Reference Manual (TRM) the process pushes the PC, LR and status registers onto the current stack on the entry to the ISR.
If I push the rest of the registers to save the context of the present task and load a new SP value from the next task's control block how would I go about restoring the rest of its context?
According to what I understand, I need to pop out the registers I push (say {r4-r11}) and the processor will push out the rest (including the return address of the new task (LR) and status registers) automatically when the ISR returns. So I'm assuming I just need to execute a BX after I'm done to switch tasks?
Here is what it says on the TRM:
Exception returns occur when one of the following instructions loads a value of 0xFFFFFFFX into the PC when 1) POP/LDM which includes loading the PC 2) LDR with PC as a destination 3) BX with any register.
How do I go about loading the EXC_RETURN value? Should I just push it on to the stack (as it supposedly does here)? Assuming I've popped out the registers I've pushed via software, how does the Cortex go about popping the registers it has saved? In general, how do I restore a task's context?
I've tried reading the TRM and other ARM references but they seem unclear.
It is indeed quite complicated. I am writing a book about the FreeRTOS operating system running on Cortex-M cores. I have written a chapter about this. From reading your question I believe this chapter will help you:
Reading a bit more and some help on the #ARM IRC channel later I was able to understand the exception return mechanism. Here is what I understand.
As mentioned in the CM3 TRM, the core pushes the registers r0-r3, r12 along with the status, LR and PC on to the current process stack when an exception is registered. Thus, upon exception entry the stack contains 8 words which includes the LR containing the address to return to. The hardware pop mechanism essentially reverses the same action i.e. it pops out the last 8 words during which it loads the LR into the PC to return to the interrupted function.
Thus, the context can be switched simply by moving the stack pointer to an appropriate location such that the current stack frame resembles the stack frame of a task which has just been interrupted i.e. contains the exact words in the same order.
Upon exception entry and after saving the registers on to the stack, the LR is loaded with the EXC_RETURN value. This value contains special status flags to indicate return conditions. This is also used to indicate the end of the interrupt. That is, if the ISR needs to return to a task and switch the stack pointer from the current (MSP) to the PSP it can load the LR with the appropriate EXC_VALUE (indicated in TRM) and simply do a BX LR to switch states.
Here is a code fragment that does exactly what you need - https://github.com/DISTORTEC/distortos/blob/master/source/architecture/ARM/ARMv7-M/ARMv7-M-PendSV_Handler.cpp - it works as you described:
exception entry automatically stacks some registers,
you manually stack remaining registers,
you switch SP,
you unstack "remaining" registers,
exception return unstacks the rest of registers.

understanding the concept of running a program in interrupt handler

Early Cisco routers running IOS operating system enhanced their packet processing speed by doing packet switching within the interrupt handler instead in "regular" operating system process. Doing packet processing in interrupt handler ensured that context switching within operating system does not affect the packet processing. As I understand, interrupt handler is a piece of software in operating system meant for handling the interrupts. How to understand the concept of packet switching done within the interrupt handler?
use of interrupts is preferred when an event requires some immediate attention by the operating system, or a program which installed an interrupt service routine. This as opposed to polling, where software checks periodically whether a condition exists, which indicates that the event has occurred.
interrupt service routines aren't commonly meant to do a lot of work themselves. They are rather written to reach their end as quickly as possible, so that normal execution can resume. "normal execution" meaning, the location and state previous processing was interrupted when the interrupt occurred. reason is that it must be avoided that the same interrupt occurs again while its handler is still executed, or it may be ignored, or lead to incorrect results, or even worse, to software failure (crashes). So what an interrupt service routine usually does is, reading any data associated with that event and storing it in a queue, signalling that the queue experienced mutation, and setting things such that another interrupt may occur, then resume by restoring pre-interrupt context. the queued data, associated with that interrupt, can now be processed asynchronously, without risking that interrupts pile up.
The following is the procedure for executing interrupt-level switching:
Look up the memory structure to determine the next-hop address and outgoing interface.
Do an Open Systems Interconnection (OSI) Layer 2 rewrite, also called MAC rewrite, which means changing the encapsulation of the packet to comply with the outgoing interface.
Put the packet into the tx ring or output queue of the outgoing interface.
Update the appropriate memory structures (reset timers in caches, update counters, and so forth).
The interrupt which is raised when a packet is received from the network interface is called the "RX interrupt". This interrupt is dismissed only when all the above steps are executed. If any of the first three steps above cannot be performed, the packet is sent to the next switching layer. If the next switching layer is process switching, the packet is put into the input queue of the incoming interface for process switching and the interrupt is dismissed. Since interrupts cannot be interrupted by interrupts of the same level and all interfaces raise interrupts of the same level, no other packet can be handled until the current RX interrupt is dismissed.
Different interrupt switching paths can be organized in a hierarchy, from the one providing the fastest lookup to the one providing the slowest lookup. The last resort used for handling packets is always process switching. Not all interfaces and packet types are supported in every interrupt switching path. Generally, only those that require examination and changes limited to the packet header can be interrupt-switched. If the packet payload needs to be examined before forwarding, interrupt switching is not possible. More specific constraints may exist for some interrupt switching paths. Also, if the Layer 2 connection over the outgoing interface must be reliable (that is, it includes support for retransmission), the packet cannot be handled at interrupt level.
The following are examples of packets that cannot be interrupt-switched:
Traffic directed to the router (routing protocol traffic, Simple Network Management Protocol (SNMP), Telnet, Trivial File Transfer Protocol (TFTP), ping, and so on). Management traffic can be sourced and directed to the router. They have specific task-related processes.
OSI Layer 2 connection-oriented encapsulations (for example, X.25). Some tasks are too complex to be coded in the interrupt-switching path because there are too many instructions to run, or timers and windows are required. Some examples are features such as encryption, Local Area Transport (LAT) translation, and Data-Link Switching Plus (DLSW+).
More here: http://www.cisco.com/c/en/us/support/docs/ios-nx-os-software/ios-software-releases-121-mainline/12809-tuning.html

Do threads/processes have to disable interrupts while executing a critical section

Let us consider a scenario:-
A Kernel thread acquires a lock and is in the middle of a critical section when an interrupt occurs.
The interrupt handler runs and arrives at the same critical section and tries to acquire lock and go to sleep.
Can this happen or are interrupts disabled during a critical section ?
what steps are taken to to avoid it ?
// Some Code
Acquire_lock()
Critical section //Interrupt occurs and arrives to acquire the same lock.
Disable_lock()
You would never allow code that holds a lock to be interrupted by code that attempts to acquire that same lock. If you mean inside the OS, it may require disabling all interrupts in code that interacts with objects that are also manipulated by interrupt handlers.
User-space threads and processes have no such issue. No interrupt handler acquires a lock that user-space threads can acquire. And if a thread that holds a user-space lock is interrupted, it will release it as soon as it gets rescheduled -- the user-space thread is still ready-to-run.

How does VxWorks prioritize interrupt bottom-halves?

Suppose I have two tasks, 'A' and 'B', of differing priority executing on SMP-supported VxWorks. Both 'A' and 'B' issue a command to an I/O device (such as a disk or NIC) and both block waiting for results. That is, both 'A' and 'B' are blocked at the same time. Some time later, the I/O device raises an interrupt and the ISR is invoked. The ISR then dispatches deferred work (aka "bottom-half") to a worker-task. Question: What is the priority of the worker-task?
VxWorks Device Driver Developer's Guide is a bit vague. It appears that the priority of the worker-task is set up a-priori. There are no automatic inheritance mechanisms that will increase the priority of the worker-task based upon the priorities of tasks ('A' and 'B') that are blocked waiting for results. This is similar to how threaded interrupt priorities work in PREEMPT_RT Linux. However, both QNX Neutrino and LynxOS will schedule the worker-task with the maximum priority of the blocked tasks-- Ex. priority(worker) = max_priority(A, B).
Can anyone clarify?
It depends exactly on which mechanism the "ISR dispatched deferred work" uses.
If a semaphore/messageQueue/Event is used, then the recipient task (A or B) will run at the priority specified when the task was created. In this scenario, the interrupt is essentially finished, and the task (A and/or B) are ready to run.
Whichever task is has the highest priority will get to run and perform it's work. Note that the task doesn't have access to any information from the interrupt context. If you use global structures (yuk) or pass data via a message queue, then the task could access those elements.
The network stack task (tNetTask) uses this approach, and a semaphore signals tNetTask when a packet has been received. When tNetTask has processed the packet (packet reassembly, etc...), it is then forwarded to whichever task is waiting on the corresponding socket.
It is possible to defer work from an ISR to tExcTask (via a call to excJobAdd). Note that with this approach, excJobAdd takes the pointer to a function and executes the function in the context of the tExcTask (which is at the highest priority in the system). It does not act as a self-contained task.
Note that some things like file systems, SCSI drivers, USB, etc... are much more than a simple driver with interrupts. They include a number of different components that unfortunately also increases complexity.

Cancel thread with read() operation on serial port

in my Cocoa project, I communicate with a device connected to a serial port. Now, I am waiting for the serial device to send a particular message of some bytes. For the read operation (and the reaction for once the desired message has been received), I created a new thread. On user request, I want to be able to cancel the thread.
As Apple suggests in the docs, I added a flag to the thread dictionary, periodically check if the flag has been set and if so, call [NSThread exit]. This works fine.
Now, the thread may be stuck waiting for the serial device to finally send the 12 byte message. The read call looks like this:
numBytes = read(fileDescriptor, buffer, 12);
Once the thread starts reading from the device, but no data comes in, I can set the flag to tell the thread to finish, but the thread is not going to read the flag unless it finally received at least 12 bytes of data and continues processing.
Is there a way to kill a thread that currently performs a read operation on a serial device?
Edit for clarification:
I do not insist in creating a separate thread for the I/O operations with the serial device. If there is a way to encapsulate the operations such that I am able to "kill" them if the user presses a cancel button, I am perfectly happy.
I am developing a Cocoa application for desktop Mac OS X, so no restrictions regarding mobile devices and their capabilities apply.
A workaround would be to make the read function return immediately if there are no bytes to read. How can I do this?
Use select or poll with a timeout to detect when the descriptor is ready for reading.
Set the timeout to (say) half a second and call it in a loop while checking to see if your thread should exit.
Asynchronous thread cancellation is almost always a bad idea. Try to stick with event-driven interfaces (and, if necessary, timeouts).
This is exactly what the pthread_cancel interface was designed for. You'll want to wrap the block with read in pthread_cleanup_push and pthread_cleanup_pop in order that you can safely clean up if the thread is cancelled, and also disable cancellation (with pthread_setcancelstate) in other code that runs in this thread that you don't want to be cancellable. This can be a pain if proper cleanup would involve multiple call frames; it essentially forces you to use pthread_cleanup_push at every call level and structure your thread code like C++ or Java with try/catch style exception handling.
An alternative approach would be to install a signal handler for an otherwise-unused signal (like SIGUSR1 or one of the realtime signals) without the SA_RESTART flag, so that it interrupts syscalls with EINTR. The signal handler itself can be a complete no-op; the only purpose of it is to interrupt things. Then you can use pthread_kill to interrupt the read (or any other syscall) in a particular thread. This has the advantage that you don't have to switch your code to using C++/Java-type idioms. You can handle the EINTR error by checking a flag (indicating whether the thread was requested to abort) and resume the read if the flag is not set, or return an error code that causes the caller to clean up and eventually pthread_exit.
If you do use interrupting signal handlers, make sure all your syscalls that can return EINTR are wrapped in loops that retry (or check the abort flag and optionally retry) on EINTR. Otherwise things can break badly.