안녕하세요! 지난 시간에 NDK 코드를 Android.mk와 CMakeLists.txt로 빌드하고 독립 실행형(Executable) 바이너리를 뽑아내는 방법까지 마쳤습니다. 이제 이 바이너리를 안드로이드 스마트폰이나 임베디드 보드가 켜질 때 자동으로 실행되고, 시스템 백그라운드에서 상주하며 관리되도록 "시스템 서비스"로 정식 등록할 시간입니다.
리눅스 서버 환경에 익숙하신 분들은 당연히 systemd나 systemctl 명령어를 떠올리시겠지만, 안드로이드의 세계에는 systemd가 존재하지 않습니다. 대신 독자적인 init 시스템과 Property Service, 그리고 악명 높은 보안 장벽인 SELinux 정책이 뼈대를 이루고 있죠. 이 시스템 메커니즘을 이해하지 못하면 아무리 루트(Root) 권한으로 바이너리를 실행해도 권한 거부(Permission Denied) 에러를 마주하게 됩니다. 오늘 그 완벽한 등록 절차와 보안 설정 패키지를 부드럽게 풀어드릴게요!

📌 핵심 요약 3줄
- Android init 시스템 제어: systemd 대신 안드로이드 고유의 init.rc 스크립트 문법을 사용하여 네이티브 데몬을 시스템 서비스로 정식 등록합니다.
- Property Service 연동: setprop 명령어를 통해 ctl.start 및 사용자 정의 트리거 속성을 조작하여 데몬의 생명주기를 유연하게 켜고 끕니다.
- SELinux 보안 장벽 돌파: 안드로이드 강제 보안 정책인 SELinux 영역에서 데몬 전용 도메인(.te)을 선언하고 컨텍스트를 매핑하는 필수 기법을 다룹니다.
1. Android 시스템에서 Daemon을 등록하는 방법
안드로이드가 부팅될 때 가장 먼저 커널 위에서 깨어나는 프로세스가 바로 init 프로세스입니다. 이 init 프로세스는 시스템 내부의 init.rc 파일들을 읽어 들여 파일 시스템을 마운트하고, 코어 서비스들을 순차적으로 가동합니다.
1.1 init.rc에서 내 데몬(mydaemon) 서비스 정의하기
시스템 스크립트에 우리 바이너리를 등록할 때는 아래와 같은 문법 구조를 따릅니다.
# 서비스이름 실행바이너리_절대경로
service mydaemon /system/bin/mydaemon
user root
group root
oneshot
disabled
seclabel u:r:mydaemon:s0
📊 init.rc 핵심 옵션 명령어 상세 분석
| 옵션 키워드 | 수행하는 역할 및 의미 | 개발 시 팁 |
| service | 새로운 서비스 정의의 시작을 알리며, 서비스명과 실행할 바이너리의 시스템 경로를 매핑함 | 중복되지 않는 고유한 서비스명을 지정해야 합니다. |
| user / group | 데몬이 실행될 때 가질 리눅스 사용자 ID(UID)와 그룹 ID(GID) 권한을 강제 지정함 | 보안을 강화하려면 root 대신 system이나 전용 UID를 할당하는 것이 좋습니다. |
| oneshot | 이 옵션이 있으면 데몬이 어떤 이유로든 한 번 종료되었을 때, init이 재시작을 시도하지 않음 | 옵션을 빼면 데몬이 죽었을 때 init 시스템이 무한히 자동 재시작(Respawn)해 줍니다. |
| disabled | 부팅 시점에 즉시 자동 실행하지 않고, 명시적인 트리거 이벤트가 올 때까지 대기 상태를 유지함 | 부팅 시 즉시 켜고 싶다면 이 라인을 과감히 삭제하면 됩니다. |
| seclabel | 이 서비스에 강제 적용할 SELinux 보안 컨텍스트 레이블을 강제로 박아넣음 | 안드로이드 8.0 이상 필수 사항으로, 빼먹으면 실행이 차단됩니다. |
2. init.rc를 활용한 Daemon 자동 실행 및 수동 제어
2.1 init.rc 파일의 위치
안드로이드의 모듈화 정책에 따라 init.rc 스크립트는 여러 파티션으로 분산되어 저장됩니다. 보통 우리가 커스텀 데몬을 추가할 때는 아래의 경로를 타겟팅합니다.
- 제조사/보드 전용 데몬: /vendor/etc/init/
- 일반 시스템 공통 데몬: /system/etc/init/
2.2 부팅 시 자동 실행 트리거 설정
만약 disabled 옵션을 주지 않고 부팅 시점에 완전히 시스템과 동기화하여 자동으로 데몬을 켜고 싶다면, on boot 섹션에 아래와 같이 등록합니다.
on boot
start mydaemon
2.3 setprop 제어 명령어를 이용한 수동 조작
만약 서비스에 disabled를 걸어둔 상태에서 개발자가 원할 때 쉘 터미널에서 제어하고 싶다면, 안드로이드 내장 시스템 통제 프로퍼티인 ctl.start와 ctl.stop을 활용합니다.
# 데몬 수동 켜기
setprop ctl.start mydaemon
# 데몬 수동 끄기
setprop ctl.stop mydaemon
3. systemd 없이 Android에서 서비스를 실행하는 방식
다시 한번 강조하지만 안드로이드 터미널에서는 systemctl start mydaemon을 입력하면 명령어를 찾을 수 없다는 에러가 뜹니다. 따라서 순수 CLI 환경에서 독립적으로 테스트하려면 두 가지 스텝을 밟아야 합니다.
3.1 NDK 컴파일을 위한 Android.mk 설정
실행 가능한 단독 바이너리를 뽑아내기 위해 jni/Android.mk 파일을 아래처럼 구성하여 빌드합니다.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := mydaemon
LOCAL_SRC_FILES := mydaemon.c
# 안드로이드 표준 로그캣 라이브러리 링크 필수!
LOCAL_LDLIBS := -llog
# 공유 라이브러리가 아닌 실행 파일 아웃풋 지정
include $(BUILD_EXECUTABLE)
3.2 CLI 터미널에서 수동 백그라운드 가동
빌드된 mydaemon 파일을 adb를 통해 /system/bin/ 또는 /data/local/tmp/ 경로에 밀어 넣은 후, 리눅스 표준 백그라운드 연산자(&)를 써서 직접 프로세스를 격리 실행할 수 있습니다.
# 실행 파일에 권한 부여 후 백그라운드 실행
chmod 755 /system/bin/mydaemon
/system/bin/mydaemon &
4. property_service 및 SELinux 정책 설정 (가장 중요)
여기서부터가 안드로이드 시스템 개발의 진짜 고비입니다. 프로퍼티 서비스와 SELinux 보안 레이블을 셋업해 보겠습니다.
4.1 property_service를 이용한 스마트 백그라운드 제어
안드로이드에서는 특정 공유 전역 변수(Property)의 상태 변화를 init 프로세스가 실시간 모니터링하다가 룰셋에 맞게 서비스를 켜고 끄는 Property Service 메커니즘을 제공합니다. init.rc에 아래와 같이 매핑 룰을 정의해 두면 매우 편리합니다.
# 개발자가 프로퍼티 값을 1로 세팅하면 데몬 스타트!
on property:mydaemon.enable=1
start mydaemon
# 개발자가 프로퍼티 값을 0으로 세팅하면 데몬 스톱!
on property:mydaemon.enable=0
stop mydaemon
이렇게 설계해 두면, 시스템 내부 어디서든 터미널이나 자바 앱 코드로 setprop mydaemon.enable 1만 날려주면 데몬이 유연하게 연동되어 구동됩니다.
4.2 SELinux 정책 설정 (mydaemon.te)
안드로이드는 강력한 SELinux(Security-Enhanced Linux) 정책이 기본적으로 강제(Enforcing) 모드로 작동합니다. 아무리 root 권한을 가진 데몬이라도 SELinux 도메인 승인을 받지 못하면 시스템에 의해 즉결 처분(액세스 차단)됩니다.
보안 정책 파일 정책을 정의하기 위해 external/sepolicy/ 또는 디바이스 전용 보드 정책 경로에 mydaemon.te 파일을 생성하고 최소한의 허용 도메인을 선언해 주어야 합니다.
# 1. mydaemon이라는 고유의 보안 타입(Domain)을 선언합니다.
type mydaemon, domain;
type mydaemon_exec, exec_type, file_type, system_file_type;
# 2. init 프로세스가 이 바이너리를 실행해서 mydaemon 도메인으로 전환할 수 있도록 허용합니다.
init_daemon_domain(mydaemon)
# 3. 필요에 따라 데몬이 시스템 리소스(예: 로그캣 기록)에 접근할 수 있도록 권한을 허가합니다.
allow mydaemon logcat_device:chr_file { read open };
5. 예제 코드: Android NDK 기반 표준 Daemon 구현
초안에 있던 리눅스 전용 syslog.h 기반 코드는 안드로이드 환경에서 로그가 유실되거나 빌드 에러가 납니다. 안드로이드 프레임워크와 완벽히 호환되어 로그캣(logcat)에서 실시간 디버깅이 가능한 안드로이드 표준 네이티브 데몬 소스코드로 정밀 수정했습니다.
5.1 mydaemon.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <android/log.h> // 안드로이드 표준 로그 헤더로 전면 교체!
#define LOG_TAG "MyDaemonSystem"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
int main() {
// 데몬 시작 로그 기록
LOGI("=============================================");
LOGI("안드로이드 커스텀 MyDaemon 서비스가 가동되었습니다.");
LOGI("=============================================");
int count = 0;
while (1) {
// 10초 주기로 백그라운드 라이브 비트 출력
LOGI("MyDaemon 실행 중... (사이클 횟수: %d)", ++count);
sleep(10);
}
// 사실상 무한 루프이므로 도달하지 않겠지만, 종료 처리를 명시합니다.
LOGW("MyDaemon 서비스가 종료 요청을 받았습니다.");
return 0;
}
🛠️ 개발을 위한 꿀팁 (Tips)
- SELinux 차단 원인은 dmesg나 logcat으로 잡으세요: 데몬을 등록하고 실행했는데 아무런 반응이 없다면 십중팔구 SELinux 룰에 걸린 것입니다. 터미널에 adb shell dmesg | grep avc 또는 adb logcat | grep denied를 입력해 보세요. 어떤 보안 컨텍스트 권한이 누락되었는지 정답(AVC 로그)을 친절하게 알려줍니다.
- 개발 단계에서는 임시로 SELinux를 끄고 테스트하세요: 보안 정책 문법을 짜느라 실행 테스트 자체가 막힌다면, 터미널에 setenforce 0 명령어를 내려 잠시 느슨한 모드(Permissive)로 전환하세요. 이 상태에서 데몬이 잘 돌아간다면 코드 문제가 아니라 100% SELinux 정책 설정 문제라고 확신할 수 있습니다. (테스트 후에는 다시 setenforce 1로 켜야 합니다!)
- 바이너리 파일 권한과 소유권 정렬: /system/bin/으로 빌드된 바이너리를 수동 이동시킬 때 파일 권한이 644 상태로 남아있으면 init 프로세스가 파일을 실행하지 못해 데몬이 먹통이 됩니다. 반드시 실행 권한(chmod 755)을 확보해 주는 루틴을 빌드 스크립트에 녹여내세요.
⚠️ 흔히 하는 실수 (Common Mistakes)
- 안드로이드에서 syslog() 사용: 순수 리눅스용 C 코드를 그대로 복사해 오다 보면 syslog() 함수를 사용하는 실수를 자주 범합니다. 안드로이드 런타임 환경은 표준 리눅스 로그 데몬(/var/log/syslog)을 운용하지 않기 때문에, 무조건 __android_log_print 함수 체계로 컨버팅해야 로그캣에서 정상적으로 추적이 가능합니다.
- disabled와 프로퍼티 트리거의 엇박자: init.rc 내부에서 서비스 정의 단에는 disabled를 명시해 두고, 하단에 on property 연동 트리거나 on boot 단에 start mydaemon 구문을 하나도 작성하지 않으면, 부팅 후 시스템 내에서 데몬은 영원히 잠자는 상태로 남게 됩니다.
- /system 파티션 Write-Protect 착각: 안드로이드 릴리즈 타겟 기기들은 기본적으로 시스템 영역이 Read-Only 마운트 상태입니다. adb remount 또는 adb shell su -c "mount -o rw,remount /system" 명령을 통해 쓰기 권한을 임시로 활성화해 주어야 데몬 바이너리를 무사히 주입할 수 있습니다.
6. 결론
이번 포스팅에서는 systemd가 없는 특수한 안드로이드 OS 환경에서, init.rc 스크립트를 활용해 C/C++ 네이티브 데몬을 커널 부팅 프로세스에 정식 등록하는 전체 아키텍처를 상세히 살펴보았습니다.
단순히 바이너리를 실행하는 수준을 넘어, 안전하게 커스텀 프로퍼티 서비스와 결합하여 상태를 제어하고 안드로이드의 철통 보안망인 SELinux 영역에서 예외 도메인 규칙(te 파일)을 부여하는 일련의 과정이야말로 진정한 안드로이드 시스템 개발자(AOSP 엔지니어)로 거듭나는 핵심 관문입니다.
오늘 정리해 드린 정밀 코드 예제와 설정 가이드를 통해 시스템 가용성이 극대화된 임베디드 백그라운드 엔진을 구축해 보시길 바랍니다. 빌드나 SELinux 정책 컴파일 과정에서 avc: denied 에러를 만나 해결이 어려우시다면 언제든 아래 댓글 창에 에러 로그를 남겨주세요. 칼같이 분석해 드리겠습니다. 열혈 코딩 하세요!