3

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 answer for more details.

High level language:

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

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.

Jet Blue
  • 5,109
  • 7
  • 36
  • 48
  • Yup, async callback functions or signal handlers in user-space are pretty much analogous to interrupt handlers for a program running on the bare metal. (e.g. a kernel on modern hardware, or any freestanding program.) Javascript is a bit special because as a managed language the VM knows when it can safely run something asynchronously. – Peter Cordes Jul 09 '18 at 04:49
  • But no, event callbacks don't *directly* hook into actual kernel interrupt handlers; there are a few layers of software inside the kernel. (Sorry, I don't have time to write up something worth posting as an answer.) – Peter Cordes Jul 09 '18 at 04:51
  • "How does the concept of attaching event listeners relate to the concept of having ISRs in assembly?" ... the event handlers/listeners are usually non-interrupt "biz-logic code" from OS kernel point of view, that's the thing done in main thread, after the interrupts are served. You don't have to process event handlers in any fast way, they can wait quite some microseconds and nothing terrible happens, plus the OS will often just delegate the main event to the user app, where the event loop runs, processing the event, and tracking its own listeners + calling them (android for example). – Ped7g Jul 09 '18 at 08:36
  • The relation to 8080 would be more like the keyboard interrupt process the keyboard, puts key hit into buffer (interrupt part), and then the running code will eventually read the buffer and act upon it (on-event handler). – Ped7g Jul 09 '18 at 08:37
  • 2
    The 8080 doesn't want to see a *vector* on the bus during the interrupt acknowledgment cycle, but rather an *opcode* it will execute. Because it can only fetch one byte, the possible opcodes are a bit limited, normally `RSTx` is used, but you could just as well put a `NOP` (not so useful) or any other single-byte command (like `STC` and wait for a set carry flag in a main loop) there. – tofro Jul 09 '18 at 15:12
  • @tofro... was simplifying for clarity (details seemed unnecessary) – Jet Blue Jul 09 '18 at 21:33
  • @JetBlue: You could have picked 8086, which does fetch a handler address from an interrupt table (https://wiki.osdev.org/Interrupt_Descriptor_Table), or any modern RISC-like CPU (which I think also have interrupt vector tables). IDK why you'd pick 8080 as an example when modern CPUs work the way you describe (especially since apparently 8080 *doesn't*.) – Peter Cordes Jul 11 '18 at 08:02

1 Answers1

3

It's a long and complex journey from a key being pressed and the code in a JavaScript keydown event listener being run. Assuming a USB keyboard, the path through hardware and software layers is something like this:

  • keyboard (hardware layer)
  • USB controller (hardware layer)
  • USB controller driver (kernel layer)
  • USB generic driver (kernel layer)
  • HID generic driver (kernel layer)
  • HID keyboard driver (kernel layer)
  • OS UI event processing (kernel/user layer)
  • browser UI event loop (user layer)
  • browser UI event handler (user layer)
  • JavaScript engine event handler (user layer)
  • keydown event listener (user layer)

Most of this doesn't work how you're probably assuming. In particular there's no keyboard interrupt. Instead there's a USB interrupt but it doesn't quite work the way you might assume. When you press a key on USB keyboard it doesn't result in a message being sent to the computer and an interrupt generated. Instead the keyboard adds the key press to its internal queue and waits for the computer to poll it.

This is because USB communication is entirely scheduled by the USB host (the computer). USB devices aren't allowed to talk on the bus except in response to a request by the host. As USB transactions are scheduled into one millisecond frames, normally an operating system will only poll a USB keyboard once a millisecond, asking it to report any events that have occurred since it last polled the device. Only once the keyboard responds to this request (or maybe when all the scheduled transfers in the frame have completed), will the USB controller generate an interrupt.

The response from the keyboard will be in the form a HID (Human Interface Device) report. The HID stack will decode it to see what keyboard events have been reported, converting them into a format common to all keyboard types. This will be further processed by some sort of user-interface layer in the operating system (eg. The "Win32" API layer on Windows, or an X Server on Linux) and then put on the UI event queue for the browser.

The browser will not be "interrupted" as a result of the key being pressed. Instead the browser will have a main UI interface event loop and events will only be processed one a time at a single defined point in the program. All this loop does is pull events from the UI event queue and dispatches them to the appropriate code in the browser. When the queue is empty, which is normally almost all of the time, the loop simply waits for an event. While it's waiting, the event loop's thread is not scheduled by the OS and is not running on any CPU.

Once the UI event loop gets a keyboard event, it's passed onto the browser's keyboard event handler which will then pass it to the JavaScript engine. The engine will then execute the function assigned as the keydown event listener. The engine may have its own event queue, as JavaScript events are normally also only processed one at a time.

None of this will be written in assembly except a tiny amount of code in the kernel that acts as a landing point for interrupts and system calls before calling the C code that actually dispatches them.

Ross Ridge
  • 38,414
  • 7
  • 81
  • 112
  • There are *some* user/kernel interfaces that do work more like an I/O interrupt, for example POSIX async I/O will send a signal instead of unblocking a thread sleeping in `epoll` or waiting to be polled with `read()` on an `O_NONBLOCK` fd. This is not a convenient API and it's rarely used for anything, but it proves such a design is possible (and many would argue illustrates that it's a *bad* design for kernel<->user). [What is the status of POSIX asynchronous I/O (AIO)?](https://stackoverflow.com/q/87892) But +1, nobody uses AIO for keyboard I/O, if that's even possible. – Peter Cordes Jul 11 '18 at 08:11