공부/CS

컴퓨터 시스템(CSAPP) 1장 핵심 정리

Sadie Kim 2022. 11. 24. 02:28

기존 책 내용에 보충하고 싶은 부분들(ex: 컴파일 과정)을 이것저것 추가해서 1.7.3 가상메모리까지만 정리해 보았다.(그 뒤는 시간관계상 생략...)
지금은 3장 읽고 있는데 책이 이상하게 잘 읽히지가 않아서 머리 싸매고 있다...
정글러 분들... 요약정리글 공유해요...😂


서문

프로그램의 수명주기를 따라가면서 주요 개념과 용어, 관련된 구성요소를 간략하게 소개하고자 한다.

다음 코드로 이루어진 hello.c 프로그램을 통해 이를 공부해 보자.

#include <stdio.h>

int main()
{
    printf("hello, world\n");
    return 0;
}

1.1 정보는 비트와 컨텍스트로 이루어진다

  • hello 프로그램은 소스 프로그램으로 생성되어, hello.c라는 텍스트 파일로 저장된다.
    • 소스 프로그램 : 0 또는 1로 표시되는 비트들의 연속. 바이트라는 8비트 단위로 구성됨
    • 각 바이트는 프로그램의 텍스트 문자를 나타냄
    • 대부분의 컴퓨터 시스템은 텍스트 문자를 아스키 표준을 사용하여 표시
      • 아스키 표준 : 각 문자를 바이트 길이의 정수 값으로 나타냄
    • hello.c처럼 오로지 아스키 문자들로만 이루어진 파일들을 텍스트 파일이라고 부름. (↔바이너리 파일)
  • 모든 시스템 내부의 정보(디스크 파일, 메모리상의 프로그램, 데이터, 네트워크를 통해 전송되는 데이터)는 비트들로 표시되며, 컨텍스트에 의해 구분된다.
    • 동일한 일련의 바이트가 컨텍스트에 따라 정수, 부동소수, 문자열, 기계어 명령 등으로 다르게 해석될 수 있다.

1.2 프로그램은 다른 프로그램에 의해 다른 형태로 번역된다

  • hello 프로그램은 생성될 당시 인간이 이해하고 읽을 수 있는 고급 C 프로그램이다.
  • 이를 시스템에서 실행시키려면 각 문장들은 저급 기계어 명령어들로 번역되어야 한다.
  • 이 명령어들은 실행가능 목적 프로그램(=실행 프로그램)이라고 하는 형태로 합쳐져서 바이너리 디스크 파일로 저장된다.
    • 실행 프로그램은 실행 파일이라고도 부른다.
    • 실행 파일 : 실행 코드로 이루어진 파일. 목적 파일에서 링킹이 완료되면 실행 파일이 된다.
  • 컴파일러 드라이버는 유닉스 시스템에서 소스파일을 실행 파일로 번역한다.
linux> gcc -o hello hello.c
  • GCC 컴파일러 드라이버(GNU에서 만든 C 컴파일러)는 소스파일 hello.c를 실행 파일인 hello로 번역한다.
  • 번역은 전처리기, 컴파일러, 어셈블러, 링커를 거쳐 다음의 4단계로 이루어진다.(*컴파일 과정)

 

각 단계는 다음과 같다.

  1. 전처리 단계
    • 전처리기(cpp)에 의해 이루어진다.
    • 소스파일에 다른 파일의 텍스트를 포함시키거나 일부 문장을 다른 문장으로 바꾸는 작업 등을 수행한다.
    • 전처리 명령어는 ‘#’ 기호로 시작하며, 본래의 C 프로그램을 해당 명령어에 따라 수정한다.
      • 예를 들어 hello.c 파일 첫 줄의 #include<stdio.h>는 전처리기로 하여금 시스템 헤더파일인 stdio.h를 프로그램 문장에 직접 삽입하라는 의미이다.
    • 그 결과 일반적으로 .i로 끝나는 새로운 C 프로그램이 생성되며, 전처리가 끝난 파일 역시 소스파일과 마찬가지로 텍스트 파일이다.
  2. 컴파일 단계
    • 컴파일러(cc1)에 의해 이루어진다.
    • 텍스트 파일 hello.i를 어셈블리어 프로그램이 저장된 텍스트 파일인 hello.s로 번역한다.
      • 어셈블리어 : 저급 언어의 한 종류로, 기계어를 읽기 편한 형태로 번역한 언어
      • 어셈블리어는 여러 상위 수준 언어의 컴파일러들을 위한 공통의 출력 언어를 제공하기 때문에 유용하다.
  3. 어셈블리 단계
    • 어셈블러(as)에 의해 이루어진다.
    • 어셈블리어 파일 hello.s를 기계어 명령어로 번역하고, 이들을 재배치가 가능한 목적 프로그램의 형태로 묶어서 hello.o라는 목적 파일에 저장한다.
      • 목적 코드 : 사람이 알아볼 수 없는 기계어로 변환된 코드
      • 목적 파일 : 목적 코드로 이루어진 파일
    • 이 파일은 main함수의 명령어들을 인코딩하기 위한 17바이트를 포함하는 바이너리 파일이다.
  4. 링크 단계
    • 링커(ld)에 의해 이루어진다.
    • 링킹이란 작성한 프로그램이 사용하는 다른 프로그램이나 라이브러리를 가져와 연결하는 과정을 뜻한다.
    • 예를 들어, hello 프로그램이 호출하는 표준 C 라이브러리의 printf 함수는 별도의 목적 파일인 printf.o에 들어 있다.
    • 이 파일을 hello.o파일과 통합하는 작업을 링커 프로그램이 수행한다.
    • 링킹이 완료되면 hello 파일은 실행가능 목적 파일(=실행파일)로 메모리에 적재되어 시스템에 의해 실행된다.

1.3 컴파일 시스템이 어떻게 동작하는지 이해하는 것은 중요하다

  • 프로그래머들이 컴파일 시스템이 어떻게 동작하는지 이해해야 하는 이유는 다음과 같다.
    1. 프로그램 성능 최적화하기
      • C 프로그램 작성 시 올바른 판단을 하기 위해서는 기계어 수준 코드에 대한 기본적인 이해를 할 필요가 있으며, 컴파일러가 어떻게 C 문장들을 기계어 코드로 번역하는지 알 필요가 있다.
      • 예를 들어, switch문은 if-else문을 연속해서 사용하는 것보다 효율적인지, 함수 호출 시 발생하는 오버헤드는 얼마나 되는지, while 루프는 for 루프보다 더 효율적인지, 포인터 참조가 배열 인덱스보다 더 효율적인지 등을 알기 위해서는 해당 지식이 필요하다.
    2. 링크 에러 이해하기
      • 가장 당혹스러운 프로그래밍 에러는 링커의 동작과 관련되어 있으며, 큰 규모의 소프트웨어 시스템을 빌드하려는 경우에는 더욱 그렇다.
      • 예를 들어 링커가 어떤 참조를 풀어낼 수 없다고 할 때는 무엇을 의미하는가? 정적변수와 전역변수의 차이는 무엇인가? 만일 각기 다른 파일에 동일한 이름의 두 개의 전역변수를 정의한다면 무슨 일이 일어나는가? 등의 질문에 대한 대답을 배울 수 있다.
    3. 보안 약점 피하기
      • 오랫동안 버퍼 오버플로우 취약성이 인터넷과 네트워크상의 보안 약점의 주요 원인으로 설명되었다.
      • 안전한 프로그래밍을 배우는 첫 단계는 프로그램 스택에 데이터와 제어 정보가 저장되는 방식 때문에 생겨나는 영향을 이해하는 것이다.

1.4 프로세서는 메모리에 저장된 인스트럭션을 읽고 해석한다

  • 위의 과정을 통해 디스크에 저장된 hello 실행파일을 유닉스 시스템에서 실행하기 위해서 이라는 응용프로그램에 그 이름을 입력한다.
linux> ./hello
hello, world
linux>
  • 쉘은 커맨드라인 인터프리터로 프롬프트를 출력하고 명령어 라인을 입력 받아 그 명령을 실행한다.
    • 프롬프트 : 컴퓨터가 입력을 받아들일 준비가 되어서 기다리고 있다고 알려주는 메시지. 위 예시에서는 linux>
  • 만일 명령어 라인이 내장 쉘 명령어가 아니면 실행파일의 이름으로 판단하고 그 파일을 로딩해서 실행해 준다.
  • 이 경우, 쉘은 hello 프로그램을 로딩하고 실행한 뒤 종료를 기다리며, 종료 이후에는 프롬프트를 출력해 주고 다음 입력 명령어 라인을 기다린다.

1.4.1 시스템의 하드웨어 조직

hello 프로그램을 실행할 때 무슨 일이 일어나는지를 전형적인 시스템 하드웨어 조직을 통해 설명해 보자.

버스(Buses)

  • 시스템 내를 관통하는 전기적 배선군을 버스라고 한다.
  • 컴포넌트들 간에 바이트 정보들을 전송한다.
  • 일반적으로 워드(word)라고 하는 고정 크기의 바이트 단위로 데이터를 전송하도록 설계된다.
    • 한 개의 워드를 구성하는 바이트 수는 시스템마다 보유하는 기본 시스템 변수로, 오늘날 대부분의 컴퓨터들은 4바이트(32비트) 또는 8바이트(64비트) 워드 크기를 갖는다.

입출력 장치(I/O Device)

  • 시스템과 외부세계와의 연결을 담당한다.
  • 입력용 키보드, 마우스, 출력용 디스플레이, 데이터 및 프로그램의 장기 저장을 위한 디스크 드라이브 등이 입출력 장치이다.
  • 각 입출력 장치는 입출력 버스에서 컨트롤러나 어댑터를 통해 연결된다.
    • 컨트롤러는 디바이스 자체에 있는 칩셋이거나 시스템의 마더보드에 장착된다.
    • 어댑터는 마더보드의 슬롯에 장착되는 카드이다.
    • 컨트롤러나 어댑터는 입출력 버스와 입출력 장치들 간에 정보를 주고받도록 한다.

메인 메모리(Main Memory)

  • 프로세서가 프로그램을 실행하는 동안 데이터와 프로그램을 모두 저장하는 임시 저장장치
  • 물리적으로 메인 메모리는 DRAM(Dynamic Random Access Memory) 칩들로 구성되어 있음
  • 논리적으로 메모리는 연속적인 바이트들의 배열로, 각각 0부터 시작해서 고유의 주소(배열의 인덱스)를 가지고 있음
    • 일반적으로 한 개의 프로그램을 구성하는 각 기계어 인스트럭션은 다양한 바이트 크기를 갖는다.

프로세서(Processor)(=CPU)

  • 주처리장치(CPU)라고도 한다.
  • 메인 메모리에 저장된 명령어들을 해독(실행)하는 엔진이다.
  • 프로세서의 중심에는 워드 크기의 저장장치(혹은 레지스터)인 프로그램 카운터(PC)가 있다.
    • PC는 다음에 실행될 명령어의 주소를 가리킨다.
  • 프로세서는 프로그램 카운터가 가리키는 곳의 명령어를 반복적으로 실행하고, 프로그램 카운터 값이 다음 명령어의 위치를 가리키도록 업데이트한다.
  • 다음은 명령어 요청에 의해 CPU가 실행하는 단순한 작업의 예이다.
    • 적재(Load) : 메인 메모리에서 레지스터에 한 바이트 또는 워드를 이전 값에 덮어쓰는 방식으로 복사한다.
    • 저장(Store) : 레지스터에서 메인 메모리로 한 바이트 또는 워드를 이전 값을 덮어쓰는 방식으로 복사한다.
    • 작업(Operate) : 두 레지스터의 값을 ALU로 복사하고 두 개의 워드로 수식연산을 수행한 뒤, 결과를 덮어쓰기 방식으로 레지스터에 저장한다.
    • 점프(Jump) : 명령어 자신으로부터 한 개의 워드를 추출하고, 이것을 PC에 덮어쓰기 방식으로 복사한다.

1.4.2 hello 프로그램의 실행

hello 프로그램이 실행되는 과정을 하드웨어 조직을 바탕으로 설명해 보자.

  1. 처음에 쉘 프로그램은 사용자가 명령을 입력하기를 기다린다. 이용자가 쉘에 “.\hello”를 입력하면 쉘 프로그램은 각각의 문자를 레지스터에 읽어들인 후 메모리에 저장한다.
  2. 키보드에서 엔터를 누르면 쉘은 명령 입력을 끝마쳤음을 이해하고 파일 내의 코드와 데이터(”hello, world\n” 스트링 데이터 포함)를 복사하는 일련의 명령어를 실행하여 실행파일 hello를 디스크에서 메인 메모리로 로딩한다.
  3. 데이터는 직접 메모리 접근 기법을 이용해 프로세서를 거치지 않고 디스크에서 메인 메모리로 직접 이동한다.
  4. hello 목적파일의 코드와 데이터가 메모리에 적재된 후, 프로세서는 hello 프로그램의 main 루틴의 기계어 명령어를 실행한다.
  5. 이 명령어들은 “hello, world\n” 스트링을 메모리로부터 레지스터 파일로 복사하고, 거기로부터 디스플레이 장치로 전송하여 화면에 글자들이 표시된다.

1.5 캐시가 중요하다

  • 위와 같은 예제에서 우리는 시스템이 정보를 한 곳에서 다른 곳으로 이동시키는 일에 많은 시간을 보낸다는 사실을 알게 된다.
    • hello 프로그램의 기계어 명령어들은 본래 하드디스크에 저장되어 있었으나 프로그램이 로딩되면서 메인 메모리로 복사된다.
    • 프로세서가 프로그램을 실행할 때에는 메인 메모리에서 프로세서로 복사된다.
    • 마찬가지로 “hello, world\n” 문자열 데이터도 본래는 디스크에 저장되어 있었지만, 메인 메모리로 복사되었다가 디스플레이 장치로 복사된다.
  • 이러한 복사 과정들이 프로그램의 실제 작업을 느리게 하는 오버헤드이며, 시스템 설계자들은 이러한 복사과정을 가능한 한 빠르게 동작하도록 하는 것을 목표로 한다.
  • 물리학의 법칙 때문에 큰 저장장치들은 작은 저장장치들보다 느리고, 빠른 장치들은 느린 장치들보다 만드는 데 많은 비용이 든다.
  • 마찬가지로 레지스터 파일은 메인 메모리에 비해 용량이 작지만, 프로세서는 레지스터 파일의 데이터를 메모리의 경우보다 훨씬 빠르게 읽을 수 있다.
  • 이러한 프로세서-메모리 간 격차에 대응하기 위해 시스템 설계자는 보다 작고 빠른 캐시 메모리(간단히 캐시)라고 부르는 저장장치를 고안하여 프로세서가 단기간에 필요로 할 가능성이 높은 정보를 임시로 저장할 목적으로 사용한다.
  • 일반적인 시스템에서의 캐시 메모리 구조는 다음과 같다.

  • 캐시는 CPU와 가까운 순서대로 계층을 구성하며, CPU와 가까운 순서대로 L1 캐시, L2 캐시, L3 캐시라고 한다.
    • 프로세서 칩 내에 들어 있는 L1 캐시는 대략 수천 바이트의 데이터를 저장할 수 있으며, 레지스터 파일만큼 빠른 속도로 액세스할 수 있다.
    • 이보다 좀 더 큰 L2 캐시는 수백 킬로바이트에서 수 메가 바이트의 용량을 가지며 프로세서와 전용 버스를 통해 연결된다.
    • 프로세서가 L2 캐시를 액세스할 때 L1 캐시보다 5배 정도 느리지만, 그래도 여전히 메인 메모리를 액세스할 때보다는 5배에서 크게는 10배까지 더 빠르다.
    • L1, L2 캐시는 SRAM이라는 하드웨어 기술을 이용해 구현한다.
    • 일반적으로 L1 캐시와 L2 캐시는 코어 내부에 위치한다.
  • 캐시 시스템은 프로그램이 지엽적인 영역의 코드와 데이터를 액세스하는 경향이 있다는 지역성(locality)을 바탕으로 한다.
    • 자주 액세스할 가능성이 높은 데이터를 캐시가 보관하도록 설정하면 빠른 캐시를 이용해서 대부분의 메모리 작업을 수행할 수 있게 된다.
  • 캐시 메모리를 이해하는 응용 프로그래머는 캐시를 활용하여 자신의 프로그램 성능을 10배 이상 개선할 수 있다.

1.6 저장장치들은 계층구조를 이룬다

  • 모든 컴퓨터 시스템의 저장장치들은 다음 그림과 같은 메모리 계층구조로 구성되어 있따.

  • 계층의 꼭대기에서부터 맨 밑바닥까지 이동할수록 저장장치들은 더 느리고, 더 크고, 바이트당 가격이 싸진다.
  • 레지스터 파일은 계층구조의 최상위인 레벨 0, 즉 L0을 차지한다. L1에서 L3까지는 캐시를 사용하는 구조를 보여주며, 메인 메모리는 레벨 4에 위치한다.
  • 메모리 계층구조의 주요 아이디어는 한 레벨의 저장장치가 다음 하위레벨 저장장치의 캐시 역할을 한다는 것이다.
    • L1와 L2의 캐시는 각각 L2와 L3의 캐시이다.
    • L3 캐시는 메인 메모리의 캐시이고, 이 캐시는 디스크의 캐시 역할을 한다.
    • 일부 분산 파일시스템을 가지는 네트워크 시스템에서 로컬 디스크는 다른 시스템의 디스크에 저장된 데이터의 캐시 역할을 수행한다.

1.7 운영체제는 하드웨어를 관리한다

  • 쉘 프로그램이 hello 프로그램을 로드하고 실행했을 때와 hello 프로그램이 메시지를 출력할 때, 프로그램이 키보드나 디스플레이, 디스크나 메인 메모리를 직접 액세스하지 않았다.
  • 이 때, 운영체제(Operation System)가 제공하는 서비스를 활용한다.
  • 운영체제는 하드웨어와 소프트웨어 사이에 위치한 소프트웨어 계층으로 생각할 수 있다.
  • 응용프로그램이 하드웨어를 제어하려면 언제나 운영체제를 통해서 해야 한다.

  • 운영체제는 두 가지 주요 목적을 지닌다.
    1. 제멋대로 동작하는 응용프로그램들이 하드웨어를 잘못 사용하는 것을 막기 위해
    2. 응용프로그램들이 단순하고 균일한 메커니즘을 사용하여 복잡하고 매우 다른 저수준 하드웨어 장치들을 조작할 수 있도록 하기 위해
  • 운영체제는 이 두 가지 목표를 다음과 같은 근본적인 추상화를 통해 달성하고 있다.

  • 파일은 입출력장치의 추상화이고, 가상메모리는 메인 메모리와 디스크 입출력 장치의 추상화, 그리고 프로세스는 프로세서, 메인 메모리, 입출력장치 모두의 추상화 결과이다. 이들 각각에 대해 차례로 설명한다.

1.7.1 프로세스

  • 프로세스는 실행 중인 프로그램에 대한 운영체제의 추상화다.
  • 다수의 프로세스들은 동일한 시스템에서 동시에 실행될 수 있으며, 각 프로세스는 하드웨어를 배타적으로 사용하는 것처럼 느끼게 해준다.
  • 동시에란 말은 한 프로세스의 명령어들이 다른 프로세스의 명령어들과 섞인다는 것을 의미한다.
  • 프로세서는 프로세스들을 바꿔주는 방식으로 한 개의 CPU가 다수의 프로세스를 동시에 실행하는 것처럼 보이게 해준다.
  • 운영체제는 문맥 전환(context switching)이라는 방법을 사용해서 이러한 교차 실행을 수행한다.
    • 운영체제는 프로세스가 실행하는 데 필요한 모든 상태정보의 변화를 추적하며, 이 상태정보를 컨텍스트라고 한다.
    • 컨텍스트는 PC, 레지스터 파일, 메인 메모리의 현재 값을 포함하고 있다.
    • 단일 프로세서 시스템은 한 순간에 한 개의 프로세스 코드만을 실행할 수 있다.
    • 따라서 운영체제는 현재 프로세스에서 다른 프로세스로 제어를 옮기려고 할 때 현재 프로세스의 컨텍스트를 저장하고, 새 프로세스의 컨텍스트를 복원시키는 문맥 전환을 실행하여 제어권을 새 프로세스로 넘겨준다.
    • 새 프로세스는 이전에 중단했던 그 위치부터 다시 실행된다.
  • 예제 hello 프로그램을 통해 프로세스 전환을 살펴보자.
    • 예제에는 쉘 프로세스, hello 프로세스 이렇게 두 개의 동시성 프로세스가 존재한다.
    • 처음에는 쉘 프로세스가 혼자서 동작하고 있다가 명령줄에서 입력을 기다린다.
    • hello 프로그램을 실행하라는 명령을 받으면, 쉘은 시스템 콜이라는 특수 함수를 호출하여 운영체제로 제어권을 넘겨준다.
    • 운영체제는 쉘의 컨텍스트를 저장하고 새로운 hello 프로세스와 컨텍스트를 생성한 뒤 제어권을 새 hello 프로세스로 넘겨준다.
    • hello가 종료되면 운영체제는 쉘 프로세스의 컨텍스트를 복구시키고 제어권을 넘겨주면서 다음 명령 줄 입력을 기다린다.
  • 하나의 프로세스에서 다른 프로세스로의 전환은 운영체제 커널에 의해 관리된다.
    • 커널은 운영체제 코드의 일부분으로 메모리에 상주한다.
    • 응용프로그램이 운영체제에 의한 어떤 작업을 요청하면, 컴퓨터는 파일 읽기나 쓰기와 같은 특정 시스템 콜을 실행해서 커널에 제어를 넘겨준다.
    • 그러면 커널은 요청된 작업을 수행하고 응용 프로그램으로 리턴한다.
    • 커널은 별도의 프로세스는 아니지만, 모든 프로세스를 관리하기 위해 시스템이 이용하는 코드와 자료구조의 집합이다.

1.7.2 스레드(Thread)

  • 프로세스가 마치 한 개의 제어흐름을 갖는 것으로 생각할 수 있지만, 최근의 시스템에서는 프로세스가 실제로 스레드라고 하는 다수의 실행 유닛으로 구성되어 있다.
    • 스레드 : 프로세스를 구성하는 실행의 흐름 단위이며, 스레드를 이용하면 하나의 프로세스에서 여러 부분을 동시에 실행할 수 있다.
  • 각자의 스레드는 해당 프로세스의 컨텍스트에서 실행되며 동일한 코드와 전역 데이터를 공유한다.
  • 다중 스레딩도 다중 프로세서를 활용할 수 있다면 프로그램의 실행 속도를 빠르게 하는 한 가지 방법이다.

1.7.3 가상메모리

  • 가상메모리는 각 프로세스들이 메인 메모리 전체를 독점적으로 사용하고 있는 것 같은 환상을 제공하는 추상화이다.
  • 각 프로세스는 가상 주소 공간이라고 하는 균일한 메모리의 모습을 갖게 된다.
  • 리눅스 프로세스들의 가상 주소 공간은 다음과 같다.

  • 리눅스에서 주소 공간의 최상위 영역은 모든 프로세스들이 공통으로 사용하는 운영체제의 코드와 데이터를 위한 공간이다.
  • 주소 공간의 하위 영역은 사용자 프로세스의 코드와 데이터를 저장한다.
  • 그림에서 위쪽으로 갈수록 주소가 증가한다.
  • 가상주소공간을 낮은 주소부터 위로 올라가면서 간단히 살펴보면 다음과 같다.
    1. 프로그램 코드와 데이터
      • 코드는 모든 프로세스들이 같은 고정 주소에서 시작하며, 다음에 C 전역변수에 대응되는 데이터 위치들이 따라온다.
      • 코드와 데이터 영역은 실행 파일인 hello로부터 직접 초기화된다.
      • 런타임 힙이다. 크기가 고정되어 있는 코드, 데이터 영역과 달리 힙은 프로세스가 실행되면서 C 표준함수인 malloc이나 free를 호출하면서 런타임에 동적으로 크기가 줄었다 늘었다 한다.
    2. 공유 라이브러리
      • 주소 공간의 중간 부근에 C 표준 라이브러리나 수학 라이브러리와 같은 공유 라이브러리의 코드와 데이터를 저장하는 영역이 있다.
    3. 스택
      • 사용자 가상메모리 공간의 맨 위에 컴파일러가 함수 호출을 구현하기 위해 사용하는 사용자 스택이 위치한다.
      • 사용자 스택은 힙과 마찬가지로 동적으로 늘어났다 줄어들었다 한다.
        • 특히 함수를 호출할 때마다 스택이 커지며, 함수에서 리턴될 때는 줄어든다.
    4. 커널 가상메모리
      • 주소 공간의 맨 윗부분은 커널을 위해 예약되어 있다.
      • 응용프로그램들은 이 영역의 내용을 읽거나 쓰는 것이 금지되어 있으며, 마찬가지로 커널 코드 내에 정의된 함수를 직접 호출하는 것도 금지되어 있다.
      • 위 작업을 수행하기 위해서는 커널을 호출해야 한다.