Fundamentals 10 min read

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.

Liangxu Linux
Liangxu Linux
Liangxu Linux
How to Access and Communicate with Linux Serial Ports Using C

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.

C++Linuxselectsignal-handlingPollingSerial PortDevice FilesTermios
Liangxu Linux
Written by

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.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.