How to Access and Communicate with Linux Serial Ports Using C
This guide explains Linux serial port device files, shows how to list them, and provides four C programming methods—polling, select‑based interrupt, SIGIO signal handling, and threaded reading—complete with code examples and detailed configuration steps.
Serial Port Device Files
Linux exposes peripheral devices as files under /dev. The traditional COM ports correspond to /dev/ttyS0 (COM1), /dev/ttyS1 (COM2), while USB‑to‑serial adapters appear as /dev/ttyUSB0, /dev/ttyUSB1, etc. The command ls /dev/ttyS* lists available serial devices.
Method 1 – Polling
Open the device with open(port, O_RDWR | O_NOCTTY | O_NDELAY), configure it using tcgetattr and tcsetattr with a 115200 baud rate, 8‑N‑1 settings, and enable CLOCAL and CREAD. The relevant flag meanings are listed.
fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) { perror("open_port: Unable to open serial port"); return -1; }
tcgetattr(fd, &options);
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_cflag &= ~CRTSCTS;
tcsetattr(fd, TCSANOW, &options);Read and write with read(fd, buf, sizeof(buf)) and write(fd, buf, strlen(buf)), then close with close(fd). A full example program is provided.
Method 2 – Select (Interrupt‑Driven)
Using select to wait for the file descriptor to become readable avoids busy‑waiting. The example opens /dev/ttyUSB0, sets the same 9600‑baud 8‑N‑1 parameters, then loops:
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
select(fd+1, &rfds, NULL, NULL, NULL);
int n = read(fd, buf, sizeof(buf));
if (n > 0) printf("Received data: %.*s
", n, buf);The descriptor must be put into non‑blocking mode with fcntl(fd, F_SETFL, flags | O_NONBLOCK).
Method 3 – Signal (SIGIO) Notification
Configure the descriptor for asynchronous I/O with fcntl(fd, F_SETOWN, getpid()) and fcntl(fd, F_SETFL, flags | O_ASYNC). Register a sigio_handler via sigaction(SIGIO, &sa, NULL) that reads the data when the signal arrives.
void sigio_handler(int sig) {
char buf[256];
int n = read(fd, buf, sizeof(buf));
if (n > 0) printf("Received data: %.*s
", n, buf);
}Method 4 – Threaded Reading
Spawn a POSIX thread that continuously calls read(fd, buf, sizeof(buf)). The main thread can perform other work while the reader thread processes incoming bytes.
void *read_thread(void *arg) {
int fd = *(int *)arg;
char buf[256];
while (1) {
int n = read(fd, buf, sizeof(buf));
if (n > 0) printf("Received data: %.*s
", n, buf);
}
return NULL;
}All four approaches demonstrate how to open, configure, read from, and close a Linux serial port using standard POSIX APIs, allowing developers to choose between simple polling, event‑driven select, signal‑based notification, or multithreaded handling based on performance and design requirements.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
