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/PID | udevadm 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"
주의:
ATTR≠ATTRS.ATTR는 이벤트 장치 자신,ATTRS는 부모 탐색입니다. VID를 tty 노드의ATTR로 적으면 보통 매칭이 안 됩니다.- 한 줄의
ATTRS는 동일 부모. 서로 다른 조상의 속성을 한 규칙에 섞지 않습니다(udev(7)의 ATTRS 설명). - 값 대소문자·공백.
udevadm info -a에 나온 문자열을 그대로 복사합니다. - 이미
/dev/serial/by-id/…가 있으면 앱 경로만 그쪽으로 바꿔도 열거 문제를 피할 수 있습니다. 커스텀SYMLINK는 짧은 고정 이름이 필요할 때 씁니다. - 일반 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" | 소유자·그룹만 RW | 0666 전역 쓰기는 피함 |
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
체크리스트:
udevadm verify종료 코드 0 — 구문/GROUP이름 해석·스타일 이슈를 먼저 잡습니다(--no-style은 스타일만 무시)./dev/my-uart→ 기대한ttyUSBn을 가리키는가.ls -l그룹이dialout(또는 지정 GROUP), 모드가 기대한가.- 재로그인 후 일반 사용자로 시리얼 도구가 열리는가.
- 동글을 빼고 다시 꽂아도 같은 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이지만 매칭 축은 동일합니다.
by-id만 쓰면 symlink 규칙이 필요 없나요?
/dev/serial/by-id/…는 배포판·udev가 제공하는 안정 경로입니다. 짧은 이름·여러 앱 공통 별칭·권한까지 한 규칙에 묶으려면 커스텀 SYMLINK(+ GROUP/MODE)가 낫습니다.
일반 udev 규칙 글과 무엇이 다른가요?
일반편은 규칙 디렉터리·ATTR/KERNEL 개요·검증 흐름을 넓게 봅니다. 이 글은 /dev/ttyUSB 열거 흔들림을 SYMLINK로 고정하는 축만 다룹니다.
출처 (Sources)
- udev(7) — ATTR / ATTRS / SYMLINK / GROUP / MODE
- udevadm(8) — verify, info, test, control, trigger
- systemd 50-udev-default.rules.in — tty* dialout 기본 관행