USB 시리얼 udev symlink, /dev/ttyUSB를 고정하려면

USB-UART(FTDI·CP210x·CH340 등)를 꽂으면 커널이 /dev/ttyUSB0, /dev/ttyUSB1처럼 열거 순서에 따라 노드를 만듭니다. 스크립트·systemd·플래시 도구가 /dev/ttyUSB0에 묶여 있으면, 동글 두 개·허브·재부팅만으로 경로가 어긋납니다.

해결의 축은 원본 노드 이름을 바꾸지 않고 SYMLINK+=로 고정 별칭을 다는 것입니다. udev 매뉴얼도 장치 노드 NAME을 마음대로 바꾸기보다 심볼릭 링크를 추가하는 쪽을 전제로 합니다. 이 글은 ttyUSB symlink만 다룹니다. 규칙 디렉터리 개론·HID·입력장치 dialout 전반은 별도 글(일반 udev 규칙편·입력장치 권한편)에 맡깁니다.

근거: udev(7), udevadm(8). 특정 보드 후기·창작 시리얼 번호 스토리는 없습니다. 예시는 플레이스홀더 VID/PID/serial입니다.

ATTR은?

한 줄 답: 이벤트 장치(sysfs)에는 ATTR{…}, 부모를 올라가며 찾을 때는 ATTRS{…}를 씁니다. USB VID/PID·시리얼은 대개 tty 노드가 아니라 USB 부모에 있으므로 ATTRS로 잡고, 한 규칙 줄의 여러 ATTRS는 같은 부모 디바이스에서 동시에 맞아야 합니다.

매칭에 자주 쓰는 키(시리얼 tty 기준):

키역할ttyUSB에서
KERNEL=="ttyUSB*" 또는 SUBSYSTEM=="tty"문자 디바이스(이벤트 장치)를 고름raw USB(SUBSYSTEM=="usb")만 매칭하면 앱이 기대하는 tty가 아닐 수 있음
ATTRS{idVendor} / ATTRS{idProduct}USB 부모의 VID/PIDudevadm info -a로 실측한 헥사 문자열
ATTRS{serial}동종 동글 구분여러 개가 있으면 반드시 넣는 편이 안전
SYMLINK+="이름"/dev/이름 고정 링크 추가원본 /dev/ttyUSBn은 그대로 둠

속성 확인:

# 현재 노드 (예: ttyUSB0) — 실측 후 규칙에 옮김
udevadm info -a -n /dev/ttyUSB0 | grep -E 'ATTRS\{(idVendor|idProduct|serial|manufacturer|product)\}'

규칙 초안(값은 본인 동글 실측으로 교체):

# /etc/udev/rules.d/99-ttyusb-symlink.rules
SUBSYSTEM=="tty", KERNEL=="ttyUSB*", \
  ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", \
  ATTRS{serial}=="SERIALPLACEHOLDER", \
  SYMLINK+="my-uart"

주의:

  1. ATTR ≠ ATTRS. ATTR는 이벤트 장치 자신, ATTRS는 부모 탐색입니다. VID를 tty 노드의 ATTR로 적으면 보통 매칭이 안 됩니다.
  2. 한 줄의 ATTRS는 동일 부모. 서로 다른 조상의 속성을 한 규칙에 섞지 않습니다(udev(7)의 ATTRS 설명).
  3. 값 대소문자·공백. udevadm info -a에 나온 문자열을 그대로 복사합니다.
  4. 이미 /dev/serial/by-id/…가 있으면 앱 경로만 그쪽으로 바꿔도 열거 문제를 피할 수 있습니다. 커스텀 SYMLINK는 짧은 고정 이름이 필요할 때 씁니다.
  5. 일반 udev 개론과의 차이: 여기서는 “규칙을 어디에 두나”보다 ttyUSB를 어떤 ATTRS로 집어 SYMLINK를 다는가만 다룹니다.

GROUP/MODE는?

한 줄 답: GROUP=·MODE=는 장치 노드 권한의 컴파일 기본값을 덮어씁니다. 경로는 SYMLINK로 고정하고, 접근은 보통 GROUP="dialout" + MODE="0660"(+ 사용자를 dialout에 추가)으로 맞춥니다.

시리얼 전용으로 확장한 예:

# /etc/udev/rules.d/99-ttyusb-symlink.rules
SUBSYSTEM=="tty", KERNEL=="ttyUSB*", \
  ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", \
  ATTRS{serial}=="SERIALPLACEHOLDER", \
  GROUP="dialout", MODE="0660", \
  SYMLINK+="my-uart"

실무 포인트:

항목권장이유
GROUP="dialout"Debian/Ubuntu·systemd 기본 tty 관행과 맞춤사용자를 dialout에 넣고 재로그인
MODE="0660"소유자·그룹만 RW0666 전역 쓰기는 피함
OWNER=특별한 서비스 계정일 때만개인 랩이면 GROUP으로 충분한 경우가 많음
SYMLINK+="my-uart"앱·유닛의 Device=/ExecStart 경로/dev/ttyUSB0 하드코딩 제거

그룹 멤버십:

sudo usermod -aG dialout "$USER"
# 적용은 로그아웃/로그인(또는 새 로그인 세션) 후
groups   # dialout 포함 여부

SYMLINK만 있고 권한이 root:root 0600이면 경로는 고정돼도 일반 사용자 도구는 Permission denied가 납니다. 반대로 GROUP만 고치고 이름을 /dev/ttyUSB0에 묶어두면 열거 순서 문제는 남습니다. 이 글의 목표는 이름(SYMLINK) + 접근(GROUP/MODE) 을 같이 고정하는 것입니다. HID·plugdev·uaccess 태그는 입력장치 권한편 범위입니다.

udevadm 검증은?

한 줄 답: 파일을 저장한 뒤 udevadm verify로 구문·의미·스타일을 보고, control --reload-rules → trigger → info/test/ls -l로 SYMLINK·GROUP이 실제로 붙었는지 확인합니다.

# 1) 규칙 파일 검증 (systemd 최근 udev)
sudo udevadm verify /etc/udev/rules.d/99-ttyusb-symlink.rules

# 2) 데몬에 규칙 리로드 (이미 꽂힌 장치에는 즉시 미반영)
sudo udevadm control --reload-rules

# 3) tty 서브시스템 재이벤트 (또는 해당 노드만)
sudo udevadm trigger --subsystem-match=tty
# sudo udevadm trigger -n /dev/ttyUSB0

# 4) 결과 확인
ls -l /dev/my-uart /dev/ttyUSB0
udevadm info -n /dev/ttyUSB0 | grep -E 'DEVNAME|DEVLINKS|ID_VENDOR|ID_MODEL|ID_SERIAL'

# 5) dry-run (syspath는 환경에 맞게)
sudo udevadm test /sys/class/tty/ttyUSB0 2>&1 | tail -n 50

체크리스트:

  1. udevadm verify 종료 코드 0 — 구문/GROUP 이름 해석·스타일 이슈를 먼저 잡습니다(--no-style은 스타일만 무시).
  2. /dev/my-uart → 기대한 ttyUSBn을 가리키는가.
  3. ls -l 그룹이 dialout(또는 지정 GROUP), 모드가 기대한가.
  4. 재로그인 후 일반 사용자로 시리얼 도구가 열리는가.
  5. 동글을 빼고 다시 꽂아도 같은 SYMLINK 이름이 돌아오는가(다른 포트·허브여도 ATTRS가 맞으면 동일).

실패 시 흔한 원인: ==(매칭)과 =(할당) 혼동, .rules 확장자 누락, VID/PID 오타, ATTRS를 서로 다른 부모에서 조합, SUBSYSTEM=="usb"에만 SYMLINK를 걸어 tty가 아닌 노드를 가리킴, reload만 하고 trigger/재플러그를 안 함.

FAQ

/dev/ttyUSB0 자체를 바꿀 수 있나요?

udev는 커널이 만든 노드 이름을 임의로 바꾸기보다 추가 심볼릭 링크(SYMLINK+=) 로 별칭을 제공합니다. 앱·유닛은 /dev/my-uart처럼 링크를 쓰십시오.

ttyACM*도 같은가?

CDC-ACM 계열은 /dev/ttyACM*입니다. KERNEL=="ttyACM*"(또는 SUBSYSTEM=="tty")로 잡고, 마찬가지로 부모 ATTRS{idVendor/idProduct/serial} + SYMLINK+= 패턴입니다. 이 글 제목은 ttyUSB이지만 매칭 축은 동일합니다.

/dev/serial/by-id/…는 배포판·udev가 제공하는 안정 경로입니다. 짧은 이름·여러 앱 공통 별칭·권한까지 한 규칙에 묶으려면 커스텀 SYMLINK(+ GROUP/MODE)가 낫습니다.

일반 udev 규칙 글과 무엇이 다른가요?

일반편은 규칙 디렉터리·ATTR/KERNEL 개요·검증 흐름을 넓게 봅니다. 이 글은 /dev/ttyUSB 열거 흔들림을 SYMLINK로 고정하는 축만 다룹니다.

출처 (Sources)