Skip to content

Instantly share code, notes, and snippets.

@jgouly
Created September 15, 2020 23:39
Show Gist options
  • Save jgouly/87e27560f2be5be2ed9e092f702ded37 to your computer and use it in GitHub Desktop.
Save jgouly/87e27560f2be5be2ed9e092f702ded37 to your computer and use it in GitHub Desktop.
Linux: Ctr-Alt-F<N> switches between virtual terminals/consoles, but how?

Linux: Ctr-Alt-F<N> switches between virtual terminals/consoles, but how?

I remembered the chvt (change foreground virtual terminal) command, so that seems like a good place to start. I looked at BusyBox's implementation in console-tools/chvt.c, hoping that it would be small enough to skim through. At 33 lines, with only 9 lines containing code I wasn't disappointed.

The call to console_make_active looks interesting. console_make_active can be found in libbb/get_console.c:

    xioctl(fd, VT_ACTIVATE, (void *)(ptrdiff_t)vt_num);

That's interesting! We can grep for VT_ACTIVATE in the Linux kernel to see how that is handled. (xioctl is just a small wrapper around ioctl)

Apart from the declaration in include/uapi/linux/vt.h, the uses are in drivers/tty/vt/vt_ioctl.c. Inside the vt_ioctl function is where the actual handling occurs. The permissions and arguments are checked, and then finally a call to set_console(arg).

	/*
	 * ioctl(fd, VT_ACTIVATE, num) will cause us to switch to vt # num,
	 * with num >= 1 (switches to vt 0, our console, are not allowed, just
	 * to preserve sanity).
	 */
	case VT_ACTIVATE:
		if (!perm)
			return -EPERM;
		if (arg == 0 || arg > MAX_NR_CONSOLES)
			return -ENXIO;

		arg--;
		console_lock();
		ret = vc_allocate(arg);
		console_unlock();
		if (ret)
			return ret;
		set_console(arg);
		break;

Another step down the rabbit hole, this time ending up at drivers/tty/vt/vt.c. This is where the implementation of set_console is. It sets want_console to the VT to switch to and calls schedule_console_callback, which schedules a console_callback on a work queue.

int set_console(int nr)
{
        ...
   
	want_console = nr;
	schedule_console_callback();

	return 0;
}

The rough call stack for console_callback would look like:

  console_callback ->
    change_console ->
      complete_change_console ->
        switch_screen ->
	  redraw_screen

redraw_screen is where fg_console is actually overwritten to contain the new virtual terminal number. The above ignores the fact that change_console might not call switch_screen right away, but might send a signal to the process that controls the current virtual terminal and waits for a VT_RELDISP ioctl before switching to the new virtual terminal.

Ok, the internals of set_console aside, how is it actually used? There's only a few calls to it, and the ones in drivers/tty/vt/keyboard.c are likely to be the interesting ones. There's 4 uses:

This last use looks like the one we're interested in:

static void k_cons(struct vc_data *vc, unsigned char value, char up_flag)
{
	if (up_flag)
		return;

	set_console(value);
}

This isn't called directly anywhere, it's part of an array:

#define K_HANDLERS\
	k_self,		k_fn,		k_spec,		k_pad,\
	k_dead,		k_cons,		k_cur,		k_shift,\
	k_meta,		k_ascii,	k_lock,		k_lowercase,\
	k_slock,	k_dead2,	k_brl,		k_ignore

typedef void (k_handler_fn)(struct vc_data *vc, unsigned char value,
			    char up_flag);
static k_handler_fn K_HANDLERS;
static k_handler_fn *k_handler[16] = { K_HANDLERS };

k_handler is only used in a single place, inside the function kbd_keycode:

	(*k_handler[type])(vc, keysym & 0xff, !down);

Let's see how type is initialised:

	type = KTYP(keysym);

KTYP is defined on line 46 of include/uapi/linux/keyboard.h

#define KTYP(x)		((x) >> 8)

Just above that is the definition of KT_CONS.

#define KT_CONS		5

So I searched the tree for usages of KT_CONS.. but there are none. That's the only mention of KT_CONS. Using git log -S KT_CONS, I looked for commits that may have added or removed usages of it. I only found two commits:

commit 607ca46e97a1b6594b29647d98a32d545c24bdff
Author: David Howells <dhowells@redhat.com>
Date:   Sat Oct 13 10:46:48 2012 +0100

    UAPI: (Scripted) Disintegrate include/linux

Which is the commit that split include into kernel and userspace APIs.

commit 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (tag: v2.6.12-rc2)
Author: Linus Torvalds <torvalds@ppc970.osdl.org>
Date:   Sat Apr 16 15:20:36 2005 -0700

    Linux-2.6.12-rc2
    
    Initial git repository build. I'm not bothering with the full history,
    even though we have it. We can create a separate "historical" git
    archive of that later if we want to, and in the meantime it's about
    3.2GB when imported into git - space that would just make the early
    git days unnecessarily complicated, when we don't have a lot of good
    infrastructure for it.
    
    Let it rip!

Which is the first commit in the repo, as far back as history will go!

That wasn't much help! So I went back to reading kbd_keycode more closely.

Aha, the keysym is found by looking up the keycode in a key_map:

	if (keycode < NR_KEYS)
		keysym = key_map[keycode];

That makes sense, key_map allows people to remap the keyboard (for customisation, internationalisation!). I remember seeing mention of a program loadkeys, let's see what the manual says:

LOADKEYS(1)                                                   General Commands Manual                                                  LOADKEYS(1)

NAME
       loadkeys - load keyboard translation tables

Ok, this looks like it could be useful!

RESET TO DEFAULT
       If the -d (or --default ) option is given, loadkeys loads a default keymap, probably the file defkeymap.map either in /usr/share/keymaps or
       in /usr/src/linux/drivers/char.  (Probably the former was user-defined, while the latter is a qwerty keyboard map for PCs - maybe not  what
       was  desired.)   Sometimes, with a strange keymap loaded (with the minus on some obscure unknown modifier combination) it is easier to type
       `loadkeys defkeymap'.

defkeymap.map, I thought this might be bundled with loadkeys, but no, it's actually part of the kernel tree, drivers/tty/vt/defkeymap.map

Looking through this file, you can find the following lines:

  keycode  59 = F1               F11              Console_13
  control keycode  59 = F1
  alt     keycode  59 = Console_1
  control alt     keycode  59 = Console_1

The format is a bit unusal, but not necessary to understand.

I found the source for loadkeys, part of the kbd-project, to try see how the maps were parsed. After some searching I found Console_1:

/*
 * Keysyms whose KTYP is KT_CONS.
 */
static const char *const cons_syms[] = {
	"Console_1",

Not only Console_1, but KT_CONS!

Another reference to KT_CONS:

const syms_entry syms[] = {
	E(iso646_syms), /* KT_LATIN */
	E(fn_syms),     /* KT_FN */
	E(spec_syms),   /* KT_SPEC */
	E(pad_syms),    /* KT_PAD */
	E(dead_syms),   /* KT_DEAD */
	E(cons_syms),   /* KT_CONS */

E(cons_syms) is at index 5, which matches up with the definition of KT_CONS in Linux's keyboard.h!

So that's it! They keymap defines keys that send Console_N key codes that Linux's tty/VT driver handles directly and changes the current virtual terminal! How the VT's keyboard driver get the keyboard input is another story..

Linux kernel fc4f28bb3daf3265d6bc5f73b497306985bb23ab

kbd 64549d5fd83dc846422467346c8e287d3e7a77f5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment