Page 1 of 2

Implementation of event listener

Posted: Sun Jul 08, 2018 11:00 pm
by quadrant
Assembly:

Consider the Intel 8080. To handle an interrupt (for example from the keyboard), you simply have the keyboard pull the 'INT' pin high and place a 'vector' on the databus. The vector points to the location of the interrupt service routine (ISR) in the assembly program code. See this StackOverflow answer for more details.

High level language:

Consider the concept of attaching event listeners. For example consider Javascript's keydown event listener. An example:

Code: Select all

document.getElementById("demo").addEventListener("keydown", myFunction);

function myFunction() {
    document.getElementById("demo").style.backgroundColor = "red";
}
When a key is pressed down while the 'demo' element is in focus, myFunction gets called.

Relationship:

How does the concept of attaching event listeners relate to the concept of having ISRs in assembly?

I assume that the function that directly responds to a keyboard interrupt is 1) part of the OS kernel and 2) written in C. (Are these assumptions correct?)

How does a user program tell this OS interrupt handler to alert it upon interrupt? Does the OS function keep a variable size list of functions to callback when it handles an interrupt? Does then addEventListener append the user callback function to the OS function's list?

Note: I chose the Intel 8080 as an example specifically because in contrast to modern CPUs, it does not have fancy capabilities to aid with OS related functionality.

Re: Implementation of event listener

Posted: Sun Jul 08, 2018 11:36 pm
by alexfru
quadrant wrote: How does the concept of attaching event listeners relate to the concept of having ISRs in assembly?
I'm not sure I understand the question.
quadrant wrote: I assume that the function that directly responds to a keyboard interrupt is 1) part of the OS kernel and 2) written in C. (Are these assumptions correct?)
The routine (since functions are often understood as returning values) is usually part of the OS. But it doesn't have to be (or there may be no OS at all). It really depends on the OS and the CPU. The 8080 (or Z80 or 8086) doesn't have any protection mechanisms and so on these CPUs the OS cannot have exclusive control over interrupts and their handling. When that's the case, the handling routine can be anywhere. Many x86 DOS games used their own keyboard interrupt handlers in order to sense both key presses and releases (and therefore know when several keys are held down simultaneously). While DOS and BIOS could provide this functionality, it's not there.
quadrant wrote: How does a user program tell this OS interrupt handler to alert it upon interrupt? Does the OS function keep a variable size list of functions to callback when it handles an interrupt?
It's a possibility. In modern OSes, user programs typically receive (or should receive) keyboard input only if it belongs to them (e.g. if the program's window is in focus). The program can ask the OS to call one of its routines when the input is available. Or the program can just ask if there's any available with an option of waiting until there's anything to read.

Re: Implementation of event listener

Posted: Sun Jul 08, 2018 11:59 pm
by quadrant
alexfru wrote:The program can ask the OS to call one of its routines when the input is available. Or the program can just ask if there's any available with an option of waiting until there's anything to read.
This is what I don't get :) ! How can the user program tell the OS to call a specific user function? Is the OS keeping some kind of list with all the callbacks requested when a specific interrupt is received? For example if you have five buttons, each with a different function to be called on a 'mouse click', how does the OS keep track of these five callbacks for a 'mouse click' interrupt?
alexfru wrote:Or the program can just ask if there's any available with an option of waiting until there's anything to read.
This one is polling which I want to avoid.

Re: Implementation of event listener

Posted: Mon Jul 09, 2018 12:08 am
by quadrant
alexfru wrote:
quadrant wrote: How does the concept of attaching event listeners relate to the concept of having ISRs in assembly?
I'm not sure I understand the question.
Basically I have a good understanding of how interrupts and interrupt service routines work in assembly. What I don't understand is how event listeners as used in high level languages get translated to assembly.

Re: Implementation of event listener

Posted: Mon Jul 09, 2018 12:17 am
by Korona
Yes, the OS generally needs to maintain lists of "event listeners" that are called when certain things happen. Note that instead of JavaScript-style callbacks, interfaces like epoll (see the Linux man page) are often used at the OS level. Furthermore, user spaces programs usually do not listen to IRQ events directly; instead, the OS provides interfaces to listen for events at a higher level, e.g. there could be a mechanism to listen for key presses instead of keyboard IRQs.

Re: Implementation of event listener

Posted: Mon Jul 09, 2018 12:21 am
by klange
quadrant wrote:Basically I have a good understanding of how interrupts and interrupt service routines work in assembly. What I don't understand is how event listeners as used in high level languages get translated to assembly.
You're asking a very high level question about a subject that involves many different layers of abstraction. It's sort of like saying you understand the basics of a chemical reaction but asking how to land a rocket on Mars - while the two concepts are linked, there are many steps in the middle.

This question (the keyboard one, not the rockets), as part of a larger question that goes into other topics like networking, was once popular during software job interviews, so much so that in-depth answers covering multiple OSes were written in considerably more detail than could ever be expected in an interview setting. I once used it as the subject of a talk about my OS, which goes all the way down to the keyboard interrupts and up to a user application.

Re: Implementation of event listener

Posted: Mon Jul 09, 2018 12:31 am
by quadrant
alexfru wrote: The routine (since functions are often understood as returning values) is usually part of the OS. But it doesn't have to be (or there may be no OS at all). It really depends on the OS and the CPU.
Hmmm.... I am aware of Arduino's attachInterrupt, which behind the scenes is AVR's ISR macro. I tried reading through the source code of avr/interrupt.h but I haven't got a clue what it is doing...

Arduino's attachInterrupt:

Code: Select all

const byte ledPin = 13;
const byte interruptPin = 2;
volatile byte state = LOW;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
}

void loop() {
  digitalWrite(ledPin, state);
}

void blink() {
  state = !state;
}
AVR's ISR macro:

Code: Select all

/** \name Macros for writing interrupt handler functions */


#if defined(__DOXYGEN__)
/** \def ISR(vector [, attributes])
    \ingroup avr_interrupts

    Introduces an interrupt handler function (interrupt service
    routine) that runs with global interrupts initially disabled
    by default with no attributes specified.

    The attributes are optional and alter the behaviour and resultant
    generated code of the interrupt routine. Multiple attributes may
    be used for a single function, with a space seperating each
    attribute.

    Valid attributes are ISR_BLOCK, ISR_NOBLOCK, ISR_NAKED and
    ISR_ALIASOF(vect).

    \c vector must be one of the interrupt vector names that are
    valid for the particular MCU type.
*/
#  define ISR(vector, [attributes])
#else  /* real code */

#if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || (__GNUC__ > 4)
#  define __INTR_ATTRS used, externally_visible
#else /* GCC < 4.1 */
#  define __INTR_ATTRS used
#endif

#ifdef __cplusplus
#  define ISR(vector, ...)            \
    extern "C" void vector (void) __attribute__ ((signal,__INTR_ATTRS)) __VA_ARGS__; \
    void vector (void)
#else
#  define ISR(vector, ...)            \
    void vector (void) __attribute__ ((signal,__INTR_ATTRS)) __VA_ARGS__; \
    void vector (void)
#endif

#endif /* DOXYGEN */

Re: Implementation of event listener

Posted: Mon Jul 09, 2018 12:36 am
by quadrant
@klange Interesting, I will read up on the links you've shared. Thanks!

Edit: Found the video of your talk and currently watching.

Re: Implementation of event listener

Posted: Mon Jul 09, 2018 12:59 am
by alexfru
quadrant wrote: Hmmm.... I am aware of Arduino's attachInterrupt, which behind the scenes is AVR's ISR macro. I tried reading through the source code of avr/interrupt.h but I haven't got a clue what it is doing...

Code: Select all

...
Perhaps you should learn some C and how this particular CPU handles interrupts?

Re: Implementation of event listener

Posted: Mon Jul 09, 2018 9:38 am
by Schol-R-LEA
quadrant wrote:
alexfru wrote:Or the program can just ask if there's any available with an option of waiting until there's anything to read.
This one is polling which I want to avoid.
I assume you mean the instance of asking for an available value without waiting; if it does wait (which is the common case in purely console-based systems, but generally undesirable in GUI software), the thread¹ is usually suspended by the scheduler until the event occurs.

Note that internally, most GUI software have (or at least in the past had) a central event loop, which does in fact operate on polling; most OSes didn't have any form of callbacks (and still don't AFAICT), so this was a necessary approach. However, most GUI frameworks hide this polling loop in one way or another. For example, my understanding is that in older implementations of Java, all programs ran in an implicit loop inside the JVM itself, which would (if any event handlers had been set up) poll the events periodically after a certain count of instructions had run.² The event listener systems operate entirely within the process, and are features of the languages and their run-time systems, not of the OS.

This is not to say that you can't have an event callback system in your OS, and IMAO having one is a Good Idea; but then, I am biased towards the Synthesis OS approach³, and besides, as a Lisp programmer, dependency injection (and other kinds of Higher-Order Functions) is natural to me.

Comments and corrections welcome.


Footnote
1. Or even all threads of the process, if user-level multithreading is done via a library and the OS only aware of the single main thread of each process. A lot of OSes (especially older ones) don't separate the concepts of 'process' (a running instance of a program) and 'thread' (the state of execution of the process, of which a process must have at least one context - these execution contexts are what we are usually talking about when talk about threads).

2. IIUC, this is also how it often handled multi-threading, in systems which didn't directly support user-level threads - meaning that Java threading was, in effect, a hybrid of cooperative multitasking and preemptive multitasking, in which the 'preemptions' were based on the internal state of the JVM instance, rather than on an external timer.

3. which is based on quajects, which can be seen as objects which allow the program to use dependency injection to replace their methods on a per-object basis, as well as providing callback and callout hooks, which again could be substituted on a per-object basis. Callouts are basically a form of continuation, and are used for finalizing the quaject - a quaject can have multiple callouts, for handling different exit conditions. Pretty classy for an OS written in heavily-macro-oriented M68000 assembly language.

Re: Implementation of event listener

Posted: Mon Jul 09, 2018 11:25 am
by quadrant
So interrupts are only used when responding to IO devices. For example on key press to store the value of the key pressed and set a 'keyIsPressed' flag as true. On the other hand programs continually poll this flag to know when a key has been pressed...?

Re: Implementation of event listener

Posted: Mon Jul 09, 2018 11:26 am
by quadrant
@Korona I hadn't heard of epoll. Thanks for the reference will look into it!

Re: Implementation of event listener

Posted: Mon Jul 09, 2018 12:59 pm
by Schol-R-LEA
quadrant wrote:So interrupts are only used when responding to IO devices.
They are used for a lot of things, but what those things are is dependent on the design of the CPU, and of the rest of the platform it is part of. That is to say, interrupts as used on an x86-based PC system will work and be used differently from ones on the 8080 you quoted earlier (and I am not entirely certain why you plucked that particular ISA out of the ether, unless you are into retrocomputing on CP/M systems - while the 8086 which is the basis of PCs is sort of a descendant of the 8080, it is not at all the same), or an ARM system such as most current maker-grade single-board computers (Raspberry Pi, Asus Tinker Board, Rock64, etc.), or even another type of x86 system such as an Udoo x86 (another type of single-board computer, using a system-on-chip version of the x86, meaning that it has the same sort of CPU - broadly speaking - as a PC, but the rest of the system isn't PC-compatible).

Mostly, interrupts are used for getting signals from something outside of the CPU (which includes peripherals such as keyboards, mice, USB hubs, PCIe cards, disk drives, etc. but also integrated motherboard components such as the memory controller and the programmable interval timer). However, many instruction set architectures (including x86) also use them for 'soft interrupts' or 'traps', which are used to make system calls. For example, on the PC (when running in real mode) with the default interrupt vectors found at boot time) instruction INT 10h and INT 13h are used to call most of the BIOS routines, and in MS-DOS, INT 21h served for making most types of operating system calls. While the later 16-bit and 32-bit extensions to x86 ISA added things like sysenter and call gates as a substitute the older soft interrupt system, and many other architectures have their own specialized instructions for system calls, these are basically specialized versions of the same basic mechanism (indeed, ARM ISA the instruction svc is just a synonym for the software interrupt instruction, swi, though svc is the preferred mnemonic now).

Some (but not all) ISAs also use them for certain classes of CPU and/or bus exceptions.
quadrant wrote:For example on key press to store the value of the key pressed and set a 'keyIsPressed' flag as true. On the other hand programs continually poll this flag to know when a key has been pressed...?
You are really confusing different levels here. Interrupts are a hardware mechanism; 'events' are an abstraction created in some programming languages and/or libraries. At the hardware level (and often at the OS level), there are no such things as 'events'. Most interrupts do not lead to a program event, and not all program events are the result of an interrupt (and even those which are, come at a considerable remove from the interrupt that caused them).

I know you don't realize it, but trying to discuss events in JavaScript or something similar in terms of the underlying hardware mechanisms is like trying to analyze the artistic merits of the Mona Lisa by studying its subatomic structure. Conversely, trying to understand interrupts by looking at JavaScript events is like trying to study the chemistry of photosynthesis by observing a forest from a hillside several miles away.

(Actually, it is worse than that. Portable, interpreted languages such as JavaScript aren't tied to a specific type of hardware, and generally have no way of introspecting on the hardware to anywhere near that degree, if at all. Indeed, since the interpreter itself is generally written in a portable compiled language such as C, using the language's standard libraries (which neatly wrap over the hardware details) rather than making system calls directly, it too is usually unaware of how the platform's interrupt mechanism works and how the OS uses it - or even if there is one at all, as there are some systems such as the Ceres workstation that don't use interrupts, though that's rare.

Your typical JavaScript interpreter does not even know what OS it is running under, never mind what hardware the OS is running on, though they usually have some way to ask for that information via a function call - but while that one function has to know to make a system call, it is usually in a separate DOM library which the interpreter is linked to at build time.)

It isn't any fault of yours, you simply haven't gotten that far in learning this stuff yet. It takes time, a lot of time. Don't worry, you'll get there if you keep at it.

Re: Implementation of event listener

Posted: Mon Jul 09, 2018 4:04 pm
by quadrant
8080 because end goal is to build a simple OS for a simple CPU like the 8080/Z80.

Yes much to learn, including patience with the learning process.

Re: Implementation of event listener

Posted: Mon Jul 09, 2018 7:55 pm
by Qbyte
quadrant wrote:This is what I don't get :) ! How can the user program tell the OS to call a specific user function? Is the OS keeping some kind of list with all the callbacks requested when a specific interrupt is received? For example if you have five buttons, each with a different function to be called on a 'mouse click', how does the OS keep track of these five callbacks for a 'mouse click' interrupt?
In general, the OS doesn't know or care about each individual callback your program has. What happens is this: when input from the mouse, keyboard or other input device occurs (such as a key press/release, mouse click/motion, etc), a hardware interrupt is generated which is handled by the OS. The OS will usually handle this interrupt by passing the information about it on to the display server, which maintains a scene graph of all the interactive programs that are currently running. The display server then determines which program to deliver the input event to. For example, keyboard input will be delivered to whichever program currently has the focus, and mouse input will be delivered to the program whose window area was clicked on or moved within.

The event information is usually delivered to the program by appending it to an event queue that the program can access, but the exact methods for this step vary. The display server then calls the programs event handler (which the program would have provided to the display server upon initialization) and the event handler will analyse the type of event and then take the appropriate action from there. For instance, if the event was a mouse click, it will have information about the x and y coordinates of the click within the window, and which mouse button was clicked/released. Your program can then look through its own scene graph to see which (if any) button was clicked on by that mouse click event, and then finally call the handler for that button. The handlers for each of the buttons are entirely within your program and neither the OS nor display server knows anything about them. Your program is simply supplied with raw event data, and your own code determines how it is to be processed.