This chapter covers the following topics:
The QNX Device Manager (Dev) is the interface between processes and terminal devices. These terminal devices are located in the I/O namespace with names starting with /dev. For example, a console device on QNX would have a name such as:
/dev/con1
QNX programs access terminal devices using the standard read(), write(), open(), and close() functions. A terminal device is presented to a QNX process as a bidirectional stream of bytes that can be read or written by the process.
The Device Manager regulates the flow of data between an application and the device. Some processing of this data is performed by Dev according to parameters in a terminal control structure (called termios), which exists for each device. Users can query and/or change these parameters by using the stty utility; programs can use the tcgetattr() and tcsetattr() functions.
The termios parameters control low-level functionality such as:
The Device Manager also provides a set of auxiliary services available to processes for managing terminal devices. The following table summarizes some of these services.
A process can: | via this C function: |
---|---|
Perform timed read operations | dev_read() or read() and tcsetattr() |
Asynchronously notify a process of data available on one or more input devices | dev_arm() |
Wait for output to be completely transmitted | tcdrain() |
Send breaks across a communications channel | tcsendbreak() |
Disconnect a communications channel | tcdropline() |
Insert input data | dev_insert_chars() |
Perform non-blocking reads and writes | open() and fcntl() (O_NONBLOCK mode) |
The most significant mode of device processing is controlled by the ICANON bit in the termios control structure. If this control bit is set, the Device Manager performs line-editing functions on received characters. Only when a line is "entered" -- typically when a carriage return (CR) is received -- will the processed data be made available to application processes. This mode of operation is referred to as edited, canonical, or sometimes cooked mode.
Most non-full-screen applications run in edited mode. The Shell is a typical example.
The following table shows several special control characters that may be set in the termios control structure to define how Dev performs this editing.
Dev will: | When it receives: |
---|---|
Move the cursor one character to the left | LEFT |
Move the cursor one character to the right | RIGHT |
Move the cursor to the beginning of the line | HOME |
Move the cursor to the end of the line | END |
Rub out the character to the left of the cursor | ERASE |
Delete the character at the current cursor position | DEL |
Rub out the entire input line | KILL |
Erase the current line and recall a previous line | UP |
Erase the current line and recall the next line | DOWN |
Toggle between insert mode and typeover mode (every new line starts in insert mode) | INS |
Line-editing characters vary from terminal to terminal. The QNX console always starts out with a full set of editing keys defined.
If a terminal is connected to QNX via a serial channel, you need to define the editing characters that apply to that particular terminal. To do this, you can use the stty utility. For example, if you have a VT100 terminal connected to a serial port (called /dev/ser1), you would use the following statement to extract the appropriate editing keys from the terminfo database and apply them to /dev/ser1:
stty term=vt100 </dev/ser1
If, instead, you had a modem connected to that serial port, which in turn was connected to another QNX system running the qtalk utility, you'd want to set the line-editing keys as follows:
stty term=qnx </dev/ser1
When ICANON isn't set, the device is said to be in raw mode. In this mode, no input editing is performed, and any received data is made immediately available to QNX processes.
Full-screen programs and serial communications programs are examples of QNX applications that put a device in raw mode.
When reading from a raw device, the application is capable of specifying under what conditions an input request is to be satisfied. The criteria used for accepting raw input data are based on two members of the termios control structure: MIN and TIME. The application can specify a further qualifier of accepting input data when it issues its read request via dev_read(). This qualifier, TIMEOUT, is useful when writing protocols or realtime applications. Note that TIMEOUT is always 0 for read().
When a QNX process issues a read request for n bytes of data, these three parameters define when that read request is to be satisfied:
MIN | TIME | TIMEOUT | Description: |
---|---|---|---|
0 | 0 | 0 | Return immediately with as many bytes as are currently available (up to n bytes). |
M | 0 | 0 | Return with up to n bytes only, when at least M bytes are available. |
0 | T | 0 | Return with up to n bytes when at least one byte is available, or T * .1 second has expired. |
M | T | 0 | Return with up to n bytes when either M bytes are available or at least one byte has been received and the interbyte time between any subsequently received characters exceeds T * .1 second. |
0 | 0 | t | RESERVED. |
M | 0 | t | Return with up to n bytes when t * .1 second has expired, or M bytes are available. |
0 | T | t | RESERVED. |
M | T | t | Return with up to n bytes when M bytes are available, or t * .1 second has expired and no characters are received, or at least one byte has been received and the interbyte time between any subsequently received characters exceeds T * .1 second. |
The following illustration shows a typical QNX device subsystem.
The Device Manager process (Dev) manages the flow of data to and from the QNX application processes. The hardware interface is managed by individual driver processes. Data flows between Dev and its drivers through a set of shared memory queues for each terminal device.
Since shared memory queues are used, it's necessary that Dev and all its drivers reside on the same physical CPU. The advantage, of course, is increased performance. |
Three queues are used for each device. Each queue is implemented using a first-in, first-out mechanism. A control structure is also associated with each queue.
Received data is placed into the raw input queue by the driver and is consumed by Dev only when application processes request data. Interrupt handlers within drivers typically call a trusted library routine within Dev to add data to this queue -- this ensures a consistent input discipline and greatly minimizes the responsibility of the driver.
Dev places output data into the output queue; the data is consumed by the driver as characters are physically transmitted to the device. Dev calls a trusted routine within the driver process each time new data is added so it can "kick" the driver into operation (in the event that it was idle). Since output queues are used, Dev implements full write-behind for all terminal devices. Only when the output buffers are full will Dev cause a process to block while writing.
The canonical queue is managed entirely by Dev and is used while processing input data in edited mode. The size of this queue determines the maximum edited input line that can be processed for a particular device.
The sizes of all these queues are configurable by the system administrator; the only restriction is that the sum total of all three queues can't exceed 64K. Default values are usually more than adequate to handle most hardware configurations, but you can "tune" these either to reduce overall system memory requirements or to accommodate unusual hardware situations.
Device drivers simply add received data to the raw input queue or consume and transmit data from the output queue. Dev decides when (and if) output transmission is to be suspended, how (and if) received data is echoed, etc.
To ensure good interactive response to input events, Dev must run at a reasonably high priority. Dev typically has very little actual work to do when it does run, so it seldom impedes overall system performance.
The drivers themselves are just like any other QNX process -- they can run at different priorities according to the nature of the hardware they're serving.
Low-level device control is implemented with a far call into an ioctl entry point within each driver. A common set of ioctl commands are supported by most drivers used directly by Dev. Device-specific ioctl commands can also be sent through Dev to the drivers by QNX processes (via the qnx_ioctl() function).
System consoles are managed by the Dev.con driver process. The display adapter and the screen, plus the system keyboard, are collectively referred to as the console.
QNX permits multiple sessions to be run concurrently on consoles by means of virtual consoles. The Dev.con console driver process typically manages more than one set of I/O queues to Dev, which are made available to user processes as a set of terminal devices with names like /dev/con1, /dev/con2, etc. From the application's point of view, there "really are" multiple consoles available to be used.
Of course, there's only one physical screen and keyboard, so only one of these virtual consoles is actually displayed at any one time. The keyboard is "attached" to whichever virtual console is currently visible.
In addition to implementing the standard QNX Terminal (as defined in the Setting up Terminals chapter of the QNX Installation & Configuration guide), the console driver also provides a set of console-specific functions that let application processes communicate via messages directly to a console driver process. Communication is established with the console_open() function. Once communication is established, a QNX process has access to the following capabilities:
A process can: | via this C function: |
---|---|
Read directly from the console screen | console_read() |
Write directly to the console screen | console_write() |
Be asynchronously notified of significant events (e.g. display data or size has changed, cursor has moved, visible console has changed, etc.) | console_arm() |
Control the console size | console_size() |
Switch the visible console | console_active() |
The QNX console driver runs as a normal QNX process. Input characters from the keyboard are mapped by the keyboard interrupt handler and placed directly into the input queue. Output data is consumed and displayed by Dev.con while it is executing as a process.
Serial communication channels are managed by the Dev.ser driver process. This driver can manage more than one physical channel; it provides terminal devices with names such as /dev/ser1, /dev/ser2, etc.
When you start Dev.ser, you can specify command-line arguments that determine which -- and how many -- serial ports are installed. To see what serial ports may be available on a QNX system, use the ls utility:
ls /dev/ser*
Dev.ser is an example of a purely interrupt-driven I/O server. After initializing the hardware, the process itself goes to sleep. Received interrupts place input data directly into the input queue. The first output character on an idle channel is transmitted to the hardware when Dev issues the first kick call into the driver. Subsequent characters are transmitted by the appropriate interrupt being received.
Parallel printer ports are managed by the Dev.par driver process. When you start Dev.par, you specify a command-line argument that determines which parallel port is installed. To see if a parallel port is available on a QNX system, use the ls utility:
ls /dev/par*
Dev.par is an output-only driver, so it has no input or canonical input queues. You can configure the size of the output buffer with a command-line argument when you start Dev.par. The output buffer can be made very large if you wish to provide a form of software print buffer.
Dev.par is an example of a completely non-interrupt I/O server. The parallel printer process normally remains RECEIVE-blocked, waiting for data to appear in its output queue and a kick from Dev. When data is available to print, Dev.par runs in a busy-wait loop (at relatively low adaptive priority), while waiting for the printer hardware to accept characters. This low-priority busy-wait loop ensures that overall system performance isn't affected, yet on the average produces the maximum possible throughput to the parallel device.
The flow of events within the device subsystem is engineered to minimize overhead and maximize throughput when a device is in raw mode. To accomplish this, the following rules are used:
These rules -- coupled with the extremely small interrupt and scheduling latencies inherent within QNX -- result in a very lean input model.