Line Control Functions
<termios.h>
POSIX / SUSv3
What is Terminal Line Control?
When you write data to a terminal or serial device, it does not get sent instantly. It sits in a kernel buffer (queue) waiting to be transmitted. Similarly, incoming data from the device sits in an input queue waiting for your program to read it.
Terminal line control gives you direct control over these queues. You can:
— Wait until all output has been sent before continuing.
— Throw away buffered data you no longer need.
— Pause or resume data flow between the computer and the device.
— Send a special BREAK signal on the serial line.
Four functions provide this control: tcsendbreak(), tcdrain(), tcflush(), and tcflow(). These are POSIX standardized replacements for older ioctl() calls.
Understanding the queues is key before learning the control functions.
Your Processwrite(fd, buf, n) |
→ | Output Queue (kernel buffer) tcflush(TCOFLUSH) discards this tcdrain() waits for this to empty |
→ | Serial Device (Terminal / UART) |
Your Processread(fd, buf, n) |
← | Input Queue (kernel buffer) tcflush(TCIFLUSH) discards this type-ahead data waits here |
← | Serial Device (Terminal / UART) |
tcflow() controls whether data is allowed to move between the device and the queues at all — acting like a tap that you can open or close.
#include <termios.h>
int tcsendbreak(int fd, int duration);
int tcdrain(int fd);
int tcflush(int fd, int queue_selector);
int tcflow(int fd, int action);
/* All return 0 on success, or -1 on error */
In all four functions, fd is a file descriptor pointing to a terminal or a remote device on a serial line.
A BREAK condition is a special signal in serial communication — it transmits a continuous stream of zero bits (logic LOW) for a defined duration. It is used to get the attention of a remote device, reset baud rate detection, or signal out-of-band events.
Think of it like pressing a “reset/alert” button on the wire — not data, just a signal.
| duration value | Effect (glibc / Linux) |
|---|---|
0 |
Transmit 0 bits for 0.25–0.5 seconds (SUSv3 mandated range) |
> 0 |
Transmit 0 bits for duration milliseconds (glibc specific; portable code should avoid nonzero) |
/* Send a standard BREAK of 0.25-0.5 seconds */
if (tcsendbreak(fd, 0) == -1) {
perror("tcsendbreak");
exit(EXIT_FAILURE);
}
tcdrain(fd) blocks (sleeps) until the entire output queue has been transmitted to the device. It returns only when the last byte has physically left the output buffer.
Real world analogy: Imagine putting letters in a mailbox and waiting by the door until the postman picks them all up before you go inside. tcdrain() makes your program wait at the door.
When to use it:
- Before changing the baud rate — you want all data sent at the old speed first.
- Before closing a serial port — to avoid cutting off data mid-transmission.
- When order of operations between send and a following action is critical.
/* Write some data to serial port */
write(fd, "AT\r\n", 4);
/* Block until the above bytes have actually been sent out */
if (tcdrain(fd) == -1) {
perror("tcdrain");
exit(EXIT_FAILURE);
}
/* Now safe to close or change settings */
printf("All data transmitted.\n");
tcflush() discards (throws away) data sitting in the input queue, the output queue, or both — without sending or processing it.
| queue_selector | What Gets Discarded | Typical Use |
|---|---|---|
TCIFLUSH |
Input queue (received but not yet read) | Discard type-ahead before a password prompt |
TCOFLUSH |
Output queue (written but not yet sent) | Cancel pending output after an error or abort |
TCIOFLUSH |
Both input and output queues | Full reset before restarting communication |
Warning on naming: The word “flush” here means discard/throw away, which is the opposite of how it is used in file I/O. In file I/O, “flush” means “force data out” (like fflush() or fsync()). In terminal line control, “flush” = “discard”.
/* Discard any type-ahead the user typed before a password prompt */
printf("Enter password: ");
fflush(stdout);
/* Throw away anything the user may have already typed */
if (tcflush(STDIN_FILENO, TCIFLUSH) == -1) {
perror("tcflush");
exit(EXIT_FAILURE);
}
/* Now read the fresh password */
char password[128];
if (fgets(password, sizeof(password), stdin) == NULL) {
perror("fgets");
exit(EXIT_FAILURE);
}
/* Cancel all pending output that has not been sent yet */
if (tcflush(fd, TCOFLUSH) == -1) {
perror("tcflush TCOFLUSH");
exit(EXIT_FAILURE);
}
tcflow() controls whether data is allowed to flow between the computer and the terminal. It can pause/resume sending in either direction.
| action | Direction | Effect |
|---|---|---|
TCOOFF |
Computer → Terminal | Suspend output from computer to terminal |
TCOON |
Computer → Terminal | Resume output from computer to terminal |
TCIOFF |
Terminal → Computer | Send STOP character to make terminal stop sending |
TCION |
Terminal → Computer | Send START character to make terminal resume sending |
TCIOFF/TCION only work if the terminal supports software flow control (XON/XOFF protocol). When the computer sends a STOP character (Ctrl+S, ASCII 0x13), the terminal pauses transmitting. A START character (Ctrl+Q, ASCII 0x11) resumes it.
/* Pause output being sent to the terminal */
if (tcflow(fd, TCOOFF) == -1) {
perror("tcflow TCOOFF");
exit(EXIT_FAILURE);
}
/* ... do some processing ... */
/* Resume output to the terminal */
if (tcflow(fd, TCOON) == -1) {
perror("tcflow TCOON");
exit(EXIT_FAILURE);
}
/* Tell the remote terminal to stop sending data to us */
if (tcflow(fd, TCIOFF) == -1) {
perror("tcflow TCIOFF");
exit(EXIT_FAILURE);
}
/* Process what we already have, then re-enable */
if (tcflow(fd, TCION) == -1) {
perror("tcflow TCION");
exit(EXIT_FAILURE);
}
This example opens a serial port, sends a BREAK, waits for output to drain, flushes the input queue, and demonstrates flow control — showing all four line control functions together.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
int open_serial(const char *device)
{
int fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
return fd;
}
int main(void)
{
int fd = open_serial("/dev/ttyS0");
/* --- 1. tcsendbreak: send a BREAK alert signal --- */
printf("Sending BREAK signal...\n");
if (tcsendbreak(fd, 0) == -1) {
perror("tcsendbreak");
exit(EXIT_FAILURE);
}
printf("BREAK sent.\n");
/* --- 2. Write some data, then use tcdrain to wait --- */
const char *msg = "Hello Serial Device\r\n";
ssize_t n = write(fd, msg, strlen(msg));
if (n == -1) {
perror("write");
exit(EXIT_FAILURE);
}
printf("Written %zd bytes. Waiting for transmission...\n", n);
/* Block until all bytes are physically transmitted */
if (tcdrain(fd) == -1) {
perror("tcdrain");
exit(EXIT_FAILURE);
}
printf("All output drained (transmitted).\n");
/* --- 3. tcflush: discard stale input before a fresh read --- */
printf("Flushing input queue (discarding stale data)...\n");
if (tcflush(fd, TCIFLUSH) == -1) {
perror("tcflush");
exit(EXIT_FAILURE);
}
printf("Input queue flushed.\n");
/* --- 4. tcflow: pause then resume output --- */
printf("Suspending output to terminal...\n");
if (tcflow(fd, TCOOFF) == -1) {
perror("tcflow TCOOFF");
exit(EXIT_FAILURE);
}
/* Write while paused -- data queues but doesn't transmit yet */
write(fd, "This is queued...\r\n", 19);
printf("Resuming output...\n");
if (tcflow(fd, TCOON) == -1) {
perror("tcflow TCOON");
exit(EXIT_FAILURE);
}
/* Wait for the queued data to actually transmit */
tcdrain(fd);
printf("Done. Closing serial port.\n");
close(fd);
return 0;
}
gcc -o line_control line_control.c
sudo ./line_control
Q1. What is a BREAK condition in serial communication?
A: A BREAK condition is when the transmitter sends a continuous stream of zero bits (logic LOW level) on the line for a specific duration — longer than a normal framing period. It is not data; it is a signaling mechanism used to alert remote devices or trigger special actions like baud rate reset. In Linux, tcsendbreak(fd, 0) generates it for 0.25–0.5 seconds.
Q2. What is the difference between tcdrain() and tcflush()?
A: tcdrain() waits for all pending output to be transmitted — it does not throw data away. tcflush() discards data in the queue without transmitting it. Use tcdrain() when you need to guarantee delivery; use tcflush() when you want to cancel/discard pending data.
Q3. In tcflush(), what is the meaning of TCIFLUSH, TCOFLUSH, and TCIOFLUSH?
A: TCIFLUSH discards the input queue (data received from device but not yet read). TCOFLUSH discards the output queue (data written but not yet sent to device). TCIOFLUSH discards both queues simultaneously.
Q4. Why is there a naming confusion around “flush” in terminal vs file I/O?
A: In file I/O, “flush” means forcing data out — fflush() pushes user-space buffers to the kernel, and fsync() pushes kernel buffers to disk. In terminal line control, tcflush() means the opposite — it discards the queued data. This is a historical naming inconsistency that developers must remember.
Q5. What are TCIOFF and TCION used for in tcflow()?
A: They are used to pause and resume data being sent from the terminal to the computer. TCIOFF sends a STOP character (Ctrl+S / XON) to the terminal, making it pause its transmission. TCION sends a START character (Ctrl+Q / XOFF) to tell it to resume. These only work if the terminal supports XON/XOFF software flow control.
Q6. When would you use tcflush(TCIFLUSH) in a real application?
A: A common use case is discarding type-ahead input before showing a password prompt. If the user typed something before the prompt appeared, that stale input could be accidentally used as the password. Calling tcflush(STDIN_FILENO, TCIFLUSH) before reading ensures only fresh input is accepted.
Q7. What were these line control functions designed to replace?
A: These POSIX functions — tcsendbreak(), tcdrain(), tcflush(), and tcflow() — were designed to replace various ioctl() operations that served the same purpose on older UNIX systems. The POSIX functions provide a standardized, portable interface.
Q8. What is the difference between TCOOFF/TCOON and TCIOFF/TCION in tcflow()?
A: TCOOFF/TCOON control output going from the computer to the terminal — like a tap the computer controls. TCIOFF/TCION control input coming from the terminal to the computer by sending XON/XOFF characters to the terminal, asking it to stop or start sending.
Next: Terminal Window Size — SIGWINCH, TIOCGWINSZ, winsize struct
