udev ACTION=add vs change — 규칙이 두 번 도는 이유
USB·시리얼·블록 장치를 꽂으면 udev 규칙의 RUN·PROGRAM이 두 번 도는 경우가 있습니다. 원인은 대체로 ACTION을 좁히지 않은 채 add와 change(또는 bind)에 모두 매칭되기 때문입니다. 이 글은 add와 change 차이 · 중복 실행 막기 · udevadm monitor로 확인만 다룹니다.
근거는 udev(7)의 ACTION 매치 키와 udevadm(8)의 이벤트 액션 목록(add / remove / change / move / online / offline / bind / unbind)입니다. 제휴·C++ 예제는 없습니다.
add와 change 차이는?
한 줄 답: ACTION은 그 이벤트의 액션 이름과 매칭합니다. add는 장치가 나타날 때, change는 장치 상태가 바뀌었을 때(드라이버·커널이 올리는 갱신, udevadm trigger 기본값 등)입니다. 둘은 서로 다른 이벤트입니다.
udev(7)은 커널이 장치를 추가·제거하거나 상태가 바뀔 때 uevent를 받고, 규칙의 매치 키로 걸러 적용한다고 설명합니다. ACTION 키 설명은 짧게 「Match the name of the event action.」 입니다.
udevadm(8)이 나열하는 대표 액션:
| ACTION | 실무에서 자주 보는 의미 |
|---|---|
add | 장치가 시스템에 나타남(/dev 노드 생성 흐름과 함께 오는 경우가 많음) |
remove | 장치가 사라짐 |
change | 장치 상태 갱신. 드라이버가 올리는 경우가 많고, udevadm trigger 기본 액션도 change |
bind / unbind | 드라이버가 장치에 붙거나 떨어짐 |
move / online / offline | 이름 변경·핫플러그 잠금/해제 등 특수 케이스 |
임베디드에서 흔한 패턴:
- 케이블을 꽂으면
add가 먼저(또는 함께) 옵니다. - 펌웨어·속성·권한이 갱신되거나, 누군가가
udevadm trigger를 돌리면change가 또 옵니다. - 물리 장치에서는
bind/unbind도 이어질 수 있습니다.
규칙을 SUBSYSTEM=="usb", ATTR{idVendor}=="…"처럼만 쓰면 액션이 달라도 매번 매칭됩니다. SYMLINK·MODE처럼 멱등한 할당은 여러 번 돌아도 보통 괜찮지만, RUN+=로 스크립트를 돌리거나 로그·카운터·외부 서비스를 건드리면 “두 번 실행”으로 보입니다.
udevadm test의 기본 시뮬레이션 액션은 add, udevadm trigger의 기본은 change 입니다. 테스트와 실제 트리거가 다른 액션을 쓰는 점도 혼동 지점입니다.
중복 실행을 막으려면?
한 줄 답: 부작용이 있는 RUN/PROGRAM은 ACTION=="add"(또는 필요한 액션만)로 좁힙니다. 속성·태그·심볼릭 링크처럼 이벤트마다 다시 쌓아야 하는 규칙은 add만으로 가두지 말고, 제거 시에만 건너뛰는 쪽이 systemd 쪽 권고에 가깝습니다.
1) 한 번만 돌릴 작업 → ACTION=="add"
# /etc/udev/rules.d/99-my-usb-once.rules
# 꽂을 때(add)만 스크립트 실행 — change/bind에서 재실행 방지
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="1234", ATTR{idProduct}=="5678", \
RUN+="/usr/local/bin/on-usb-plug.sh"
remove 정리 스크립트가 필요하면 별도 줄로 ACTION=="remove"를 둡니다. add|change에 한꺼번에 RUN을 걸면 의도한 “한 번”이 깨집니다.
2) 속성·MODE·TAG는 매 이벤트에 다시 적용
권한·ENV·TAG는 change·bind에서도 다시 맞춰 주는 편이 안전합니다. systemd 247 전후 안내에서도, 예전 헤더 가드 ACTION!="add|change", GOTO="…_end" 대신 ACTION=="remove", GOTO="…_end" 로 바꿔 bind/unbind에서도 속성이 쌓이게 하라고 권합니다. add만 허용하면 bind 때 태그가 빠질 수 있습니다.
# 속성/권한: remove만 스킵 (예시 골격)
ACTION=="remove", GOTO="mydev_end"
SUBSYSTEM=="tty", ATTRS{idVendor}=="1234", ATTRS{idProduct}=="5678", \
MODE="0660", GROUP="dialout", TAG+="uaccess"
LABEL="mydev_end"
3) 역할 분리 체크리스트
| 규칙 종류 | ACTION 전략 |
|---|---|
RUN+= 스크립트·외부 알림 | 보통 ACTION=="add" (필요 시 remove 별도) |
SYMLINK / MODE / OWNER / ENV / TAG | remove만 제외하거나 액션 제한 없이, 멱등하게 |
udevadm trigger로 재적용 검증 | 기본이 change이므로, add-only RUN은 trigger만으로는 안 돌 수 있음 |
4) 재로드
sudo udevadm control --reload-rules
sudo udevadm trigger # 기본 ACTION=change — add-only RUN은 기대하지 말 것
trigger로 “스크립트가 또 도는지”를 보면, add-only 규칙에서는 의도적으로 안 도는 것이 정상입니다. 재현은 실제 플러그 또는 udevadm test --action=add로 확인합니다.
팁: “규칙이 두 번”인지 “커널이 add+change(+bind)를 연쇄로 올리는지”를 먼저 나눕니다. 후자면 규칙을 좁히는 것이 답이고, 전자면 파일 중복·IMPORT/PROGRAM 재진입을 의심합니다.
udevadm monitor로 확인은?
한 줄 답: 장치를 꽂고 빼는 동안 udevadm monitor --environment --udev(필요하면 --kernel)로 같은 DEVPATH에 ACTION=add와 ACTION=change가 잇따르는지 봅니다.
# 터미널 1 — udev 처리 결과(규칙 적용 후) 이벤트
sudo udevadm monitor --environment --udev
# 터미널 2 — 대상 장치 연결/해제, 또는
# sudo udevadm trigger -c add /sys/… # 특정 액션으로 재현
볼 포인트:
- 동일
DEVPATH(또는DEVNAME)에ACTION만 다른 줄이 연속되는지. SEQNUM이 증가하는지(별개 이벤트인지).SUBSYSTEM/ID_VENDOR_ID/ID_MODEL_ID등 규칙 매치에 쓰는 키가 기대한 값인지.--kernel과--udev를 나누어 보면, 커널 uevent와 udev 처리 결과를 구분하기 쉽습니다.
디버그용 한 줄 시뮬레이션:
sudo udevadm test --action=add /sys/devices/…/tty/ttyUSB0
sudo udevadm test --action=change /sys/devices/…/tty/ttyUSB0
test는 실제로 RUN을 실행하지 않고 어떤 규칙이 매칭될지를 보여 줍니다. add와 change에서 RUN 후보가 둘 다 나오면, 규칙에 ACTION==이 빠졌을 가능성이 큽니다.
주의: OPTIONS+="watch"가 켜진 노드를 쓰기 모드로 열었다가 닫으면 change uevent가 합성될 수 있습니다(udev(7) watch). “아무도 trigger 안 했는데 change가 또 옴”일 때 후보입니다.
자주 묻는 질문
ACTION=="add|change"로 묶으면 안 되나?
속성·심볼릭 링크처럼 멱등한 할당에는 쓸 수 있습니다. 다만 RUN 중복을 막으려는 목적에는 보통 반대입니다. bind까지 커버하려면 remove-only 스킵 가드가 더 맞습니다.
udevadm trigger 후에도 스크립트가 안 돈다.
trigger 기본 액션은 change 입니다. 규칙이 ACTION=="add"이면 정상적으로 스킵됩니다. --action=add를 주거나 실제 플러그를 사용합니다.
add만 허용하면 권한이 빠질 때가 있다.
bind 이후에야 드라이버·속성이 완전해지는 장치가 있습니다. MODE/TAG는 remove만 제외하고, RUN만 add로 제한하는 식의 역할 분리가 안전합니다.
규칙 파일을 고쳤는데 반영이 안 된다.
udevadm control --reload-rules 후, add-only는 플러그 또는 test --action=add로 확인합니다. 파일 확장자는 반드시 .rules입니다.
정리
ACTION은 이벤트 종류 이름입니다. add는 등장, change는 상태 갱신(그리고 trigger 기본값)입니다. 부작용 있는 RUN은 ACTION=="add"로 한 번만, 속성·태그는 remove만 건너뛰고 여러 액션에서 멱등 적용. 재현·증명은 udevadm monitor --environment 로 같은 장치에 add/change가 연속되는지 보는 것입니다. 근거: udev(7), udevadm(8). 제휴 없음.
공식 문서는 어디인가?
- udev(7) —
ACTION매치 키,RUN,OPTIONS=watch - udevadm(8) —
monitor,trigger --action=,test --action=(add/remove/change/move/online/offline/bind/unbind) - udev — ArchWiki — 액션 유형 요약(보조 설명)