Linux Programming Hints

This article will explain how to program the VT interface to do things which can't easily be done with “escape sequences” on a Linux console, giving a reference for the ioctl()'s needed to do this. Much of this column is derived from a document written by Orest, as he is interested in disseminating this information further.

Around Linux versions .12 and .95 (Which were consecutive, for those of you who aren't up on some of the weirdness of Linux history ...), Orest Zborowski1 undertook the task of getting the X Windowing System to run on Linux. Instead of taking the short-sighted methods and spending his time porting X to Linux, Orest ported Linux to X. To do this, he wrote the orginal Unix-domain sockets for Linux and the VT interface, which is a subset of the VT interface under SVR4. Later, Andries Brouwer2, who did most of the work for loadable keymaps, added more keyboard handling functionality.

This article will explain how to program the VT interface to do things which can't easily be done with “escape sequences” on a Linux console, giving a reference for the ioctl()'s needed to do this. Much of this column is derived from a document written by Orest, as he is interested in disseminating this information further.

The VT interface

The VT interface is a set of ioctl()'s that can be performed on any console device. VT's are tightly bound to VC's, or virtual consoles. They are named differently because they are called VT's in SVR4, and also because in the source there is some differentiation between VT operations and VC operations. VT numbering is the same as VC numbering: 0 is a synonym for the “current” VT, and all real VTs start from 1. In all the ioctls below, it is legal to use VT 0 as the target of the ioctl—it simply affects the VT that's currently active.3

This is different from SVR4, where 0 is the first VT, and /dev/console is the current VT. This difference is due to the fact that the orignal VC's in Linux used VC 0 as /dev/console, whereas SVR4 makes /dev/console a seperate device. This does not cause problems in practice, fortunately.

The header files sys/vt.h and sys/kd.h are pretty much complete, according to SVR4 rules, but much of their content is unsupported by Linux. The header file

The linux/keyboard.h file maintains more information dealing with keyboard mapping, and contains the parts written by Brouwer.

VT Reference

ioctl(int ttyfd, KIOCSOUND, unsigned int count)

KIOCSOUND will turn on a sound using the relation

                  hz = 1193180

If count = 0, then sound is turned off. The sound will continue until it is explicitly turned off.

 ioctl(int ttyfd, KDMKTONE, unsigned int count_ticks)

KDMKTONE will turn on a sound for a specific number of ticks. count_ticks is composed of two pieces: the upper 16 bits hold the number of ticks (hundredths of a second under Linux/86, at least; see the HZ define in linux/sched.h) that you want the sound to last, while the lower 16 bits hold the count, which is the same as the count argument to KIOCSOUND. The call returns immediately.

ioctl(int ttyfd, KDGKBTYPE, unsigned char *kb)

KDGKBTYPE returns the keyboard type in kb. This can be:

        KB_84           84 key keyboard
        KB_101          101 key keyboard
        KB_OTHER         other keyboard


Currently, KB_101 is always returned.

ioctl(int ttyfd, KDADDIO, int port)

KDADDIO will enable access to the specified port. The port must be in the range 0x3b4 to 0x3df (which covers the common graphics ports). For access to ports outside this range, use the ioperm(2) system call.

ioctl(int ttyfd, KDDISABIO, int port)

KDDISABIO will disable access to the specified ports. See KDADDIO for further details.

ioctl(int ttyfd, KDSETMODE, int mode)

KDSETMODE changes the mode of the VT to be either text or graphics:

        KD_GRAPHICS                  graphics mode KD_TEXT
        text mode KD_TEXT0             same as KD_TEXT KD_TEXT1
        same as KD_TEXT

ttyfd must be the current console. If the mode specified is already in place, nothing is done. When going to text mode, the screen is unblanked and the blanking timer is enabled (as in normal operation). When going to graphics mode, the screen is blanked and will remain so until switched back into text mode.

Bugs: No special provisions are made to save or restore the contents of the VT during this call. It is up to the application to save any necessary information for later restoration. This is because chipset-specific information is required to properly save or restore the contents of the VT.

ioctl(int ttyfd, KDGETMODE, int mode)

KDGETMODE returns the current mode of the specified VT. See KDSETMODE for further details.

ioctl(int ttyfd, KDSKBMODE, int kbmode)

KDSKBMODE sets the translation mode on the keyboard. The options are:

Switching from one to the other also flushes the input queue to avoid confusion. The kernel maintains correct state information for shift, lock, etc. keys regardless of the current mode.

ioctl(int ttyfd, KDGKBMODE, unsigned long *mode)

KDGKBMODE returns the keyboard mode associated with the particular tty.

ioctl(int ttyfd, KDGETLED, unsigned char *leds)

KDGETLED returns the state of the LED's as the flags:

        LED_SCR scroll lock is down
        LED_NUM num lock is down
        LED_CAP caps lock is down
ioctl(int ttyfd, KDSETLED, unsigned char leds)

KDSETLED sets the LED's according to the flags passed in. The correct use is to use KDGETLED, then make changes to those flags, then use KDSETLED to change the flags.

ioctl(int ttyfd, VT_SETMODE, struct vt_mode *vtm)

VT_SETMODE sets the control mode of the VT according to the structure:

struct vt_mode {
        char mode;
        char waitv;
        short relsig;
        short acqsig;
        short frsig;

In VT_AUTO mode, the kernel takes care of VT switches, etc. This is the default mode. In VT_PROCESS mode, one process takes control over a VT. It is responsible for acknowledging switch requests and performing any duties required. For example, a graphics program may want to run in VT_PROCESS mode, so if the user wants to switch to another VT and back, the graphics mode is properly saved and restored.

A full description of the switching semantics is described in a section below.

Bugs: The waitv mode of writes is not supported.

ioctl(int ttyfd, VT_GETMODE, struct vt_mode *vtm)

VT_GETMODE returns the current control state of the VT. See VT_SETMODE, above, for further details.

ioctl(int ttyfd, VT_GETSTATE, struct vt_stat *vts)

VT_GETSTATE returns the state of all VT's in the kernel in the structure:

struct vt_stat {
        ushort v_active;
        ushort v_signal;
        ushort v_state;
v_active        the currently active VT
v_state         mask of all the opened VT's

v_active holds the number of the active VT (starting from 1), while v_state holds a mask where there is a 1 for each VT that has been opened by some process. Note that VT 0 is always opened in this scenario, since it refers to the current VT.


The v_signal member is unsupported.

ioctl(int ttyfd, VT_OPENQRY, long *num)

VT_OPENQRY returns the number of the first available VT, one that hasn't been opened by any process. If there is no free VT, -1 is returned in num.

ioctl(int ttyfd, VT_ACTIVATE, int num)

VT_ACTIVATE will cause a switch to VT number num, as if caused from the keyboard. In particular, if VT number num is in VT_PROCESS mode, then negotiation with the process in charge is begun. The call may return before the switch is complete. Use VT_WAITACTIVE to wait until the switch is completed.

ioctl(int ttyfd, VT_WAITACTIVE, int num)

VT_WAITACTIVE will wait until the specified VT has been activated (the switch to it has been completed).


This call does not actually do the switch, but it may need to do the switch as well, as SVR4 does, to be compatible with some applications.

ioctl(int ttyfd, VT_RELDISP, int val)

VT_RELDISP is used to signal the kernel about the switch in progress. If ttyfd is the current console, then it must be in VT_PROCESS mode.

If switching from one VT to another VT, the “from” VT is signalled about the switch-from request. The reply is through the VT_RELDISP ioctl with the following values:

0       switch is disallowed, and the kernel aborts the attempt
1       switch is allowed, and the kernel continues with the switch
2       switch has been completed

If switching to a VT from another VT, the kernel will signal about the switch-to request. The reply is through the VT_RELDISP ioctl with the following value:

VT_ACKACQ               switch-to is allowed


The switch-to response is a nonstandard behavior in SVR4. Currently, Linux doesn't require the switch-to VT_RELDISP ioctl, but if made, it must have the argument VT_ACKACQ.

ioctl(int fd, KDSKBMETA, int flags)

KDSKBMETA specifies if pressing the meta (alt) key generates an ESC (\033) prefix followed by the keysym, or the keysym marked with the high bit set.

K_METABIT       generate an ESC  prefix
0               generates a high-bit marked keysym
ioctl(int fd, KDGKBMETA, unsigned long *flags)

KDGKBMETA returns the state of the META prefix, as described in KDSKBMETA above.

ioctl(int fd, KDGKBENT, struct kbentry *kbe)

KDGKBENT returns the keysym mapping for a particular key and modifier.

struct kbentry {
        u_char kb_table;
        u_char kb_index;
        u_short kb_value;

The user sets the kb_table to the modifier table requested, and kb_index to the keycode requested. KDGKBENT returns the keysym in kb_value. The modifier table is generated by the logical “or” of the following values:

        K_NORMTAB       normal table
        K_SHIFTTAB      shift
        K_ALTTAB        alt (meta)
        K_SRQTAB        right alt (altgr)
ioctl(int fd, KDSKBENT, struct kbentry *kbe)

KDSKBENT sets the keysym mapping for a particular keycode and modifier combination. See KDGKBENT above for more information.

ioctl(int fd, KDGKBSENT, struct kbsentry *kbs)

KDGKBSENT returns the string bound to a particular function key:

struct kbsentry {
        u_char kb_func;
        u_char kb_string[512];

kb_func is the index of the function key (0 - NR_FUNC), and KDGKBSENT will return the currently mapped string in kb_string.

ioctl(int fd, KDSKBSENT, struct kbsentry *kbs)

KDSKBSENT sets the string mapped to a function key. When this function key is depressed, the string is emitted. See KDGDBSENT above for an explanation of struct kbsentry.

ioctl(int fd, KDGKBDIACR, struct kbdiacrs *kbds)

KDGKBDIACR returns the kernel diacritical mapping table:

struct kbdiacr {
        u_char diacr,
        base, result;
        }; struct kbdiacrs {
        unsigned int kb_cnt;
        struct kbdiacr kbdiacr[256];

See the keymap package for details.

ioctl(int fd, KDSKBDIACR, struct kbdiacrs *kbds)

KDSKBDIACR sets the diacritical table. See KDGKBDIACR above for details. See the keymap package for details.

ioctl(int fd, PIO_FONT, unsigned char font[8192])

PIO_FONT sets the console video font. The font is 8192 bytes long and specific to the particular mode one is using. See the keymap package for details.

ioctl(int fd, GIO_FONT, unsigned char font[8192])

GIO_FONT gets the console video font. Returns 8192 bytes of font information. See the keymap package for details.

ioctl(int fd, PIO_SCRNMAP, unsigned char trans[256])

PIO_SCRNMAP sets the user console translation table. This maps an 8 bit code to a video font code. The user table is selectable by sending ESC(K to the console. See the keymap package for details.

ioctl(int fd, GIO_SCRNMAP, unsigned char trans[256])

GIO_SCRNMAP returns the console translation table. See the keymap package for details. VT switching When the user types <Alt>-<Fn>, where n is the number of a VT, the kernel will switch to that VT. The same sequence happens if some process performs an ioctl(fd, VT_ACTIVATE, n);

First, if the “switch-to” VT is in VT_AUTO mode, then the kernel will ignore the switch request if it's also in KD_GRAPHICS mode, else it will continue the switch.

If the “switch-to” VT is in VT_PROCESS mode, the relsig signal is sent to the “switch-from” process so it can release the VT. If the process accepts the signal, the kernel will await the VT_RELDISP ioctl from it. If the process has died, the VT is forcib-ly reset to KD_TEXT and VT_AUTO mode. This can cause great confusion and unhappiness, but the kernel can't do anything better.

The “switch-from” process will need to perform any cleanup, and issue the VT_RELDISP ioctl, telling the kernel that it is OK to continue the switch. It is also possible for it to deny the switch, in which case the kernel discontinues the switch.

If the “switch-from” process has consented to the switch, the kernel will change to the new VT, changing the keyboard mode and LED's as well. Then, if the new VT is under VT_PROCESS control, the “switch-to” process is sent the acqsig signal. If this process is missing, the new VT is reset to KD_TEXT and VT_AUTO mode. In this fashion, there is a certain amount of auto-resetting going on during normal use. Of course, if a process makes graphics changes in KD_GRAPHICS mode, these will not be undone by the kernel.

At this point the switch is complete. The “switch-to” process may call VT_RELDISP VT_ACKACQ, but this is not required by the kernel. If there were any processes waiting for this new VT to become active, they are woken at this point.


For most people, the X source code is far too large to easily download, and far too large to easily study. There are, however, other examples available. svgalib provides an easy-to-use interface to these functions, as well as providing a consistent interface to VGA and some SVGA video boards. It also serves as example code for those who want to roll their own code, as it includes examples of using mmap() and ioperm() to directly access video memory, once it has used the ioctl()'s described above so that it is allowed to. The following code fragments descibe one way to access ports and memory, without using svgalib. PAGE_SIZE is defined in <linux/page.h>, and GRAPH_SIZE and GRAPH_BASE may differ from video card to video card. This code is based on code in vgalib version 1.2.

FILE *mem_fd;
char *graph_get, *graph_mem;
if (ioperm(port, 1, 1)) {
   fprintf(stderr, "Can't access port %x\n", port); exit(1);
} if ((mem_fd = open("/dev/mem", O_RDWR)) < 0) {
   fprintf(stderr, "Can't open /dev/mem\n"); exit(1);
} if ((graph_get = malloc(GRAPH_SIZE + (PAGE_SIZE-1))) === NULL) {
fprintf(stderr, "Insufficient memory\n");
graph_mem = graph_get; if ((unsigned long)graph_mem % PAGE_SIZE)
   graph_mem += PAGE_SIZE - ((unsigned long)graph_mem % PAGE_SIZE);
graph_mem = (unsigned char *)
                        GRAPH_SIZE, PROT_READ|PROT_WRITE,
                        MAP_SHARED|MAP_FIXED, mem_fd, GRAPH_BASE);
if ((long)graph_mem < 0) {
   fprintf(stderr, "mmap error\n"); exit(1);

At this point, writing to graph_mem is actually writing to screen memory. iopl() and ioperm() can also be used to get permission to write to ports, as can the KDADDIO ioctl(), described above. The Linux Documentation Project man pages include man pages on iopl() and ioperm(), so I will not document them here, as those man pages should have come with your Linux distribution. They are accessible on as /pub/Linux/docs/LDP/man-pages/* if you do not have them.

The DOS emulator also uses some of these calls to provide the interface that DOS is used to having to real DOS programs, and to allow DOS sessions to use the video bios provided with the video card.

The definitive example code for most of the KD*i ioctl()'s is the keymap package, distributed with the Linux kernel.

For next month, I plan to explain how these calls can be used to write a screen-lock package for Linux, as my space and time are up for this month.


Geek Guide
The DevOps Toolbox

Tools and Technologies for Scale and Reliability
by Linux Journal Editor Bill Childers

Get your free copy today

Sponsored by IBM

8 Signs You're Beyond Cron

Scheduling Crontabs With an Enterprise Scheduler
On Demand
Moderated by Linux Journal Contributor Mike Diehl

Sign up now

Sponsored by Skybot