See How do keyboard input and text output work? for an overview of the topic.
In more detail, under Linux, the kernel receives scan codes from the hardware and converts them into keycodes. (This terminology isn't completely standard; you may find “scan code” or “key code” used for both.) How this conversion works depends on the keyboard driver. For PS/2 keyboards, you can configure it with
setkeycodes. For USB keyboards, you can configure it via udev. See also the Arch wiki. All user input devices, including keyboards and mice, are exposed via event devices
In a Linux console, keycodes are mapped to escape sequences according to the console keymap. Since a console is a text terminal, applications see bytes, with printable characters representing themselves and function keys encoded using control characters or escape sequences. You can change the mapping with
loadkeys. The mapping uses two levels of indirection, from keycode+modifier combination (with three modifiers:
alt) to keysym and from keysym to string (character or escape sequence). The set of keysyms is fixed, so if you want to define custom combinations you'll need to use existing keysyms that aren't otherwise used such as
If you want to look at the source code that implements these translations, look at keyboard drivers and generic input code, and udev and libudev.
X11 (the GUI) has its own way to map keycodes to what applications receive. X11 applications see keysyms and modifiers, so function keys don't need to be encoded further. There are in fact two ways to define keymaps under X: the classical method xmodmap, and the newer mechanism XKB which is more powerful but more complex.