What You Will Learn
When a Linux process reads from a terminal, the terminal driver can process characters in very different ways depending on the mode it is operating in. This section explains the three classic modes โ Cooked, Cbreak, and Raw โ and a subtle portability trap around the VMIN and VTIME noncanonical settings. Understanding these modes is essential for writing any interactive terminal application: serial consoles, embedded shells, editors, and BLE device configuration CLIs all depend on correct mode switching.
Key Terms
In noncanonical mode the c_cc array has two important slots: c_cc[VMIN] (minimum bytes before read returns) and c_cc[VTIME] (timeout in tenths of a second). In canonical mode the same array holds c_cc[VEOF] (the EOF character, default Ctrl-D) and c_cc[VEOL] (additional line-delimiter character).
The problem: SUSv3 (the POSIX standard) allows VMIN and VEOF to share the same array index, and similarly VTIME and VEOL may share an index. On Linux these constants have distinct values, so there is no overlap. But on some older or embedded UNIX systems they are the same index.
| c_cc Index | Canonical Mode Name | Noncanonical Mode Name | Risk on Portable UNIX? |
|---|---|---|---|
| e.g. index 4 | VEOF (Ctrl-D = 0x04) | VMIN (e.g. set to 1) | โ Same slot on some UNIX |
| e.g. index 11 | VEOL (extra line end) | VTIME (timeout) | โ Same slot on some UNIX |
| Linux: VMIN โ VEOF index and VTIME โ VEOL index โ safe, but write portable code anyway. | |||
Why does this matter in practice? Suppose your program:
- Reads the original terminal settings with
tcgetattr(). - Switches to noncanonical mode and sets
c_cc[VMIN] = 1. - Later tries to return to canonical mode by toggling the
ICANONflag.
On a system where VMIN == VEOF (same constant value), step 2 silently overwrote the EOF character with the value 1 (ASCII SOH, not Ctrl-D). When canonical mode is restored in step 3, the user presses Ctrl-D and nothing happens โ EOF is now value 1, which is nearly impossible to type.
The portable, correct solution:
/* Save BEFORE switching to noncanonical */
struct termios saved, t;
tcgetattr(STDIN_FILENO, &saved); /* snapshot of canonical settings */
/* Switch to noncanonical */
tcgetattr(STDIN_FILENO, &t);
t.c_lflag &= ~(ICANON | ECHO);
t.c_cc[VMIN] = 1;
t.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSAFLUSH, &t);
/* ... do work ... */
/* Restore using the SAVED structure, not by fiddling flags */
tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved); /* safe on all UNIX */
The saved structure still has the original VEOF = 0x04 (Ctrl-D), so restoring it puts the terminal back exactly as it was.
struct termios before making any mode change, and use that copy to restore. Never reconstruct the original settings by toggling individual flags.Seventh Edition UNIX (1979) and the early BSD terminal driver defined three named modes. Although modern POSIX replaced the single-bit RAW/CBREAK switches with the full termios structure, the concepts remain central to terminal programming. Every interactive application โ from vi to a BLE configuration shell โ maps to one of these modes.
| Feature | ๐ณ Cooked | ๐ฅ Cbreak | ๐ฉ Raw |
|---|---|---|---|
| Input available | Line by line | Char by char | Char by char |
| Line editing (Backspace etc.)? | โ Yes | โ No | โ No |
| Signal chars (Ctrl-C/Z/\)? | โ Yes | โ Yes | โ No |
| START/STOP (Ctrl-S/Q) flow? | โ Yes | โ Yes | โ No |
| Other special chars (EOF, EOL)? | โ Yes | โ No | โ No |
| Other input processing (ICRNL etc.)? | โ Yes | โ Yes | โ No |
| Output processing (OPOST etc.)? | โ Yes | โ Yes | โ No |
| Input echoed? | โ Yes | โ Maybe | โ No |
โ Cbreak echoing: typically disabled by the application (e.g. password input), but the mode itself does not mandate it.
Cooked mode is simply canonical mode with all the default special-character processing left enabled. This is what you get on a fresh terminal โ the normal shell prompt experience.
keystroke
discipline
to screen
(Backspace works)
full line on Enter
What is active in Cooked mode:
- ICANON โ input collected line by line; backspace erases.
- ECHO โ typed characters echoed to screen.
- ISIG โ Ctrl-C sends SIGINT, Ctrl-Z sends SIGTSTP, Ctrl-\ sends SIGQUIT.
- ICRNL โ incoming CR (0x0D from Enter key) mapped to NL (0x0A).
- OPOST โ output processing: NL expanded to CR+NL for display.
- Special chars โ EOF (Ctrl-D), EOL all work normally.
When to use: Normal shell, simple line-input programs (scanf, fgets). The kernel does all the work for you.
Cbreak mode sits between Cooked and Raw. The application receives each keystroke immediately (no waiting for Enter), but signals (Ctrl-C, Ctrl-Z) and some I/O transformations still work.
keystroke
no line buffer
Ctrl-C works โ
char immediately
What is ON in Cbreak:
- ISIG โ signal-generating characters still active (Ctrl-C, Ctrl-Z, Ctrl-\).
- Input/output transformations (ICRNL, OPOST) can still be configured independently.
What is OFF in Cbreak:
- ICANON โ no line buffering, no backspace editing.
- ECHO โ usually disabled (application echoes manually if needed).
- Special chars beyond signal chars (VEOF, VEOL, VSTART, VSTOP, etc.) are ignored.
Classic use cases:
- less, more โ page through output one key at a time, Ctrl-C still quits.
- vim command mode โ immediate response to
h/j/k/l, but Ctrl-Z suspends as expected. - Any full-screen TUI app using ncurses: the library’s
cbreak()function sets this mode.
Key flag to set: disable ICANON and ECHO, keep ISIG.
t.c_lflag &= ~(ICANON | ECHO); /* OFF: line-by-line, echo */
t.c_lflag |= ISIG; /* ON: Ctrl-C/Z/\ still work */
t.c_iflag &= ~ICRNL; /* optional: stop CRโNL mapping */
t.c_cc[VMIN] = 1; /* return after 1 character */
t.c_cc[VTIME] = 0; /* block indefinitely */
Raw mode is the opposite extreme of Cooked. The terminal driver becomes a transparent pipe: every byte typed reaches your program unchanged, and every byte your program writes goes to the terminal unchanged.
byte
NO ISIG
NO ECHO
transformations
transformations
raw byte
Everything that gets turned OFF in Raw mode:
| Flag | What it controls | Effect of turning OFF |
|---|---|---|
ICANON |
Canonical (line) mode | Chars available one at a time immediately |
ISIG |
Signal generation | Ctrl-C, Ctrl-Z, Ctrl-\ pass as raw bytes |
IEXTEN |
Extended input processing | LNEXT (Ctrl-V), DISCARD (Ctrl-O) disabled |
ECHO |
Input echoing | Typed chars not echoed; app must do it |
BRKINT |
BREAK โ SIGINT | BREAK condition passes as null byte |
ICRNL |
CR โ NL mapping on input | CR (0x0D) reaches process as-is |
IXON |
START/STOP flow control | Ctrl-S/Ctrl-Q pass as raw bytes |
OPOST |
Output post-processing | NL not expanded to CR+NL; exact bytes sent |
INPCK / ISTRIP / PARMRK |
Parity checking & 8th-bit strip | All 8 bits pass through unmodified |
Raw mode is used for:
- Serial line forwarding / pass-through (modem communication).
- Full-screen editors (
vimitself uses raw for its internal I/O layer). - Embedded firmware update tools โ every byte matters, no transformations allowed.
- BLE HCI sniffer tools that read raw bytes from a UART-attached dongle.
- Binary protocol terminals (XMODEM, ZMODEM file transfer over serial).
shell, fgets(), scanf()
less, ncurses, pagers
serial relay, editors, binary protocols
Applications that use the ncurses library do not need to manipulate termios directly. ncurses provides wrapper functions that handle all the flag setting for you:
#include <ncurses.h>
int main(void)
{
initscr(); /* initialise ncurses, opens terminal */
cbreak(); /* equivalent to Cbreak mode: char-by-char, signals kept */
/* OR */
raw(); /* equivalent to Raw mode: all processing off */
noecho(); /* turn off echo (ncurses manages display itself) */
keypad(stdscr, TRUE); /* enable F-keys, arrow keys */
int ch = getch(); /* blocks until one character typed */
endwin(); /* restore terminal */
return 0;
}
Behind the scenes cbreak() calls tcsetattr() after clearing ICANON and ECHO while keeping ISIG, just like our manual implementation in Part 6B. raw() additionally clears ISIG and all input processing flags.
termios manipulation โ ncurses handles portability, terminal capability detection (via terminfo), and restores the terminal correctly even on crash via endwin().Q1. What is the difference between Cooked mode and Cbreak mode?
In Cooked mode input is buffered line by line; the process receives a full line only when the user presses Enter, and line-editing (Backspace) works. In Cbreak mode (ICANON off), each keystroke is delivered immediately to the process without waiting for Enter and without backspace editing. However, signal- generating characters (Ctrl-C โ SIGINT, Ctrl-Z โ SIGTSTP) still work in Cbreak, whereas in Raw mode they are disabled too.
Q2. Why is it unsafe to toggle only the ICANON flag when returning from noncanonical mode?
On some UNIX implementations VMIN and VEOF share the same c_cc array index. Setting c_cc[VMIN] = 1 in noncanonical mode therefore silently overwrites the EOF character. When you later toggle ICANON back on, EOF is no longer Ctrl-D (0x04) and the terminal becomes unusable. The safe approach is to save the entire struct termios before making any change and restore that saved copy.
Q3. Which flags must be cleared to put a terminal into Raw mode?
c_lflag: ICANON | ISIG | IEXTEN | ECHO
c_iflag: BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON | PARMRK
c_oflag: OPOST
Additionally set c_cc[VMIN]=1 and c_cc[VTIME]=0 for blocking single-character input.
Q4. What does OPOST control and why must it be disabled in Raw mode?
OPOST enables output post-processing โ the most common effect is expanding a newline (0x0A) to a CR+NL pair (0x0D 0x0A) for display. In Raw mode the application is responsible for sending exact byte sequences (e.g., binary firmware images, XMODEM packets, BLE HCI commands) so any automatic character substitution would corrupt the data. Clearing OPOST ensures the kernel writes exactly what the application sends.
Q5. When would you choose Cbreak mode over Raw mode in an embedded Linux application?
Choose Cbreak when you need immediate character-by-character input but still want the OS to handle process control signals. For example, a BLE device configuration CLI on an embedded board: the user presses single keys to navigate menus (requiring immediate delivery, so not Cooked), but pressing Ctrl-C should abort the program cleanly via SIGINT rather than passing the 0x03 byte to the application logic. Use Raw mode only when you need to intercept all input including control characters โ for instance, when implementing your own signal handling or forwarding bytes over a serial link.
Q6. What is the difference between VMIN/VTIME and VEOF/VEOL in the c_cc array?
VEOF and VEOL are indices used in canonical mode: c_cc[VEOF] holds the EOF character (default Ctrl-D) and c_cc[VEOL] holds an optional additional line delimiter. VMIN and VTIME are indices used in noncanonical mode: c_cc[VMIN] is the minimum number of bytes before a read() returns, and c_cc[VTIME] is a timeout in tenths of a second. SUSv3 allows these pairs to share the same physical array slot because the two sets are never active simultaneously, but on Linux they have distinct constant values.
Q7. What does ncurses cbreak() do that differs from ncurses raw()?
Both disable line buffering (ICANON) for character-by-character input. cbreak() keeps ISIG active so Ctrl-C/Z/\ still send signals. raw() additionally clears ISIG and all input/output processing, giving the application complete control over every byte โ no automatic signal generation, no CR/NL mapping, no flow control.
Continue Learning
Next: See how to implement ttySetCbreak() and ttySetRaw() in C with full signal handling
โ Part 6B: ttySetCbreak & ttySetRaw Code EmbeddedPathashala Home
