0 前言
Linux桌面有时会卡死且快捷键“Ctrl + Alt + F2...F6”无法切换到字符终端的情况,本文梳理TTY的切换流程。
1 字符终端
没有图形系统也是可以切换TTY的,此时它有内核独立完成:
kbd_keycode(unsigned int keycode, int down, int hw_raw)@linux-4.19.67/drivers/tty/vt/keyboard.c
|-->(*k_handler[type])(vc, keysym & 0xff, !down)
|-->k_cons(struct vc_data *vc, unsigned char value, char up_flag)@linux-4.19.67/drivers/tty/vt/keyboard.c
|-->if (up_flag)
| |-->return
|-->set_console(value)
注:服务器通常是不安装桌面的,其TTY切换默认就是上述流程;Linux桌面默认不会用上述流程,除非将图形关闭,例如
sudo systemctl stop lightdm # 立即生效,重启后无效
2 图形系统
图形系统的其输入事件将会由图形服务器或合成器处理[1]:
kernel -> libevdev -+-> xf86-input-evdev -> X server -> X client # Xorg
|-> libinput -+-> Wayland compositor -> Wayland client # Weston/Wayland compositor
|-> xf86-input-libinput -> X server -> X client # xorg-xserver-1.16+
例如Xorg切换TTY的流程如下:
dix_main()
|-->Dispatch()
|-->ProcessInputEvents()@hw/xfree86/common/xf86Events.c
|-->mieqProcessInputEvents()
|-->mieqProcessDeviceEvent()
|-->AccessXFilterPressEvent()
|-->XkbProcessKeyboardEvent()
|-->XkbHandleActions()
|-->XkbActionGetFilter()
|-->_XkbFilterSwitchScreen(XkbSrvInfoPtr xkbi, XkbFilterPtr filter, unsigned keycode, XkbAction *pAction)@xorg-server-1.20.8/xkb/xkbActions.c
|-->DeviceIntPtr dev = xkbi->device
|-->if (dev == inputInfo.keyboard)
| |-->return 0
|-->if (filter->keycode == 0) // initial press
|-->filter->keycode = keycode
|-->filter->active = 1
|-->filter->filterOthers = 0
|-->filter->filter = _XkbFilterSwitchScreen
|-->AccessXCancelRepeatKey(xkbi, keycode)
|-->XkbDDXSwitchScreen(dev, keycode, pAction)
|<------------------------------------------|
|-->XkbDDXSwitchScreen(DeviceIntPtr dev, KeyCode key, XkbAction *act)@xorg-server-1.20.8/hw/xfree86/xkb/xkbVT.c
|-->int scrnnum = XkbSAScreen(&act->screen)
|-->if (act->screen.flags & XkbSA_SwitchApplication)
|-->if (act->screen.flags & XkbSA_SwitchAbsolute)
| |-->xf86ProcessActionEvent(ACTION_SWITCHSCREEN, (void *) &scrnnum)
|-->else
|-->if (scrnnum < 0)
| |-->xf86ProcessActionEvent(ACTION_SWITCHSCREEN_PREV, NULL)
|-->else
|-->xf86ProcessActionEvent(ACTION_SWITCHSCREEN_NEXT, NULL)
|<------------------|
|
|-->xf86ProcessActionEvent(ActionEvent action, void *arg)@xorg-server-1.20.8/hw/xfree86/common/xf86Events.c
|-->case ACTION_SWITCHSCREEN
|-->if (!xf86Info.dontVTSwitch && arg)
|-->int vtno = *((int *) arg);
|-->if (vtno != xf86Info.vtno)
|-->xf86VTActivate(vtno)
|-->xf86VTActivate(int vtno)@xorg-server-1.20.8/hw/xfree86/os-support/shared/VTsw_usl.c
|-->ioctl(xf86Info.consoleFd, VT_ACTIVATE, vtno) // 可见也是通过内核完成切换的
|-->...
|<-----------------------------|
|-->vt_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)@linux-4.19.67/drivers/tty/vt/vt_ioctl.c
| // 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)
| |-->ret = -ENXIO
|-->else
|-->arg--
|-->console_lock()
|-->ret = vc_allocate(arg)
|-->console_unlock()
|-->if (ret)
| |-->break
|-->set_console(arg)
3 总结
由第2节可见,图形系统的TTY切换是由图形服务(X)或合成器(Wayland)实现的,因此Linux桌面的内核或Xorg异常都有可能导致无法切换TTY的情况,需要依次排查。
参考资料
[1]evdev(WIKIPEDIA)