운영체제의 기본을 보신 뒤에 보시면 도움이 되실거라 생각합니다.
앞에서 응용 프로그램은 운영체제 위에서 동작한다고 했습니다. 순서를 쉽게 한번 살펴보면
이렇게 사람들은 응용 프로그램을 사용하고 있습니다. 그런데 사용자는 어떤 방식으로 운영체제와 대화할 수 있는걸까요?
사용자가 운영체제의 기능 및 서비스에 쉽게 접근할 수 있도록 인터페이스를 제공하는 하나의 프로그램으로 사용자가 화면을 통해 실제로 볼 수 있고 사용자의 지시를 해석해서 커널에 전달해주는 역할을 합니다.
사용자는 운영체제와 대화를 하기 위해서 셸이라는 프로그램을 필요로 합니다. 운영체제에서 커널과 사용자 사이에서 사용자의 명령어를 해석하고 결과를 전달하는 하나의 프로그램인 셸은 일반적인 사용자들에게 익숙한 그래픽 셸인 GUI 와 개발자들에게 익숙한 CLI 두 가지가 있습니다.
GUI는 윈도우를 CLI는 터미널을 떠올릴 수 있습니다.
사용자가 운영체제의 기능을 사용하고 싶다면 shell
을 이용하면 됩니다.
그렇다면 응용 프로그램이 운영체제의 기능을 사용하고 싶다면 어떻게 할까요??
운영체제는 사용자에게 인터페이스를 제공하는 것과 마찬가지로 응용 프로그램에게도 인터페이스를 제공합니다. 우리는 그것을 API
라고 부릅니다.
당연히 컴퓨터가 알아들을 수 있는 프로그래밍 언어(함수)의 형태로 제공하게 됩니다. 덕분에 사용자는 혹은 개발자들은 이러한 API를 통해 컴퓨터가 알아들을 수 있는 기계어를 알지 못해도 이미 정의된 여러가지 API를 사용하여 편리하게 운영체제에게 원하는 기능 및 서비스를 요청해서 개발 혹은 프로그래밍을 할 수 있는 것입니다.
Application Programming Interface
API는 단지 프로그램들이 서로 상호작용하는 것을 도와주는 도구일 뿐입니다.
어렵게 생각하면 끝없이 어렵지만 우리를 도와주는 편리한 도구라고 생각하면 어렵지 않습니다.
이제 API를 알았으니 셸(shell)을 다시 한번 살펴보면 셸도 하나의 응용 프로그램이라는걸 알았습니다. 그렇다면 당연히 셸을 만들기 위해서 다시 말해 응용 프로그램인 셸을 만들기 위해서 운영체제가 제공하는 API를 이용해서 셸(응용 프로그램)을 만들었을 것이고 사용자 인터페이스인 셸은 사용자의 입력을 받아 해당 요청에 맞는 API를 사용해서 운영체제에게 기능 및 서비스를 요청하게 되며, 운영체제는 요청 받은 시스템 자원(하드웨어)를 제공하게 되는겁니다.
당연히 셸을 제외한 다른 응용 프로그램 또한 운영체제에 있는 기능 및 서비스를 제공받기 위해서는 API가 필요하다는 의미입니다.
시스템 호출 혹은 시스템 호출 인터페이스라고 부르기도 합니다.
응용 프로그램이 운영체제의 기능을 사용할 수 있도록 운영체제가 제공하는 명령 혹은 함수입니다.
앞에서 API는 프로그래밍 언어(함수)의 형태로 제공된다고 이야기했는데요. API가 운영체제에게 기능 및 서비스를 요청할 수 있는 이유를 살펴보면 API 내부에는 운영체제의 기능 및 서비스를 요청할 수 있는 시스템 콜을 호출하는 형태로 만들어져 있는 경우가 대부분이기 때문입니다.
코드를 예시로 들어보면 더 쉽게 이해할 수 있습니다.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
위의 open()
이라는 시스템 콜은 이미 존재하는 파일을 열거나 새로운 파일을 생성하는 함수(프로그램)입니다.
그런데 파일은 HDD/SSD(보조기억장치)에 위치해 있습니다. 그리고 해당 보조기억장치는 운영체제에 의해 관리가 되죠. 그 말은 즉, 해당 파일을 열기 위해서는 운영체제에게 요청을 해야한다는 말입니다.
따라서 사용자가 파일을 열기 위해 운영체제는 open()
이라는 시스템 콜을 제공한다고 생각할 수 있습니다.
그렇다면 위의 제목처럼 API의 필요성에 대해서 의문점이 생길 수 있습니다. 시스템 콜을 이용해서 운영체제에게 요청할 수 있는데 굳이 API라는 것이 필요한가??
쉽게 설명하면 운영체제는 사용자 혹은 응용 프로그램이 운영체제의 기능을 사용할 수 있도록 시스템 콜이라는 명령 혹은 함수를 제공한다고 했습니다.
하지만 시스템 콜은 운영체제의 입장에서 각각의 기능들을 정의해 놓았기 때문에 프로그래밍에서 사용하기에는 다소 복잡합니다. 사용법이 익숙하지 않다는 의미인데요.
그래서 API라는 각 언어별
로 운영체제의 기능을 요청할 수 있는 인터페이스를 제공하는 것이고 해당 API의 내부는 대부분 시스템 콜을 통해서 운영체제에게 요청하는 것이라 생각할 수 있습니다. 그리고 해당 API를 묶어놓은 것을 통해서 응용 프로그램을 만드는 것입니다. 그리고 사용자는 그렇게 만들어진 shell과 같은 응용 프로그램을 통해서 컴퓨터를 사용하는 것입니다.
어떤 개발을 하던지 표준은 중요합니다.
모든 기술은 빠르게 발전하고 진화하고 있지만 표준에서 크게 멀어지지 않습니다.
유닉스(UNIX)계열의 운영체제들은 대부분 POSIX라는 시스템 콜의 규정을 따르고 있습니다.
POSIX는 IEEE(I-triple-E)가 제정한 유닉스의 애플리케이션 프로그래밍 인터페이스(API) 규격
이 규격을 따르면 유닉스와 직접적인 연관이 없어도 유닉스 호환(Unix-like) 운영 체제라고 부릅니다. 리눅스가 이런 케이스인데, 유닉스와는 별도로 개발되었지만 POSIX 표준을 거의 다 준수하기 때문에 리눅스는 유닉스 호환 운영 체제로 봅니다. 다만 공식 인증을 받은 경우는 흔치 않으며, 넓게 봐서 호환 운영 체제라는 얘기입니다. | 출처: 나무위키
그리고 놀랍게도 윈도우
를 제외한 리눅스, 안드로이드, macOS, iOS 등의 대부분의 운영체제가 유닉스를 기반으로 하고 있습니다. 개발을 위한 운영체제로 리눅스와 mac을 사용하는것이 이러한 이유 때문입니다.
이제 우리는 응용 프로그램을 어떻게 만드는지 알게되었습니다. 그러면 반대로 운영체제를 만든다고 가정하면 어떠한 과정이 필요할까요?
커널(Kernel)
프로그램이 요청한 처리를 하드웨어에 나누어 처리를 요구하며, 시스템 콜 수행, 메모리 제어 등 운영체제에서는 없어서는 안되는 요소이다. | 출처: 나무위키
위에서 운영체제를 개발하기 위해 가장 먼저 필요한것은 운영체제의 핵심 기능인 커널(kernel) 개발이라고 했습니다. 이러한 커널은 프로그램이 운영체제에 요청하는 시스템 콜 등을 수행하는 부분으로 운영체제 맨 하부에서 돌아가는데 이러한 커널 위에 여러가지 레이어가 올라가져 있기 때문에 커널이 날아간다는 뜻은 운영체제를 사용하지 못한다는 의미와 같습니다.
출처 : UNIX Operating System Overview
하드웨어 바로 위에 커널이 올라가며 커널 위에는 셸과 응용 프로그램이 실행되는 모습입니다.
Protection Ring
CPU에서 악성 소프트웨어나 버그로 인한 충돌로부터 커널을 보호하기 위한 기능입니다.
가장 높은 권한을 가지는 커널이 Ring 0 로 실행되며 가장 낮은 권한을 가지는 Ring 3 는 일반적인 프로그램들을 위한 것입니다.Ring 1 과 Ring 2는 장치 드라이버를 위해 있지만 윈도우나 리눅스에서는 딱히 사용하지 않습니다.
위의 사진을 다시 가져와서 살펴보면 사용자 모드(User mode)와 커널 모드(Kernel mode)가 있는것을 확인할 수 있습니다. CPU의 권한 모드인데요.
운영체제는 응용 프로그램이 운영체제의 기능을 요청하기 위해서 시스템 콜을 제공한다고 했습니다.
하지만 일반적인 응용 프로그램들은 Ring 3에 속하는 사용자 모드에서 실행되기 때문에 Ring 0에 속하는 커널 모드에 직접적인 접근을 할 수 없습니다. 따라서 사용자 모드에서 운영체제에 요청하는 것들을 커널에 요청해 커널 모드에서 처리하고 그 결과를 사용자 모드의 응용 프로그램에게 전달하는것이 시스템 콜인 것입니다. 다시말해 시스템 콜은 커널 모드에서 실행이 된다는 말입니다.
시스템 콜은 프로그램의 거의 모든 코드의 실행에서 발생하며 파일 생성이나 쓰기 또는 읽기, 키보드 입력, 그래픽 출력, 스레드 생성 및 제어 같은 것도 시스템 콜을 통해 커널에 요청하여 커널 모드에서 처리합니다.
위에서 Ring을 CPU의 보호 기능이라고 이야기 했습니다. 말 그대로 보안을 위해서인데요.
바로 하드웨어의 자원에 대한 보안입니다. 운영체제의 핵심 기능은 시스템(하드웨어) 자원의 관리라고 처음부터 계속 이야기하고 있으니 이해할 수 있을겁니다. 그 중에서도 CPU, 메모리, I/O 장치의 관리는 매우 중요합시다.
가정을 해보자면 만약에 이렇게 중요한 시스템 자원들을 Ring 3에서만 동작할 수 있는 어떠한 응용 프로그램이 운영체제와 직접 이야기할 수 있는 기계어, 어셈블리어로 작성해서 명령을 내린다면 사용자의 중요한 시스템 자원을 직접적으로 관섭할 수 있게됩니다. 다시말해 모든 권한이 오픈되어 있다는 의미이고 이말은 즉, 다른 누군가 나의 컴퓨터에 접근할 수만 있다면 개인정보를 빼가거나 악성 코드를 심거나 하는 것들이 매우 쉽다는 말입니다.
그렇기 때문에 Ring 3에서 수행되는 응용 프로그램은 스스로 Ring Level을 바꿀 수 없게하며 시스템 콜 혹은 인터럽트와 같은 특정 조작으로만 Ring Level을 변경과 함께 운영체제의 믿을 수 있는 코드를 통해서만 하드웨어를 조작하게 하여 사전에 방지하는것이 Protection Ring의 주요 목적입니다.
그래서 사용자 모드와 커널 모드를 계속 왔다갔다하며 프로그램을 동작하는것입니다.
#include <unistd.h> // 1. 사용자 모드에서 프로그램 실행
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main() {
int fd;
fd = open("data.txt".O_RDONLY); //2. 저장 매채에 있는 파일을 연다. | open() 시스템 콜 호출 =>
// 3. 커널 모드로 전환 =>
// 4. open() 함수를 처리하는 sys_open() 커널 함수 호출 =>
// 5. 파일 열기
// 6. 5의 결과값을 가지고 사용자 모드로 다시 변환
if(fd == -1) {
// 위의 조건문에서 사용자 모드로 코드를 읽는다.
printf("Error: can not open fileWn");
//파일을 열지 못했으므로 그냥 종료
return 1;
} else {
printf("File opened and now close_Wn");
close(td);
return ();
}
}
이렇게 위와 같이 사용자 모드와 커널 모드로 계속 왔다갔다하며 작업을 수행합니다.