본 게시글의 내용은 플랫폼(시스템, 운영체제) 또는 컴파일러/인터프리터에 따라 달라질 수 있습니다.
컴퓨터 시스템 또는 컴파일러/인터프리터가 이런 방식으로 동작할 수도 있겠구나 정도로만 참조해주시면 감사하겠습니다. 👍
Word(단어)
중앙처리장치가 한 번에 엑세스 하는 데이터를 word(단어)라고 합니다.
단어의 크기는 일반적으로 바이트의 배수입니다.
그리고, 중앙처리장치 내부의 레지스터의 비트 수도 단어의 크기와 같은 것이 일반적입니다.
예를 들면, 32비트 프로세서는 내부 레지스터의 크기도 32비트, 기억장치를 엑세스 하는 단어의 크기도 32비트입니다.
데이터 버스는 시스템 구성 요소들 간에 데이터를 전달하는 경로입니다.
한 개의 신호선은 한 번에 한 비트의 정보를 전달합니다. 데이터 선이 32개라면,
한 번에 32비트의 데이터를 전달할 수 있습니다.
데이터 버스의 폭은 일반적으로 8의 배수이며, 기억장치의 단어의 크기, 레지스터의 크기와 같습니다.
데이터 정렬
16비트 이상의 단어를 처리하는 컴퓨터 시스템에서는 한 개의 단어는 여러 개의 주소 공간에 배정되어야 합니다.
즉, 크기가 n 바이트인 한 개의 단어는 기억 장치의 n개의 공간을 차지하고,
기억 장치에 주소 A+0부터 A+(n-1)까지 n개의 공간에 할당됩니다.
데이터가 저장되는 기억장치의 시작 주소 A가 n의 배수일 때, "데이터가 단어 크기에 정렬되어 있다"라고 말하는데요,
CPU는 정렬되어 있는 단어를 한 번에 엑세스 할 수 있지만,
정렬되어 있지 않은 한 개의 단어를 엑세스 하려면, 기억 장치를 두 번 엑세스하여 데이터를 조합하여야합니다.
바이트 순서 (Byte Order)
한 개의 단어가 한 바이트 이상의 기억장치 공간을 차지할 때,
단어를 구성하는 바이트들을 주어진 공간에 배치하는 방법을 바이트 순서라고 합니다.
빅 엔디안(Big endian)
상위바이트부터 순차적으로 저장하는 방식
리틀 엔디안(Little endian)
하위바이트부터 순차적으로 저장하는 방식
⚠️ 네트워크상에서 데이터를 전송할 때에는 데이터의 배열을 빅 엔디안(Big Endian) 기준으로 변경해서 송수신
계층적 버스
시스템 버스에 여러 장치가 연결될 수 있는데요,
시스템 버스에 연결되는 장치가 많아질수록 시스템 버스에 대한 경쟁이 심해지고,
결과적으로 데이터 전송 속도가 느려지는 현상이 발생합니다.
이 문제를 해결하기 위해서, 최근의 컴퓨터 시스템은 위 그림처럼 버스를 계층적으로 구성합니다.
속도가 빠른 중앙처리장치와 기억장치를 시스템 버스에 연결하고,
중앙처리장치나 기억장치보다 속도가 느린 입출력 장치들을 입출력 버스에 연결합니다.
그리고, 브리지는 두 개의 버스를 서로 연결하는 역할을 수행합니다.
계층적 버스를 3계층으로 구성하는 경우도 있고, 각 버스의 이름은 컴퓨터 시스템마다 다릅니다.
마이크로오퍼레이션
중앙처리장치가 실행하는 동작의 최소 단위를 마이크로오퍼레이션이라고 하고,
이 마이크로오퍼레이션은 한 개의 클럭 사이클 안에서 이루어집니다.
명령어 인출 단계
1. MAR <- PC
2. MBR <- Mem[MAR], PC <- PC + [명령어의 길이]
3. IR <- MBR
명령어 실행 단계
// 데이터 읽기
1. MAR <- 주소 레지스터
2. MBR <- Mem[MAR]
3. 데이터 레지스터 <- MBR
// 데이터 쓰기
1. MAR <- 주소 레지스터
2. MBR <- 데이터 레지스터
3. Mem[MAR] <- MBR
이 마이크로오퍼레이션은 운영체제에서 경쟁 조건 (Race Condition)을 설명할 때 필요한 내용이라 가져와봤습니다.
경쟁 조건(Race Condition)
경쟁조건이란, 공유 자원에 대해 여러 개의 프로세스가 동시에 접근을 시도할 때
접근의 타이밍이나 순서 등이 결과값에 영향을 줄 수 있는 상태를 의미합니다.
만약, 두 스레드가 공유하는 변수의 값을 1 증가시키는 상황을 가정해보았을 때,
스레스1이 공유 변수의 값을 가져와서 데이터 레지스터에 저장한 후에,
스케줄링으로 인해 스레드 2로 Context Switching 되었습니다.
(Context Switching 될 때, 레지스터의 값, 프로세스 상태 등을 PCB에 저장합니다.)
이후, 스레드2가 다시 메모리에 접근하여 공유 변수의 값을 데이터 레지스터로 가져와서 값을 1 증가 시킨 후, 메모리에 저장하였습니다.
이후, 스레드1으로 Context Switching되었지만, 스레드1이 처음 Context Switching 되기 전, 데이터 레지스터에 저장된 값은 1 증가 되지 않은 값입니다.
따라서, 두 스레드가 각각 공유 변수에 1씩 증가시켜도 올바른 값이 메모리에 저장되지 않습니다.
상호 배제, 임계영역
이러한 이유로 인해서 상호 배제라는 개념이 필요합니다.
둘 이상의 스레드가 동시에 접근해서는 안되는 공유 자원을 접근하는 코드를 임계 영역(Critical Section)이라 하고,
상호 배제(Mutual Exclusion)는 둘 이상의 프로세스 또는 스레드가 동시에 임계 영역에 진입하는 것을 방지하기 위해 사용되는 알고리즘을 의미합니다.
운영체제 스터디 전에 간단하게만 알아보려고 가져와봤습니다.
베이스 레지스터
베이스 레지스터는 스택의 한 기준점을 가리키는 용도로 사용됩니다.
void caller(){
...
callee(x, y, z);
...
}
void callee(int param1, int param2, int param3){
int local1, local2;
}
callee()가 호출될 때 동작은 아래와 같습니다.
- 함수 호출 전 :
스택 포인터 (SP)와베이스 포인터 (BP)는 스택의 어딘가를 가리키고 있다. - 파라미터 전달 : 함수를 호출하는 문장을 실행하면서, 파라미터 param1, 2, 3을 스택에 푸시한다.
- 복귀 주소 저장 : 복귀 주소를 스택에 저장하고,
PC는 이 함수의 시작 위치로 설정된다. - 이전 BP 저장 : 이전
베이스 포인터(BP)의 값을 잃어버리지 않도록, 스택에베이스 포인터(BP)의 값을 푸시한다. - 새로운 BP 설정 :
베이스 포인터 (BP)의 값을 현재스택 포인터(SP)의 값으로 설정한다. - 지역 변수 할당 : 스택에 지역 변수 local1, local2를 위한 공간을 만들고, 그 크기만큼
스택 포인터(SP)를 증가시킨다.
이제 베이스 포인터(BP)는 함수 callee() 스택 프레임의 기준점을 가리키게 됩니다.
이 기준점을 기준으로부터 스택의 아래는 파라미터들이 저장되어 있고, 스택의 위는 지역 변수들이 저장되어 있습니다.
즉, 스택 프레임은 함수 호출 순서에 따라 다르게 쌓이지만, 스택 프레임 내에서 각 변수들의 메모리 주소, 복귀 주소 등은 베이스 포인터(BP)의 상대적인 위치로 고정될 수 있습니다.
int func1(int a) {
a += 1;
return 20;
}
아래는 위 C언어 코드를 어셈블리어로 변환한 코드입니다.
파라미터 변수 a에 접근할 때, ebp 레지스터(BP)를 이용하여 접근하도록 컴파일 되는 것을 확인할 수 있습니다.
3: int func1(int a) {
/*
func1() 함수의 실행을 준비하는 prologue
*/
4: a += 1;
00A51808 mov eax,dword ptr [ebp+8] // eax 레지스터에 8이 들어감
00A5180B add eax,1
00A5180E mov dword ptr [ebp+8],eax
5: return 20;
00A51811 mov eax,14h
6: }
이 함수에서 복귀하면, 스택 포인터와 베이스 포인터는 다시 함수 호출 이전의 상태로 복구됩니다.
인터럽트
컴퓨터의 정상적인 프로그램 실행을 방해하는 사건이 발생하는 것을 인터럽트라고 합니다.
인터럽트는 내부 인터럽트와 외부 인터럽트로 나누어집니다.
- 내부 인터럽트
하드웨어 고장,실행할 수 없는 명령어,명령어 실행 오류,사용 권한 위배등
- 외부 인터럽트
- 주로
입출력 장치에 의하여 발생 - 외부 인터럽트에서 중요한 두 가지 인터럽트에는
타이머 인터럽트와입출력 인터럽트가 있음. - 입출력 인터럽트
- 속도가 느린 입출력 장치가 입출력 준비가 완료되었음을 중앙처리장치에게 알리기 위한 인터럽트
- 타이머 인터럽트
- 타이머가 일정한 시간 간격으로 중앙처리장치에게 요청하는 인터럽트로, 컴퓨터는 이 사건으 기반으로 시간을 측정하고, 프로세스의 실행시간을 측정하여 프로세스를 스케줄하고, 주기적으로 수행할 작업을 실행시키는 등 아주 중요한 운영체제 작업을 수행합니다.
- 주로
명령어 사이클
인터럽트가 발생하면, 중앙처리장치는 프로그램의 실행을 중단하고, 현재의 상태를 저장한 후, 인터럽트 서비스 루틴을 실행합니다.
명령어 사이클의 실행 단계가 끝날 때마다, 중앙처리장치는 인터럽트 요청이 존재하는 지 검사하고, 존재한다면, 인터럽트 서비스를 위한 조치를 취합니다.
프로그램 카운터(PC)에 인터럽트 서비스 루틴의 시작 주소를 적재함으로서, 이후의 명령어 사이클에 따라 인터럽트 서비스 루틴이 실행됩니다.
인터럽트 서비스 루틴은 기억장치에 준비되어 있어야 하는데,
- 시스템 스택 설정
- 인터럽트 서비스 루틴을 기억 장치에 적재
- 인터럽트 벡터(인터럽트 서비스 루틴의 시작 주소 모음) 설정
위와 같은 준비 과정은 컴퓨터에 전원이 인가된 후 처음으로 시작하는 프로그램인 부트 로더(Boot Loader) 혹은 운영체제가 수행합니다.
소프트웨어 인터럽트
프로그래머가 직접 인터럽트를 호출할 수도 있는데요, 이러한 인터럽트를 소프트웨어 인터럽트라고 합니다.
일반적으로 운영체제의 기능을 호출하는 용도로 소프트웨어 인터럽트를 사용합니다.
입출력 장치는 운영체제가 관리하는데요, System.out.println(), readLine()의 내부에서는 입출력 장치를 직접 구동시키는 것이 아니라,
운영체제의 시스템 콜을 호출하여 입출력 장치를 사용하게 됩니다.
시스템 콜이 호출되면, 사용자 모드에서 커널 모드로 전환되고, 커널이 동작하게 됩니다.
인터럽트 가능 플래그
또한, 인터럽트 가능 플래그가 존재하는데요, 이 인터럽트 가능 플래그는 인터럽트 요청 신호와 AND 연산되기 때문에, 인터럽트 요청이 존재하더라도, 인터럽트 가능 플래그가 0이라면, 제어장치는 인터럽트가 발생하였음을 알지 못합니다.
이 인터럽트 가능 플래그는 임계 영역을 제어하는데에도 사용됩니다.
인터럽트는 프로그램의 실행 상태와 무관하게 아무때나 발생할 수 있기 때문에,
인터럽트가 발생하면 프로그램의 실행이 중단되고, 인터럽트 처리가 끝난 후에 프로그램이 다시 실행됩니다.
하지만, 임계영역의 경우 실행이 중단되었다가 다시 시작되면 문제가 발생할 수 있기 때문에, 임계영역 진입 전에 인터럽트 가능 플래그를 0으로 설정하고, 임계영역을 수행한 후에 인터럽트 가능 플래그를 1로 설정하여, 임계영역에서 인터럽트가 발생하는 것을 방지할 수 있습니다.
부동 소수점 표현
부동소수점 표현은 부호, 가수, 기수, 지수로 구분됩니다.
가수에 포함된 소수점의 위치를 변경하면서 지수를 조정할 수 있기 때문에, 부동소수점 표현이라고 합니다.
컴퓨터의 2진수 부동 표현에서는 기수를 표현하지 않고, 부호, 가수, 지수만으로 실수를 표현합니다.
제한된 비트 크기에 대하여 가능한 큰 수와 정밀한 수를 표현할 수 있도록, 컴퓨터는 가수와 지수에 대해 각각 다른 정규화 방식을 사용합니다.
가수 정규화
임의의 실수가 아래와 같을 때,
여기서 b는 0또는 1입니다.
- 가수를 1.bbb..b 형식으로 표현할 수 있도록 지수를 조정한다.
- 가수를 표현할 때, 1.을 표현하지 않고 bbb...b만 표현한다.
- 1.은 항상 존재하므로, 저장할 필요가 없기 때문
- 단, 수가 0인 경우, 가수를 모두 0으로 채운다.
지수 정규화
지수를 부호 있는 수로 표현한다면, 부호 비트를 고려한 비교기가 필요합니다.
따라서 부동소수점 표준 방법에서는 지수를 부호 없는 수로 표현하는 방법을 채택하고 있고, 이 방법을 바이어스 지수라고 합니다.
지수를 8비트로 표현하는 경우, 실제 지수 값에 127을 더하여 부호 없는 수로 표시합니다. (11비트인 경우, 바이어스는 1023)
따라서 지수로 표현된 2진수에서 127을 빼야 실제 지수입니다.
단정도, 배정도 표현방식
IEEE 754 형식 중에서는 단정도 표현 방식과 배정도 표현 방식을 가장 많이 사용합니다.
단정도 표현 방식은 4바이트이고, 부호부에 1비트, 지수부에 8비트, 가수부에 23비트를 사용합니다.
배정도 표현 방식은 8바이트이고, 부호부에 1비트, 지수부에 11비트, 가수부에 52비트를 사용합니다.
부동 소수점 표현의 오차
아래 코드는 0.1을 10번 더하는 코드인데요, 실행 결과를 보시면 1이 아닌 값이 출력됩니다.
왜 이런 오차가 발생할까요?
실수 0.1을 부동 소수점 표현으로 나타내면, 아래와 같습니다.
0.001100110011...처럼 0011이 계속 반복되는 겁니다.
하지만, 컴퓨터의 부동소수점 표현에서는 가수에 제한된 비트를 사용하기 때문에 모두 담을 수 없습니다.
따라서 0.1이 작은 값이라고 하더라도 정확하게 메모리에 저장할 수 없습니다.
하지만, 배정도 표현 방식은 단정도 표현방식보다 가수, 지수 표현에 더 많은 비트를 사용하기 때문에
단정도 표현 방식보다 실수를 표현하는 범위도 확장되고, 실수를 표현하는 정확성도 높아집니다.
그럼 왜 0.1을 10번 더했고, 제한된 비트만 메모리에 저장했는데 왜 1보다 작은 값이 아니라 더 큰 값이 출력된 걸까요?
그건, 가수의 잘려서 저장할 수 없는 부분이 반올림 되었기 때문입니다.
질문
- 임계영역 안의 프로세스 또는 스레드는 스케줄링 되지 않는 것인가?
- 그렇다면, 영원히 종료되지 않는 임계영역에 진입하기 전에 인터럽트 가능 플래그를 0으로 설정하면 해당 임계영역을 실행하는 코어는 반환되지 않는 것인가?
- 프로세스 또는 스레드를 스케줄링할 때 인터럽트가 사용되는가?
- 왜 int형의 연산이 더 빠른가? - word와 연관된 듯
'스터디' 카테고리의 다른 글
데코레이터 패턴(Decorator Pattern)이란? (0) | 2023.04.04 |
---|---|
22.11.15 SSAFY 스터디 CS 발표 - 디자인 패턴 (전략 패턴) (0) | 2022.12.01 |
22.11.8 SSAFY 스터디 CS 발표 - 디자인 패턴 (템플릿 메서드 패턴) (0) | 2022.11.07 |
22.10.11 SSAFY 스터디 CS 발표 - 컴퓨터 구조, 기억장치 (0) | 2022.10.11 |
22.08.18 SSAFY 스터디 CS 발표 - Java 어셈블리어 분석 (0) | 2022.08.16 |