Chapter 20. Programming Virtual Consoles

The Linux virtual console programming interface is modeled on the one provided with some versions of UNIX. It is not a complete reimplementation (although it is complete enough for source code compatibility with almost all programs), and it provides several valuable extensions.

Linux can multiplex multiple terminal sessions over one screen and one keyboard. Special key sequences allow the user to control which terminal session is currently being displayed. Each of these login sessions has its own keyboard settings (such as whether the Caps Lock key is engaged), terminal settings (such as what the terminal size is, whether the screen is in graphics mode, and what the fonts are), and device entries (such as /dev/tty1, /dev/vcs1, and /dev/vcsa1).

The keyboard and terminal settings together make up virtual consoles (VCs), so called because of their similarity to virtual memory, in which the system uses disk space to provide more usable memory than is physically present in the machine.

Unless you wish to manage or manipulate VCs, you can skip this chapter. A few programming libraries manage VCs for you, but you may still need to know what they are doing behind your back, so that you work with them rather than against them.

For instance, svgalib, a library for using graphics on several types of graphics controllers, has functions that do most of the basic VC manipulation for you. It still requires that you avoid writing random bits to the graphics controller while it is in text mode; doing so would scramble the screen. The current lack of documentation for svgalib makes it even more important that you know what is going on underneath.[1]

VCs provide users with many options, but the majority of users ignore the options and simply use the X Window System. Those users who do use VCs can

  • Choose a separate font for each VC

  • Choose a separate terminal size for each VC

  • Choose key mappings (more on these later) for all VCs

  • Choose a different keystroke encoding for all VCs

  • Switch VCs on command with user-specifiable keystrokes

The Linux Documentation Project (LDP) has documents explaining how the user can use programs that already exist in order to take advantage of these capabilities. Your goal is different—you wish to program VCs, not just use them. Although font setting and keyboard settings are well encapsulated in utilities[2] that you can simply call from within your programs, there are cases in which those external programs are insufficient.

Getting Started

Here is a list of some of the things that you can do with VCs. Some are specific to an individual VC (usually, the currently active VC); some apply to all VCs in use:

  • Find the current VC

  • Initiate a VC switch

  • Refuse or allow a switch to or from a VC

  • Disable VC switching completely

  • Find an unused VC

  • Allocate and deallocate VCs dynamically in the kernel

  • Make simple sounds

In all cases, the same preparation is needed. You will use ioctl() commands on /dev/tty—so you need to start out by including the header files that define the ioctl() arguments:

#include <signal.h>
#include <sys/ioctl.h>
#include <sys/vt.h>
#include <sys/kd.h>
#include <sys/param.h>

Then open /dev/tty:

if ((fd = open("/dev/tty", O_RDWR)) < 0) {
    perror("myapp: could not open /dev/tty");
    exit (1);
}

If you find that you cannot open /dev/tty, you probably have a permission problem: /dev/tty should be readable and writable by everyone.

Note that in addition to ioctl.h, there are two main header files that define the ioctl() calls that manipulate VCs. vt.h defines calls that start with VT and control the virtual terminal, or screen, part of the virtual consoles. kd.h defines calls that start with KD and that control the keyboard and fonts. You can ignore most of the contents of kd.h because the functionality it covers is so nicely encapsulated in utility programs, but it is useful for making the console beep at controlled frequencies.

Those two main header files also define structures that are used with the ioctl()s.

You use the vt_mode structure to find and change the current VC:

struct vt_mode {
    char mode;
    char waitv;
    short relsig;
    short acqsig;
    short frsig;
};
  • mode is either VT_AUTO, which tells the kernel to switch VCs automatically when keys are pressed or when a program sends a request to the kernel to change VCs, or VT_PROCESS, which tells the kernel to ask before switching.

  • waitv is unused, but should be set to one for SVR4 compatibility.

  • relsig names a signal that the kernel should raise to request that the process release the VC.

  • acqsig names a signal that the kernel should raise to alert the process that it has acquired the VC.

  • frsig is unused and should be set to zero for SVR4 compatibility.

struct vt_stat {
    unsigned short v_active;
    unsigned short v_signal;
    unsigned short v_state;
};
  • v_active holds the number of the currently active VC.

  • v_signal is not implemented.

  • v_state holds a bitmask telling which of the first 16 VCs are currently open (Linux supports up to 63). There is rarely any reason to consult this bitmask under Linux; you should probably ignore it because it is not sufficiently large to contain complete information, and because in most cases you need to know only the number of some open VC, which you can get with VT_OPENQRY (see page 507).

Beeping

Last things first: Causing the console to beep for a certain amount of time at a certain frequency is fairly simple. There are two ways to make the console beep. The first is to turn on or off a constant tone. KIOCSOUND turns off the sound if its argument is zero; if its argument is nonzero, it specifies the frequency in a rather bizarre way, as this code shows:

void turn_tone_on(int fd, int hertz) {
    ioctl(fd, KIOCSOUND, 1193180/hertz)
}
void turn_tone_off(int fd) {
    ioctl(fd, KIOCSOUND, 0)
}

The second way to cause the console to beep is to use the KDMKTONE ioctl to turn on a tone for a period specified in jiffies, which are ticks of the system clock. Unfortunately, the time per tick varies from architecture to architecture; the HZ macro defined by sys/param.h gives ticks per second. The tone() function below shows how to derive the number of ticks from hundredths of a second and the value of HZ:[3]

#include <sys/param.h>
void tone(int fd, int hertz, int hundredths) {
    unsigned int ticks = hundredths * HZ / 100;

    /* ticks & 0xffff will not work if ticks == 0xf0000
     * need to round off to highest legal value instead */
    if (ticks > 0xffff) ticks = 0xffff;
    /* now the other rounding error */
    if (hundredths && ticks == 0) ticks = 1;
    ioctl(fd, KDMKTONE, (ticks<<16 | (1193180/hertz)));
}

Determining Whether the Terminal Is a VC

To find out whether the current terminal is a VC, you can open /dev/tty and use VT_GETMODE to query the mode:

struct vt_mode vtmode;

 fd = open("/dev/tty", O_RDWR);
 retval = ioctl(fd, VT_GETMODE, &vtmode);
 if (retval < 0) {
     /* This terminal is not a VC; take appropriate action */
 }

Finding the Current VC

To find the number of the current VC, use the VT_GETSTATE ioctl, which takes a pointer to a struct vt_stat and returns the number of the current VC in the v_active element:

unsigned short get_current_vc(int fd) {
    struct vt_stat vs;

    ioctl(fd, VT_GETSTATE, &vs);
    return (vs.v_active);
}

To locate the correct device entry for the current VC, use

sprintf(ttyname, "/dev/tty%d", get_current_vc(fd));

Managing VC Switching

To find an unused VC (that is, a VC currently referenced by no processes’ open file descriptors) to activate, use the VT_OPENQRY ioctl:

retcode = ioctl(fd, VT_OPENQRY, &vtnum);
if ((retcode < 0) || (vtnum == -1)) {
    perror("myapp: no available virtual terminals");
    /* take appropriate action /*
}

If fewer than 63 VCs are in use and all the currently allocated VCs are in use, a new VC is allocated dynamically by the kernel.[4]

To trigger a switch to another VC (perhaps the available one that you just found), use the VT_ACTIVATE ioctl. Use the VT_WAITACTIVE ioctl if you wish to wait for the VC to become active. Changing VCs can take some time—possibly several seconds—because the console to which you are switching may be in graphical mode and the contents of the screen may have to be reconstructed from memory, fetched from swap, or rebuilt in some other time-consuming manner.[5]

ioctl(fd, VT_ACTIVATE, vtnum);
ioctl(fd, VT_WAITACTIVE, vtnum);

To exercise control over when VC switches take place, or simply to be notified of such switches, you must provide reliable signal handlers with sigaction, as discussed in Chapter 12. We use SIGUSR1 and SIGUSR2 here; if you prefer, you can reuse almost any other two signals that you do not intend to use otherwise, such as SIGPROF or SIGURG. Just make sure that the signals you choose meet the following criteria:

  • They are not needed for other system functions, especially signals that cannot be trapped or ignored.

  • They are not used elsewhere in your application for other purposes.

  • They are not the same signal number with two different names, such as SIGPOLL and SIGIO (see the definitions in /usr/include/asm/signal.h, or restrict yourself to signals from Table 12.1 on page 217).

void relsig (int signo) {
    /* take appropriate action to release VC */
}
void acqsig (int signo) {
    /* take appropriate action to acquire VC */
}

void setup_signals(void) {
    struct sigaction sact;

    /* Do not mask any signals while these
     * handlers are being called. */
    sigemptyset(&sact.sa_mask);
    /* You may wish to add calls to sigaddset()
     * here if there are signals that you wish to mask
     * during VC switching. */
    sact.flags = 0;
    sact.sa_handler = relsig;
    sigaction(SIGUSR1, &sact, NULL);
    sact.sa_handler = acqsig;
    sigaction(SIGUSR2, &sact, NULL);
}

You then need to change the VC mode from VT_AUTO (the default) to VT_PROCESS, while telling the VC about your signal handlers by setting relsig and acqsig:

void control_vc_switching(int fd) {
    struct vt_mode vtmode;

    vtmode.mode = VT_PROCESS;
    vtmode.waitv = 1;
    vtmode.relsig = SIGUSR1;
    vtmode.acqsig = SIGUSR2;
    vtmode.frsig = 0;
    ioctl(fd, VT_SETMODE, &vtmode);
}

The signal handlers that are called when a VC is in VT_PROCESS mode do not have to agree to the switch. More precisely, the relsig handler may refuse to allow the VC switch to take place. The acqsig handler usually manages the process of taking over the console, but it could conceivably initiate a switch to yet another VC. Be careful in coding your signal handlers not to call any nonreentrant library functions that cannot be called from a signal handler. POSIX.1 specifies that the functions listed in Table 12.2 are reentrant; you should consider all others nonreentrant, especially if you wish to write a portable program. Note in particular that malloc() and printf() are nonreentrant.

Here are examples of relsig() and acqsig() functions that do useful work. Note that, for the relsig() function, calling VT_RELDISP is required, but for the acqsig() function, calling VT_RELDISP is recommended only for the sake of portability.

void relsig (int signo) {
    if (change_vc_ok()) {
        /* VC switch allowed */
        save_state();
        ioctl(fd, VT_RELDISP, 1);
    } else {
        /* VC switch disallowed */
        ioctl(fd, VT_RELDISP, 0);
    }
}
void acqsig (int signo) {
    restore_state();
    ioctl(fd, VT_RELDISP, VT_ACKACQ);
}

It is up to you to implement the change_vc_ok(), save_state(), and restore_state() code.

VCs are allocated dynamically when they are opened, but they are not reaped automatically when they are closed. To reap the kernel memory used to store the state of a VC, you need to call an ioctl:

ioctl(fd, VT_DISALLOCATE, vtnum);

You can disable and reenable VC switching completely with a few simple ioctls:

void disallow_vc_switch(int fd) {
    ioctl(fd, VT_LOCKSWITCH, 0);
}
void allow_vc_switch(int fd) {
    ioctl(fd, VT_UNLOCKSWITCH, 0);
}

Example: The open Command

Here is sample code to find an unused VC, to run a shell on it, to wait for the shell to exit, and to switch back and deallocate the VC when the program exits. The open program, which is distributed with Linux, does the same, but it has more options and error checking, and is thus more robust. Although this version works, it is hardly friendly, robust, or secure.

 1: /* minopen.c */
 2:
 3: #include <stdio.h>
 4: #include <unistd.h>
 5: #include <stdlib.h>
 6: #include <signal.h>
 7: #include <fcntl.h>
 8: #include <sys/ioctl.h>
 9: #include <sys/vt.h>
10: #include <sys/stat.h>
11: #include <sys/types.h>
12: #include <sys/wait.h>
13:
14: int main (int argc, const char **argv) {
15:     int vtnum;
16:     int vtfd;
17:     struct vt_stat vtstat;
18:     char device[32];
19:     int child;
20:
21:     vtfd = open("/dev/tty", O_RDWR, 0);
22:     if (vtfd < 0) {
23:         perror("minopen: could not open /dev/tty");
24:         exit (1);
25:     }
26:     if (ioctl(vtfd, VT_GETSTATE, &vtstat) < 0) {
27:         perror("minopen: tty is not virtual console");
28:         exit (1);
29:     }
30:     if (ioctl(vtfd, VT_OPENQRY, &vtnum) < 0) {
31:         perror("minopen: no free virtual consoles");
32:         exit (1);
33:     }
34:     sprintf(device, "/dev/tty%d", vtnum);
35:     if (access(device, (W_OK|R_OK)) < 0) {
36:         perror("minopen: insufficient permission on tty");
37:         exit (1);
38:     }
39:     child = fork();
40:     if (child == 0) {
41:         ioctl(vtfd, VT_ACTIVATE, vtnum);
42:         ioctl(vtfd, VT_WAITACTIVE, vtnum);
43:         setsid();
44:         close (0); close (1); close (2);
45:         close (vtfd);
46:         vtfd = open(device, O_RDWR, 0); dup(vtfd); dup(vtfd);
47:         execlp("/bin/bash", "bash", NULL);
48:     }
49:     wait (&child);
50:     ioctl(vtfd, VT_ACTIVATE, vtstat.v_active);
51:     ioctl(vtfd, VT_WAITACTIVE, vtstat.v_active);
52:     ioctl(vtfd, VT_DISALLOCATE, vtnum);
53:     exit(0);
54: }


[1] We do not recommend using svgalib to do graphics programming. Many books document programming for the X Window System, and X provides a far saner, safer, and portable method of graphics programming. On the other hand, if you actually want to program the X Window server, you may need to program VCs. You have then come full circle and still need to read this chapter.

[2] Read the man pages for the loadkeys, dumpkeys, keytables, setfont, and mapscrn utilities.

[3] This interface is flawed; it should have been specified in some constant fraction of a second and converted. HZ is no longer constant even on a particular platform, but at least for the Intel i86 architecture, Linus Torvalds has decreed that all interfaces specified in regard to HZ shall present a synthetic 100Hz interface. It is possible that in the future, there will be no periodic system clock, in which case jiffies will become a completely synthetic concept.

[4] Most other systems with virtual consoles or virtual terminals do not dynamically allocate them.

[5] Certain systems (but not Linux) initiate a switch automatically if one is not in progress and VT_WAITACTIVE is called.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset