In Ada you have device drivers, which manipulate device registers and respond to interrupts. Ada assumes that the device registers are shared memory, and an interrupt is essentially a hardware protected procedure call (devices are kinda like hardware
Ada has lots of facilities for specifying the implementation of data types, which means you can do nifty abstraction on top of registers that are just low-level integers of some kind. For enumerated types you do something like:
type Function_T is (Read, Write, Seek); for Function_T use (Read=>1,Write=>2,Seek=>3);
Records are a little more long winded, but still fairly obvious. You start by defining the record as normal, and then you add additional stuff; for example you define the size, alignment, and bit order, which are attributes of the record. The compiler will then complain if it can’t do what you’ve asked. There’s an example on slide 7 on the slides which makes it reasonably obvious. On slide 8 there’s also an example of actually using it. Because the system is using memory mapped I/O you need to specify where to put the record (you specify where the register is, basically. Imagine that you are overlaying this record definition on top of data that is already there). You use the
System package to do this, the declaration for which is in the slide.
When an interrupt occurs, it is delivered to the Ada interrupt handler. There is a latency involved between the generation and delivery of an interrupt.
While an interrupt is handled, further interrupts from the same source are blocked. That interrupt either gets queued or you lose it; depending on the device.
Certain interrupts are reserved – you can’t do anything with them. For example the clock interrupt that implements the delay statement. Unsurprising really. There’s also a default interrupt handler for every interrupt which tends to throw an error or something.
Each interrupt has an implementation-specific unique identifier. It might be something like the address of the interrupt vector.
Identifying something as an interrupt handler is done using pragmas:
pragma Interrupt_Handler(Handler_Name);appears in the
adsfile of the protected unit. It allows dynamic association of the named parameterless procedure as an interrupt handler for one or more interrupts.
pragma Attach_Handler(Handler_Name, Expression);appears in the specification or body of a protected unit. Similar to the above I think but restricted to just one handler; the handler is attached when the protected object is created.
There’s then an
Ada.Interrupts package for dealing with dynamic attachment; but you can only dynamically attach if you’ve specified in the specification that that’s allowed (using the above 2 pragmas).
There’s then an example of a Device Driver in the slides, it’s an ADC. It’s pretty big. At first it uses static allocation but then he explains how to do it using dynamic allocation as well (slide 28).
The system in the practicals is actually port based, not memory based. However you can still use records in the same way (or rather, he wants us to), and we are given an Ada package to sort the rest out for us, I think…
In Ada you need to be a bit more concerned about memory management than you do in Java (urgh…).
In Ada nothing is garbage collected. So if you dynamically create tasks they will never be collected up, so you will leak memory, by default.
The only time you get garbage collection is when an access type goes out of scope.
In Ada the heap is represented by one or more storage pools. Not actually sure what the point of this all is though; not sure what you can do about it. I think memory can be explicitly deallocated if you define your own storage pools to put stuff in; though you may be able to deallocate it on the standard heap.