11 분 소요

Docker를 공부하다보니 아무래도 운영체제에 대한 기본적인 지식은 있어야겠다는 필요성을 느끼게되었다. Docker는 자체 OS kernel를 탑재하여 혼자서도 작동할 수 있는 방식이 아니라, 기존의 호스트 OS 위에서 돌아가는 방식이기도 하고, Docker 관련 서적을 읽으면서 본 “docker image화된 운영체제는 사실 운영체제라고 부르기보다는 운영체제 비스무리한 것이다”라는 말의 의미를 이해할 수 없었던 것도 있었다. 그리고 Docker의 이론을 배우면서 “가상화”라는 개념이 나오는데, 이에 대한 제대로 된 이해를 위해서라도 운영체제의 기본적인 지식은 있어야겠다 생각했다. 따라서 이번 글은 운영체제의 개념과 기본적인 구조에 대해 살펴보는 글이 되겠다.

운영체제

개념 및 특징

생각해보자. 컴퓨터에는 CPU, 메모리 등의 자원들이 서로 연결되어 있지만 과연 그것만 있으면 컴퓨터를 사용할 수 있을까? 사람이 직접 CPU, 메모리를 만져가면서 원하는 소프트웨어를 동작시키고 원하는 명령을 내리지는 않는다. 자동차 엔진을 운전자가 직접 터치, 조종하면서 운전을 하진 않듯이. 우린 그저 마우스, 키보드 등의 입출력 장치만으로도 원하는 명령을 컴퓨터에 내릴 수 있고, 프로그램도 화면에 띄워 원하는 작업을 수행할 수 있다. 그렇다는 것은 사람인 사용자와 하드웨어 사이에서 사용자의 명령을 하드웨어가 동작할 수 있도록 전달해주는 어떠한 매개체가 있다는 뜻이다. 그것이 바로 운영체제(Operating System, OS)이다.

운영체제란,

  • 응용 프로그램이 실행될 수 있게 해주는 환경을 제공한다.
  • 사용자의 개입없이 CPU, RAM 등의 하드웨어 자원들을 자동으로 관리해주는 프로그램이다.
  • 사용자와 응용 프로그램이 컴퓨터와 상호작용할 수 있도록 인터페이스를 제공해주어 중간에서 매개체 역할을 하는 소프트웨어이다.

그리고 운영체제가 제공하는 기능 및 특징들로는 다음과 같이 알려져 있다.

특징 설명
사용자 편리성 제공 한정된 컴퓨팅 자원을 효과적으로 사용할 수 있도록 운영체제가 자동으로 관리해주며, 하드웨어, 운영체제에 대한 깊은 지식이 없어도 사용자가 원하는 작업을 할 수 있도록 하드웨어와 사용자를 연결해주는 인터페이스를 제공한다.
자원 관리 CPU, RAM 등의 메모리, 입출력 장치 등의 컴퓨팅 자원들을 관리한다.
스케줄링 다중 사용자 및 다중 응용 프로그램 환경에서 현재 자원 상태를 파악하고 적절한 자원 분배를 위한 스케줄링 기능을 제공.
제어 입출력 장치 및 응용 프로그램을 제어한다.

구성요소

운영체제의 기본적인 구조 및 구성요소들을 그림으로 그려본다면 다음과 같다.

사진 1-1. 운영체제의 구성요소들

사진 1-1. 운영체제의 구성요소들

위 그림에 포함된 각각의 구성요소들에 대해 살펴보면 다음과 같다.

  • User Interface (UI) : 사용자가 명령어나 마우스 등의 입출력 등을 이용하여 컴퓨터에 명령을 내리고 그 결과를 사용자에게 전달하는 사용자와 컴퓨터 사이에 존재하는 인터페이스이다. 여기에는 텍스트 명령어 기반의 CLI(Command Line Interface)와 마우스를 이용할 수 있으며 시각적인 표현이 가능한 GUI(Graphical User Interface), 그리고 모바일에서 사용자가 손으로 화면을 터치하여 명령을 내리는 Touch UI 등이 있다.
    • 쉘(Shell): 사용자가 입력한 명령어를 커널이 이해할 수 있도록(정확히는 시스템 콜의 함수(API)를 호출할 수 있도록) 해석하여 커널에게 전달하고, 그 결과를 사용자에게 출력하여 보여주는 명령어 해석기이다. 사용자와 커널 간 인터페이스의 역할을 한다. 이러한 쉘에는 bash(Linux), cmd.exe(Windows, CLI), explorer.exe(Windows, GUI) 등 여러 종류가 있다. 쉘이라고 해서 무조건 CLI 기반인 것은 아니고 GUI에도 쉘이 사용되어 사용자가 마우스 클릭 등으로 내린 명령을 커널에 전달하고 그 결과를 사용자에게 보여주는 역할을 한다.
    • CLI 기반 쉘에서 접할 수 있는 pwd , cd , uptime, ls 등의 명령어들은 사실 쉘 자체에 해당 명령을 수행할 수 있는 코드가 내장되어 있거나, 아니면 해당 코드가 적힌 파일들이 외부에 있어 사용자가 해당 명령어를 입력할 때 쉘이 해당 파일을 찾아 실행하는 구조이다. 예를 들어 리눅스에서 mkdir my_folder 와 같이 명령하면, 쉘은 mkdir 명령어에 해당하는 파일을 찾고 my_folder라는 매개변수로 해당 명령을 실행하는 구조이다. 이러한 구조에서는 나중에 개발자가 새로운 커스텀 명령어를 파일 형태로 작성하여 추가하기 쉽다. 참고로 리눅스에서 which 명령어를 이용하여 해당 명령 파일이 어디에 있는지도 파악 가능하다. 예) which mkdir/usr/bin/mkdir 가 출력됨. 만약 which 명령어를 입력했음에도 아무것도 출력되지 않는다면 해당 명령어는 쉘 자체에 내장되어 있는 것이다.
  • System Call: 사용자 또는 응용 프로그램이 커널 혹은 CPU 등의 컴퓨팅 자원에 직접 접근하는 것을 막고 보호하기 위한 사용자, 응용 프로그램과 커널 간의 인터페이스이다. 커널에서 제공 가능한 파일 시스템 관리, 프로세스 관리 등의 여러 핵심 기능들을 사용할 수 있도록 API 형태로 제공하는 코드들의 집합이다. 즉, write() , CreateProcess() 등의 함수들이 존재한다.
    • 만약 이러한 시스템 호출이란 요소 없이 사용자 또는 응용 프로그램이 커널, 또는 메모리 등의 컴퓨팅 자원에 직접 접근할 수 있게 된다면 어떻게 될까? 둘 이상의 응용 프로그램들이 각자의 데이터들을 메모리에 저장하고자 할 때 서로 같은 메모리 공간을 지정할 수도 있어 엉뚱한 데이터가 저장될 수 있고, 외부에서 해커에 의해 민감한 정보가 들어있는 메모리 공간에 자유롭게 접근 가능할 수 있어 보안적으로도 문제가 될 수 있다. 따라서 중간에 System Call이 사용자 또는 응용 프로그램의 요청을 가로채어 이를 대신 수행해 커널과 컴퓨팅 자원을 보호해주고 예기치 않은 데이터 오염을 방지할 수 있다.
    • 이러한 운영체제 API로는 Windows API, POSIX API, Java API 등이 있다고 한다.
  • Kernel: 운영체제의 핵심이 되는 기능들을 모아놓은 프로그램. 여기에는 메모리 관리, 프로세스 관리, 파일 시스템 등의 각각의 기능들이 모듈화되어 커널을 구성한다. 시스템 호출에 의해 커널이 특정 기능을 수행하는 방식이다. 예를 들어 사용자가 특정 텍스트 파일을 읽어 화면에 출력하는 명령을 내렸다면 System Call에서 이에 해당하는 함수를 호출한다. 해당 함수 호출로 인해 커널은 파일 시스템 관련 기능을 이용하여 특정 파일을 찾고 읽어 들여 결국 사용자에게 파일 내용이 도달하게 되는 식이다.
    • 컴퓨터가 부팅될 때 주기억장치에 커널 프로그램이 적재된 후, 백그라운드에서 계속 실행되는 방식.
    • 이러한 kernel에는 다음과 같은 기능들이 존재한다.
      • 프로세스 관리: 프로세스 스케줄링 및 동기화를 담당한다. 프로세스의 생명주기를 관리한다.
      • 기억장치(메모리) 관리: 메모리 할당 및 회수 작업을 관리한다. 특정 프로세스가 사용할 수 있는 메모리 공간을 할당해준다.
      • 입출력 장치 관리: 입출력 장치 서비스를 제공해준다.
      • 프로세스 간 통신 관리: 프로세스 간의 통신 환경 지원 및 관리.
      • 파일 시스템 관리: 파일의 생성, 삭제, 변경, 유지 등을 관리하며, 데이터 저장 및 접근 인터페이스를 제공.
  • Driver: 마우스, 키보드, 프린터 등의 여러 하드웨어 장치와 컴퓨터 내 운영체제 간의 통신을 위한 소프트웨어. 하드웨어(외부 장치)와 커널 간의 인터페이스라 볼 수 있겠다. 프린터기 등의 몇몇 기기들은 특수한 목적만을 위해 설계된 경우가 많아 일반적인 컴퓨터와 그 구조가 다르다. 따라서 외부 기기와 연결되어 컴퓨터에서도 해당 기기를 조작할 수 있도록 하기 위해 필요한 장치이다. 드라이버에는 커널 자체에서 보유하고 있는 드라이버도 있고, 하드웨어 제작자가 제공하는 드라이버를 설치하여 사용할 수도 있다.

사용자 모드와 커널 모드

앞선 그림 1-1을 보면 알 수 있듯, 운영체제는 크게 사용자 공간과 커널 공간으로 분리할 수 있다. 그리고 실제로도 커널 프로그램은 메모리 공간 상에서 커널 공간이라는 별도로 구분된 공간에 적재되어 실행된다. 그 외 나머지 메모리 영역은 사용자 공간으로, 응용 프로그램들이 여기에 적재된다.

하드웨어, 즉 컴퓨팅 자원을 보호하기 위해선 사용자 또는 응용 프로그램이 직접 컴퓨팅 자원에 함부로 접근하도록 해선 안된다. 이러한 이유로 운영체제에서는 하드웨어를 직접 제어할 수 있는 CPU에 대해, 사용자 모드와 커널 모드로 분리하여 다룬다.

  • 사용자 모드(user mode. 보호 모드라고도 함): 사용자 또는 응용 프로그램은 하드웨어를 직접 제어할 수 있는 CPU 명령어를 사용하지 못하는 모드. 이로 인해 결과적으로 응용 프로그램은 해당 모드에서 하드웨어에 직접 접근할 수 없다. 커널에서 제공하는 핵심 기능들을 사용할 수 없다. 응용 프로그램들은 기본적으로는 이 사용자 모드에서 실행된다.
  • 커널 모드(kernel mode. supervisor mode, system mode, previleged mode(특권 모드)라고도 불림): 하드웨어를 직접 제어할 수 있는 CPU 명령어를 사용할 수 있는 모드. 커널에서 제공하는 핵심 기능들을 사용할 수 있는 모드. 이 모드에서는 운영체제가 하드웨어에 접근할 수 있게 된다. 운영체제가 이 모드에서 실행된다.

그리고 현재 CPU가 어떤 모드로 실행되고 있는지는 CPU에 있는 mode bit를 사용하여 확인할 수 있다. 1은 사용자 모드, 0은 커널 모드를 의미한다. 이를 통해, CPU는 어떤 명령어를 수행하기 전에 먼저 mode bit를 살펴본 뒤, 1이면 제한적인 명령어만을 실행하고, 0이면 하드웨어와 관련된 명령어까지 모든 명령어들을 수행할 수 있게 된다.

응용 프로그램이 실행될 때에는 사용자 모드와 커널 모드 사이를 종종 전환하곤 한다. 응용 프로그램에서도 중간에 파일을 저장한다든가 하는 이유로 커널에서 제공하는 기능들을 사용해야할 때가 있기 때문이다. 예를 들어, 자바, C와 같은 프로그래밍 언어로 어떤 프로그램을 만들었다고 해보자. 중간에 파일을 읽거나 데이터를 파일에 써서 저장해야하는 작업을 마주친다면, 이 때부터는 사용자 모드에서 커널 모드로 전환된다. 자바, 파이썬과 같은 고수준 프로그래밍 언어들은 그 아래에 운영체제 API를 호출하는 코드를 감싸 숨기고 있다. 그래서 내부적으로는 응용 프로그램에서 요구한 기능에 맞는 운영체제 API(read() , write() 등)를 호출하는 System call이 발생하여 파일 입출력 관련 커널 기능이 실행된다. 여기서 CPU가 파일 입출력 연산을 실질적으로 수행하게 된다. 그 후, 파일 입출력 결과를 응용 프로그램에 전달하게 되면 자연스레 사용자 모드로 재전환된다.

사용자 모드에서 커널 모드로 전환시키는 트리거 - Interrupt

한 편, 사용자 모드에서 커널 모드로 전환되는 조건으로 앞서 살펴본 system call 말고도 또 하나가 있다. 인터럽트가 그것이다. 시스템 콜은 앞서 살펴보았듯, 응용 프로그램에서 커널이 제공할 수 있는 서비스를 요청할 때 커널 모드로 전환된다. 이는 앞서 설명했으므로 여기서는 인터럽트에 대해 조금 더 자세히 살펴보겠다.

여기서 말하는 인터럽트(interrupt)라는 것은, 시스템에서 발생하는 여러 이벤트들에 의해 인터럽트 신호가 들어오면 CPU는 즉각 하던 일을 중단하고 해당 일을 처리하는 것을 말한다. 프로그램 실행 중 예외가 갑자기 발생한다든가, 사용자가 여러 프로그램들을 화면에 동시에 띄워놓고 있을 때 원래 동작하고 있던 프로그램이 아닌 다른 프로그램에서 키보드 입력을 할 때와 같은 상황들이 여기에 해당한다. 이러한 여러 다양한 이유로 갑자기 발생하는 신호를 CPU가 받으면 그 일을 바로 처리하기 위해 사용자 모드에서 커널 모드로 전환하게 된다.

인터럽트 발생 시 이를 처리하는 과정은 다음과 같다.

  1. 인터럽트 요청이 들어오면 CPU에서는 현재 실행하고 있던 프로세스를 모두 중단한다.
  2. 중단한 프로세스의 상태를 PCB(Process Control Block, 프로세스 제어 블록)이란 곳에 저장한다.
  3. 인터럽트의 종류에는 여러가지가 있어서 이를 숫자로 구분한다. 이 숫자를 CPU가 인식하여 이를 처리할 수 있는 함수 내지는 프로그램을 찾아 호출하여 해당 인터럽트를 처리한다. 이 때, 각 인터럽트마다 처리할 수 있는 함수 또는 프로그램들을 인터럽트 서비스 루틴(Interrupt Service Routine) 또는 인터럽트 핸들러(Interrept Handler)라고도 부른다. 그리고 이러한 인터럽트 서비스 루틴의 시작 주소를 포함하는 식별 정보를 인터럽트 벡터(Interrupt Vector)라고 부른다. 그리고 이러한 인터럽트 벡터들은 테이블 형태로 저장되어 있는데 이를 Interrupt Descriptor Table 또는 Intterupt Vector Table이라고 부른다.
    1. 즉, 다시 정리하자면, 요청으로 들어온 인터럽트 종류를 파악하고, 해당 인터럽트를 처리할 수 있는 인터럽트 서비스 루틴을 찾기 위해 인터럽트 벡터 테이블을 참고하여 이를 찾은 후, 해당 인터럽트 서비스 루틴을 실행하여 해당 인터럽트를 처리한다.
  4. 처리 완료 후 앞서 저장했던 프로세스 상태를 복구하여 중단되었던 프로세스를 재개시킨다.

그림 2-1. 인터럽트 발생 시 이를 처리하는 과정.

그림 2-1. 인터럽트 발생 시 이를 처리하는 과정.

참고로, CPU가 처리하고 있던 프로세스의 상태 정보를 PCB에 저장하여 다른 일을 처리한 후, 다시 PCB로부터 해당 프로세스 상태들을 복구하여 작업을 재개하는 것을 문맥 교환(Context Switching)이라 한다.

이러한 인터럽트에는 크게 2가지 종류로 나뉠 수 있다.

  • 하드웨어 인터럽트 (외부 인터럽트): 마우스, 모니터 등의 외부 장치로 인해 비동기적으로 발생되는 인터럽트. 마우스 클릭, 키보드 입력 등의 이벤트가 발생하면 CPU에게 인터럽트 요청을 전달하게 된다.
  • 소프트웨어 인터럽트 (내부 인터럽트) : 현재 실행되고 있는 소프트웨어로부터 동기적으로 발생되는 인터럽트. 여기에는 또 크게 2가지로 세분화된다.
    • 트랩(Trap): 0으로 나누는 연산 등의 비정상적인 작업에 의해 발생되는 예외로 인한 인터럽트. 디버깅을 하는 경우에도 여기에 포함된다.
    • SVC 인터럽트: Supervisor call로, 앞서 살펴본 System Call에 의한 인터럽트를 일컫는다.

그래서 사실 엄밀히 말하자면, 인터럽트라는 개념 안에 System call이 포함되어 있는 관계이다.

그 외 운영체제에 대한 정보들

앞선 그림 1-1을 보면 알 수 있듯, 운영체제에서는 커널과 UI가 분리되어 있기에 같은 종류의 커널을 사용하면서도 서로 다른 UI를 가지는 운영체제들이 존재할 수 있다. 일례로 스마트폰의 운영체제 중 하나인 안드로이드는 리눅스 커널을 사용한다고 하는데, 그럼에도 안드로이도와 리눅스의 UI에는 차이가 있어보이는 것처럼 말이다.

운영체제에서는 기본적으로 하나의 운영체제에서 컴파일된 응용 프로그램이 다른 운영체제에서도 실행될 수는 없는 구조이다. 이로 인해 하나의 앱이 윈도우 버전, 리눅스 버전, 안드로이드 버전 등 다양하게 배포되는 이유이기도 하다. 하나의 앱이 여러 운영체제에서 실행될 수 있도록 하려면 각 운영체제에서 제공하는 API들을 사용하여 앱을 운영체제마다 별도로 제작하거나, 아니면 Java의 JVM처럼 프로그램을 실행할 수 있는 가상환경을 각 운영체제마다 설치하고, 이 기능을 제공하는 언어로 앱을 한번만 작성, 컴파일하여 어느 운영체제에서든지 실행하도록 하는 방법을 택할 수도 있다.

운영체제의 여러 구조들

앞서 살펴본 운영체제 구성요소들은 여러 가지 방법으로 연결될 수 있는데 여기에는 모놀리식 구조, 계층형 구조, 마이크로 커널 구조, 모듈, 하이브리드 시스템 등이 있다.

  • 모놀리식 구조 (Monolithic structure): 앞서 살펴본 그림 1-1이 여기에 해당된다. 커널을 구성하는 여러 핵심 기능들이 단 하나의 커널 내부에 모두 모여있는 구조이다. 초창기의 운영체제 구조로, MS-DOS, 초기 Unix 등이 이 구조를 사용했다고 한다.
    • 장점: 커널을 구성하는 여러 핵심 기능들이 단일 커널 내부에 모여있기에 서로 상호작용하는 속도가 빠르다.
    • 단점: 여러 핵심 기능들이 단일 커널 내부에 모두 모여 있기에 새로운 모듈을 추가할 수 없어 확장성이 떨어진다. 모든 핵심 기능들이 단일 커널로 묶여 의존성이 높기에 오류가 발생하면 다른 핵심 기능들에도 악영향을 끼칠 수 있다.

그림 3-1. 운영체제 - 계층형 구조

그림 3-1. 운영체제 - 계층형 구조

  • 계층형 구조(Layered structure): 커널을 구성하는 여러 핵심 기능들을 비슷한 계층끼리 묶어두고, 이러한 계층들이 서로 통신하는 방식이다. 백엔드에서의 컨트롤러, 서비스, 영속성 계층으로 구성된 레이어드 아키텍처와 비슷하다고 보면 된다. 서로 인접한 계층끼리만 통신이 가능하다.
    • 장점: 커널을 구성하는 핵심 기능들 중 오류가 발생했을 때 정확히 어느 계층에서 발생했는지 파악하기 쉽고, 다른 계층에 영향을 주지 않고 오류를 수정할 수 있다. 즉, 디버깅도 수월한 구조. 또한 필요에 따라 추가적인 계층을 더할 수도 있다.
    • 단점: 핵심 기능들을 계층으로 구분하긴 했지만 여전히 모놀리식 구조처럼 단일형 커널 안에 존재하므로, 단일형 커널의 단점을 일부 물려받는다. 서로 인접한 계층끼리만 통신이 가능하기에 속도, 성능적인 측면에서는 비효율적이다.

그림 3-2. 운영체제 - 마이크로 커널 구조

그림 3-2. 운영체제 - 마이크로 커널 구조

  • 마이크로 커널 구조(Micro kernel structure): 커널을 구성하는 기능들 중 정말 필요한 기능들만을 남긴 채 나머지 기능들은 사용자 공간으로 뺀 구조. Darwin, Mach 운영체제가 알려져 있다.
    • 장점: 정말 필요한 기능들만 커널에 남겼기에, 사용자 영역으로 빠진 기능에서 오류가 발생하더라도 커널에 영향을 끼치지 않는다. 커널이 가벼워지기에 CPU 용량이 적은 시스템에서도 적용 가능하다. 또한 추가하고자 하는 기능이 있다면 사용자 공간에 추가하면 되기에 확장성에도 좋다.
    • 단점: 커널 내부 기능들과 외부 기능들로 분리가 된 것이기에 서로 통신할 때 오버헤드가 생겨 속도 저하가 일어날 수 있다. 기존의 커널에 속한 기능들이 사용자 공간으로 빠졌기에 메모리 상에서 사용자 공간에 더 부담을 줄 수 있는 방식이다.
    • 커널에 남은 기능들과 사용자 영역으로 분리된 기능들은 다음과 같다.
      • 커널에 남은 기능들.
        • 메모리 관리자
        • IPC (프로세스 간 통신)
        • CPU 스케줄링.
      • 사용자 공간으로 빠진 기능들
        • 파일 시스템
        • 장치 드라이버
        • 프로세스 관리자.
  • 모듈 구조 (Module structure): 기존 커널에 필요한 부가 기능들을 모듈로 구현하여 이를 동적으로 불러와 메모리 공간에 로드하는 방식. 필요할 때마다 부가 기능을 구현한 모듈을 동적으로 적재하거나, 불필요할 땐 메모리 상에서 삭제하는 유연한 구조이다. 이러한 방식의 모듈을 LKM(Loadable Kernel Module)이라고 한다. 현대의 대부분 OS에서 채택하고 있는 구조.
    • 장점: 필요한 부가 기능들을 모듈로 구현할 수 있어 확장성이 좋다. 프로그램을 실행하면서 필요한 부가 기능들을 실시간으로 동적으로 불러오거나 불필요해진 부가 기능 커널 영역에서 제거할 수 있다는 유연성이 있다.
    • 단점: 부가 기능을 구현한 모듈은 커널과 직접 소통할 수 있기에 모듈을 직접 구현하거나, 다른 사람이 구현한 외부 모듈을 사용할 때 보안적으로 문제가 없는지 확인해야한다.
  • 하이브리드 시스템 (Hybrid System): 기존에 존재하는 둘 이상의 구조들을 결합하여 운영체제를 구현하는 방식.

References

[1] 윤영빈 외 3인, “수제비 2025 - 정보처리기사 실기 기본서 vol.2”, ch 11-1 “운영체제의 특징” 참고, 수제비

[2] 운영체제(OS)란? 개념과 구조(커널, 시스템콜 등)

[3] 운영체제: 유저모드, 커널모드, 시스템콜

[4] [Operating System - Chapter 2] 운영체제 구조

[5] 나무위키 - 운영체제

[6] [운영체제] 운영체제와 컴퓨터 : 운영체제의 구조

[7] 01-2 운영체제의 정의

[8] 운영체제

[9] 유저랜드 커널랜드 윈도우API 개념 | 레드팀 플레이북

[10] [운영체제] 커널 모드, 사용자 모드

[11] 사용자 공간

[12] [운영체제] 커널모드와 사용자모드

[13] [운영 체제] 운영 체제(커널 영역과 사용자 영역)

[14] [운영체제란?] 커널의 개념, 이중 모드와 시스템 호출

[15] [운영체제] 유저모드(User-Mode)와 커널모드(Kernel-Mode), 인터럽트(Interrupt)와 시스템콜(System Call)

[16] 사용자 모드

[17] 인터럽트

[18] 트랩

[19] 시스템 호출

[20] [운영체제 7편] 인터럽트가 무엇인가

[21] [컴퓨터 구조] 명령어 사이클과 인터럽트

[22] Chapter 2. 운영체제 구조

[23] [OS] 여러 가지 운영체제 구조

This content is licensed under CC BY-NC 4.0

댓글남기기