첫 포스팅으로 책 앞부분의 리눅스 소개와 커널 컴파일 빌드 내용은 생략하고
리눅스 커널과 User Space Application(사용자 공간 애플리케이션)의 차이점을 통해 리눅스 커널의 특징에 대해 정리해보려고한다.
먼저 요약하자면 다음과 같은 7가지로 정리할 수 있다.
- 리눅스 커널은 C 라이브러리나 표준 C 헤더 파일을 사용할 수 없다.
- 리눅스 커널은 GNU C를 사용한다.
- 리눅스 커널에서는 메모리 보호 기능이 없다.
- 리눅스 커널은 부동소수점 연산을 쉽게 실행할 수 없다.
- 리눅스 커널은 프로세스당 작은 크기의 스택을 사용한다.
- 리눅스 커널은 비동기식 인터럽트를 지원하며, 선점형이며, 대칭형 다중 프로세싱을 지원하므로 커널 내에서는 동기화 및 동시성 문제가 매우 중요하다.
- 리눅스 커널은 이식성이 중요한다.
아래에서 하나씩 살펴보도록 하겠다.
- libc와 표준 헤더 파일을 사용할 수 없다.
User Space Application과 달리, 커널은 표준 C 라이브러리와도 링크되지 않는다. 여러 이유가 있지만 주요한 이유는 속도와 크기 때문이다. 전체 C 라이브러리는 커널 입장에서는 너무 크고 비효율적이다.
대신 일반적인 libc의 함수는 커널 안에 구현되어 있다.
예를 들어, 문자열(string) 처리 함수를 사용할 때는 <linux/string.h> 헤더 파일을 추가하면 된다.
빠진 함수 중 대표적인 함수로 printf가 있는데, 커널 코드는 printk함수를 제공하며, printf와 거의 같은 방식으로 사용할 수 있다.
이 메세지는 주로 syslog 프로그램이 처리한다.
사용방법은 http://guruseed.tistory.com/5 를 참고하자.
- GNU C
리눅스 커널은 C로 프로그램되어 있다. 하지만 엄격한 ANSI C로 작성되지 않았고, 대신 커널 개발자들은 필요하다고 생각되는 곳에 gccGNU Compiler Collection이 제공하는 다양한 언어 확장 기능을 사용한다.
커널 개발자들은 C언어의 ISO C99와 GNU C확장 기능을 모두 사용한다. gcc 4.4 이후 버전을 이용하여 컴파일 하는것을 권장한다.
표준 ANSI C에 비해 더 생소한 확장 기능은 GNU C가 제공하는 확장 기능들이다.
아래 몇가지 확장 기능들이 있다.
- inline 함수
컴파일 시 각 함수 호출이 일어나는 자리의 줄 안에 삽입되는 함수이다.
코드의 크기가 커지며, 이로 인해 메모리 사용량과 명령어 캐시 사용량이 늘어난다.
따라서 실행시간이 중요한 함수에 대해서만 인라인 함수 사용한다.
함수 정의 부분에 static과 inline 지시어를 사용해 선언하며,
사용하기 전에 정의를 해야하며 헤더 파일에 두고 사용하는것이 일반적이다.
static으로 지정했으므로 외부에서 사용할 수 없다.
커널에서는 type 보호 및 가독성 등의 이유로 복잡한 매크로를 사용하는 것보다 인라인 함수를 사용하는 것을 선호한다.
- inline assembly
gcc C 컴파일러는 일반적인 C 함수 안에 어셈블리 명령을 삽입하는 기능을 제공한다. 이 기능은 시스템 아키텍처에서만 사용하는 커널 소스에서 사용한다.
asm() 컴파일러 지시자를 사용한다. - 분기 구문 표시
gcc C 컴파일러는 분기 시에 어느 쪽이 발생할 가능성이 높은지를 이용해 분기 구문을 최적화하는 내장 지시자를 가지고 있다. 컴파일러는 이 지시자를 통해 분기를 예측할 수 있고 커널은 사용자가 사용하기 쉽게 likely()와 unlikely()라는 매크로로 만들어 사용한다.
if(error) { ... } 과 같은 코드가 있을 때,
if(unlikely(error)) { ... } 와 같은 방식으로
이 분기가 어쩌다 한번 시행된다고 표시할 수 있다.
반대로
if(likely(success)) { ... }
로 항상 실행될 것 같은 분기를 표시할 수 있다.
분기의 방향이 거의 대부분 알려진 한 방향으로만 일어나는 경우, 또는 다른 경우를 무시하고 한가지 경우에 대해서만 최적화가 필요한 경우에만 이 지시자를 사용 해야 한다.
이 지시자를 제대로 사용한 경우에는 성능향상을 얻을 수 있지만, 잘못 표시한 경우에는 심각한 성능저하를 가져올 수 있다.
일반적으로 오류가 발생하는 상황에서 사용하며, 주로 unlikely() 지시자를 사용한다.
- 메모리 보호 없음
User Space Application이 메모리 접근을 잘못하면, 커널은 오류를 탐지해 SIGSEGV 시그널을 보내고 프로세스를 종료시킨다. 하지만 커널이 메모리 접근을 잘못한 경우는 이를 제어하기 쉽지 않다.
커널이 메모리 접근을 잘못한 경우는 oops를 발생시킨다.
또한 커널 메모리는 페이징 기능을 사용할 수 없다.
따라서 커널에서 사용하는 모든 메모리는 실제 물리적인 메모리에 해당한다.
- 부동 소수점을 쉽게 사용할 수 없음
User Space 어플리케이션이 부동 소수점 연산을 할 경우 커널이 와 부동소수점 연산 모드 전환을 관리한다.
아키텍처에 따라 다르지만 보통은 커널이 트랩을 받아 정수 연산에서 부동 소수점 연산 모드로 전환하는 방식으로 동작한다.
커널은 자신의 트랩을 받을 수 없기 때문에 수동으로 부동 소수점 레지스터를 저장하고 복원하는 등의 잡다한 일을 직접해야 한다.
아주 특별한 경우를 제외하고는 사용하지 말아야 한다.
- 스택 크기가 작고 고정되어 있음
User Space에는 동적으로 확장 가능한 커다란 스택이 있어서 문제가 되지 않는다.
커널이 사용하는 스택은 크기도 작고 동적으로 확장할 수 없다.
커널 스택의 크기는 아키텍처에 따라 다르다. x86의 경우 컴파일 시 4KB 또는 8KB로 정할 수 있다.
관습적으로 커널 스택은 두 페이지로 구성하므로,
이는 커널 스택의 크기가 32비트 아키텍처에서는 8KB, 64비트 아키텍처에서는 16KB로 고정되어 있으며,
바꿀 수 없다는 뜻이 된다.
- 동기화와 동시성
커널은 race condition(경쟁 상태)에 놓이기 쉽다. 다음과 같은 경우 커널은 경쟁을 방지하기위한 동기화가 필요하다.
– 리눅스는 선점형 멀티태스킹 운영체제이므로 커널의 프로세스 스케줄러에 의해 프로세스의 실행 순서가 조정된다. 커널은 이 작업들 간의 동기화를 책임져야 한다.
– 리눅스는 대칭형 다중 프로세서(SMP)를 지원한다. 따라서 적절한 보호 장치가 없으면 동일한 자원에 하나 이상의 프로세스가 동시에 접근하는 커널 코드를 실행할 수 있다.
– 현재 실행하고 있는 코드와 관계없이 비동기식으로 인터럽트가 발생한다. 따라서 적절한 보호 장치가 없으면 자원을 사용하는 도중에 인터럽트가 발생하고 인터럽트 핸들러에서 같은 자원에 접근하는 상황이 발생할 수 있다.
– 리눅스 커널은 선점형이다. 따라서 적절한 보호 장치가 없으면 같은 자원에 접근하는 다른 커놀 코드가 실행 중인 커널 코드를 선점하는 일이 발생 할 수 있다.
spinlock이나 semaphore를 이용하여 경쟁 상태를 해결 할 수 있다.
- 이식성이 중요함
리눅스는 이식성이 좋은 운영체제이며 그 특성을 유지해야 한다.
이는 아키텍처 독립적인 C 코드가 여러 다양한 시스템에서 컴파일되고 실행되어야 한다는 뜻이며,
커널 소스 트리에서 아키텍처 독립적인 코드는 특정 시스템에 의존적인 코드와 적절하게 분리되어 있어야 한다는 뜻이다.
Endian 중립성, 64비트 지원, 워드 및 페이지 크기 지정 등이 있다.
'리눅스 커널(Linux Kernel)' 카테고리의 다른 글
리눅스 커널 공부 (0) | 2018.10.25 |
---|