컴퓨터 시스템과 운영체제

Coding-Luizy·2023년 3월 21일
0

운영체제

목록 보기
1/1
post-thumbnail

사용자 공간과 커널 공간

32비트 Windows 운영체제 전체 4GB의 메모리 영역에서 하위 2GB는 사용자 공간, 상위 2GB는 커널 공간으로 나뉜다.

  • 사용자 공간
    응용 프로그램이 적재되고, 응용프로그램 변수가 만들어지고 동적 할당 받는 공간으로 활용하는 공간
  • 커널 공간
    커널 코드와 커널 데이터, 그리고 커널 함수들이 실행될 떄 필요한 스택 공간, 디바이스 드라이버 등이 탑재되는 공간

사용자 공간과 커널 공간으로 나누는 이유

응용 프로그램으로부터 커널 코드와 데이터를 지키기 위해서이다. 커널이 개방되어 있으면 시스템 훼손, 시스템 중단 등 심각한 문제가 발생할 수 있다. 만일, 응용 프로그램에서 cli(clear interrupt flag)과 같은 특권 명령을 실행할 수 있도록 허용한다면 cli를 실행하고 sti(set interrupt flag) 명령을 실행하지 않은 채 중단되거나 종료되어 버린다면, 더이상의 인터럽트를 인지할 수 없게된다. 따라서, 메모리 공간을 분리해서 커널공간을 보호해야 한다.


사용자 모드와 커널 모드

사용자 모드 커널 모드
CPU의 메모리 엑세스 범위 사용자 공간에 국한. 커널 공간 엑세스 불가 커널 공간을 포함한 모든 메모리 공간
CPU의 하드웨어 엑세스 여부 불가(X) 모든 하드웨어 엑세스 가능(O)
CPU가 처리 가능한 명령 특권 명령을 제외한 모든 CPU 명령 특권 명령을 포함한 모든 CPU 명령
오류 발생 시 처리 사용자 프로그램만 실행 종료. 시스템이 종료되지 않으므로 안전 시스템에 심각한 오류가 발생한 것으로 시스템 종료

사용자 모드와 커널 모드로 나눈 이유

커널 코드와 데이터에 대한 보안과 보호를 위해서이다. 응용프로그램에 오류가 있을 수 있으므로 사용자 모드에서만 실행시키고, 특권 명령은 실행시키지 못하게 하여 하드웨어 접근을 막고 커널 공간에 접근하지 못하게 한다.

용자 모드에서 커널 모드로 바뀌는 경우

  • 시스템 호출
    응용프로그램이 시스템 호출 라이브러리에 작성된 시스템 호출 함수를 통해 간접적으로 커널 함수를 호출한다. 시스템 호출 라이브러리에 있는 시스템 호출 함수는 고유 번호( 커널 함수의 고유 ID, 시스템 호출 번호)로 커널 함수를 구분한다. '시스템 호출'은 CPU 모드를 커널 모드로 바꾸고 커널 공간 내에 미리 정해진 주소에 있는 '시스템 호출 핸들러(system call handler)' 코드를 실행하는 과정이다.
  • 인터럽트 발생
    입출력 장치나 저장장치가 CPU에게 인터럽트를 거는 경우 CPU는 자동으로 커널모드로 전환되고, CPU는 ISR(Interrupt Service Routine)로 점프하여 실행한다. ISR이 끝나면 CPU는 다시 사용자 모드로 돌아간다.

32비트 Windows 운영체제에서 응용프로그램과 라이브러리

응용프로그램의 크기

응용프로그램 크기는 최대 2GB로 제한되며, 코드, 전역변수, 힙, 스택을 모두 합친 크기를 의미한다.
운영체제가 설정한 2GB를 넘을 수 없다. 너머에 커널 공간이 있기 때문이다.

응용프로그램의 라이브러리

응용프로그램은 사용자가 작성한 함수들과 이들에 의해 호출되는 라이브러리 함수들이 링크 과정(linking)을 거쳐 하나의 실행 파일 내에 결합되어 저장된다. 응용프로그램이 실행될 때 실행 파일이 사용자 공간에 적재된다. 그러므로 사용자가 작성한 코드들과 전역 변수들, 그리고 라이브러리 함수들과 라이브러리에 선언된 전역 변수들이 응용프로그램에게 할당된 사용자 공 간에 함께 있으며, 사용자가 작성한 함수와 라이브러리 함수 모두 사용자 모드에서 실행된다.


특권 명령

특권 명령은 CPU 제조업체에 의해 특별히 설계된 CPU 명령들이다.

• I0 명령
I/0 명령은 컴퓨터 본체 내 하드웨어들을 제어하거나 입출력 장치나 저장장치를 제어하고 읽기 쓰기에 사용되는 CPU 기계 명령이다. 그래픽 카드나 네트워크 카드 등 입출력 장치들은 CPU 가 액세스할 수 있는 여러 개의 레지스터를 내장하고 있는데 이들을 I/0 포트(port)라고 부르 며, I/0 포트마다 I/0 주소가 할당된다. CPU는 I/0 포트에 값을 읽거나 쓰는 방식으로 입출력 장치를 제어하거나 입출력 장치에 값을 쓰거나 값을 읽는다.

• Halt 명령
커널은 현재 처리할 작업이 없을 때, Halt 명령을 실행하여 CPU의 작동을 중지시키고 CPU를 유휴(idle) 상태로 만든다.

• 인터럽트 플래그 켜고 끄기
대부분의 CPU는 인터럽트가 발생할 때 처리할 지 아니면 무시할 지를 나타내는 비트를 가지 고 있는데 이를 인터럽트 플래그(interrupt flag)라고 부른다.

• 타이머 설정
타이머를 설정하는 명령은 특권 명령이다.

• 컨텍스트 스위칭
현재 CPU의 레지스터들을 커널 영역에 저장하고, 저장된 컨텍스트 정보를 CPU 레지스터에 복귀시키는 컨텍스트 스위칭 명령은 커널 모드에서 실행되는 특권 명령이다.


시스템 유휴 프로세스(system idle process)

Windows10에서 작업관리자를 사용하여 CPU 실 행한 전체 실행 시간과 커널 모드로 작동한 시간을 비교하여 보여준다. 그래프에서 약간 진하 게 표시된 부분이 커널 모드로 작동하는 시간이다. 커널 모드의 시간 비율이 높은 경우는, 현재 windows에서 아무 작업도 이루어지고 있지 않을 때 커널 모드에서 실행되는 시스템 유휴 프로 세스(system idle process) 때문이다.


커널

커널은 부팅 시에 커널 공간에 적재되는 함수들과 데이터들의 집합이다,
커널은 컴파일된 바이너리 형태로 운영체제가 설치되는 하드 디스크의 특정 영역에 있다가 부팅 시에 메모리에 적재되며, 커널 모드에서 실행될 함수들과 시스템을 관리하기 위한 여러 종 류의 테이블과 구조체 등으로 구성된다.
커널 코드는 함수들의 집합이다.
컴퓨터에서 실행되는 단위는 프로세스나 스레드이지만, 커널은 프로세스도 스레드도 아니며 실행 단위가 아니고 그저 커널 공간에 적재된 함수들과 자료 구조들이다.
커널 공간은 수백 개의 함수들과 이들이 생성하고 사용하는 많은 자료구조들, 그리고 여러 디바이스 드라이버들로 이루어져 있다.
커널은 실행중이라고 표현할 수 없는 코드일 뿐이고, 프로세스가 아니므로 스택이나 힙도 가지지 않는다.


시스템 호출

시스템 호출은 사용자 공간의 코드에서 커널 공간의 코드를 호출하는 과정으로 커널 콜 (kernel call)이라고도 부른다.시스템 호출은 응용프로그램이 이러한 커널의 기능을 활용하도록 마련된 유일한 기술이다.
운영체제들은 사용자가 응용프로그램 내에서 직접 커널 함수를 호출하는 시스템 호출 코드를 작성하는 수고를 들어주기 위해 시스템 호출 라이브러리를 제공한다. 따라서, 기계명령을 직접 작성하지 않고, 시스템 호출을 일으킬 수 있다. 시스템 호출 라이브러리에 포함된 함수들을 시스템 호출 함수, 혹은 커널 API라고 부른다.

java에서는 직접 활용할 수 없는 경우가 많아, native call을 통해 C로 이루어진 코드를 불러올때가 많다.

native 매서드는 운영체제 라이브러리를 활용하기 위해 C코드를 불러와 매서드 내용이 없는것을 볼 수 있다

시스템 호출은 응용프로그램에 서 커널 기능을 활용하는 유일한 경로(interface)이다.

CPU마다 '시스템 호출'을 일으키는 특별한 기계 명령(machine instruction)을 두고 있다.
이 기계 명령을 '시스템 호출 CPU 명령'이라고 하며 다음과 같은 것들이 있다.
• int exge/iret - 인텔의 x86계열의 CPU의 명령, 32비트에서 사용
• syscall/sysret - AMD에서 최초 구현. 64비트에서만 작동
• sysenter/sysexit - Intel에서 최초 구현, x86/64 CPU, AMD에서 사용
시스템 호출을 트랩(trap)'이라고도 부르며, 시스템 호출이 진행되는 과정을 '트랩을 실행한다고 한다'고 한다. 시스템 호출을 일으키는 이 CPU 명령들을 '트랩 명령'이라고도 한다.

printf()실행 과정

printf()는 디스플레이에 정수, 실수, 문자열 등을 출력하는 C 표준 라이브러리 함수이다.
디스플레이 등 장치에 접근하는 것은 커널만이 할 수 있기 때문에, printf()가 직접 디스플레이에 쓰는 작업 은 할 수 없다.
대신, printf() 함수는 디스플레이에 출력하기 위해 시스템 호출 라이브러리의 write() 함수를 호출하여, write() 함수가 시스템 호출을 일으켜 커널 함수를 실행시키도록 한다.
printf()는 매번 write()를 호출하는 것은 아니다. 표준 라이브러리에는 디스플레이에 출력할 데이터를 임시로 모아두는 '출력 버퍼'를 두고 있는데. printf()는 먼저 이 버퍼에 저장하고 이 버퍼가 차게 되거나 'n'을 만나면 write() 시스템 호출 함수를 호출하여 디스플레이에 출력시킨다. 이렇게 하는 이유는 시스템 호출 횟수를 줄여 시스템 호출로 인한 성능 저하를 막기 위해서이다. 시스템 호출에는 사용자 모드와 커널 모드 사이의 전환, 커널 함수 실행 등 많은 시간이 소요되므로, 시스템 호출 횟수를 줄이는 것이 성능 향상 을 위해 무엇보다도 필요하다.

java에서 System.out.println() 과 System.err.println() 는 서로 다른 버퍼를 가진다.

 System.err.print("err");
 System.out.print("out");

함수 호출과 시스템 호출의 차이

함수 호출 시스템 호출
메모리 영역 사용자 영역의 코드에서 사용자 영역의 함수 호출 사용자 영역의 코드에서 커널 함수 호출
CPU 실행 모드 사용자 모드 사용자 모드에서 커널 모드로 전환
비용 함수 호출에 따른 비용 커널 모드로 전환하는 등 함수 호출에 비해 큰 비용

인터럽트 발생 및 처리 과정

CPU와 인터럽트 제어기

컴퓨터 시스템에서 인터럽트가 처리되기 위해서는 CPU와 인터럽트 제어기 (Interrupt
Controller) 등의 하드웨어가 상호 협력해야 한다.
일반적으로 CPU에는 인터럽트 수신 핀이 1개뿐이기 때문에, 여러 입출력 장치로부터 인 터럽트를 받기 위해, CPU와 입출력 창치 사이에 APIC(Advanced Programmable Interrupt Controller)라는 하드웨어가 사용되며 인터럽트 제어기라고 한다. APIC는 입출력 장치로부 터 직접 인터럽트 신호를 받는 I/0 APIC 장치와 I/0 APIC로부터 인터럽트 정보를 받아 CPU의 INTR 핀에 직접 인터럽트 신호를 발생시키는 Local APIC 장치로 분리 구성된다. 현대의 I0APIC는 24개의 인터럽트 수신 핀을 두고 있어 24개의 장치로부터 인터럽트 신호를 받을 수 있 다. APIC 장치에 있는 각 인터럽트 입력 핀을 IRQ라고 하면, 핀에 고유 번호를 매겨 IRO1, IR03 과 같이 부른다.
여러 CPU가 있는 병렬 시스템에서 각 CPU마다 혹은 멀티 코어 CPU에서 각 코어에, Local APIC 장치가 하나씩 연결되며, I/O APIC는 대체로 컴퓨터당 1개만 사용된다.

인터럽트 벡터 테이블

인터럽트 벡터 테이블은 256개의 인터럽트에 대해 인터럽트 서비스 루틴 (ISR)의 주소를 저장 하고 있는 테이블이다.
인터럽트 벡터 테이블은 커널 영역에 저장되고 커널 코드에 의해서만 수정된다. 인터 럽트 벡터 테이블은 부팅 시에 만들어지고 ISR의 주소들이 저장된다. 인터럽트 벡터 테이블이 저장된 메모리 주소는 CPU 내에 레지스터에 저장되어 CPU가 인터럽트 서비스 루틴의 주소를 알 아낼 때 이용된다.
IRC 핀마다 인터럽트 벡터가 결정되어 있으며, 이에 따라 운영체제는 부팅 시 I/0 APIC 내부에 24개의 IRQ마다 인터럽트 벡터(인터럽트 번호)를 저장한다.

처리 과정

장치로부터 인터럽트가 발생하면 I/0 APIC는 해당하는 IRQ의 인터럽트 벡터를 Local APIC에게 전송하고 Local APIC가 CPU에게 이를 알려주고 CPU는 인터럽트 벡터 테이블에 서 현재 발생한 인터럽트를 처리할 서비스 루틴의 주소를 알아내고 서비스 루틴을 실행한다.


고민해보기

자바에서 다음과 같은 코드를 수행하면 붉은 글씨로 "err"과 흰 글씨로 "out"이 출력된다.

public class Main {
    public static void main(String[] args) {
        System.err.print("err");
        System.out.print("out");
    }

System.err.print("err");
System.out.print("out");

위 두개의 함수는 서로 다른 버퍼를 가지고 출력한다. 다음과 같은 코드의 출력을 예측해보자.

public class Main {
    static final int MAX = 1_000_000;
    public static void main(String[] args) {
        for (int i = 0; i < MAX; i++) {
            System.err.println("err");
            System.out.println("out");
        }
    }
}
  1. 알 수 없음.

profile
Better Tomorrow

0개의 댓글