Linux 사용자 공간 · POSIX termios

직렬 통신 코드는 읽은 길이부터 확인합니다

termios 설정의 수명과 read/poll 결과를 구분하는 C 예제입니다.

tcgetattr가 성공한 뒤에 구조체를 사용합니다

tcgetattr가 실패하면 출력 구조체에 올바른 설정이 들어 있다고 볼 수 없습니다. 원래 설정을 보관하고 사본을 수정한 다음 tcsetattr로 적용합니다. 직렬 장치의 baud rate, 데이터 비트, parity, stop bit, 흐름 제어는 상대 장치와 맞춰야 합니다.

struct termios saved, next;
if (tcgetattr(fd, &saved) == -1) {
    perror("tcgetattr");
    return -1;
}
next = saved;
next.c_lflag &= ~(ICANON | ECHO);
next.c_cc[VMIN] = 1;
next.c_cc[VTIME] = 0;
if (tcsetattr(fd, TCSANOW, &next) == -1) {
    perror("tcsetattr");
    return -1;
}

이 부분 코드는 비정규 입력과 echo만 바꿉니다. 완전한 바이너리 직렬 설정은 아닙니다. tcsetattr는 일부 설정만 적용되어도 성공할 수 있으므로 중요한 설정은 다시 읽어 확인합니다. 종료 때 saved로 복원하며 복원 실패도 확인합니다.

glibc 2.42: 속도 상수는 c_cflag 비트가 아닙니다

B115200은 속도 설정 함수에 넘기는 값입니다. 이를 c_cflag에 직접 OR하는 것은 같은 일이 아닙니다. Linux용 glibc 2.42에서는 공개 Bxxx 상수가 실제 속도 수치로 바뀌었지만, 커널과 주고받는 제어 플래그에는 여전히 별도의 속도 인코딩이 있습니다. 예전 비트 배치를 전제로 한 소스를 새 헤더로 재빌드하면 속도 이외의 비트까지 건드릴 수 있습니다.

glibc 2.42의 공통 속도 헤더와 Linux/generic 인코딩 예
구분값의미
공개 B115200115200 = 0x1C200속도 함수에 전달하는 요청값
내부 __B1152000010002 = 0x1002위 헤더 경로의 legacy 속도 비트
내부 CBAUD 마스크000000010017 = 0x100F이 예의 출력 속도 필드

앞에 0이 붙은 두 상수는 C의 8진 표기입니다. 0x1C200 & 0x100F = 0이며, 수치 115200은 이 마스크 밖에도 비트를 갖습니다. 이것은 두 표현이 서로 다르다는 계산 예이지 특정 UART에서 관측한 결과가 아닙니다. 아키텍처별 인코딩이 다를 수 있고, __B115200 같은 내부 이름을 응용 프로그램의 대체 API로 사용해서도 안 됩니다.

요청값과 커널 표현 사이의 변환
  1. B115200응용 프로그램이 속도 API에 값을 전달합니다.
  2. cfset*speedglibc가 속도 필드와 필요한 제어 비트를 맞춥니다.
  3. tcsetattr장치 드라이버에 적용을 요청합니다.

함수의 성공, 읽어 온 설정값, 실제 선로의 전송 결과는 각각 확인해야 합니다.

속도만 바꾸고 결과를 다시 읽는 예

다음 함수는 이미 열린 fd의 입력·출력 속도만 115200으로 요청합니다. 데이터 비트·parity·흐름 제어나 RS485 모드를 함께 정하는 완전한 장치 초기화 코드는 아닙니다. 현재 설정을 읽은 뒤 사본을 수정하므로 관련 없는 플래그를 상수 한 묶음으로 덮어쓰지 않습니다.

#include <termios.h>

/* 0: readback matches, 1: differs, -1: API failure. */
int set_115200_checked(int fd)
{
    struct termios current, requested, observed;
    if (tcgetattr(fd, &current) == -1)
        return -1;
    requested = current;
    if (cfsetospeed(&requested, B115200) == -1)
        return -1;
    if (cfsetispeed(&requested, B115200) == -1)
        return -1;
    if (tcsetattr(fd, TCSANOW, &requested) == -1)
        return -1;
    if (tcgetattr(fd, &observed) == -1)
        return -1;
    return (cfgetospeed(&observed) == B115200 &&
            cfgetispeed(&observed) == B115200) ? 0 : 1;
}

속도 setter에는 장치 fd가 없으므로 성공해도 그 UART의 지원 속도까지 확인한 것은 아닙니다. 드라이버가 요청을 거부하거나 가까운 값으로 조정할 수 있고, 재조회가 실제 선로 속도를 그대로 보여 주지 않을 수도 있습니다. 위 함수의 0도 물리적 통신 성공을 뜻하지 않습니다. 실패 후에도 일부 설정이 이미 바뀌었을 수 있으며 자동 복원하지 않습니다. 호출자는 원래 설정의 보존·복원과 동시 설정 변경 방지 정책을 정해야 합니다.

재빌드와 RS485 모드 변경을 구분합니다

glibc는 관련 속도 함수에 GLIBC_2.42 심볼 버전을 추가하면서 구버전 호환 경로도 유지했습니다. 따라서 새 헤더로 재빌드한 잘못된 직접 비트 조작과, 기존 동적 연결 바이너리를 실행하는 상황을 같은 문제로 단정할 수 없습니다. 이전 바이너리가 전부 깨진다는 뜻도 아닙니다. 빌드에 쓰인 헤더·libc와 실행 시 연결되는 libc를 각각 확인합니다.

RS485 모드와 방향 전환 설정은 TIOCGRS485/TIOCSRS485라는 별도 경로입니다. 기존 RS485 문서의 지원 플래그와 장치 트리 초기값 설명을 함께 봅니다. 명령행 인자가 장치 트리 설정을 항상 덮어쓴다는 보편 규칙은 없습니다. 문제를 추적할 때는 프로그램의 설정 순서, 실제 ioctl 인자, 적용 후 재조회 결과를 비교하며 심볼 이름 차이만으로 실패 원인을 확정하지 않습니다.

English summary: speed values and control bits

In Linux glibc 2.42, Bxxx constants denote numeric rates; they are not c_cflag bit patterns. Set speeds through cfsetospeed/cfsetispeed, preserve unrelated settings, check tcsetattr, and read back the result. Neither setter success nor matching readback proves the physical UART rate. Versioned compatibility functions distinguish rebuilding old source from running an existing binary. RS485 mode ioctls are a separate configuration path. This is a section summary, not a full translation of the article.

분석 기준: glibc 2.42 출시 커밋 d2097651. 고정 소스와 수치 계산 대조이며, 이 예제의 C 컴파일·UART 실행·선로 측정은 수행하지 않았습니다.

read는 문자열을 만들어 주지 않습니다

#include <errno.h>
#include <unistd.h>

ssize_t read_retry(int fd, void *buffer, size_t capacity)
{
    ssize_t n;
    do {
        n = read(fd, buffer, capacity);
    } while (n < 0 && errno == EINTR);
    return n;
}

양수이면 그 바이트 수만 유효합니다. %s로 출력하려면 별도의 NUL 종료 공간과 텍스트라는 조건이 필요합니다. 바이너리는 NUL을 포함할 수 있으므로 길이를 기준으로 처리합니다. 음수이면 errno를 확인하며, EAGAIN은 비차단 입력에서 지금 읽을 데이터가 없을 수 있다는 뜻입니다.

TTY에서 0 반환의 뜻은 설정에 따라 다릅니다. 비정규 모드의 VMIN=0에서는 데이터가 없거나 VTIME이 만료되어도 0이 될 수 있습니다. 모든 0을 장치 연결 종료로 처리하면 안 됩니다. VTIME의 단위는 0.1초이며 VMIN과 조합한 동작을 확인해야 합니다.

poll의 반환 개수와 revents를 따로 봅니다

결과처리
poll = -1errno를 확인합니다. EINTR이면 종료 요청과 재시도 정책을 판단합니다.
poll = 0지정한 대기 시간이 지났습니다.
poll > 0revents가 설정된 항목의 개수입니다. 각 항목의 비트를 검사합니다.
POLLIN과 POLLHUP동시에 올 수 있습니다. 남은 데이터를 읽을지와 종료 처리를 함께 설계합니다.
POLLERR / POLLNVAL오류와 잘못된 fd입니다. 단순한 데이터 도착으로 처리하지 않습니다.
입력을 읽고 사용하는 순서
  1. 준비 상태 확인poll 결과와 revents를 검사합니다.
  2. 실제 read 결과 확인준비 상태 이후에도 경쟁이나 오류가 생길 수 있습니다.
  3. n바이트만 처리프로토콜의 프레임이 완성됐는지 별도로 판단합니다.

한 번의 poll이나 read가 메시지 하나의 완성을 보장하지 않습니다. 상자는 처리 순서입니다.

쓰기와 오류 보고도 길이와 반환 규약을 따릅니다

write가 요청한 길이보다 적게 쓸 수 있으므로 남은 범위를 처리해야 합니다. 상대 장치가 메시지를 처리했다는 확인은 write 성공과 별개입니다. close만으로 다른 프로세스에도 공유되는 터미널 설정이 이전 상태로 돌아온다고 가정하지 않습니다.

perror는 현재 errno를 설명하므로 실패 직후 사용하거나 errno를 보존합니다. pthread 함수들은 보통 오류 번호를 반환하므로 pthread_create의 실패를 perror로 설명하면 다른 오류를 출력할 수 있습니다. 반환값을 strerror에 넘기는 등 API의 오류 규약에 맞춰야 합니다.

확인한 문서

Linux 사용자 공간 · POSIX termios