Ⅰ. 개요
안드로이드(Android)에 대해 살펴본다. Linux와 Java를 기반으로 한 안드로이드는 Linux Kernel 계층부터 HAL(Hardware Abstraction Layer), Native C/C++ Libraries, Android Runtime, Java API Framework, System Apps의 계층으로 이어진다. 각 계층이 무슨 역할을 담당하고 어떻게 동작하는지 알아본다. Linux Kernel은 하드웨어, 네트워크, 파일 시스템 접근 등 시스템 전체를 관리하고 제어한다. HAL은 Java API 프레임워크(Framework)를 통한 기기의 하드웨어 기능에 대한 표준 인터페이스를 제공한다. Native C/C++ Libraries는 FreeFont(폰트), Webkit(브라우저 엔진), Media(코덱), SQLite(경량 DB) 등을 이용할 수 있게 해준다. Android Runtime은 안드로이드 애플리케이션을 운영체제가 사용할 수 있게 해준다. Java API Framework는 Activity Manager(Telephony, Resource, Notification 등), Content Providers, View System 등이 있으며, 하드웨어 제어를 위한 인터페이스를 제공한다. 애플리케이션 계층은 시스템 및 사용자 애플리케이션으로 구분된다. 시스템 애플리케이션은 최고 사용자 권한(root)로 관리가 가능한 앱으로 전화, 메시지 등이 있다. 사용자 애플리케이션은 일반 사용자가 관리할 수 있으며, 카카오톡, 네이버 등이 있다.
Ⅱ. 안드로이드(Android)
Linux Kernel과 Hardware Abstraction Layer(HAL)
안드로이드(Android)는 모바일용으로 제작된 운영체제이다. Linux 커널 기반으로 만들어졌으며, Java언어를 기반으로 최근에 Kotlin으로도 애플리케이션이 제작되고 있다. Native Development Kit을 통해 Android에서 C, C++을 통한 개발도 가능하다.

안드로이드는 일반적으로 이 구조를 따르는데, 아래 Linux Kernel(리눅스 커널) 계층부터 살펴보자. 리눅스 커널을 기반으로 개발된 OS(운영체제)로서 커널은 다른 운영체제와 마찬가지로 하드웨어, 네트워크, 파일 시스템 접근 등 시스템 전체를 관리하고 제어한다. 조금 더 자세한 기능으로 보안 설정, 메모리 관리, 전원 관리, 네트워크 시스템 관리, 장치(블루투스 장치, 네트워크 장치, 배터리, AP칩, 여러 센서 등) 드라이버 관리가 있다. 그 다음에 HAL(Hardware Abstraction Layer) 계층이 있다. HAL은 추상화된 하드웨어를 의미하는데, 윈도우에서 하드웨어를 사용하기 위해 여러 API 인터페이스를 통해 접근할 수 있듯, 안드로이드에서는 Java API 프레임워크를 통한 기기의 하드웨어 기능에 대한 표준 인터페이스를 제공한다. 위 사진처럼 Audio를 사용할 수 있게 해준다거나 Bluetooth, Camera, Sensors 등의 특정 유형의 하드웨어 구성 요소에 대한 모듈을 제공한다.
※ 안드로이드에서 사용하는 CPU는 일반적으로 RISC를 사용하지만, 인텔 기반 CPU를 사용하는 안드로이드도 있다. CPU에 대한 조금 더 자세한 정보는 아래의 게시글을 참조하면 좋다.
[SK쉴더스 SeSAC 성동1기] CPU Architecture, Memory Architecture
2. Native C/C++ Libraries와 ART(Android Runtime)
다음으로 Native C/C++ Libraries(네이티브 C/C++ 라이브러리) 계층이 있다. 이 네이티브 계층은 위에서 잠깐 언급한 Android Native Development Kit(NDK)가 포함되어 있다. Android NDK는 C나 C++ 언어를 기반으로 한 앱 개발을 가능하게 해주며, 라이브러리인 만큼 FreeFont(폰트), Webkit(브라우저 엔진), Media(코덱), SQLite(경량 DB) 등을 이용할 수 있게 해준다. 위 사진에서 네이티브 계층 바로 옆에 있는 Android Runtime 계층이 있다. 간단히 얘기하면, 여러 안드로이드 애플리케이션을 운영체제가 사용할 수 있게 해준다. 이전에 Dalvik이라고 불리기도 했지만, 요즘에 ART(Android Runtime)라고 불리며, 자바 컴파일 과정을 거친 바이트 코드를 DEX 포맷으로 변환 후 APK로 패키징 한 후 실행시킬 수 있게 해준다. 이렇게만 얘기하면 이해가 어려울 수 있으니 Java가 어떻게 컴파일되고 실행될 수 있는지 알아보자.
2-1. JVM(Java Virtual Machine)과 DVM(Dalvik Virtual Machine)

Java언어로 개발(코딩)을 하고 난 후 Java Compiler가 Class를 기반으로 Java Bytecode를 만든다. 이를 가지고 Java Virtual Machine이 프로그램을 실행시킬 수 있는데, Java Bytecode로 실행 가능한 상태의 파일이 바로 Jar이다. 이를 조금 더 자세한 사진으로 보자면,

위 사진 처럼 Java로 개발한 프로그램이 Compiler를 통해 ByteCode로 변환되고 이 ByteCode가 일반 Java 프로그램 JAR가 된다면, 안드로이드 애플리케이션은 Dalvik ByteCode(.dex)로 그리고 한 번 더 변환 후 APK로 변환되는 것이다. 그렇게 변환된 JAR와 APK는 JVM(Java Virtual Machine)과 ART(Android Runtime)을 통해 실제 우리가 네이버 지도를 통해 가고 싶은 곳을 알아보는 것처럼 애플리케이션을 실사용할 수 있게 된다.
※ JVM(Java Virtual Machine)은 셸과 사용자 프로그램(애플리케이션) 사이에서 돌아간다. Android용 이름은 Dalvik Virtual Machine으로 크로스 컴파일이 된다. * 크로스 컴파일은 A 플랫폼에서 Java를 컴파일하고 B 플랫폼에서 동작하게 하는 애플리케이션을 만들어줄 수 있다. 즉, 리눅스에서 컴파일하고 Android에서 실행할 수 있게 해준다고 생각하면 좋다. JVM 자체는 어느 운영체제에서든 가능하지만, 무거운 단점이 있어 Android에선 더 가볍고 크로스 컴파일이 가능한 Dalvik ByteCode(정확히 classes.dex)를 가지고 Dalvik Virtual Machine에서 애플리케이션을 동작시킨다.
조금 더 정확히 JVM과 DVM을 알고가자. JVM은 자바 바이트 코드를 실행하기 위한 자바 가상머신이다. DVM은 안드로이드 애플리케이션을 실행하기 위한 달빅 가상머신이다. 자바 가상머신은 플랫폼에 의존적이다. 즉,

운영체제별로 JVM은 달라진다. 하지만 컴파일된 자바 바이트 코드(JAR)는 어떤 JVM에서든 실행할 수 있다. 달빅 가상머신은 모바일 디바이스 환경에 최적화되어 낮은 메모리에서 실행할 수 있게 설계(아무래도 모바일 장치의 한계인 배터리와 성능 문제 때문이다.)되어 있다. 이 두 가상머신 모두 Java를 기반으로 하는데 Java로 코딩해보시면 .java 파일을 볼 수 있을텐데 이건 자바 소스코드이고 .class는 자바 바이트 코드이다. 달빅 가상머신은 이 .class 파일을 dex로 변경해서 실행가능하게 해주는 것이다. 또 하나의 특징으로

JVM은 스택(Stack)을 기반으로 하지만, DVM은 레지스터(Register)를 기반으로 한다. 그래서 연산 과정에서 JVM은 스택을 사용해서 지난 번에 잠깐 알아봤던 어셈블리어의 PUSH, POP과 유사한 연산자를 사용한다. 반면, DVM은 피연산자가 저장되므로 PUSH, POP 연산자 없이 간단한 명령어로 동작하기 때문에, 명령어 수가 더 적지만 코드는 더 길다. 어셈블리어로 기계어를 사람이 어느 정도 알아볼 수 있듯 안드로이드에서 DEX를 디컴파일하여 Smali 코드(안드로이드 DEX 형식 어셈블리어)로 확인할 수 있다.

※ 참고로 Windows도 스택기반이다.
2-2. ART(Android Runtime)과 APK(Android Package Kit), DEX(Dalvik Executable)
ART는 구글이 DVM에서 성능 개선을 위해 변경되었다. DVM의 JIT 컴파일 프로세스를 AOT 컴파일 프로세스로 변경했다. 그렇지만, 두 프로세스 모두 사용되고 있으며, AOT컴파일은 Garbage Collection이 개선됐다. JIT(Just In Time)을 조금 더 살펴보자. JIT은 동적 번역(Dynamic Translation)이라고도 한다. 앱을 최초 실행하는 시점에 기계어로 번역하기 때문이다. 대신 하드웨어에 부하가 가므로 배터리 시간 등 부정적 영향(배터리, CPU에)을 끼칠 수 있고 실행속도가 느리다. 그래도 용량이 낮고 설치 속도가 빠르다. AOT(Ahead of Time)은 앱을 설치하는 시점에 컴파일을 수행한다. 그래서 전력 소비가 줄어들며 성능은 2배 향상될 수 있다. 대신 용량이 높고 설치 속도가 느리다. 그래도 실행속도가 빠르며, 하드웨어(배터리, CPU) 사용이 적어진다. 2016년 안드로이드 누가(7.0) 이후 ART에서는 JIT과 AOT 컴파일 모두 사용하는데, 설치 시에는 JIT을 사용하고 상황에 따라 유연하게 AOT를 적용한다.
APK는 안드로이드에서 프로그램 형태로 배포되는 형식의 파일이다. Java 프로그램 형식인 JAR에서 확장한 포맷으로 ZIP 압축 구조를 가진다. APK 패키지 파일이 설치되는 경로로 /data/app/패키지명, /data/data/패키지명이 있다. DEX는 Dalvik Executable로서 Java로 짜여진 Class 파일들의 집합체이다.

이 사진을 보면 이해가 더 용이할텐데, Java Source Code가 Class별로 만들어지고 이 Class들을 합쳐서 Classes.dex가 만들어지며 DVM에서 실행되는 것이다.
※ 지난 게시글에 설명했었지만, 윈도우에선 PE 구조가 있다. 이 PE 구조는 윈도우가 Prefetch를 통해 최적화를 시키는데, pf 확장자로 된 파일들이다. 프로그램을 처음 사용하고 난 후 최적화를 시켜놓으면 나중에 더 빠르게 실행될 수 있다. 아래 사이트에서 WinPrefetchView 프로그램을 설치하고 열어보면 여러 pf 파일들이 보일텐데, 같은 프로그램인데도 여러 개가 있다면 경로가 다르기 때문이다. 이와 같이 위에서 설명한 안드로이드 DEX가 애플리케이션 최적화를 담당하고 있다고 보면 된다. 단, 안드로이드는 경로가 달라질 수 있다.
View the content of Windows Prefetch (.pf) files


3. Java API Framework와 애플리케이션 계층
안드로이드 계층 구조에 대한 사진을 위에서 다시 가져와봤다(이미 너무 많이 내려와버려서..). Java API Framework는 위에서 잠깐 언급한 적 있다. HAL 계층에서 이 하드웨어를 사용할 수 있게 하는 인터페이스 사용을 위해 프레임워크를 참조한다고 했다. 안드로이드 OS의 여러 기능들은 이 API를 통해 접근이 되고 Activity Manager(Telephony, Resource, Notification 등), Content Providers, View System이 있다. 이 부분은 애플리케이션 계층에서 앱 컴포넌트를 설명하다보면 더 자세히 이야기하게 될 것 같다.
애플리케이션 계층은 시스템 및 사용자 애플리케이션으로 구분된다. 시스템 애플리케이션은 /system 디렉토리에 존재하며, 사용자가 제거나 변경이 불가하다. 안드로이드 핸드폰 구매 시 기본적으로 내장된 앱(전화, 메시지 등)들을 생각하면 좋다. 사용자 애플리케이션은 /data 디렉토리에 존재하며, 사용자가 제거나 변경이 가능하다. 각 애플리케이션은 샌드박스(Sandbox)에 설치되어 개별적으로 영향을 주거나 데이터 통신을 할 수 없다. 각 애플리케이션의 권한을 가진 리소스만 접근할 수 있으며, 카카오톡, 네이버와 같은 일반 애플리케이션들이 사용자 애플리케이션이다. 우리가 앱을 사용하며 권한을 주는 것도 다 샌드박스와 관련이 있다고 생각하면 된다.
※ 샌드박스를 조금 더 설명하고 가자면, 일종의 격리된 공간, 들어오면 안되는 영역이다. 각각의 애플리케이션은 독립된 공간에서 실행되는 건 당연하며, 거기다 샌드박스 안에 들어가 다른 애플리케이션끼리 영향도 줄 수 없다. 사용자 앱은 일반 사용자 권한으로 동작하고 시스템 앱은 제거나 변경이 관리자 권한으로만 가능하다. 마찬가지로 샌드박스에 의해 보호되는 것도 관리자 권한으로 우회가 가능한데, 이 샌드박스 보호가 깨지는 순간이 안드로이드 핸드폰 사용자라면 들어봤을 루팅이다. 루팅은 일반 사용자이지만 관리자 권한으로 제어할 수 있게 하며, 리눅스에서 setuid, setgid로 프로세스에 대한 권한을 변경하듯 프로그램 소유자를 바꿔버린다. 즉, 리눅스 커널의 보안을 손상시키는 게 루팅이라고 할 수 있다(리눅스에서 sudo su 명령어를 사용해본 적 있을텐데 이 su가 switch user이다.).
샌드박스와 루팅을 조금 더 자세히 설명하고 넘어가자면,
모든 보안 기능과 마찬가지로 애플리케이션 샌드박스도 깨지지 않는다. 그러나 제대로 구성된 장치에서 샌드박스를 벗어나려면 리눅스 커널의 보안을 손상시켜야 한다.
구글
리눅스 기반인 안드로이드에서도 root 권한은 모든 작업을 수행할 수 있는 최고 사용자 수준이다. 소수의 핵심 유틸리티만 root 권한으로 실행될 수 있지만, 안드로이드에서 Rooting(루팅)을 하게 되면 실행 중인 모든 앱을 root 권한으로 사용 가능하다. 사실 제조사가 기술적으로 우리가 구매하는 핸드폰의 소유자라고 볼 수 있다. 물리적으로 우리가 소유자지만, 그렇기 때문에 우리는 root 권한이 아닌데, 이 루팅을 하는 이유는 사용자마다 목표가 다르겠지만, 디바이스를 완전히 제어하기 위함이다. 더 아름다운 테마로 핸드폰을 꾸미기 위함이든지, 루팅으로만 설치할 수 있는 특별한 앱을 사용하기 위함일 수도 있다. 혹은 개발자나 보안 관련 공부나 업무 때문일 수도 있다. 하지만 이에 따른 단점도 존재하는데, 디바이스의 보안이 손상되어 악성 앱에도 취약해지고 원래는 샌드박스로 보호되었어야 할 연락처, 메시지 등에 다른 앱이 침투해서 추가적인 액션(정보를 빼오는 등의)도 할 수 있다. 또는 디바이스가 손상되어 사용할 수 없게 되거나 보증이 무효되어 수리 받을 수 없을 수도 있다.
※ 아이폰은 애초부터 루트 사용자 권한으로 사용한다. 아이폰도 사실 리눅스 기반이다. 그리고 마찬가지로 샌드박스로 다른 앱에 침투할 수 없게 구성되어 있는데, 아이폰에서의 루팅은 탈옥(Jail Breaking)이라고 부른다. 그럼에도 아이폰이 보안이 더 좋다고 하는 이유는 무엇일까? 바로 앱에 대한 서명 때문이다. 코드 서명이라는 게 있는데, 안드로이드든 아이폰이든 개발된 애플리케이션에 서명을 남겨 인증서를 통해 신뢰 관계를 확보한다. 그런데 안드로이드는 개발자가 서명을 할 수 있지만, 아이폰의 경우 앱스토어에서 애플이 자체적으로 하기 때문에, 인증되지 않은 앱은 사용할 수 없다. 그래서 보안이 더 좋다고 하며, 안드로이드는 웹상에 많은 apk파일이 돌아다니지만, 아이폰은 그렇지 않은 이유이기도 하다.
3-1. 애플리케이션 컴포넌트(Activity, Service, Broadcast Receiver, Content Provider)
애플리케이션 컴포넌트(앱 컴포넌트)는 안드로이드 애플리케이션이 가져야 할 필수적인 구성 요소로 각각 독립된 형태로 존재하며 정해진 역할들을 수행한다. 총 4개의 구성 요소가 있으며, 시스템이나 사용자가 앱에 어떻게 들어올 수 있는 지에 대한 진입점이다. 먼저 Activity(액티비티)가 있다. 액티비티는 사용자와 상호 작용을 직접적으로 하는 진입점이다. 사용자 인터페이스(UI)라고 생각하면 된다. Service(서비스)는 백그라운드에서 앱을 계속 실행하기 위한 다목적 진입점이다. 여기서 백그라운드(Background)는 앱을 실행하지 않아도 음악 재생이 되거나, 시간이 흐르거나 대용량 파일을 다운받거나 복사하거나, 네트워크 통신하는 등의 일련의 과정을 말한다. 즉, 우리가 윈도우에서 키보드나 마우스 반응으로 프로그램을 제어하는 것은 포그라운드(Foreground)라고 하는데 안드로이드에서 앱 화면을 키고 직접 터치하며 제어하는 걸 포그라운드라고 보면 된다. 반면, 백그라운드는 직접적인 제어없이 알아서 동작할 수 있어야 한다. 앱을 실행하지 않은 상태에서도 작동한다. 그래서 백그라운드는 사용자 인터페이스를 제공하지 않는다.
※ 이 포그라운드, 백그라운드 개념은 모든 운영체제에 해당되는 개념이다.
Broadcast Receiver(브로드 캐스트 리시버)는 시스템이 정기적으로 발생하는 이벤트를 앱에 전달하도록 지원하는 컴포넌트이다. 사용자 플로우(Flow - 비동기적 데이터 스트림, 즉, 연속적으로 데이터를 주고 받는 형태) 밖에서(내가 특정 앱을 사용하면서 계속 데이터를 주고 받고 있더라도) 대다수의 브로드 캐스트는 화면이 꺼지거나 배터리가 부족하거나 사진을 캡쳐하는 등의 동작을 시스템이 발생시킨다. 일반 사용자 앱도 브로드 캐스트를 통해 상태 표시줄에 알림을 표시하는 등의 작업을 할 수 있다.
※ 브로드 캐스트 리시버 구성 요소에 있는 Boot Complete(부트 컴플리트)는 윈도우의 시작프로그램과 유사하다고 보면 된다. 악성코드가 이 부트 컴플리트에 등록되어 핸드폰을 껐다 켜도 계속 동작하게 할 수 있다.
마지막으로 Content Provider(콘텐츠 제공자)는 파일 시스템, SQLite과 같이 데이터 영구 저장소에 앱 데이터의 공유형 집합을 관리한다. 앱 데이터의 공유형 집합은 웹상이나 일반 사용자 앱이 접근할 수 있는 것을 뜻한다. 예를 들어, 콘텐츠 제공자는 사용자의 연락처 정보를 관리하고 이를 통해, 일반 사용자 앱도 연락처 정보를 통한 별도의 기능을 제공할 수 있는 것이다.
※ 컴포넌트에서 Intent를 알면 좋은데, Intent는 다른 앱 컴포넌트로부터 특정 작업을 해달라고 요청하는 데 사용할 수 있는 메시징 객체이다. 즉, 여러 앱 간 통신 혹은 동일한 앱 내에서 통신할 때 Intent를 쓰는데, 화면을 보여달라고 하면 액티비티 컴포넌트를 작업한 것이고 백그라운드에서 동작해달라고 서비스 컴포넌트로 작업시킬 수 있고, 알림을 전달하도록 브로드 캐스트 리시버 컴포넌트를 작업시킬 수 있고 콘텐츠 제공자를 통해 공유 데이터를 가져와달라고 지시할 수 있는 메시지를 보내는 게 바로 Intent이다.
Ⅲ. 네트워크 추가로 배운 간단 정리
이 부분은 그냥 TIP으로 알고 넘어가면 좋다. 통신망은 국가 기반으로 구성되어 있고 통신사들이 빌려 쓰고 있다. 본인을 드러내지 않기 위해, 즉, 익명성을 갖기 위해 일반적인 네트워킹 과정을 우회할 수 있는데, Proxy, VPN, SNI 등이 있다. 일반적으로 출발지에서 여러 라우터를 거쳐 목적지에 도착(군대에서 시작된 기술이다.)하는데, Proxy는 일종의 대리인으로서 방금 이야기 한 일반적인 네트워킹 과정을 나를 대신해서 해준다. 그래서 Source IP가 Proxy로 나오고 Proxy는 일반적으로 서비스를 제공하는 서버의 바깥에 있다. VPN(Virtual Private Network)은 외부에 가상 컴퓨터가 있는 것처럼 나에게서 출발했지만 이 가상 컴퓨터를 통해 목적지로 간다. 즉, 내가 가상 컴퓨터에 의해 감춰진다. 여러 VPN 업체가 있는데 이 업체를 통해 VPN을 사용해보면 Source IP는 이 업체의 가상 컴퓨터가 된다. SNI(Server Name Indication)는 ISP(Internet Service Provider) 업체가 Host의 정보를 필터링하고 있어 유해사이트로 접근을 차단하는 방식이다. ISP 업체는 우리나라로 치면 SKT, KT, LG에 해당하고 즉, 우리의 네트워킹 내역은 실제로 어느 정도 감시 당하고 있는 것이다. 일반 사용자가 HTTP(80번 포트), HTTPS(암호화 된 443번 포트)로 국가가 지정한 유해사이트에 접속하려고 시도하면 차단시켜서 되돌려버리는 것이다. 즉, 라우터로 넘어가 일반적인 네트워킹 과정을 하기 전에 중간에서 모니터링을 통해 차단하는 것이다. Source IP의 경우는 그대로 나 자신이다.
※ 유해사이트에 들어가지 맙시다(아니면 .....).
후기
오늘부터 기초 교육의 마지막 주가 시작되었습니다. 모바일에 대한 개념부터 시작했는데 점점 더 확장되는 게 재미있기도 하지만, 갈수록 양이 많아 걱정도 늘어가는 것 같습니다. 내일부터 여러 실습 위주의 수업이 진행될텐데 잘 따라갈 수 있을지 걱정되지만, 한편으로 정말 재미있을 것 같아 기대됩니다.😁
Log in