devkobe24.com
AWS
Algorithm
2024
Architecture
Archive
AWS_archive
CPP_DS
CS_archive
DataStructure
Database
HackTheSwift
Java_archive
Leet-Code
MySQL
Network_archive
OS
Post
Read English Book
SQL_archive
Spring & Spring Boots
TIL
Web
CS
2024
Code Review
DB
Data Structure
Development tools and environments
Interview
Java
Java多識
Java
Network
2024
Others
SQL
2024
Server
Spring
Troubleshooting
Home
Contact
Copyright © 2024 |
Yankos
Home
>
Archive
> CS_archive
Now Loading ...
CS_archive
💾 [CS] 다양한 입출력 방법
1️⃣ 다양한 입출력 방법. 가장 보편적인 입출력 방법인 프로그램 입출력과 인터럽트 기반 입출력, DMA 입출력에 대해 알아보겠습니다. 1️⃣ 다양한 입출력 방법. 입출력 작업을 수행시 CPU와 장치 컨트롤러가 정보를 주고받아야 합니다. 여기에는 크게 세 가지 방법이 있습니다. 프로그램 입출력. 인터럽트 기반 입출력. DMA 입출력. 2️⃣ 프로그램 입출력 프로그램 입출력(programmed I/O) 은 기본적으로 프로그램 속 명령어로 입출력 장치를 제어하는 방법입니다. CPU가 프로그램 속 명령어를 실행하는 과정에서 입출력 명령어를 만나면 CPU는 입출력장치에 연결된 장치 컨트롤러와 상호작용하며 입출력 작업을 수행합니다. 메모리에 저장된 정보를 하드 디스크에 백업하는 상황을 생각해 봅시다. CPU는 대략 아래 과정으로 입출력 작업을 합니다. 1. ‘메모리에 저장된 정보를 하드 디스크에 백업한다’는 말은 ‘하드 디스크에 새로운 정보를 쓴다’는 말과 같습니다. 우선 CPU는 하드 디스크 컨트롤러의 제어 레지스터에 쓰기 명령을 보냅니다. 2. 하드 디스크 컨트롤러는 하드 디스크 상태를 확인합니다. 하드 디스크가 준비된 상태라면 하드 디스크 컨트롤러는 상태 레지스터에 준비되었다고 표시합니다. 3. (1) CPU는 상태 레지스터를 주기적으로 읽어 보며 하드 디스크의 준비 여부를 확인합니다. (2) 하드 디스크가 준비됐음을 CPU가 알게 되면 백업할 메모리의 정보를 데이터 레지스터에 씁니다. 아직 백업 작업(쓰기 작업)이 끝나지 않았다면 (1)번부터 반복하고, 쓰기가 끝났다면 작업을 종료합니다. 이렇듯 프로그램 입출력 방식에서의 입출력 작업은 CPU가 장치 컨트롤러의 레지스터 값을 읽고 씀으로써 이루어집니다. 3️⃣ 메모리 맵 입출력과 고립형 입출력. CPU 내부에 있는 레지스터들과 달리 CPU는 여러 장치 컨트롤러 속 레지스터들을 모두 알고 있기란 어렵습니다. 그렇다면 아래와 같은 명령어들은 어떻게 명령어로 표현되고, 메모리에 어떻게 저장 되어 있을까요? 프린터 컨트롤러의 상태 레지스터를 읽어라. 프린터 컨트롤러의 데이터 레지스터에 100을 써라. 키보드 컨트롤러의 상태 레지스터를 읽어라. 하드 디스크 컨트롤러의 데이어 레지스터에 ‘a’를 써라. 여기에는 크게 두 가지 방식이 있습니다. 바로 메모리 맵 입출력 과 고립형 입출력 입니다. 1️⃣ 메모리 맵 입출력. 메모리 맵 입출력(memory-mapped I/O) 은 메모리에 접근하기 위한 주소 공간과 입출력장치에 접근하기 위한 주소 공간을 하나의 주소 공간으로 간주하는 방법입니다. 가령 1,024 개의 주소를 표현할 수 있는 컴퓨터가 있을 때 1,024개 전부 메모리 주소를 표현하는 데 사용하지 않습니다. 512개는 메모리 주소를, 512개는 장치 컨트롤러의 레지스터를 표현하기 위해 사용합니다. 주소 공간 일부를 아래와 같이 약속했다고 가정해 봅시다. 516번지: 프린터 컨트롤러의 데이터 레지스터 517번지: 프린터 컨트롤러의 상태 레지스터 518번지: 하드 디스크 컨트롤러의 데이터 레지스터 519번지: 하드 디스크 컨트롤러의 상태 레지스터 그렇다면 CPU는 ‘517번지를 읽어 들여라’라는 명령어로 키보드 상태를 읽을 수 있습니다. 그리고 ‘518 번지에 a를 써라’ 라는 명령어로 하드 디스크 컨트롤러의 데이터 레지스터로 데이터를 보낼 수 있습니다. 이때 중요한 점은 메모리 맵 입출력 방식에서 CPU는 메모리의 주소들이나 장치 컨트롤러의 레지스터들이나 모두 똑같이 메모리 주소를 대하듯 하면 된다는 점입니다. 그래서 메모리에 접근하는 명령어와 입출력장치에 접근하는 명령어는 굳이 다를 필요가 없습니다. CPU가 ‘517번지를 읽어라’라는 명령어를 실행했을 때 517번지가 메모리상의 주소를 가리킨다면 CPU는 메모리 517번지에 저장된 정보를 읽어 들일 것이고, 517번지가 프린터 컽츠롤러의 상태 레지스터를 가리킨다면 CPU는 프린터의 상태를 확인할 수 있기 때문입니다. 2️⃣ 고립형 입출력. 고립형 입출력(isolated I/O) 은 메모리를 위한 주소 공간과 입출력장치를 위한 주소 공간을 분리하는 방법입니다. 가령 1,024개의 주소 공간을 가진 컴퓨터가 있다고 가정해 봅시다. 아래 그림처럼 제어 버스에 ‘메모리 읽기/쓰기’ 선 이외에 ‘입출력장치 읽기/쓰기’ 선이 따로 있다면 메모리에도 1,024 개의 주소 공간을 활용하고, 입출력장치도 1,024개의 주소 공간을 활용할 수 있습니다. CPU가 메모리 읽기/쓰기 선이 활성화되는 명령어를 실행할 때는 메모리에 접근하고, 입출력장치 읽기/쓰기 선이 활성화되는 명령어를 실행할 때는 장치 컨트롤러에 접근하기 때문입니다. 고립형 입출력 방식에서 CPU는 입출력장치에 접근하기 위해 메모리에 접근하는 명령어와는 다른(입출력 읽기/쓰기 선을 활성화시키는) 입출력 명령어를 사용합니다. 메모리에 접근하는 명령어와 입출력장치에 접근하는 명령어는 굳이 다를 필요가 없었던 메모리 맵 입출력과 대조적입니다. 메모리 맵 입출력 고립형 입출력 메모리와 입출력장치는 같은 주소 공간 사용 메모리와 입출력장치는 분리된 주소 공간 사용 메모리 주소 공간이 축소됨 메모리 주소 공간이 축소되지 않음 메모리와 입출력장치에 같은 명령어 사용 가능 입출력 전용 명령어 사용 2️⃣ 인터럽트 기반 입출력 인터럽트는 ‘CPU가 입출력장치에 처리할 내용을 명령하면 입출력장치가 명령어를 수행하는 동안 CPU는 다른 일을 할 수 있다’라고 했습니다. 또한 ‘입출력장치가 CPU에게 인터럽트 요청 신호를 보내면 CPU는 하던 일을 잠시 멈추고 해당 인터럽트를 처리하는 프로그램인 인터럽트 서비스 루틴을 실행한 뒤 다시 하던 일로 되돌아온다’라고 했습니다. 입출력장치에 의한 하드웨어 인터럽트는 정확히 말하자면 입출력장치가 아닌 장치 컨트롤러에 의해 발생합니다. CPU는 장치 컨트롤러에 입출력 작업을 명령하고, 장치 컨트롤러가 입출력장치를 제어하며 입출력을 수행하는 동안 CPU는 다른 일을 할 수 있습니다. 장치 컨트롤러가 입출력 작업을 끝낸 뒤 CPU에게 인터럽트 요청 신호를 보내면 CPU는 하던 일을 잠시 백업하고 인터럽트 서비스 루틴을 실행합니다. 이렇게 인터럽트를 기반으로 하는 입출력을 인터럽트 기반 입출력(Interrupt-Drive I/O) 이라고 합니다. 폴링 인터럽트와 자주 비교되는 개념 중 폴링(polling) 이라는 개념이 있습니다. ‘CPU는 주기적으로 장치 컨트롤러의 상태 레지스터를 확인하며 입출력장치의 상태를 확인한다’ 라고 했습니다. 이처럼 폴링이란 입출력장치의 상태는 어떤지, 처리할 데이터가 있는지를 주기적으로 확인하는 방식입니다. 폴링 방식은 당연하게도 인터럽트 방식보다 CPU의 부담이 더 큽니다. 인터럽트를 활용하면 CPU가 인터럽트 요청을 받을 때까지 온전히 다른 일에 집중할 수 있기 때문입니다. 이번에는 조금 더 일반적인 입출력장치가 많을 때를 생각해 봅시다. 예를 들어 키보드, 모니터, 스피커, 마우스를 사용하고 있다고 생각해봅시다. 이것은 컴퓨터 속 CPU가 동시다발적으로 발생하는 키보드, 마우스, 모니터, 스피커 인터럽트를 모두 처리해야 한다는 말이기도 합니다. 어떻게 여러 입출력장치에서 인터럽트가 동시에 발생한 경우에는 인터럽트들을 어떻게 처리해야 할까요? 간단하게 생각하면 인터럽트가 발생한 순서대로 인터럽트를 처리하는 방법이 있습니다. 가령 인터럽트 A를 처리하는 도중 발생한 또 다른 인터럽트 B의 요청을 받아들이지 않고, 인터럽트 A 서비스 루틴이 끝나면 그때 비로소 인터럽트 B 서비스 루틴을 실행하는 것이죠. CPU가 플래그 레지스터 속 인터럽트 비트를 비활성화한 태 인터럽트를 처리하는 경우 다른 입출력장치에 의한 하드웨어 인터럽트를 받아들이지 않기 때문에 CPU는 이렇듯 순차적으로 하드웨어 인터럽트를 처리하게 됩니다. 하지만 현실적으로 모든 인터럽트를 전부 순차적으로만 해결할 수 없습니다. 인터럽트 중에서도 더 빨리 처리해야 하는 인터럽트가 있기 때문입니다. 즉, CPU는 인터럽트 간에 우선순위를 고려하여 우선순위가 높은 인터럽트 순으로 여러 인터럽트를 처리할 수 있습니다. 예를 들어 아래 그림과 같이 현재 CPU가 인터럽트 A를 처리하는 도중에 또 다른 인터럽트 B가 발생했다고 가정해 봅시다. 만약 지금 처리 중인 인터럽트 A보다 B의 우선순위가 낮다면 CPU는 A를 모두 처리한 뒤 B를 처리합니다. 하지만 인터럽트 A보다 B의 우선순위가 높다면 CPU는 인터럽트 A의 실행을 잠시 멈추고 인터럽터 B를 처리한 뒤 다시 A를 처리합니다. 플래그 레지스터 속 인터럽트 비트가 활성화되어 있는 경우, 혹은 인터럽트 비트를 비활성화해도 무시할 수 없는 인터럽트인 NMI(Non-Mashable Interrupt) 가 발생한 경우 CPU는 이렇게 우선순위가 높은 인터럽트부터 처리합니다. 우선순위를 반영하여 다중 인터럽트를 처리하는 방법에는 여러 가지가 있지만, 많은 컴퓨터에서는 프로그래머블 인터럽트 컨트롤러(PIC: Programmable Interrupt Controller) 라는 하드웨어를 사용합니다. PIC 는 여러 장치 컨트롤러에 연결되어 장치 컨트롤러에서 보낸 하드웨어 인터럽트 요청들의 우선 순위를 판별한 뒤 CPU에 지금 처리해야 할 하드웨어 인터럽트는 무엇인지를 알려주는 장치입니다. PIC에는 여러 핀이 있는데, 각 핀에는 CPU에 하드웨어 인터럽트 요청을 보낼 수 있는 약속된 하드웨어가 연결되어 있습니다. 가령 첫 번째 핀은 타이머 인터럽트를 받아들이는 핀, 두 번째 핀은 키보드 인터럽트를 받아들이는 핀… 이런 식으로 말이죠. PIC에 연결된 장치 컨트롤러들이 동시에 하드웨어 인터럽트 요청을 보내면 PIC는 이들의 우선순위를 판단하여 CPU에 가장 먼저 처리할 인터럽트를 알려줍니다. PIC의 다중 인터럽트 처리 과정을 조금 더 정확히 알아봅시다. 1. PIC가 장치 컨트롤러에서 인터럽트 요청신호(들) 를 받아들입니다. 2. PIC는 인터럽트 우선순위를 판단한 뒤 CPU에 처리해야 할 인터럽트 요청 신호를 보냅니다. 3. CPU는 PIC에 인터럽트 확인 신호를 보냅니다. 4. PIC는 데이터 버스를 통해 CPU에 인터럽트 벡터를 보냅니다. 5. CPU는 인터럽트 벡터를 통해 인터럽트 요청의 주체를 알게 되고, 해당 장치의 인터럽트 서비스 루틴을 실행합니다. 일반적으로 더 많고 복잡한 장치들의 인터럽트를 관리하기 위해 아래와 같이 PIC를 두 개 이상 계층적으로 구성합니다. 이렇게 PIC를 여러 개 사용하면 훨씬 더 많은 하드웨어 인터럽트를 관리할 수 있습니다. 참고로 PIC가 무시할 수 없는 인터럽트인 NMI까지 우선순위를 판별하지 않습니다. NMI는 우선 순위가 가장 높아 우선순위 판별이 불필요하기 때문입니다. PIC가 우선순위를 조정해주는 인터럽트는 인터럽트 비트를 통해 막을 수 있는 하드웨어 인터럽트입니다. 3️⃣ DMA 입출력 앞에 설명한 프로그램 기반 입출력과 인터럽트 기반 입출력의 공통점이 있다면 입출력장치와 메모리 간의 데이터 이동은 CPU가 주도하고, 이동하는 데이터도 반드시 CPU를 거친다는 점입니다. 예를 들어 입출력장치 데이터를 메모리에 저장하는 경우 CPU는 (1) 장치 컨트롤러에서 입출력장치 데이터를 하나씩 읽어 레지스터에 적재하고, (2) 적재한 데이터를 메모리에 저장합니다. 메모리 속 데이터를 입출력장치에 내보내는 경우도 마찬가지입니다. CPU는 (1) 메모리에서 데이터를 하나씩 읽어 레지스터에 적재하고, (2) 적재한 데이터를 하나씩 입출력장치에 내보냅니다. 입출력장치와 메모리 사이에 전송되는 모든 데이터가 반드시 CPU를 거쳐야 한다면 가뜩이나 바쁜 CPU는 입출력장치를 위한 연산 때문에 시간을 뺏기게 됩니다. 하드 디스크 백업과 같이 대용량 데이터를 옮길 때는 CPU 부담이 더욱 커집니다. 그래서 입출력장치와 메모리가 CPU를 거치지 않고도 상호작용할 수 있는 입출력 방식인 DMA(Direct Memory Access) 가 등장하였습니다. DMA는 이름 그대로 직접 메모리에 접근할 수 있는 입출력 기능입니다. DMA 입출력을 하기 위해서는 시스템 버스에 연결된 DMA 컨트롤러 라는 하드웨어가 필요합니다. DMA 입출력 과정. 일반적으로 DMA 입출력은 아래와 같이 이루어집니다. (1) CPU는 DMA 컨트롤러에 입출력장치의 주소, 수행할 연산(읽기/쓰기), 읽거나 쓸 메모리 주소 등과 같은 정보로 입출력 작업을 명령합니다. (2) DMA 컨트롤러는 CPU 대신 장치 컨트롤러와 상호작용하며 입출력 작업을 수행합니다. 이때 DMA 컨트롤러는 필요한 경우 메모리에 직접 접근하여 정보를 읽거나 씁니다. (3) 입출력 작업이 끝나면 DMA 컨트롤러는 CPU에 인터럽트를 걸어 작업이 끝났음을 알립니다. 이번에는 메모리 내의 정보를 하드 디스크에 백업하는 작업이 DMA 입출력으로 어떻게 이루어지는지도 알아봅시다. 1. CPU는 DMA 컨트롤러에 하드 디스크 주소, 수행할 연산(쓰기), 백업할 내용이 저장된 메모리 주소 등의 정보와 함께 입출력 작업을 명령합니다. 2. (1) DMA 컨트롤러는 CPU를 거치지 않고 메모리와 직접 상호작용하며 백업할 정보를 읽어오고, (2) 이를 하드 디스크의 장치 컨트롤러에 내보냅니다. 3. 백업이 끝나면 DMA 컨트롤러는 CPU에게 인터럽트를 걸어 작업이 끝났음을 알립니다. 위 입출력 과정을 보면 알 수 있듯 입출력장치와 메모리 사이에 주고받을 데이터는 CPU를 거치지 않습니다. CPU는 DMA 컨트롤러에게 입출력 작업 명령을 내리고, 인터럽트만 받으면 되기 때문에 작업 부담을 훨씬 줄일 수 있습니다. 다시 말해 CPU는 오직 입출력의 시작과 끝에만 관여하면 됩니다. 그런데 여기서 생각해 봐야 할 문제가 있습니다. DMA 컨트롤러는 시스템 버스로 메모리에 직접 접근이 가능하지만, 시스템 버스는 동시 사용이 불가능합니다. 시트템 버스는 공용 자원이기 때문입니다. CPU가 시스템 버스를 사용할 떄 DMA 컨트롤러는 시스템 버스를 사용할 수 없고, DMA 컨트롤러가 시스템 버스를 사용할 때는 CPU가 시스템 버스를 사용할 수 없습니다. 그래서 DMA 컨트롤러는 CPU가 시스템 버스를 이용하지 않을 때마다 조금씩 시스템 버스를 이용하거나, CPU가 일시적으로 시스템 버스를 이용하지 않도록 허락을 구하고 시스템 버스를 집중적으로 이용합니다. CPU 입장에서는 마치 버스에 접근하는 주기를 도둑 맞는 기분이 들 겁니다. 그래서 이러한 DMA의 시스템 버스 이용을 사이클 스틸링(cycle stealing) 이라고 부릅니다. 입출력 버스 마지막으로 DMA 컨트롤러와 장치 컨트롤러의 연결 방식과 입출력 버스에 대해 알아봅시다. CPU, 메모리, DMA 컨트롤러, 장치 컨트롤러가 모두 같은 버스를 공유하는 구성에서는 DMA를 위해 한 번 메모리에 접근할 때마다 시스템 버스를 두 번 사용하게 되는 부작용이 있습니다. 예로 들었던 메모리 내 정보를 하드 디스크로 백업하는 상황을 다시 생각해 봅시다. 이 경우 (1) 메모리에서 DMA 컨트롤러로 데이터를 가져오기 위해 시스템 버스를 한 번 사용하고, (2) DMA 컨트롤러의 데이터를 장치 컨트롤러로 옮기기 위해 시스템 버스를 또 한 번 사용합니다. DMA를 위해 시스템 버스를 너무 자주 사용하면 그만큼 CPU가 시스템 버스를 이용하지 못합니다. 이 문제는 DMA 컨트롤러와 장치 컨트롤러들을 입출력 버스(input/output bus) 라는 별도의 버스에 연결하여 해결할 수 있습니다. 아래 그림과 같이 장치 컨트롤러들이 시스템 버스가 아닌 입출력 버스로 DMA 컨트롤러에 연결된다면 DMA 컨트롤러와 장치 컨트롤러가 서로 데이터를 전송할 때는 시스템 버스를 이용할 필요가 없으므로 시스템 버스의 사용 빈도를 줄일 수 있습니다. 현대 대부분 컴퓨터에는 입출력 버스가 있습니다. 다시 말해 대부분의 입출력장치(장치 컨트롤러)는 시스템 버스가 아닌 입출력 버스와 연결됩니다. 이런 점에서 볼 때 입출력 버스는 입출력장치를 컴퓨터 내부와 연결 짓는 통로라고도 볼 수 있습니다. 입출력 버스에는 PIC(Peripheral Component Interconnect) 버스, PCI Express(PCIe) 버스 등 여러 종류가 있습니다. 다음 그림은 여러 입출력 장치들을 PCIe 버스와 연결해 주는 통로인 PCIe 슬롯 입니다. 사용하는 거의 모든 입출력장치들은 이렇게 입출력 버스와 연결되는 통로를 통해 시스템 버스를 타고 CPU와 정보를 주고받습니다. 🙋♂️ 마무리. 키워드로 정리하는 핵심 포인트 프로그램 입출력은 프로그램 속 명령어로 입출력 작업을 하는 방식입니다. 메모리 맵 입출력은 메모리에 접근하기 위한 주소 공간과 입출력장치에 접근하기 위한 주소 공간을 하나의 주소 공간으로 간주하는 입출력 방식입니다. 고립형 입출력은 메모리에 접근하기 위한 주소 공간과 입출력장치에 접근하기 위한 주소 공간을 별도로 분리하는 입출력 방식입니다. 인터럽트 기반 입출력은 인터럽트로써 입출력을 수행하는 방법입니다. DMA 입출력은 CPU를 거치지 않고 메모리와 입출력장치 간의 데이터를 주고받는 입출력 방식입니다. 입출력 버스는 입출력장치와 컴퓨터 내부를 연결 짓는 톨로로, 입출력 작업 과정에서 시스템 버스 사용 횟수를 줄여줍니다.
Archive
· 2024-05-29
💾 [CS] 장치 컨트롤러와 장치 드라이버
1️⃣ 장치 컨트롤러와 장치 드라이버. 1️⃣ 장치 컨트롤러. 입출력장치는 CPU, 메모리보다 다루기가 더 까다롭습니다. 여기에는 크게 두 가지 이유가 있습니다. 첫째, 입출력장치에는 종류가 너무나도 많습니다. 키보드, 모니터, USB 메모리, CD-ROM, SSD, 마우스, 스피커, 프린터 등 매우 많습니다. 장치가 이렇게 다양하면 자연스레 장치마다 속도, 데이터 전송 형식 등도 다양합니다. 따라서 다양한 입출력장치와 정보를 주고받는 방식을 규격화하기가 어렵습니다. 이는 마치 CPU와 메모리는 한국어를 사용하는데, 프린터는 영어, 스피커는 일본어, 모니터는 중국어를 사용하는 상황과 같습니다. 둘째, 일반적으로 CPU와 메모리의 데이터 전송률은 높지만 입출력장치의 데이터 전송률은 낮습니다. 여기서 전송률(transfer rate) 이란 데이터를 얼마나 빨리 교환할 수 있는지를 나타내는 지표입니다. CPU와 메모리처럼 전송률이 높은 장치는 1초에도 수많은 데이터를 주고받을 수 있지만, 키보드나 마우스와 같은 상대적으로 전송률이 낮은 장치는 같은 시간 동안 데이터를 조금씩만 주고받을 수 있습니다. 전송률의 차이는 CPU와 메모리, 입출력 장치간의 통신을 어렵게 합니다. 장치 컨트롤러(Derive Controller) 물론 어떤 입출력장치는 CPU나 메모리보다 전송률이 높은 경우도 있습니다. 하지만 결과적으로 CPU나 메모리와 전송률이 비슷하지 않기 때문에 같은 어려움을 겪게 됩니다. 이와 같은 이유로 입출력장치는 컴퓨터에 직접 연결되지 않고 장치 컨트롤러(Drive Controller) 라는 하드웨어를 통해 연결됩니다. 장치 컨트롤러는 입출력 제어기(I/O Controller), 입출력 모듈(I/O Module) 등으로 다양하게 불립니다. 모든 입출력장치는 각자의 장치 컨트롤러를 통해 컴퓨터 내부와 정보를 주고받고, 장치 컨트롤러는 하나 이상의 입출력장치와 연결되어 있습니다. 예를 들어 하드 디스크 또한 장치 컨트롤러가 있습니다. 2️⃣ 장치 컨트롤러의 역할. 장치 컨트롤러는 대표적으로 다음과 같은 역할을 통해 앞에서 언급한 문제들을 해결합니다. CPU와 입풀력장치 간의 통신 중개 오류 검출 데이터 버퍼링 입풀력장치 종류가 많이 정보 규격롸가 어려웠던 문제는 장치 컨트롤러가 일종의 번역가 역할을 함으로써 해결할 수 있습니다. 그 과정에서 장치 컨트롤러는 자신과 연결된 입출력장치에 문제는 없는지 오류를 검출하기도 합니다. 장치 컨트롤러의 세 번째 기능인 데이터 버퍼링은 무엇일까요? 버퍼링(buffering) 이란 전송률이 높은 장치와 낮은 장치 사이에 주고받는 데이터를 버퍼(buffer) 라는 임시 저장 공간에 저장하여 전송률을 비슷하게 맞추는 방법입니다. 쉽게 말해 버퍼링은 ‘버퍼에 데이터를 조금씩 모았다가 한꺼번에 내보내거나, 데이터를 한 번에 많이 받아 조금씩 내보내는 방법’이라고 보면 됩니다. 즉, 장치 컨트롤러는 일반적으로 전송률이 높은 CPU와 일반적으로 전송률이 낮은 입출력장치와의 전송률 차이를 데이터 버퍼일으로 완화합니다. 3️⃣ 장치 컨트롤러의 내부 구조. 이번에는 장치 컨트롤러의 간략화된 내부 구조를 살펴봅시다. 장치 컨트롤러 내부는 아래와 같습니다. 실제로는 이보다 복잡하지만, 기억해야 하는 것은 데이터 레지스터(data register) 와 상태 레지스터(status register), 제어 레지스터(control register) 세 가지 입니다. 데이터 레지스터는 CPU와 입출력장치 사이에 주고받을 데이터가 담기는 레지스터입니다. 앞서 장치 컨트롤러는 데이터 버퍼링으로 전송률 차이를 완화한다고 했습니다. 데이터 레지스터가 그 버퍼 역할을 합니다. 최근 주고받은 데이터가 많은 입출력장치에서는 레지스터 대신 RAM을 사용하기도 합니다. 상태 레지스터에는 입출력장치가 입출력 작업을 할 준비가 되었는지, 입출력 작업이 완료되었는지, 입출력장치에 오류는 없는지 등의 상태 정보가 저장됩니다. 제어 레지스터는 입출력장치가 수행할 내용에 대한 제어 정보와 명령을 저장합니다. 이 레지스터들에 담긴 값들은 버스를 타고 CPU나 다른 입출력장치로 전달되기도 하고, 장치 컨트롤러에 연결된 입출력장치로 전달됩니다. 2️⃣ 장치 드라이버 새로운 장치를 컴퓨터에 연결하려면 장치 드라이버를 설치해야 합니다. 1️⃣ 장치 드라이버 장치 드라이버(device driver) 란 장치 컨트롤러의 동작을 감지하고 제어함으로써 장치 컨트롤러가 컴퓨터 내부와 정보를 주고받을 수 있게 하는 프로그램입니다. 프로그램이기에 당연히 실행 과정에서 메모리에 저장됩니다. 장치 컨트롤러가 입출력장치를 연결하기 위한 하드웨어적인 통로라면, 장치 드라이버는 입출력장치를 연결하기 위한 소프트웨어적인 통로입니다. 컴퓨터가 연결된 장치의 드라이버를 인식하고 실행할 수 있다면 그 장치는 어떤 회사에서 만들어진 제품이든, 생김새가 어떻든 상관없이 컴퓨터 내부와 정보를 주고받을 수 있습니다. 반대로 장치 드라이버를 인식하거나 실행할 수 없는 상태라면 그 장치는 컴퓨터 내부와 정보를 주고받을 수 없습니다. 장치 드라이버를 인식하고 실행하는 주체 장치 드라이버를 인식하고 실행하는 주체는 정확히 말하자면 윈도우, macOS와 같은 운영체제입니다. 즉, 운영체제가 장치드라이버를 인식하고 실행할 수 있다면 그 장치는 컴퓨터 내부와 정보를 주고받을 수 있습니다. 장치 드라이버는 운영체제가 기본으로 제공하는 것도 있지만, 장치 제작자가 따로 제공하기도 합니다. 물론 장치 제작자가 장치 드라이버를 따로 제공하는 경우 입출력장치는 해당 드라이버를 직접 설치해야만 사용이 가능합니다. 3️⃣ 키워드로 정리하는 핵심 포인트 입출력장치는 장치 컨트롤러 를 통해 컴퓨터 내부와 정보를 주고받습니다. 장치 드라이버는 장치 컨트롤러가 컴퓨터 내부와 정보를 주고받을 수 있게 하는 프로그램입니다.
Archive
· 2024-05-27
💾 [CS] 다양한 보조기억장치
다양한 보조기억장치 보조기억장치에는 다양한 종류가 있습니다. 그중 가장 태중적인 보조기억장치는 하드 디스크와 플래시 메모리입니다. 우리가 흔히 사용하는 USB 메모리, SD 카드, SSD 같은 저장 장치를 말합니다. 하드 디스크(HDD: Hard Disk Drive) 하드 디스크(HDD: Hard Disk Drive) 는 자기적인 방식으로 데이터를 저장하는 보조기억장치입니다. 이 때문에 하드 디스크를 자기 디스크(magnetic disk) 의 일종으로 지칭하기도 합니다. 대용향 저장 장치가 필요한 작업이나 서버실에 자주 출입하는 작업을 한다면 하드 디스크를 자주 접하게 될 겁니다. 하드 디스크의 생김새. 다음 그림이 바로 하드 디스크입니다. 우리가 아는 CD나 옛날 음향 장치는 LP가 떠오를 겁니다. 실제로도 하드 디스크는 CD나 LP와 비슷하게 동작합니다. 동그란 원판에 데이터를 저장하고, 그것을 회전시켜 뾰족한 리더기로 데이터를 읽는 점에서 비슷합니다. 하드 디스크에서 실질적으로 데이터가 저장되는 곳은 아래 그림 속 동그란 원판입니다. 이를 플래터(platter) 라고 합니다. 하드 디스크는 자기적인 방식으로 데이터를 저장합니다. 플래터는 자기 물질로 덮여 있어 수많은 N극과 S극을 저장합니다. N극과 S극은 0과 1의 역할을 수행합니다. 그 플래터를 회전시키는 구성 요소를 스핀들(spindle) 이라고 합니다. 스핀들이 플래터를 돌리는 속도는 분당 회전수를 나타내는 RPM(Revolution Per Minute) 이라는 단위로 표현됩니다. 가령 RPM이 15,000인 하드 디스크는 1분에 15,000바퀴를 회전하는 하드 디스크입니다. 플래터를 대상으로 데이터를 읽고 쓰는 구성 요소는 헤드(head) 입니다. 헤드는 플래터 위에서 미세하게 떠 있는 채로 데이터를 읽고 쓰는, 마치 바늘같이 생긴 부품입니다. 그리고 헤드는 원하는 위치로 헤드를 이동시키는 디스크 암(disk arm) 에 부착되어 있습니다. CD나 LP에 비해 하드 디스크는 훨씬 더 많은 양의 데이터를 저장해야 하므로 일반적인 여러 겹의 플래터로 이루어져 있고 플래터 양면을 모두 사용할 수 있습니다. 양면 플래터를 사용하면 위아래로 플러터당 두 개의 헤드가 사용됩니다. 이 때 일반적으로 모든 헤드는 디스크 암에 부착되어 다같이 이동합니다. 데이터가 저장되는 방법. 그럼 이제 플래터에 데이터가 어떻게 저장되는지 알아봅시다. 플래터는 트랙(track) 과 섹터(sector) 라는 단위로 데이터를 저장합니다. 아래 그림터럼 플래터를 여러 동심원으로 나누었을 때 그중 하나의 원을 트랙이라고 부릅니다. 그리고 트랙은 마치 피자처럼 여러 조각으로 나우어지는데, 이 한 조각을 섹터라고 부릅니다. 섹터는 하드 디스크의 가장 작은 전송 단위입니다. 하나의 섹터는 일반적으로 512바이트 정도의 크기를 가지고 있지만, 정확한 크기는 하드 디스크에 따라 차이가 있습니다. 일부 하드 디스크의 섹터 크기는 4,096바이트에 이르기도 합니다. 여러 겹의 플래터가 사용 될 수 있습니다. 이때 여러 겹의 플래터 상에서 같은 트랙이 위치한 곳을 모아 연결한 논리적 단위를 실린더(cyilnder) 라고 부릅니다. 쉽게 말해 한 플래터를 동심원으로 나눈 공간은 트랙, 같은 트랙끼리 연결한 원통 모양의 공간은 실린더입니다. 연속된 정보는 보통 한 실린더에 기록됩니다. 예를 들어 두 개의 플래터를 사용하는 하드 디스크에서 네 개 섹터에 걸쳐 데이터를 저장할 때는 첫 번째 플래터 윗면, 뒷면과 두 번째 플래터 윗면, 뒷면에 데이터를 저장합니다. 연속된 정보를 하나의 실린더에 기록하는 이유는 디스크 암을 움직이지 않고도 바로 데이터에 접근할 수 있기 때문입니다. 데이터에 접근하는 과정 데이터가 하드 디스크의 섹터, 트랙, 실린더에 저장된다는 것을 알았다면 저장된 데이터에 접근하는 과정을 생각해 봅시다. 하드 디스크가 저장된 데이터에 접근하는 시간은 크게 탐색 시간, 회전 지연, 전송 시간 으로 나뉩니다. 탐색 시간(seek time) : 접근하려는 데이터가 저장된 트랙까지 헤드를 이동시키는 시간을 의미합니다. 회전 지연(rotational latency) : 헤드가 있는 곳으로 플래터를 회전시키는 시간을 의미합니다. 전송 시간(transfer time) : 하드 디스크와 컴퓨터 간에 데이터를 전송하는 시간을 의미합니다. 위 시간들은 별것 아닌 것 같아도 성능에 큰 영향을 끼치는 시간입니다. 일례로 구글의 AI를 주도하고 있는 제프 딘은 과거 ‘프로그래머가 꼭 알아야 할 컴퓨터 시간들’을 공개한 바 있는데, 일부를 발췌하면 다음과 같습니다. 물론 2011년에 자료가 공개된 이후 오늘날 하드 디스크 성능은 많이 향상되었지만, 하드 디스크에서 다량의 데이터를 탐색하고 읽어 들이는 시간은 생각보다 어마어마하다는 사실을 쉽게 짐작할 수 있습니다. 탐색 시간과 회전 지연을 단축시키기 위해서는 플래터를 빨리 돌려 RPM을 높이는 것도 중요하지만, 참조 지역성, 즉 접근하려는 데이터가 플래터 혹은 헤드를 조금만 옮겨도 접근할 수 있는 곳에 위치해 있는 것도 중요합니다. 플래시 메모리 하드 디스크는 최근에 많이 사용하는 보조기억장치이지만, 플래시 메모리(flush memory) 기반의 보조기억장치 또한 많이 사용합니다. 우리가 흔히 사용하는 USB 메모리, SD 카드, SSD가 모두 플래시 메모리 가반의 보조기억장치입니다. 플래시 메모리 내부. 다음 그림에서 붉은 박스로 표기한 부분이 플래시 메모리입니다. 플래시 메모리는 전기적으로 데이터를 읽고 쓸 수 있는 반도체 기반의 저장 장치입니다. 사실 플래시 메모리는 보조기억장치 범주에만 속한다기보다는 다양한 곳에서 널리 사용하는 저장 장치로 보는 것이 옳습니다. 주기억장치 중 하나인 ROM에도 사용되고, 우리가 일상적으로 접하는 거의 모든 전자 제품안에 플래시 메모리가 내장되어 있다고 봐도 무방합니다. 두 종류의 플래시 메모리 플래시 메모리에는 크래 NAND 플래시 메모리 와 NOR 플래시 메모리 가 있습니다. NAND 플래시와 NOR 플래시는 각각 NAND 연산을 수행하는 회로(NAND 게이트)와 NOR 연산을 수행하는 회로(NOR 게이트)를 기반으로 만들어진 메모리를 뜻합니다. 이 둘 중 대용량 저장 장치로 많이 사용되는 플래시 메모리는 NAND 플래시 메모리 입니다. 플래시 메모리에는 셀(cell) 이라는 단위가 있습니다. 셀이란 플래시 메모리에서 데이터를 저장하는 가장 작은 단위입니다. 이 셀이 모이고 모여 MB, GB, TB 용량을 갖는 저장 장치가 되는 것입니다. 이 때 하나의 셀에 몇 비트를 저장할 수 있느냐에 따라 플래시 메모리 종류가 나뉩니다. 한 셀에 1비트를 저장할 수 있는 플래시 메모리를 SLC(Single Level Cell) 타입, 한 셀에 2비트를 저장할 수 있는 플래시 메모리를 MLC(Multiple Level Cell) 타입, 한 셀에 4비트를 저장할 수 있는 플래시 메모리를 TLC(Triple-Level Cell) 타입이라고 합니다. 큰 차이가 아닌 것처럼 보여도 이는 플래시 메모리의 수명, 속도, 가격에 큰 영향을 끼칩니다. 참고로 한 셀에 4비트를 저장할 수 있는 QLC 타입도 있습니다. 플래시 메모리도 수명이 있나요? 플래시 메모리에는 수명이 있습니다. 플래시 메모리 뿐만 아니라 하드 디스크 또한 수명이 있습니다. 우리가 사용하는 USB 메모리, SSD, SD 카드는 수명이 다하면 더 이상 저장 장치로써 사용이 불가능합니다. 종이에 연필로 쓰고 지우개로 지우고를 반복하다 보면 결국 종이가 찢어지는 것처럼 한 셀에 일정 횟수 이상 데이터를 쓰고 지우면 그 셀은 더 이상 데이터를 저장할 수 없기 때문입니다. SLC, MLC, TCL 타입의 특징과 차이점. 사람 한 명을 비트, 셀을 집에 비유하면 SLC 타입은 한 집에 한 명, MLC 타입은 한 집에 두 명, TLC 타입은 세 명이 사는 구조로 비유할 수 있습니다. SLC 타입 SLC 타입은 아래 그림과 같이 한 셀로 두 개의 정보를 표현할 수 있습니다. 홀로 거주하는 집에 제약 없이 출입이 가능하듯 SLC 타입은 MLC나 TLC 타입에 비해 비트의 빠른 입출력이 가능합니다. 수명도 MLC나 TLC 타입보다 길어서 수만에서 수십만 번 가까이 데이터를 쓰고 지우고를 반복할 수 있습니다. 하지만 SLC 타입은 용량 대비 가격이 높습니다. 이는 마치 혼자서 살면 감당해야 할 주거 비용이 커지는 것과 같습니다. 그렇기에 보통 기업에서 데이터를 읽고 쓰기가 매우 많이 반복되며 고성능의 빠른 저장 장치가 필요한 경우에 SLC 타입을 사용합니다. MLC 타입 MLC 타입은 다음 그림과 같이 한 셀로 네 개의 정보를 표현할 수 있습니다. SLC 타입보다 일반적으로 속도와 수명은 떨어지지만, 한 셀에 두 비트씩 저장할 수 있다는 점에서 MLC 타입은 SLC 타입도다 대용량화하기 유리합니다. 집의 개수가 같다면 한 집에 한 명씩 사는 것보다 한 집에 두 명씩 사는 것이 훨씬 더 많은 사람을 수용할 수 있는 것과 같은 이치입니다. 두 명이 한 집에서 주거 비용을 나눠 내면 혼자 감당해야 하는 주거 비용보다 저렴해지듯 MLC 타입은 SLC 타입보다 용량 대비 가격이 저렴합니다. 시중에서 사용되는 많은 플래시 메모리 저장 장치들이 MLC 타입(혹은 후술할 TLC 타입)으로 만들어집니다. TLC 타입 한 셀당 3비트씩 저장할 수 있는 TLC 타입은 한 셀로 여덟 개의 정보를 표현할 수 있습니다. 그렇기에 대용화 하기 유리합니다. 일반적으로 SLC나 MLC 타입보다 수명과 속도가 떨어지지만 용량 대비 가격도 저렴합니다. 정리. 정리하면, 같은 용량의 플래시 메모리 저장 장치라고 할지라도 셀의 타입에 따라 수명, 가격, 성능이 다릅니다. 썻다 지우기를 자주 반복해야 하는 경우 혹은 높은 성능을 원하는 경우에는 고가의 SLC 타입을 선택하는 것이 좋고, 저가의 대용량 저장 장치를 원한다면 TLC 타입, 그 중간을 원한다면 MLC 타입의 저장 장치를 선택하는 것이 좋습니다. 플래시 메모리의 셀보다 더 큰 단위. 이제 플래시 메모리의 가장 작은 단위인 셀보다 더 큰 단위를 알아봅시다. 셀들이 모여 만들어진 단위를 페이지(page), 그리고 페이지가 모여 만들어진 단위를 블록(block) 이라고 합니다. 블록이 모여 플레인(plane), 플레인이 모여 다이(die) 가 됩니다. 플레시 메모리에서 읽기와 쓰기는 페이지 단위로 이루어 집니다. 하지만 삭제는 페이지보다 큰 블록 단위로 이루어집니다. 읽기/쓰기 단위와 삭제 단위가 다르다는 것이 플래시 메모리의 가장 큰 특징 중 하나입니다. 페이지의 상태. 페이지는 세 개의 상태를 가질 수 있습니다. 이는 각각 Free, Valid, Invalid 상태입니다. Free 상태 : 어떠한 데이터도 저장하고 있지 않아 새로운 데이터를 저장할 수 있는 상태. Valid 상태 : 이미 유효한 데이터를 저장하고 있는 상태. Invalid 상태 : 쓰레기값이라 부르는 유효하지 않은 데이터를 저장하고 있는 상태. 플래시 메모리는 하드 디스크와는 달리 덮어쓰기가 불가능하여 Vaild 상태인 페이지에는 새 데이터를 저장할 수 없습니다. 플래시 메모리의 간단한 동작 예시. 플래시 메모리의 간단한 동작을 예시로 알아봅시다. X라는 블록이 네 개의 페이지로 이루어져 있다고 가정해 보겠습니다. 그리고 그중 두 개의 페이지에는 왼쪽 아래와 같이 A와 B라는 데이터가 저장 되어 있다고 합시다. 여기서 블록 X에 새로운 데이터 C를 저장한다면 아래 그림과 같이 저장됩니다. 플래시 메모리의 읽기 쓰기 단위는 페이지이기 때문입니다. 여기서 새롭게 저장된 C와 기존에 저장되어 있던 B는 그대로 둔 채 기존의 A만을 A’로 수정하고 싶다면 플래시 메모리에서 덮어쓰기는 불가능하기 때문에 기존에 저장된 A는 Invalid 상태가 되어 더 이상 값이 유효하지 않은 쓰레기값이 되고, 새로운 A’ 데이터가 저장됩니다. 결과적으로 블록 X의 Valid 페이지는 B, C, A’가 됩니다. 그런데 여기서 문제가 있습니다. A와 같이 쓰레기 값을 저장하고 있는 공간은 사용하지 않을 공간인데도 불구하고 용량을 차지하고 있습니다. 이는 엄연히 용량 낭비입니다. 그렇다고 A만 지울 수도 없습니다. 앞서 언급했듯이 플래시 메모리에서 삭제는 블록 단위로 수행되기 때문입니다. 그래서 최근 SSD를 비롯한 플래시 메모리는 이런 쓰레기 값을 정리하기 위해 가비지 컬렉션(Garbege Collection) 기능을 제공합니다. 가비지 컬렉션은 1. 유효한 페이지들만을 새로운 블록으로 복사한 뒤, 2. 기존의 블록을 삭제하는 기능입니다. 즉, 블록 X의 모든 유효한 페이지를 새로운 블록 T로 옮기고 블록 X를 삭제하는 것입니다. 키워드로 정리하는 핵심 포인트 하드 디스크 의 구성요소에는 플래터, 스핀들, 헤드, 디스크 암이 있습니다. 플래터는 트랙과 섹터로 나뉘고, 여러 플래터의 동일한 트랙이 모여 실린더를 이룹니다. 하드 디스크의 데이터 접근 시간은 크게 탐색 시간, 회전 지연, 전송 시간으로 나뉩니다. 플래시 메모리는 한 셀에 몇 비트를 저장할 수 있느냐에 따라 SLC, MLC, TLC로 나뉩니다. 플래시 메모리의 읽기과 쓰기는 페이지 단위로, 삭제는 블록 단위로 이루어 집니다.
Archive
· 2024-05-22
💾 [CS] RAID의 정의와 종류
RAID의 정의와 종류. 1TB 하드 디스크 네 개로 RAID를 구성하면 4TB 하드 디스크 한 개의 성능과 안전성을 능가할 수 있습니다. RAID의 정의. ‘보조기억장치에도 수명이 있습니다.’ 그래서 ‘하드 디스크와 같은 보조기억장치에 어떻게든 저장만 하면 됩니다’ 와 같은 단순한 답변은 다소 부족한 해법입니다. 이럴 때 사용할 수 있는 방법 중 하나가 RAID입니다. RAID(Redundant Array of Independent Disks) 는 주로 하드 디스크와 SSD를 사용하는 기술로, 데이트의 안정선 혹은 높은 성능을 위해 여러 개의 물리적 보조기억장치를 마치 하나의 논리적 보조기억장치처럼 사용하는 기술을 의미합니다. RAID의 종류 RAID 구성 방법을 RAID 레벨 이라고 표현합니다. RAID 레벨에는 대표적으로 RAID 0, RAID 1, RAID 2, RAID 3, RAID 4, RAID 5, RAID 6 이 있고 그로부터 파생된 RAID 10, RAID 50 등이 있습니다. RAID 0 RAID 0 은 여러 개의 보조기억장치에 데이터를 잔순히 나누어 저장하는 구성 방식입니다. 가령 1TB 하드 디스크 네 개로 RAID 0을 구성했다고 가정해 봅시다. 이제 어떠한 데이터를 저장할 때 각 하드 디스크는 아래와 같이 번갈아 가며 데이터를 저장합니다, 즉, 저장되는 데이터가 하드 디스크 개수만큼 나위어 자장되는 것입니다. 이때 마치 줄무늬처럼 분산되어 저장된 데이터를 스트라입(Stripe) 이라 하고, 분산하여 저장하는 것을 스트라이핑(Striping) 이라고 합니다. 위와 같이 데이터가 분산되어 저장되면, 다시 말해 스트라이핑되면 저장된 데이터를 읽고 쓰는 속도가 빨라집니다. 하나의 대용량 저장 장치를 이용했더라면 여러 번에 걸쳐 일고 썻을 데이터를 동시에 읽고 쓸 수 있기 때문입니다. 그렇기에 4TB 저장 장치 한 개를 읽고 쓰는 속도보다 RAID 0로 구성된 1TB 저장 장치 네 개의 속도가 이론상 네 배가량 빠릅니다. RAID 0의 단점 RAID 0에는 단점이 있습니다. 저장된 정보가 안전하지 않습니다. RAID 0으로 구성된 하드 디스크 중 하나에 문제가 생긴다면 다른 모든 하드 디스크의 정보를 읽는 데 문제가 생길 수 있습니다. 그래서 등장한 것이 RAID 1 입니다. RAID 1 RAID 1 은 복사본을 만드는 방식입니다. 마치 거울처럼 완전한 복사본을 만드는 구성이기에 미러링(mirroring) 이라고도 부릅니다. 아래 그림은 네 개의 하드 디스크를 RAID 1으로 구성한 모습입니다. RAID 0처럼 데이터 스트라이핑이 사용되긴 했지만, 오른쪽의 두 하드 디스크는 마치 거울처럼 왼쪽의 두 하드 디스크와 동일한 내용을 저장하고 있습니다. 이처럼 RAID 1에 어떠한 데이터를 쓸 때는 원본과 복사본 두 군데에 씁니다. 그렇기에 쓰기 속도는 RAID 0보다 느립니다. RAID 1 방식은 복구가 매우 간단하다는 장점이 있습니다. 똑같은 디스크가 두 개 있는 셈이니, 하나에 문제가 발생해도 잃어버린 정보를 금방 되찾을 수 있기 때문입니다. RAID 1의 단점 RAID 1은 하드 디스크 개수가 한정되었을 때 사용 가능한 용량이 적어지는 단점이 있습니다. 위 그림만 보아도 RAID 0 구성은 4TB의 정보를 저장할 수 있는 반면, RAID 1에서는 2TB의 정보만 저장할 수 있습니다. 즉, RAID 1에서는 복사본이 만들어지는 용량만큼 사용자가 사용하지 못합니다. 결국 많은 양의 하드 디스크가 필요하게 되고, 비용이 증가한다는 단점으로 이어집니다. RAID 4 RAID 4는 RAID 1처럼 완전한 복사본을 만드는 대신 오류를 검출하고 복구하기 위한 정보를 저장한 장치를 두는 구성 방식입니다. 이때 ‘오류를 검출하고 복구하기 위한 정보’를 패리티 비트(parity bit) 라고 합니다. RAID 4에서는 패리티를 저장한 장치를 이용해 다른 장치들의 오류를 검출하고, 오류가 있다면 복구합니다. 이로써 RAID 4는 RAID 1보다 적은 하드 디스크로도 데이터를 안전하게 보관할 수 있습니다. 오류를 검출하는 패리트 비트 원래 패리트 비트는 오류 검출만 가능할 뿐 오류 복구는 불가능합니다. 하지만 RAID에서는 패리트 값으로 오류 수정도 가능합니다. 다만 구체적인 방법인 패리티 계산법은 다루지 않을 예정입니다. 여기서 다음 두 가지만 기억하면 됩니다. RAID 4에서는 패리티 정보를 저장한 장치로서 나머지 장치들의 오루를 검출.복구한다. 패리티 비트는 본래 오류 검출용 정보지만, RAID에서는 오류 복구도 가능하다. RAID 5 RAID 4에서는 어떤 새로운 데이터가 저장될 때마다 패리티를 저장하는 디스크에도 데이터를 쓰게 되므로 패리티를 저장하는 장치에 병목 현상이 발생한다는 문제가 있습니다. RAID 5는 아래 그림처럼 패리티 정보를 분산하여 저장하는 방식으로 RAID 4의 문제인 병목 현상을 해소합니다. RAID 6 RAID 6 의 구성은 기본적으로 RAID 5와 같으나, 다음 그림과 같이 서로 다른 두 개의 패리티를 두는 방식입니다. 이는 오류를 검출하고 복구할 수 있는 수단이 두 개가 생긴 셈입니다. 따라서 RAID 6은 RAID 4나 RAID 5보다 안전한 구성이라 볼 수 있습니다. 다만 새로운 정보를 저장할 때마다 함께 저장할 패리티가 두 개이므로, 쓰기 속도는 RAID 5보다 느립니다. 따라서 RAID 6은 데이터 저장 속도를 조금 희생하더라도 데이터를 더욱 안전하게 보관하고 싶을 때 사용하는 방식입니다. 정리 이 외에도 RAID 0과 RAID 1을 혼합한 RAID 10 방식도 있고, RAID 0과 RAID 5를 혼합한 RAID 5방식도 있습니다. note: 이렇게 여러 RAID 레벨을 혼합한 방식을 Nested RAID 라고 합니다. 각 RAID 레벨마다 장단점이 있으므로 어떤 상황에서 무엇을 최우선으로 원하는지에 따라 최적의 RAID 레벨은 달라질 수 있습니다. 그렇기에 각 RAID 레벨의 대략적인 구성과 특징을 아는것이 중요합니다. 키워드로 정리하는 핵심 포인트 RAID란 데이터의 안전성 혹은 높은 성능을 위해 여러 하드 디스크나 SSD를 마치 하나의 장치저럼 사용하는 기술입니다. RAID 0은 데이터를 단순히 병렬로 분산하여 저장하고, RAID 1은 완전한 복사본을 만듭니다. RAID 4는 패리티를 저장한 장치를 따로 두는 방식이고, RAID 5는 패리티를 분산하여 저장하는 방식입니다. RAID 6은 서로 다른 두 개의 패리티를 두는 방식입니다.
Archive
· 2024-05-21
💾 [CS] 메모리의 주소 공간
메모리의 주소 공간. 주소에는 물리 주소와 논리 주소가 있다. 이번 절에서는 이 두 주소의 개념과 차이, 그리고 두 주소 간의 변환 방법을 학습한다. 1. 주소의 종류. 지금까지 ‘메모리에 저장된 정보의 위치는 주소로 나타낼 수 있다’ 정도로만 설명했지만, 사실 주소에는 두 종류가 있습니다. 1. 물리주소 : 메모리 하드웨어가 사용하는 주소. 2. 논리주소 : CPU와 실행 중인 프로그램이 사용하는 주소. 2. 물리 주소와 논리 주소. CPU와 실행 중인 프로그램은 현재 메모리 몇 번지에 무엇이 저장되어 있는지 다 알고 있지 않습니다. 그 이유는 메모리에 저장된 정보는 시시각각 변하기 때문입니다. 메모리에는 새롭게 실행되는 프로그램이 시시때때로 적재되고, 실행이 끝난 프로그램은 삭제됩니다. 게다가, 같은 프로그램을 실행하더라도 실행할 때마다 적재되는 주소가 달라질 수 있습니다. 예를 들어, 1500번지에 적재되었던 프로그램을 다시 실행하면 3000번지, 또 다시 실행하면 2700번지에 적재될 수 있습니다. 그렇다면 CPU와 실행 중인 프로그램이 이해하는 주소는 무엇일까요? 주소에는 메모리가 사용하는 물리 주소가 있고, CPU와 실행 중인 프로그램이 사용하는 논리 주소가 있습니다. 물리 주소(Physical address) : 정보가 실제로 저장된 하드웨어상의 주소를 의미. 논리 주소(logical address) : CPU와 실행 중인 프로그램이 사용하는 주소, 실행 중인 프로그램 각각에게 부여된 0번지부터 시작되는 주소를 의미함. 예를 들어 현재 메모리에 메모장, 게임, 인터넷 브라우저 프로그램이 적재되어 있다고 가정해 보겠습니다. 메모장, 게임, 인터넷 브라우저 프로그램은 현재 다른 프로그램들이 메모리 몇 번지에 저장되어 있는지, 다시 말해 다른 프로그램들의 물리 주소가 무엇인지 굳이 알 필요가 없습니다. 새로운 프로그램이 언제든 적재될 수 있고, 실행되지 않은 프로그램은 언제든 메모리에서 사라질 수 있기 때문입니다. 그래서 메모장, 게임, 인터넷 브라우저는 모두 물리 주소가 아닌 0번지부터 시작하는 자신만을 위한 주소인 논리 주소를 가지고 있습니다. 예를 들어, ‘10번지’라는 주소는 메모장에도, 게임에도, 인터넷 브라우저에도 논리 주소로써 존재할 수 있습니다. 프로그램마다 같은 논리 주소가 얼마든지 있을 수 있다는 뜻입니다. 그리고 CPU는 이 논리 주소를 받아들이고, 해석하고, 연산합니다. 정리하면, 메모리가 사용하는 주소는 하드웨어상의 실제 주소인 물리 주소이고, CPU와 실행 중인 프로그램이 사용하는 주소는 각각의 프로그램에 부여된 논리 주소입니다. 그런데 CPU가 이해하는 주소가 논리 주소라고는 해도 CPU가 메모리와 상호작용하려면 논리 주소와 물리 주소 간의 변환이 이루어져야 합니다. 논리 주소와 물리 주소 간에 어떠한 변환도 이루어지지 않는다면 CPU와 메모리는 서로 이해할 수 없는 주소 체계를 가지고 각자 다른 이야기만 할 뿐 결코 상호작용할 수 없을 테니까요. 그렇다면 논리 주소는 어떻게 물리 주소로 변환될까요? 논리 주소와 물리 주소 간의 변환은 CPU와 주소 버스 사이에 위치한 메모리 관리 장치(MMU: Memory Management Unit) 라는 하드웨어에 의해 수행됩니다. MMU는 CPU가 발생시킨 논리 주소에 베이스 레지스터 값을 더하여 논리 주소를 물리 주소로 변환합니다. 예를 들어 현재 베이스 레지스터에 15000이 저장되어 있고 CPU가 발생시킨 논리 주소가 100번지라면 이 논리 주소는 아래 그림처럼 물리 주소 15100번지(100+15000)로 변환됩니다. 물리 주소 15000번지부터 적재된 프로그램 A의 논리 주소 100번지에는 이렇게 접근이 가능한 것 입니다. 베이스 레지스터는 프로그램의 가장 작은 물리 주소, 즉 프로그램의 첫 물리 주소를 저장하는 셈이고, 논리 주소는 프로그램의 시작점으로부터 떨어진 거리인 셈입니다. 3. 메모리 보호 기법. 메모장 프로그램의 물리 주소가 1000번지부터 1999번지, 인터넷 브라우저 프로그램의 물리 주소가 2000번지부터 2999번지, 게임 프로그램의 물리 주소가 3000번지부터 3999번지라고 가정해 보겠습니다. 만약 메모장 프로그램 명령어 중 ‘(논리 주소) 1500번지에 숫자 100을 저장하라’와 같은 명령어가 있다면 숫자 100은 어떤 물리 주소에 저장될까요? 이 명령어는 실행되어도 안전할까요? 혹은 인터넷 브라우저 프로그램 명령어 중 ‘(논리 주소) 1100번지의 데이터를 삭제하라’와 같은 명령어가 있다면 어떤 물리 주소의 데이터가 삭제될까요? 이 명령어는 실행되어도 안전할까요? 위와 같은 명령어들은 실행되어서는 안 됩니다. 프로그램의 논리 주소 영역을 벗어났기 때문입니다. 위 명령어들이 실행된다면 메모장 프로그램 명령어는 애꿏은 인터넷 브라우저 프로그램에 숫자 10을 저장하고, 인터넷 브라우저 프로그램 명령어는 자신과는 전혀 관련 없는 게임 프로그램 정보를 삭제합니다. 이렇게 다른 프로그램의 영역을 침범할 수 있는 명령어는 위험하기 때문에 논리 주소 범위를 벗어나는 명령어 실행을 방지하고 실행 중인 프로그램이 다른 프로그램에 영향을 받지 않도록 보호할 방법이 핑요합니다. 이는 한계 레지스터(limit register) 라는 레지스터가 담당합니다. 베이스 레지스터가 실행 중인 프로그램의 가장 작은 물리 주소를 저장한다면, 한계 레지스터는 논리 주소의 최대 크기를 저장합니다. 즉, 프로그램의 물리 주소 범위는 베이스 레지스터 값 이상, 베이스 레지스터 값 + 한계 레지스터 값 미만이 됩니다. CPU가 접근하려는 논리 주소는 한계 레지스터가 저장한 값보다 커서는 안 됩니다. 한계 레지스터보다 높은 주소 값에 접근하는 것은 곧 프로그램의 범위에 벗어난 메모리 공간에 접근하는 것과 같디 때문입니다. 베이스 레지스터에 100, 한계 레지스터에 150이 저장되어 있다고 해 봅시다. 이는 물리 주소 시작점이 100번지, 프로그램의 크기(논리 주소의 최대 크기)는 150임을 의미합니다. 따라서 이 프로그램은 150번지를 넘어서는 논리 주소를 가질 수 없습니다. 이번에는 베이스 레지스터에 1500, 한계 레지스터에 1000이 저장되어 있다고 해 봅시다. 이는 물리주소 시작점이 1500번지, 프로그램 크기는 1000임을 의미합니다. 따라서 이 프로그램은 1000번지를 넘어서는 논리 주소를 가질 수 없습니다. CPU는 메모리에 접근하기 전에 접근하고자 하는 논리 주소가 한계 레지스터보다 작은지를 항상 검사합니다. 만약 CPU가 한계 레지스터보다 높은 논리 주소에 접근하려고 하면 인터럽트(트랩)를 발생시켜 실행을 중단합니다. 이러한 방식으로 실행 중인 프로그램의 독립적인 실행 공간을 확보하고 하나의 프로그램이 다른 프로그램을 침범하지 못하게 보호할 수 있습니다. 5. 키워드로 정리하는 핵심 키워드 물리 주소는 메모리 하드웨어상의 주소이고, 논리 주소는 CPU와 실행 중인 프로그램이 사용하는 주소입니다. MMU는 논리 주소를 물리 주소로 변환합니다. 베이스 레지스터는 프로그램의 첫 물리 주소를 저장합니다. 한계 레지스터는 실행 중인 프로그램의 논리 주소의 최대 크기를 저장합니다. 컴퓨터 시스템에서 “물리 주소(Physical Address)”와 “논리 주소(Logical Address)”는 메모리 관리의 중요한 개념입니다. 각각은 다음과 같은 의미를 가지며, 시스템의 효율적인 메모리 관리를 위해 사용됩니다. 1.1 논리 주소(Logical Address) 정의 : 논리 주소는 프로그램이 사용하는 주소입니다. 이 주소는 프로그램이 실행되면서 생성되는 주소로, 사용자 또는 프로그램이 접근할 수 있는 주소입니다. 이 주소는 가상 메모리 주소라고도 하며, 실제 메모리의 물리적 위치와는 독립적입니다. 목적 : 논리 주소의 주요 목적은 각 프로세스가 독립된 주소 공간을 갖게 하여, 프로세스간의 메모리 충돌을 방지하고 보안을 강화하는 데 있습니다. 또한, 프로그래밍을 단순화시키고 메모리 관리를 더 유연하게 만듭니다. 1.2 물리 주소(Physical Address) 정의 : 물리 주소는 메모리 장치 내의 실제 위치를 가리키는 주소입니다. 이 주소는 시스템의 메모리 관리 유닛(Memory Management Unit, MMU)에 의해 사용되며, 실제 RAM에서 데이터를 찾는 데 사용됩니다. 목적 : 물리 주소는 시스템의 메모리를 효율적으로 할당하고 관리하는 데 필요합니다. 이를 통해 시스템은 실제 메모리 공간을 최적화하고, 필요한 데이터와 프로그램을 정확한 위치에서 처리할 수 있습니다. 1.3 주소 변환(Address Translation) 논리 주소에서 물리 주소로의 변환은 주로 메모리 관리 유닛(MMU)에 의해 수행됩니다. 이 과정은 다음과 같은 방법으로 이루어 집니다. 1. 페이지 테이블 : 운영체제는 페이지 테이블을 사용하여 논리 주소를 물리 주소로 매핑합니다. 페이지 테이블을 논리 주소를 페이지로 나누고, 각 페이지가 실제 메모리의 어느 부분에 해당하는지를 나타내는 테이블입니다. 2. 변환 조회 버퍼(TLB) : 변환 조회 버퍼는 자주 사용되는 주소 매핑을 캐시하는 작은 메모리로, 주소 변환 과정을 빠르게 만듭니다. 3. 주소 변환 과정 프로세스가 논리 주소를 생성합니다. MMU는 논리 주소의 페이지 번호를 확인하고, 해당 페이지 번호가 페이지 테이블에 있는지 확인합니다. 페이지 테이블에서 해당 페이지의 물리 주소를 찾아 매핑합니다. 물리 주소를 사용하여 실제 메모리에서 데이터를 엑세스합니다. 📝 정리 이러한 주소 변환 메커니즘은 메모리 보호, 프로세스 격리, 메모리 사용의 효율성 증가 등을 가능하게 하며, 복잡한 현대의 멀티태스킹 환경에서 중요한 역할을 합니다. Q1. 물리 주소(Physical Address)’와 ‘논리 주소(Logical Address)’에 대해 설명해 주시겠습니까? 이 두 주소의 개념과 차이점을 구체적으로 말씀해 주시고, 어떻게 논리 주소가 물리 주소로 변환되는지 그 과정에 대해서도 설명해 주세요. 논리 주소는 프로그램이 사용하는 주소로, 프로그램 코드에 의해 참조되는 주소입니다. 이는 운영체제에 의해 관리되며, 프로그램이 메모리에 로드되는 위치와 무관하게 일관성을 유지합니다. 즉, 프로그램이 메모리의 어느 위치에 로드되든지 간에 같은 논리 주소를 사용할 수 있습니다. 논리 주소는 가상 메모리 주소라고도 하며, 이를 통해 개발자는 실제 메모리 구조를 신경 쓰지 않고 프로그래밍할 수 있습니다. 물리 주소는 메모리 장치 내의 실제 물리적 위치를 가리킵니다. 즉, 물리 주소는 RAM 내의 실제 데이터나 명령어가 저장된 위치를 나타내며, 메모리 관리 유닛(MMU)에 의해 논리 주소로부터 변환됩니다. 논리 주소에서 물리 주소로의 변환은 주로 메모리 관리 유닛(MMU)을 통해 이루어집니다. 이 과정은 다음과 같습니다: 프로세스가 생성하는 논리 주소는 페이지 번호와 오프셋으로 구성됩니다. 페이지 번호는 페이지 테이블을 참조하여 해당 페이지가 메모리의 어느 물리적 위치에 있는지 결정합니다. 이 페이지 테이블은 운영 체제에 의해 관리되며, 각 페이지의 물리 주소를 저장합니다. 물리 주소는 결정된 페이지 시작 주소에 오프셋을 추가하여 최종적으로 결정됩니다. 변환 조회 버퍼(TLB)는 이러한 변환 과정을 가속화하기 위해 자주 사용되는 주소 변환을 캐시합니다. 이러한 변환 과정을 통해 시스템은 효율적으로 메모리를 관리하며, 프로세스 간 메모리 격리와 보안을 유지할 수 있습니다.
Archive
· 2024-05-06
💾 [CS] CISC와 RISC
CISC와 RISC. 명령어 파이프라이닝과 슈퍼스칼라 기법을 실제로 CPU에 적용하려면 명령어가 파이프라이닝에 최적화되어 있어야 합니다. 쉽게 말해 CPU가 파이프라이닝과 슈퍼스칼라 기법을 효과적으로 사용하려면 CPU가 인출하고 해석하고 실행하는 명령어가 파이프라이닝 하기 쉽게 생겨야 합니다. ‘파이프라이닝 하기 쉬운 명령어’란 무엇일까요? 명령어가 어떻게 생겨야 파이프라이닝에 유리할까요? 이와 관련해 CPU의 언어인 ISA와 각기 다른 성격의 ISA를 기반으로 설계된 CISC와 RISC를 알아봅시다. 명령어 집합 세상에는 수많은 CPU 제조사들이 있고, CPU마다 규격과 기능 만듦새가 다 다릅니다. 그러므로 CPU가 이해하고 실행하는 명령어들이 다 똑같지 않습니다. 물론 명령어의 기본적인 구조와 작동원리는 큰 틀에서 크게 벗어나지 않습니다. 그러나 명령어의 세세한 생김새, 명령어로 할 수 있는 연산, 주소 지정 방식 등은 CPU마다 조금씩 차이가 있습니다. 명령어 집합(instruction set) 또는 명령어 집합 구조(ISA: Instruction Set Architecture) : CPU가 이해할 수 있는 명령어들의 모음. CPU마다 ISA가 다를 수 있습니다. 명령어 집합에 ‘구조’라는 단어가 붙은 이유는 CPU가 어떤 명령어를 이해하는지에 따라 컴퓨터 구조 및 설계 방식이 달라지기 때문입니다. 가령 인텔의 노트북 속 CPU는 x86 혹은 x86-64 ISA를 이해하고, 애플의 아이폰 속 CPU는 ARM ISA를 이해합니다. x86(x86-64)과 ARM은 다른 ISA이기 때문에 인텔 CPU를 사용하는 컴퓨터와 아이폰은 서로의 명령어를 이해할 수 없습니다. 실행 파일은 명령어로 이루어져 있고 서로의 컴퓨터가 이해할 수 있는 명령어가 다르기 때문입니다. x86은 32비트용, x86-64는 64비트용 x86 ISA입니다. 어셈블리어는 명령어를 읽기 편하게 표현한 언어입니다. ISA가 다르다는 건 CPU가 이해할 수 있는 명령어가 다르다는 뜻입니다. 명령어가 달라지면 어셈블리어도 달라집니다. 다시 말해 같은 소스 코드로 만들어진 같은 프로그램이라 할지라도 ISA가 다르면 CPU가 이해할 수 있는 명령어도 어셈블리어도 달라진다는 것입니다. 예를 들어 보겠습니다. 동일한 소스 코드를 작성하고 ISA가 다른 컴퓨터에서 어셈블리어로 컴파일하면 아래와 같은 결과를 얻을 수 있습니다. 왼쪽은 x86-64 ISA, 오른쪽은 ARM ISA입니다. 똑같은 코드로 만든 프로그램임에도 CPU가 이해하고 실행할 수 있는 명령어가 달라 어셈블리어도 다른 것을 알 수 있습니다. 참고로 사용한 컴파일러에 따라서도 어셈블리어가 달라질 수 있는데, 위 예시에서는 gcc 11.2라는 동일한 컴파일러를 이용했습니다. ISA가 같은 CPU끼리는 서로의 명령어를 이해할 수 있지만, ISA가 다르면 서로의 명령어를 이해하지 못합니다. 이런 점에서 볼 때 ISA는 일종의 CPU의 언어인 샘입니다. CPU가 이해하는 명령어들이 달라지면 비단 명령어의 생김새만 달라지는게 아닙니다 ISA가 다르면 그에 따른 나비 효과로 많은 것이 달라집니다. 제어장치가 명령어를 해석하는 방식, 사용되는 레지스터의 종류와 개수, 메모리 관리 방법 등 많은 것이 달라집니다. 그리고 이는 곧 CPU 하드웨어 설계에도 큰 영향을 미칩니다. ISA는 CPU의 언어임과 동시에 CPU를 비롯한 하드웨어가 소프트웨어를 어떻게 이해할지에 대한 약속이라고도 볼 수 있습니다. 앞서 명령어 병렬 처리 기법들을 학습했습니다. 이를 적용하기에 용이한 ISA가 있고, 그렇지 못한 ISA도 있습니다. 다시 말해 명령어 파이프라인, 슈퍼스칼라, 비순차적 명령어 처리를 사용하기에 유리한 명령어 집합이 있고, 그렇지 못한 명령어 집합도 있습니다. 그렇다면 명령어 병렬 처리 기법들을 도입하기 유리한 ISA는 무엇일까요? 이와 관련해 현대 ISA의 양대 산맥인 CISC와 RISC에 대해 알아보겠습니다. CISC CISC(Complex Instruction Set Computer) : ‘복잡한 명령어 집합을 활용하는 컴퓨터’ 여기서 ‘컴퓨터’를 ‘CPU’라고 생각해도 좋습니다. 이름 그대로 복잡하고 다양한 명령어들을 활용하는 CPU 설계 방식입니다. ISA의 한 종류로 소개한 x86, x86-64는 대표적인 CISC 기반의 ISA입니다. 다양하고 강력한 기능의 명령어 집합을 활용하기 때문에 명령어의 형태와 크기가 다양한 가변 길이 명령어를 활용합니다. 메모리에 접근하는 주소 지정 방식도 다양해서 아주 특별한 상황에서만 사용되는 독특한 주소 지정 방식들도 있습니다. 다양하고 강력한 명령어를 활용한다는 말은 상대적으로 적은 수의 명령어로도 프로그램을 실행할 수 있다는 것을 의미합니다. 프로그램을 실행하는 명령어 수가 적다는 말은 ‘컴파일된 프로그램의 크기가 작다’는 것을 의미합니다. 같은 소스 코드를 컴파일해도 CPU마다 생성되는 실행 파일의 크기가 다를 수 있다는 것입니다. 이런 장점 덕분에 CISC는 메모리를 최대한 아끼며 개발해야 했던 시절에 인기가 높았습니다. ‘적은 수의 명령어만으로도 프로그램을 동작시킬 수 있다’는 점은 메모리 공간을 절약할 수 있다는 장점이기 때문입니다. 하지만 CISC에는 치명적인 단점이 있습니다. 활용하는 명령어가 워낙 복잡하고 다양한 기능을 제공하는 탓에 명령어의 크기와 실행되기까지의 시간이 일정하지 않습니다. 그리고 복잡한 명령어 때문에 명령어 하나를 실행하는 데에 여러 쿨럭 주기를 필요로 합니다. 이는 명령어 파이프라인을 구현하는 데에 큰 걸림돌이 됩니다. 명령어 파이프라인 기법을 위한 이상적인 명령어는 다음 그림과 같이 각 단계에 소요되는 시간이 (가급적 1 클럭으로) 동일해야 합니다. 그래야 파이프라인이 마치 공장의 생산 라인처럼 결과를 내기 때문입니다. 하지만 CISC가 활용하는 명령어는 명령어 수행 시간이 길고 가지각색이기 때문에 파이프라인이 효율적으로 명령어를 처리할 수 없습니다. 한마디로 규격화되지 않은 명령어가 파이프라이닝을 어렵게 만든 셈입니다. 명령어 파이프라인이 제대로 동작하지 않는다는 것은 현대 CPU에서 아주 치명적인 약점입니다. 현대 CPU에서 명령어 파이프라인은 높은 성능을 내기 위해 절대 놓쳐서는 안 되는 핵심 기술이기 때문입니다. 게다가 CISC가 복잡하고 다양한 명령어를 활용할 수 있다고는 하지만, 사실 대다수의 복잡한 명령어는 그 사용 빈도가 낮습니다. 1974년 IBM 연구소의 존 코크(John Cocke)는 CISC 명령어 집합 중 불과 20% 정도의 명령어가 사용된 전체 명령어의 80%가량을 차지한다는 것을 증명하기도 했습니다. CISC 명령어 집합이 다양하고 복잡한 기능을 지원하지만 실제로는 자주 사용되는 명령어만 쓰였다는 것입니다. 정리하자면, CISC 명령어 집합은 복잡하고 다양한 기능을 제공하기에 적은 수의 명령으로 프로그램을 동작시키고 메모리를 절약할 수 있지만, 명령어의 규격화가 어려워 파이프라이닝이 어렵습니다. 그리고 대다수의 복잡한 명령어는 그 사용 빈도가 낮습니다. 이러한 이유로 CISC 기반 CPU는 성장에 한계가 있습니다. RISC CISC의 한계가 우리들에게 준 교훈은 크게 아래와 같습니다. 빠른 처리를 위해 명령어 파이프라인을 십분 활용해야 한다. 원활한 파이프라이닝을 위해 ‘명령어 길이와 수행 시간이 짧고 규격화’되어 있어야 한다. 어차피 자주 쓰이는 명령어만 줄곧 사용된다. 복잡한 기능을 지원하는 명령어를 추가하기보다는 ‘자주 쓰이는 기본적인 명령어를 작고 빠르게 만드는 것’이 중요하다. 이런 원칙 하에 등장한 것이 RISC입니다. RISC(Reduced Instruction Set Computer) : 이름처럼 CISC에 비해 명령어의 종류가 적습니다. 그리고 CISC와는 달리 짧고 규격화된 명령어, 되도록 1클럭 내외로 실행되는 명령어를 지향합니다. 즉, 고정 길이 명령어를 활용합니다. 명령어가 규격화되어 있고, 하나의 명령어가 1클럭 내외로 실행되기 때문에 RISC 명령어 집합은 명령어 파이프라이닝에 최적화되어 있습니다. 그리고 RISC는 메모리에 직접 접근하는 명령어를 load, store 두 개로 제한할 만큼 메모리 접근을 단순화하고 최소화를 추구합니다. 그렇기 때문에 CISC보다 주소 지정 방식의 종류가 적은 경우가 많습니다. 이런 점에서 RISC를 load-store 구조라고 부르기도 합니다. RISC는 메모리 접근을 단순화, 최소화하는 대신 레지스터를 적극적으로 활용합니다. 그렇기에 CISC보다 레지스터를 이용하는 연산이 많고, 일반적인 경우보다 범용 레지스터 개수도 더 많습니다. 다만 사용 가능한 명령어 개수가 CISC보다 적기 때문에 RISC는 CISC보다 많은 명령으로 프로그램을 작동시킵니다. 키워드로 정리하는 핵심 포인트 ISA는 CPU의 언어이자 하드웨어가 소프트웨어를 어떻게 이해할지에 대한 약속입니다. CISC는 복잡하고 다양한 종류의 가변 길이 명령어 집합을 활용합니다. RISC는 단순하고 적은 종류의 고정 길이 명령어 집합을 활용합니다.
Archive
· 2024-04-25
💾 [CS] 명령어 병렬 처리 기법
명령어 병렬 처리 기법 명령어 병령 처리 기법(ILP: Instruction-Level Parallelism): 명령어를 동시에 처리하여 CPU를 한시도 쉬지 않고 작동시키는 기법. 대표적인 명령어 병렬 처리 기법 명령어 파이프 라이닝 슈퍼스칼라 비순차적 명령어 처리 명령어 파이프라인 명령어 파이프라인을 이해하려면 하나의 명령어가 처리되는 전체 과정을 비슷한 시간 간격으로 나누어 보아야 합니다. 명령어 처리 과정을 클럭 단위로 나누어 보면 일반적으로 다음과 같이 나눌 수 있습니다. 명령어 인출(Instruction Fetch) 명령어 해석(Instruction Decode) 명령어 실행(Execute Instruction) 결과 저장(Write Back) 참고: 이 단계가 정답은 아닙니다. 전공서에 따라 명령어 인출 -> 명령어 실행으로 나누기도 하고, 명령어 인출 -> 명령어 해석 -> 명령어 실행 -> 메모리 접근 -> 결과 저장으로 나누기도 합니다. 여기서 중요한 점은 같은 단계가 겹치지만 않는다면 CPU가 ‘각 단계를 동시에 실행할 수 있다’는 것입니다. 예를 들어 CPU는 한 명령어를 ‘인출’하는 동안에 다른 명령어를 ‘실행’할 수 있고, 한 명령어가 ‘실행’되는 동안에 연산 결과를 ‘저장’할 수 있습니다. 이를 그림으로 표현하면 다음과 같습니다. t1에는 명령어 1, 2를 동시에 처리할 수 있고 t2에는 명령어 1,2,3을 동시에 처리할 수 있습니다. 이처럼 명령어를 겹처서 수행하면 명령어를 하나하나 실행하는 것보다 훨씬 더 효율적으로 처리할 수 있습니다. 이처럼 마치 공장 생산 라인과 같이 명령어들을 “명령어 파이프라인(instruction pipeline)” 에 넣고 동시에 처리하는 기법을 “명령어 파이프라이닝(instruction pipelining)” 이라고 합니다. 명령어 파이프라인을 사용하지 않고 모든 명령어를 순차적으로만 처리한다면 아래와 같이 처리했을것입니다. 한눈에 봐도 명령어 파이프라이닝을 이용하는 것이 더 효율적임을 알 수 있습니다. 파이프라이닝이 높은 성능을 가져오기는 하지만, 특정 상황에서는 성능 향상에 실패하는 경우도 있습니다. 이러한 상황을 파이프라인 위험(pipeline hazard) 이라고 부릅니다. 파이프라인 위험에는 크게 3가지가 있습니다. 데이터 위험 제어 위험 구조적 위험 데이터 위험 데이터 위험(data hazard) 은 명령어 간 ‘데이터 의존성’에 의해 발생합니다. 모든 명령어를 동시에 처리할 수는 없습니다. 어떤 명령어는 이전 명령어를 끝까지 실행해야만 비로소 실행할 수 있는 경우가 있습니다. 예를 들어 아래 두 명령어를 봅시다. 편의상 레지스터 이름을 R1, R2, R3, R4, R5라 하고 ‘왼쪽 레지스터에 오른쪽 결과를 저장하라’는 기호는 <- 기호로 표기하겠습니다. 명령어 1: R1 <- R2 + R3 // R2 레지스터 값과 R3 레지스터 값을 더한 값을 R1 레지스터에 저장 명령어 2: R4 <- R1 + R5 // R1 레지스터 값과 R5 레지스터 값을 더한 값을 R4 레지스터에 저장 위의 경우 명령어 1을 수행해야만 명령어 2를 수행할 수 있습니다. 즉, R1에 R2 + R3 결괏값이 저장되어야 명령어 2를 수행할 수 있습니다. 만약 명령어 1 실행이 끝나기 전에 명령어 2를 인출하면 R1에 R2 + R3 결괏값이 저장되기 전에 R1 값을 읽어 들이므로 원치 않은 R1 값으로 명령어 2를 수행합니다. 따라서 명령어 2는 명령어 1의 데이터에 의존적입니다. 이처럼 데이터 의존적인 두 명령어를 무작정 동시에 실행하려고 하면 파이프라인이 제대호 작동하지 않는 것을 ‘데이터 위험’이라고 합니다. 제어 위험 제어 위험(control hazard) 은 주로 분기 등으로 인한 ‘프로그램 카운터의 갑작스러운 변화’에 의해 발생합니다. 기본적으로 프로그램 카운터는 ‘현재 실행 중인 명령어의 다음 주소’로 갱신됩니다. 하지만 프로그램 실행 흐름이 바뀌어 명령어가 실행되면서 프로그램 카운터 값에 갑작스러운 변화가 생긴다면 명령어 파이프라인에 미리 가지고 와서 처리 중이었던 명령어들은 아무 쓸모가 없어집니다. 이를 ‘제어 위험’이라고 합니다. 참고: 참고로 이를 위해 사용하는 기술 중 하나가 분기 예측(branch prediction) 입니다. 분기 예측은 프로그램이 어디로 분기할지 미리 예측한 후 그 주소를 인출하는 기술입니다. 구조적 위험 구조적 위험(structural hazard) 은 명령어들을 겹쳐 실행하는 과정에서 서로 다른 명령어가 동시에 ALU, 레지스터 등과 같은 CPU 부품을 사용하려고 할 때 발생합니다. 구조적 위험은 자원 위험(resource hazard) 이라고도 부릅니다. 슈퍼스칼라 파이프라이닝은 단일 파이프라인으로도 구현이 가능하지만, 오늘날 대부분의 CPU에서는 여러 개의 파이프라인을 이용합니다. 이처럼 CPU 내부에 여러 개의 명령어 파이프라인을 포함한 구조를 슈퍼스칼라(superscalar) 라고 합니다. 명령어 파이프라인을 하나만 두는 것이 마치 공장 생산 라인을 한 개 두는 것과 같다면, 슈퍼스칼라는 공장 생산 라인을 여러 개 두는 것과 같습니다. 슈퍼스칼라 구조로 명령어 처리가 가능한 CPU를 슈퍼스칼라 프로세서 또는 슈퍼스칼라 CPU라고 합니다. 슈퍼스칼라 프로세서는 매 클럭 주기마다 동시에 여러 명령어를 인출할 수도, 실행할 수도 있어야 합니다. 가령 멀티스레드 프로세서는 한 번에 여러 명령어를 인출하고, 해석하고, 실행할 수 있기 때문에 슈퍼스칼라 구조를 사용할 수 있습니다. 슈퍼스칼라 프로세서는 이론적으로 파이프라인 개수에 비례하여 프로그램 처리 속도가 빨라집니다. 하지만 파이프라인 위험 등의 예상치 못한 문제가 있어 실제로는 반드시 파이프라인 개수에 비례하여 빨라지지는 않습니다. 이 때문에 슈퍼스칼라 방식을 차용한 CPU는 파이프라인 위험을 방지하기 위해 고도로 설계되어야 합니다. 여러 개의 파이프라인을 이용하면 하나의 파이프라인을 사용할 때 보다 데이터 위험, 제어 위험, 자원 위험을 피하기가 더욱 까다롭기 때문입니다. 비순차적 명령어 처리 비순차적 명령어 처리(OoOE: Out-of-order execution): 보통 OoOE로 줄여 부릅니다. 이 기법은 많은 전공서에서 다루지 않지만, 오늘날 CPU 성능 향상에 크게 기여한 기법이자 대부분의 CPU가 차용하는 기법입니다. 비순차적 명령어 처리 기법은 이름에서도 알 수 있듯 명령어들을 순차적으로 실행하지 않는 기법입니다. 명령어의 ‘합법적인 새치기’라고 볼 수 있습니다. 지금까지 설명했던 명령어 파이프라이닝, 슈퍼스칼라 기법은 모두 여러 명령어의 순차적인 처리를 상정한 방법이었습니다. 프로그램을 위에서 아래로 차례차례 실행하는 방식이었습니다. 하지만 파이프 라인 위험과 같은 예상치 못한 문제들로 인해 이따금씩 명령어는 곧바로 처리되지 못하기도 합니다. 만약 모든 명령어를 순차적으로만 처리한다면 이런 예상치 못한 상황에서 명령어 파이프라인은 멈춰버리게 됩니다. 예를 들어 아래와 같은 명령어들로 이루어진 소스 코드가 있다고 해봅시다. 편의상 ‘메모리 N번지’는 M(N)으로. ‘메모리 N번지에 M을 저장하라’는 M(N) <- M으로 표기하겠습니다. 1. M(100) <- 1 2. M(101) <- 2 3. M(103) <- M(100) + M(101) 4. M(150) <- 1 5. M(151) <- 2 6. M(152) <- 3 여기서 주목해야 할 점은 3번 명령어를 실행하기 위해서는 M(100) 값은 물론 M(101) 값이 결정되어야 하기에 1번과 2번 명령어 실행이 끝날 때까지 기다려야 한다는 점입니다. 이 명령어들을 순차적으로 실행되는 CPU로 실행하면 다음과 같습니다. 2번 명령어 실행이 끝날 때까지 3, 4, 5, 6번 명령어들은 기다립니다. 앞의 코드를 이루는 명령어들 중에 서로 데이터 의존성이 전혀 없는, 순서를 바꿔 처리해도 수행 결과에 영향을 미치지 않는 명령어들이 있습니다. 가령 3번은 다음과 같이 뒤의 명령어와 순서를 바꾸어 실행해도 크게 문제될 것이 없습니다. 이렇게 순서를 바꿔 실행하면 아래와 같이 수행됩니다. 순차적으로 명령어를 처리할 때보다 더 효율적입니다. 이렇게 명령어를 순차적으로만 실행하지 않고 순서를 바꿔 실행해도 무방한 명령어를 먼저 실행하여 명령어 파이프라인이 멈추는 것을 방지하는 기법을 비순차적 명령어 처리 기법 이라고 합니다. 하지만 아무 명령어나 순서를 바꿔서 수행할 수는 없습니다. 예를 들어서 다음 예시를 봅시다. 1. M(100) <- 1 2. M(101) <- 2 3. M(102) <- M(100) + M(101) 4. M(103) <- M(102) + M(101) 5. M(104) <- M(100) 위 코드에서 3번 명령어와 1번 명령어의 순서를 바꿀 수는 없습니다. 3번 명령어를 수행하려면 반드시 M(100) 값이 결정되어야 하기 때문입니다. 마찬가지로 4번 명령어와 1번 명령어는 순서를 바꿀 수 없습니다. 1번 명령어를 토대로 3번 명령어가 수행되고, 3번 명령어를 토대로 4번이 수행되기 때문입니다. 하지만 위 코드에서 4번 명령어와 5번 명령어는 순서를 바꾸어 실행할 수 있습니다. 다시 말해 이 두 명령어는 어떤 의존성도 없기에 순서를 바꿔도 전체 프로그램의 실행 흐름에는 영향이 없습니다. 이처럼 비순차적 명령어 처리가 가능한 CPU는 명령어들이 어떤 명령어와 데이터 의존성을 가지고 있는지, 순서를 바꿔 실행할 수 있는 명령어에는 어떤 것들이 있는지를 판단할 수 있어야 합니다. 키워드로 정리하는 핵심 포인트 명령어 파이프라이닝은 동시에 여러 개의 명령어를 겹쳐 실행하는 기법입니다. 슈퍼 스칼라는 여러 개의 명령어 파이프라인을 두는 기법입니다. 비순차적 명령어 처리 기법은 파이프라인의 중단을 방지하기 위해 명령어를 순차적으로 처리하지 않는 기법입니다.
Archive
· 2024-04-19
💾 [CS] 빠른 CPU를 위한 설계 기법
빠른 CPU를 위한 설계 기법. 클럭 조금이라도 더 빠른 CPU를 만들려면 어떻게 CPU를 설계해야 할까요? 이전에 학습한 내용을 상기해봅시다. 컴퓨터 부품들은 ‘클럭 신호’에 맞춰 일사분란하게 움직인다. CPU는 ‘명령어 사이클’이라는 정해진 흐름에 맞춰 명령어들을 실행한다. 클럭 신호가 빠르게 반복되면 CPU를 비롯한 컴퓨터 부품들은 그만큼 빠른 박자에 맞춰 움직일 것 입니다. 즉, 클럭 속도가 높아지면 CPU는 명령어 사이클을 더 빠르게 반복할 것이고, 다른 부품들도 그에 발맞춰 더 빠르게 작동할 것입니다. 실제로 클럭 속도가 높은 CPU는 일반적으로 성능이 좋습니다. 그래서 클럭 속도는 CPU 속도 단위로 간주되기도 합니다. 클럭 속도: 헤르츠(Hz) 단위로 측정합니다. 이는 1초에 클럭이 몇 번 반복되는지를 나타냅니다. 가령 클럭이 ‘똑-딱-‘하고 1초에 한 번 반복되면 CPU 클럭 속도는 1Hz인 것이고, 클럭이 1초에 100번 반복되면 CPU 클럭 속도는 100Hz인 셈입니다. 실제 CPU 클럭 속도는 위 사진 속 CPU를 보면 알 수 있습니다. 위 사진 속 CPU를 보면 기본 속도(Base)는 2.5GHz, 최대 속도(Max)는 4.9GHz라는 것을 알 수 있습니다. 이는 1초에 클럭이 기본적으로 25억(2.5 x 10⁹)번 반복된다는 것을 나타냅니다. 참고: 1GHz는 1,000,000,000(10⁹)Hz입니다. “클럭 속도는 일정하지 않다.” ‘클럭’이라는 단어만 보고 시계를 떠올려 클럭 속도가 매번 일정하게 유지된다고 생각할 수도 있지만, 실제로는 그렇지 않습니다. CPU 사진을 다시 보면 기본 클럭 속도(Base)와 최대 속도(Max)로 나위어 있습니다. 이처럼 CPU는 계속 일정한 클럭 속도를 유지하기보다는 고성능을 요하는 순간에는 순간적으로 쿨럭 속도를 높이고, 그렇지 않을 때는 유연하게 쿨럭 속도를 낮추기도 합니다. 최대 클럭 속도를 강제로 더 끌어올릴 수도 있는데, 이런 기법을 오버클럭킹(overclocking) 이라고 합니다. 클럭 속도를 무지막지하게 높이면 CPU는 무작정 빨라지지 않습니다. 그래픽이 많이 요구되는 게임이나 영상 편집과 같이 CPU에 무리가 가는 작업을 장시간 하면 컴퓨터가 뜨겁게 달아오르는 것을 경험해 본 적이 있을 겁니다. 클럭 속도를 무작정 높이면 이러한 발열 문제가 더 심각해집니다. 이처럼 클럭 속도를 높이는 것은 분명 CPU를 빠르게 만들지만, 클럭 속도만으로 CPU의 성늘을 올리는 것에는 한계가 있습니다. 코어와 멀티 코어 클럭 속도를 높이는 방법 외에 CPU의 성능을 높이는 방법에는 대표적으로 CPU의 코어와 스레드 수를 늘리는 방법이 있습니다. 먼저 코어를 늘리는 방법을 알아봅시다. 코어를 이해하려면 현대적인 관점에서 CPU라는 용어를 재해석해야 합니다. 앞서 CPU를 ‘명령어를 실행하는 부품’이라고 소개했습니다. 많은 전공 서적들의 전통적인 관점에서 ‘명령어를 실행하는 부품’은 원칙적으로 하나만 존재했습니다. 하지만 오늘날 CPU는 많은 기술적 발전을 거듭하였고, 그 결과 CPU 내부에는 ‘명령어를 실행하는 부품’을 얼마든지 만들 수 있게 되었습니다. 우리가 지금까지 CPU의 정의로 알고 있었던 ‘명령어를 실행하는 부품’은 오늘날 코어(core) 라는 용어로 사용됩니다. 다시 말해, 오늘날의 CPU는 단순히 ‘명령어를 실행하는 부품’에서 ‘명령어를 실행하는 부품을 여러 개 포함하는 부품’으로 명칭의 범위가 확장 되었습니다. 예를 들어 8코어(Core) CPU는 ‘명령어를 실행하는 부품’을 여덟 개 포함하고 있다고 보면 됩니다. 코어를 여러 개 포함하고 있는 CPU를 멀티코어(multi-core) CPU 또는 멀티코어 프로세서라고 부릅니다. 이는 CPU 내에 명령어를 처리하는 일꾼이 여러 명 있는 것과 같습니다. 당연히 멀티코어의 처리 속도는 단일코어보다 더 빠릅니다. 다령 클럭 속도가 2.4GHz인 단일 코어 CPU와 클럭 속도가 1.9GHz인 멀티코어 CPU를 비교하면 일반적으로 후자의 성능이 더 좋습니다. CPU 종류는 CPU 안에 코어가 몇 개 포함되어 있는지에 따라 아래 표와 같이 싱글코어, 듀얼코어, 트리플코어 등으로 나뉩니다. 코어를 늘릴수록 연산 처리 속도도 빨라질까요? CPU의 연산 속도가 꼭 코어 수에 비례하여 증가하지는 않습니다. 코어마다 처리할 연산이 적절히 분배되지 않는다면 코어 수에 비례하여 연산 속도가 증가하지 않습니다. 또한 처리하고자 하는 작업량보다 코어 수가 지나치게 많아도 성능에는 크게 영향이 없습니다. 중요한 것은 코어마다 처리할 명령어들을 얼마나 적절하게 분배하느냐이고 그에 따라서 연산 속도는 크게 달라집니다. 스레드와 멀티스레드 스레드(thread): 사전적 의미는 ‘실행 흐름의 단위’입니다. 하지만 이 정의를 활자 그대로 받아들이지 말고 더욱 엄밀하게 이해해야 합니다. CPU에서 사용되는 스레드와 프로그래밍에서 사용되는 스레드는 용례가 다르기 때문입니다. 스레드에는 CPU에서 사용되는 하드웨어적 스레드가 있고, 프로그램에서 사용되는 소프트웨어적 스레드가 있습니다. 하드웨어적 스레드 스레드를 하드웨어적으로 정의하면 ‘하나의 코어가 동시에 처리하는 명령어 단위’를 의미합니다. CPU에서 사용하는 스레드라는 용어는 보통 CPU 입장에서 정의된 하드웨어적 스레드를 의미합니다. 하나의 코어로 여러 명령어를 동시에 처리하는 CPU를 멀티스레드(multithread) 프로세서 또는 멀티스레드 CPU라고 합니다. 하이퍼스레딩(hyper-threading): 인텔의 멀티스레드 기술을 의미합니다. 인텔이 자신들의 멀티스레드 기술에 하이퍼스레딩이라는 명칭을 부여한 것입니다. 소프트웨어적 스레드 소프트웨어적으로 정의된 스레드는 ‘하나의 프로그램에서 독립적으로 실행되는 단위’를 의미합니다. 프로그래밍 언어나 운영체제를 학습할 때 접하는 스레드는 보통 이렇게 소프트웨어적으로 정의된 스레드를 의미합니다. 하나의 프로그램은 실행되는 과정에서 한 부분만 실행될 수도 있지만, 프로그램의 여러 부분이 동시에 실행될 수도 있습니다. 가령 워드 프로세서 프로그램을 개발한다고 가정해봅시다. 그리고 아래의 기능이 동시에 수행되길 원한다고 해 봅시다. 사용자로부터 입력받은 내용을 화면에 보여 주는 기능 사용자가 입력한 내용이 맞춤법에 맞는지 검사하는 기능 사용자가 입력한 내용을 수시로 저장하는 기능 이 기능들을 작동시키는 코드를 각각의 스레드로 만들면 동시에 실행할 수 있습니다. 정리하면, 스레드의 하드웨어적 정의는 ‘하나의 코어가 동시에 처리하는 명령어의 단위’를 의미하고, 소프트웨어적 정의는 ‘하나의 프로그램에서 독립적으로 실행되는 단위’를 의미합니다. 한 번에 하나씩 명령어를 처리하는 1코어 1스레드 CPU도 소프트웨어적 스레드를 수십 개 실행할 수 있습니다. 1 코어 1 스레드 CPU로도 프로그램의 여러 부분을 동시에 실행할 수 있습니다. 만약 스레드의 사전적 정의(실행 흐름의 단위)만을 암기한다면 ‘1코어 1스레드 CPU가 여러 스레드로 만들어진 프로그램을 실행할 수 있다’라는 말이 어려울 겁니다. 이런 이유로 하드웨어적 스레드와 소프트웨어적 스레드는 구분하여 기억하는 것이 좋습니다. 멀티스레드 프로세서 하나의 코어로 여러 명령어를 동시에 처리하는 기술인 하드웨어적 스레드를 “멀티스레드 프로세서” 라고 합니다. 멀티스레드 프로세서는 하나의 코어로 여러 명령어를 동시에 처리하는 CPU라고 했었습니다. 어떨게 이런 일이 가능할까요? “멀티스레드 프로세서” 를 실제로 설계하는 일은 매우 복잡하지만, 가장 큰 핵심은 레지스터입니다. 하나의 코어로 여러 명령어를 동시에 처리하도록 만들려면 프로그램 카운터, 스택 포인터, 메모리 버퍼 레지스터, 메모리 주소 레지스터와 같이 하나의 명령어를 처리하기 위해 꼭 필요한 레지스터를 여러개 가지고 있으면 됩니다. 가열 프로그램 카운터가 두 개 있다면 ‘메모리에서 가져올 명령어 주소’를 두 개 지정할 수 있을 것이고, 스택 포인터가 두 개 있다면 두 개의 스택을 관리할 수 있을것 입니다. 아래의 그림을 봅시다. 하나의 명령어를 실행하기 위해 꼭 필요한 레지스터들을 편의상 ‘레지스터 세트’라고 표기했습니다. 레지스터 세트가 한 개인 CPU는 한 개의 명령어를 처리하기 위한 정보들을 기억할 뿐이지만, 레지스터 세트가 두 개인 CPU는 두 개의 명령어를 처리하기 위한 정보들을 기억할 수 있습니다. 여기서 ALU와 제어장치가 두 개의 레지스터 세트에 저장된 명령어를 해석하고 실행하면 하나의 코어에서 두 개의 명령어가 동시에 실행됩니다. 하드웨어 스레드를 이용해 하나의 코어로도 여러 명령어를 동시에 처리할 수 있습니다. 그러나 메모리 속 프로그램 입장에서 봤을 때 하드웨어 스레드는 마치 ‘한 번에 하나의 명령어를 처리하는 CPU’나 다름없습니다. 가령 2코어 4스레드 CPU는 한 번에 네 개의 명령어를 처리할 수 있는데, 프로그램 입장에서 봤을 땐 한 번에 하나의 명령어를 처리하는 CPU가 네 개 있는 것처럼 보입니다. 그래서 하드웨어 스레드를 논리 프로세서(logical processor) 라고 부르기도 합니다. “코어” 는 명령어를 실행할 수 있는 ‘하드웨어 부품’이고, “스레드” 는 ‘명령어를 실행하는 단위’입니다. “멀티코어 프로세서” 는 명령어를 실행할 수 있는 하드웨어 부품이 CPU 안에 두 개 이상 있는 CPU를 의미하고, “멀티스레드 프로세서” 는 하나의 코어로 여러 개의 명령어를 동시에 실행할 수 있는 CPU를 의미합니다. 키워드로 정리하는 핵심 포인트 클럭 속도가 높은 CPU는 빠르게 작동합니다. 코어 란 CPU 내에서 명령어를 실행하는 부품입니다. 멀티코어 프로세서란 여러 개의 코어를 포함하는 CPU를 말합니다. 스레드에는 하드웨어적 스레드와 소프트웨어적 스레드가 있습니다. 멀티스레드 프로세서란 하나의 코어로 여러 개의 명령어를 동시에 실행할 수 있는 CPU를 말합니다.
Archive
· 2024-04-19
💾 [CS] 명령어 사이클과 인터럽트
명령어 사이클과 인터럽트. 명령어 사이클 : CPU가 하나의 명령어를 처리하는 과정에는 어떤 정해진 흐름이 있고, CPU는 그 흐름을 반복하며 명령어를 처리해 나갑니다. 이렇게 하나의 명령어를 처리하는 정형화된 흐름을 “명령어 사이클” 이라고 합니다. 인터럽트 : CPU는 정해진 흐름에 따라 명령어를 처리해 나가지만, 이 흐름이 끊어지는 상황이 발생합니다. 이를 “인터럽트” 라고 합니다. 명령어 사이클 프로그램은 수많은 명령어로 이루어져있고, CPU는 이 명령어들을 하나씩 실행합니다. 이때 프로그램 속 각각의 명령어들은 일정한 주기가 반복되며 실행되는데, 이 주기를 명령어 사이클(instruction cycle) 이라고 합니다. 즉, 프로그램 속 각각의 명령어들은 명령어 사이클이 반복되며 실행됩니다. 메모리에 저장된 명령어 하나를 실행한다고 가정해 봅시다. 가장 먼저 해야할 것은 명령어를 메모리에서 CPU로 가져와야 합니다. 이게 명령어 사이클의 첫 번째 과정입니다. 인출 사이클(fetch cycle) : 메모리에 있는 명령어를 CPU로 가지고 오는 단계. CPU로 명령어를 인출했다면 이제 명령어를 실행합니다. 이것이 명령어 사이클의 두 번째 과정입니다. 실행 사이클(execution cycle) : CPU로 가져온 명령어를 실행하는 단계, 제어장치가 명령어 레지스터에 담긴 값을 해석하고, 제어 신호를 발생시키는 단계. 프로그램을 이루는 수많은 명령어는 일반적으로 인출과 실행 사이클을 반복하며 실행됩니다. 즉, CPU는 프로그램 속 명령어를 가져오고 실행하고, 또 가져오고 실행하고를 반복하는 것입니다. 하지만 모든 명령어가 이렇게 간단히 실행되는 건 아닙니다. 명령어를 인출하여 CPU로 가져왔다하더라도 곧바로 실행할 수 없는 경우도 있기 때문입니다. 예를 들어 간접 주소 지정 방식을 생각해 봅시다. 간접 주소 지정 방식은 오퍼랜드 필드에 유효 주소의 주소를 명시한다고 했습니다. 이 경우 명령어를 인출하여 CPU로 가져왔다 하더라도 바로 실행 사이클에 돌입할 수 없습니다. 명령어를 실행하기 위해서는 메모리 접근을 한 번 더 해야 하기 때문입니다. 이 단계를 간접 사이클(indirect cycle) 이라고 합니다. 인터럽트. 프로그램을 개발하다 보면 아래 인터럽트라는 단어를 쉽게 접할 수 있습니다. 인터럽트는 영어로 interrupt이며, ‘방해하다, 중단시키다’를 의미합니다. 즉, CPU가 수행 중인 작업은 방해를 받아 잠시 중단될 수 있는데, 이렇게 CPU의 작업을 방해하는 신호를 인터럽트(interrupt) 라고 합니다. CPU가 작업을 잠시 중단해야 할 정도라면 인터럽트는 ‘CPU가 꼭 주목해야 할 때’ 혹은 ‘CPU가 얼른 처리해야 할 다른 작업이 생겼을 때’ 발생합니다. 인터럽트의 종류에는 크게 동기 인터럽트와 비동기 인터럽트가 있습니다. 동기 인터럽트(synchronous interrupt) : CPU에 의해 발생하는 인터럽트입니다. CPU가 명령어들을 수행하다가 예상치 못한 상황에 마주쳤을 때, 가령 CPU가 실행하는 프로그래밍상의 오류와 같은 예외적인 상황에 마추쳤을 때 발생하는 인터럽트입니다. 이런 점에서 동기 인터럽트는 예외(execption) 라고 부릅니다. 비동기 인터럽트(asynchronous interrupt) : 주로 입출력장치에 의해 발생하는 인터럽트입니다. 입출력장치에 의한 비동기 인터럽트는 세탁기 완료 알리므 전자레인지 조리 완료 알림과 같은 알림 역할을 합니다. 구체적으로 다음과 같이 사용됩니다. CPU가 프린터와 같은 입출력장치에 입출력 작업을 부탁하면 작업을 끝낸 입출력장치가 CPU에 완료 알림(인터럽트)을 보냅니다. 키보드, 마우스와 같은 입출력 장치가 어떠한 입력을 받아들였을 때 이를 처리하기 위해 CPU에 입력 알림(인터럽트)을 보냅니다. 하드웨어 인터럽트 하드웨어 인터럽트는 알림과 같은 인터럽트 입니다. CPU는 입출력 작업 도중에도 효율적으로 명령어를 처리하기 위해 이런 알림과 같은 하드웨어 인터럽트를 사용합니다. 하드웨어 인터럽트를 이용하면 CPU는 주기적으로 하드웨어 완료 여부를 확인할 필요가 없습니다. CPU는 하드웨어로부터 하드웨어 완료 인터럽트를 받을 때까지 다른 작업을 처리할 수 있습니다. 이렇듯 하드웨어 인터럽트는 입출력 작업 중에도 CPU로 하여금 효율적으로 명령어를 처리할 수 있게 합니다. 하드웨어 인터럽트 처리 순서 입출력장치는 CPU에 인터럽트 요청 신호를 보냅니다. CPU는 실행 사이클이 끝나고 명령어를 인출하기 전 항상 인터럽트 여부를 확인합니다. CPU는 인터럽트 요청을 확인하고 인터럽트 플래그를 통해 현재 인터럽트를 받아들일 수 있는지 여부를 확인합니다. 인터럽트를 받아들일 수 있다면 CPU는 지금까지의 작업을 백업합니다. CPU는 인터럽트 백터를 참조하여 인터럽트 서비스 루틴을 실행합니다. 인터럽트 서비스 루틴이 끝나면 4에서 백업해 둔 작업을 복구하여 실행을 재개합니다. 인터럽트 요청 신호 : 인터럽트는 CPU의 정상적인 실행 흐름을 끊는 것이기에 다른 누군가가 인터럽트하기 전에 “지금끼어들어도 되나요?” 하고 CPU에 물어봐야 합니다. 이를 인터럽트 요청 신호라고 합니다. 이때, CPU가 인터럽트 요청을 수용하기 위해서는 플래그 레지스터의 인터럽트 플래그(interrupt flag) 가 활성화되어 있어야 합니다. 인터럽트 플래그는 말 그래도 하드웨어 인터럽트를 받아들일지, 무시할지를 결정하는 플래그입니다. CPU가 중요한 작업을 처리해야 하거나 어떤 방해도 받지 않아야 할 때 인터럽트 플래그는 불가능으로 설정됩니다. 만약 인터럽트 플래그가 ‘불가능’으로 설정되어 있다면 CPU는 인터럽트 요청이 오더라도 해당 요청을 무시합니다. 반대로 인터럽트 플래그가 ‘가능’으로 설정되어 있다면 CPU는 인터럽트 요청 신호를 받아들이고 인터럽트를 처리합니다. 다만, 모든 하드웨어 인터럽트를 인터럽트 플래그로 막을 수 있는 것은 아닙니다. 인터럽트 플래그가 불가능으로 설정되어 있을지라도 무시할 수 없는 인터럽트 요청도 있습니다. 무시할 수 없는 하드웨어 인터럽트 가장 우선순위가 높은, 다시 말해 반드시 가장 먼저 처리해야 하는 인터럽트입니다. 정전이나 하드웨어 고장으로 인한 인터럽트가 이에 해당합니다. CPU가 인터럽트 요청을 받아들이기로 했다면 CPU는 서비스 루틴이라는 프로그램을 실행합니다. 인터럽트 서비스 루틴(ISB: Interrupt Service Routine): 인터럽트를 처리하기 위한 프로그램. 인터럽트 핸들러(Interrupt handler) 라고도 불립니다. 어떤 인터럽트가 발생했을 때 해당 인터럽트를 어떻게 처리하고 작동해야 할지에 대한 정보로 이루어진 프로그램입니다. 요컨태 ‘CPU가 인터럽트를 처리한다’는 말은 ‘인터럽트 서비스 루틴을 실행하고, 본래 수행하던 작업으로 다시 되돌아온다’ 라는 말과 같습니다. 인터럽트를 처리하는 방법은 입출력장치마다 다르므로 각기 다른 인터럽트 서비스 루틴을 가지고 있습니다. 즉, 메모리에는 위 그림처럼 여러 개의 인터럽트 서비스 루틴이 저장되어 있습니다. 이들 하나하나가 ‘인터럽트가 발생하면 어떻게 행동해야 할지를 알려주는 프로그램’이라고 보면 됩니다. 인터럽트 벡터(Interrupt vector) : CPU는 수많은 인터럽트 서비스 루틴을 구분하기 위해 인터럽트 벡터를 이용합니다. 인터럽트 서비스 루틴을 식별하기 위한 정보입니다. 인터럽트 벡터를 알면 인터럽트 서비스 루틴의 시작 주소를 알 수 있기 때문에 CPU는 인터럽트 벡터를 통해 특정 인터럽트 서비스 루틴을 처음부터 실행할 수 있습니다. CPU는 하드웨어 인터럽트 요청을 보낸 대상으로부터 데이터 버스를 통해 인터럽트 벡터를 전달받습니다. 가령, CPU가 작업을 수행하는 도중 키보드 인터럽트가 발생한 경우라면 CPU는 인터럽트 벡터를 참조하여 키보드 인터럽트 서비스 루틴의 시작 주소를 알아내고, 이 시작 주소부터 실행해 나가며 키보드 인터럽트 서비스 루틴을 실행합니다. 정리하면 ‘CPU가 인터럽트를 처리한다’는 말은 ‘인터럽트 서비스 루틴을 실행하고, 본래 수행하던 작업으로 다시 되돌아온다’는 말과 같습니다. 그리고 CPU가 인터럽트 서비스 루틴을 실행하려면 인터럽트 서비스 루틴의 시작 주소를 알아야 하는데, 이는 인터럽트 벡터를 통해 알 수 있습니다. 인터럽트 서비스 루틴은 여느 프로그램과 마찬가지로 명령어와 데이터로 이루어져 있습니다. 그렇기에 인터럽트 서비스 루틴도 프로그램 카운터를 비롯한 레지스터들을 사용하며 실행됩니다. 그럼, 인터럽트가 발생하기 전까지 레지스터에 저장되어 있던 값들은 어떻게 할까요? 인터럽트 요청을 받기 전까지 CPU가 수행하고 있었던 일은 인터럽트 서비스 루틴이 끝나면 되돌아와서 마저 수행을 해야 하기 때문에 지금까지의 작업 내역들은 어딘가에 백업을 해둬야 합니다. 그렇기에 CPU는 인터럽트 서비스 루틴을 실행하기 전에 프로그램 카운터 값 등 현재 프로그램을 재개하기 위해 필요한 모든 내용을 스택에 백업합니다. 그러고 나서 인터럽트 서비스 루틴의 시작 주소가 위치한 곳으로 프로그램 카운터 값을 갱신하고 인터럽트 서비스 루틴을 실행합니다. 인터럽트 서비스 루틴을 모두 실행하면, 다시 말해 인터럽트를 처리하고 나면 스택에 저장해 둔 값을 다시 불러온 뒤 이전까지 수행하던 작업을 재개합니다. 키워드 정리 인터럽트 요청 신호 : CPU의 작업을 방해하는 인터럽트에 대한 요청 인터럽트 플래그 : 인터럽트 요청 신호를 받아들일지 무시할지를 결정하는 비트 인터럽트 벡터 : 인터럽트 서비스 루틴의 시작 주소를 포함하는 인터럽트 서비스 루틴의 식별 정보 인터럽트 서비스 루틴 : 인터럽트를 처리하는 프로그램 CPU는 이와 같은 과정을 반복해 나가며 프로그램을 실행한다고 볼 수 있습니다. 키워드로 정리하는 핵심 포인트 명령어 사이클은 하나의 명령어가 처리되는 주기로, 인출, 실행, 간접, 인터럽트 사이클로 구성되어 있습니다. 인터럽트 는 CPU의 정상적인 작업을 방해하는 신호입니다. 인터럽트의 종류에는 예외와 하드웨어 인터럽트가 있습니다. 인터럽트 서비스 루틴은 인터럽트를 처리하기 위한 동작들로 이루어진 프로그램입니다.
Archive
· 2024-04-15
💾 [CS] 레지스터
레지스터. 프로그램 속 명령어와 데이터는 실행 전후로 반드시 레지스터에 저장됩니다. 따라서 레지스터에 저장된 값만 잘 관찰해도 프로그램의 실행 흐름을 파악할 수 있습니다 다시 말해 레지스터 속 값을 유심히 관찰하면 프로그램을 실행할 때 CPU 내에서 무슨 일이 벌어지고 있는지, 어떤 명령어가 어떻게 수행되는지 알 수 있습니다. 반드시 알아야 할 레지스터. 프로그램 카운터 명령어 레지스터 메모리 주소 레지스터 메모리 버퍼 레지스터 플래스 레지스터 범용 레지스터 스택 포인터 베이스 레지스터 프로그램 카운터. 프로그램 카운터(PC: Program Counter) : 메모리에서 가져올 명령어의 주소, 즉 메모리에서 읽어 들일 명령어의 주소를 저장합니다. 프로그램 카운터를 명령어 포인터(IP: Instruction Pointer) 라고 부르는 CPU도 있습니다. 명령어 레지스터. 명령어 레지스터(IR: Instruction Register) : 해석할 명령어, 즉 방금 메로미에서 읽어 들인 명령어를 저장하는 레지스터입니다. 제어장치는 명령어를 레지스터 속 명령어를 받아들이고 이를 해석한 뒤 제어 신호를 내보냅니다. 메모리 주소 레지스터. 메모리 주소 레지스터(MAR: Memory Address Register) : 메모리 주소를 저장하는 레지스터입니다. CPU가 읽어 들이고자 하는 주소 값을 주소 버스로 보낼 때 메모리 주소 레지스터를 거치게 됩니다. 메모리 버퍼 레지스터. 메모리 버퍼 레지스터(MBR: Memory buffer register) : 메모리와 주고받을 값(데이터와 명령어)을 저장하는 레지스터입니다. 즉, 메모리에 쓰고 싶은 값이나 메모리로부터 전달받은 값은 메모리 버퍼 레지스터를 거칩니다. CPU가 주소 버스로 내보낼 값이 메모리 주소 레지스터를 거친다면, 데이터 버스로 주고 받을 값은 메모리 버퍼 레지스터를 거칩니다. 메모리 버퍼 레지스터는 메모리 데이터 레지스터(MDR: Memory Data Register)라고도 불립니다. 메모리에 저장된 프로그램을 실행하는 과정에서 프로그램 카운터, 명령어 레지스터, 메모리 주소 레지스터, 메모리 버퍼 레지스터에 어떤 값들이 담기는지 알아봅시다. 1. CPU로 실행할 프로그램이 1000번지부터 1500번지까지 저장되어 있다고 가정하겠습니다, 그리고 1000번지에는 1101₍₂₎이 저장되어 있다고 가정하겠습니다. 2. 프로그램을 처음부터 실행하기 위해 프로그램 카운터에는 1000이 저장됩니다. 이는 메모리에서 가져올 명령어가 1000번지에 있다는 걸 의미합니다. 3. 1000번지를 읽어 들이기 위해서는 주소 버스로 100번지를 내보내야 합니다. 이를 위해 메모리 주소 레지스터에는 1000이 저장됩니다. 4. ‘메모리 읽기’ 제어 신호와 메모리 주소 레지스터 값이 각각 제어 버스와 주소 버스를 통해 메모리로 보내집니다. 5. 메모리 1000번지에 저장된 값은 데이터 버스를 통해 메모리 버퍼 레지스터로 전달되고, 프로그램 카운터는 증가되어 다음 명령어를 읽어 들일 준비를 합니다. 6. 메모리 버퍼 레지스터에 저장된 값은 명령어 레지스터로 이동합니다. 7. 제어장치는 명령어 레지스터의 명령어를 해석하고 제어 신호를 발생시킵니다. 5단계에서 프로그램 카운터 값이 증가한 것을 확인했습니다. 프로그램 카운터 값이 증가했으니 1000번지 명령어 처리가 끝나면 CPU는 다음 명령어(1001번지)를 읽어 들입니다. 이처럼 프로그램 카운터는 지속적으로 증가하며 계속해서 다음 명령어를 읽어 들일 준비를 합니다. 이 과정이 반복되면서 CPU는 프로그램을 차례대로 실행해 나갑니다. 결국 CPU가 메모리 속 프로그램을 순차적으로 읽어 들이고 실행해 나갈 수 있는 이유는 CPU 속 프로그램 카운터가 꾸준히 증가하기 때문입니다. 범용 레지스터 범용 레지스터(general purpose register) : 다양하고 일반적인 상황에서 자유롭게 사용할 수 있는 레지스터입니다. 메모리 버퍼 레지스터는 테이터 버스로 주고받을 값만 저장하고, 메모리 주소 레지스터는 주소 버스로 내보낼 주소값만 저장하지만, 범용 레지스터는 데이터와 주소를 모두 저장할 수 있습니다. 일반적으로 CPU 안에는 여러 개의 범용 레지스터들이 있고, 현대 대다수 CPU는 모두 범용 레지스터를 가지고 있습니다. 플레그 레지스터 플래그 레지스터(Flag register) : 연산 결과 또는 CPU 상태에 대한 부가적인 정보를 저장하는 레지스터입니다. 특정 레지스터를 이용한 주소 지정 방식(1): 스택 주소 지정 방식. 스택 주소 지정 방식 : 스택과 스택 포인터를 이용한 주소 지정 방식 스택은 한쪽 끝이 막혀 있는 통과 같은 저장 공간입니다. 그래서 스택은 가장 최근에 저장하는 값부터 꺼낼 수 있습니다. 여기서 스택 포인터란 스택의 꼭대기를 가리키는 레지스터입니다. 즉, 스택 포인터는 스택에 마지막으로 저장한 값의 위치를 저장하는 레지스터입니다. 예를 들어 봅시다. 가령 다음과 같이 위에서부터 주소가 매겨져 있고 아래부터 차곡차곡 데이터가 저장되어 있는 스택이 있다고 가정해봅시다. 이때 스택 포인터는 스택의 제일 꼭대기 주소, 즉 4번지를 저장하고 있습니다. 이는 ‘스택 포인터가 스택의 꼭대기를 가리키고 있다’고 볼 수 있겠죠. 쉽게 말해, 스택 포인터는 스택의 어디까지 데이터가 캐워져 있는지에 대한 표시라고 보면 됩니다. 그럼 이 스택에서 데이터를 꺼낼 때는 어떤 데이터부터 꺼내게 될까요? 1 -> 2 -> 3 순서대로 꺼낼 수 있습니다. 여기서 하나의 데이터를 꺼내면 스택에는 2와 3이 남고, 스택의 꼭대기 주소가 달라졌기 때문에 스택 포인터는 5번지를 가리킵니다. 반대로 스택에 데이터를 추가한다면 어떻게 될까요? 현재 스텍세 4라는 데이터를 저장하면 스택의 꼭대기에 4가 저장됩니다. 이때 스택의 꼭대기 주소가 달라졌기 때문에 스택 포인터는 4번지를 가리킵니다. 그런데 스택이라는 것은 도대체 어디에 있는 걸까요? 스택은 메모리 안에 있습니다. 정확히는 메모리 안에 스택처럼 사용할 영역이 정해져 있습니다. 이를 스택 영역이라고 합니다. 이 영역은 다른 주소 공간과는 다르게 스택처럼 사용하기 암묵적으로 약속된 영역입니다. 특정 레지스터를 이용한 주소 지정 방식(2): 변위 주소 지정 방식 변위 주소 지정 방식(displacement addressing mode) : 오퍼랜드 필드의 값(변위)과 특정 레지스터의 값을 더하여 유효 주소를 얻어내는 주소 지정 방식입니다. 그래서 변위 주소 지정방식을 사용하는 명령어는 다음 그림과 같이 연산 코드 필드, 어떤 레지스터의 값과 더할지를 나타내는 레지스터 필드, 그리고 주소를 담고있는 오퍼랜드 필드가 있습니다. 이때, 변위 주소 지정 방식은 오퍼랜드 필드의 주소와 어떤 레지스터를 더하는지에 따라 상대 주소 지정 방식, 베이스 레지스터 주소 지정 방식 등으로 나뉩니다. 상대 주소 지정 방식 상대 주소 지정 방식(relative addressing mode) : 오퍼랜드와 프로그램 카운터와 값을 더하여 유효 주소를 얻는 방식입니다. 프로그램 카운터에는 읽어 들일 명령어의 주소가 저장되어 있습니다. 만약 오퍼랜드가 음수, 가령 -3이였다면 CPU는 읽어 들이기로 한 명령어로부터 ‘세 번째 이전’ 번지로 접근합니다. 한마디로 실행하려는 명령어의 세 칸 이전 번지 명령어를 실행하는 것이지요 반면, 오퍼랜드가 양수, 가열 3이었다면 CPU는 읽어 들이기로 했던 명령어의 ‘세 번째 이후’ 번지로 접근합니다. 즉, 실행하려는 명령어에서 세 칸 건너뛴 번지를 실행하는 겁니다. 상대 주소 지정 방식은 프로그래밍 언어의 if문과 유사하게 모든 코드를 실행하는 것이 아닌, 분기하여 특정 주소의 코드를 실행할 때 사용됩니다. 베이스 레지스터 주소 지정 방식 베이스 레지스터 주소 지정 방식(base-register addressing mode) : 오퍼랜드와 베이스 레지스터의 값을 더하여 유효 주소를 얻는 방식입니다. 여기서 베이스 레지스터는 ‘기준 주소’, 오퍼랜드는 ‘기준 주소로부터 떨어진 거리’로서의 역할을 합니다. 즉, 베이스 레지스터 주소 지정 방식은 베이스 레지스터 속 기준 주소로부터 얼마나 떨어져 있는 주소에 접근할 것인지를 연산하여 유효 주소를 얻어내는 방식입니다. 가령 베이스 레지스터에 200이라는 값이 있고 오퍼랜드가 40이라면 이는 “기준 주소 200번지로부터 40만큼 떨어진 240번지로 접근하라”를 의미합니다. 또 베이스 레지스터에 550이라는 값이 담겨 있고 오퍼랜드가 50이라면 이는 “기준 주소 550번지로부터 50만큼 떨어진 600번지로 접근하라”를 의미하는 명령어 입니다. 키워드로 정리하는 핵심 포인트 프로그램 카운터 는 메모리에서 가져올 명령어의 주소, 명령어 레지스터는 해석할 명령어를 저장합니다. 메모리 주소 레지스터는 메모리의 주소, 메모리 버퍼 레지스터는 메모리와 주고받을 데이터를 저장합니다. 범용 레지스터는 데이터와 주소를 모두 저장하고, 플래그 레지스터는 연산 결과 혹은 CPU 상태에 대한 부가 정보를 저장합니다. 스택 포인터는 스택 최상단의 위치를 저장합니다. 베이스 레지스터에 저장된 주소는 기준 주소로서의 역할을 합니다. 더 알아보기 Jump Jump 명령어는 프로그램의 실행 흐름을 끊고 지정된 주소로 점프합니다. Conditional Jump Conditional Jump 명령어는 특정 조건이 충족될 때에만 주어진 주소로 점프합니다. Call Call 명령어는 현재 위치를 저장하고 지정된 주소로 이동합니다. 현재 위치를 저장하기 위해 스택을 사용합니다. 주로 서브루틴(하위 루틴 또는 함수)을 호출할 때 사용됩니다. Return Return 명령어는 서브루틴에서 호출자로 복귀합니다. 호출된 서브루틴이 실행을 마치고 호출자로 돌아갈 때 사용됩니다.
Archive
· 2024-04-11
💾 [CS] ALU와 제어장치
ALU와 제어장치. CPU: 메모리에 저장된 명령어를 읽어 들이고, 해석하고, 실행하는 장치 ALU: CPU 내부에 계산을 담당 레지스터: 명령어를 읽어 들이고 해석하는 제어장치, 작은 임시 저장 장치 ALU ALU: 레지스터를 통해 피연산자 를 받아들이고, 제어장치로부터 수행할 연산을 알려주는 제어 신호 를 받아 들입니다. 레지스터와 제어장치로부터 받아들인 피연산자와 제어 신호로 산술 연산, 논리 연산 등 다양한 연산을 수행합니다. ALU가 내보내는 정보. 연산을 수행한 결과는 특정 숫자나 문자가 될 수도 있고, 메모리 주소가 될 수도 있습니다. 그리고 이 결괏값은 바로 메모리에 저장되지 않고 일시적으로 레지스터에 저장됩니다. CPU가 메모리에 접근하는 속도는 레지스터에 접근하는 속도보다 훨씬 느립니다. ALU가 연산할 때마다 결과를 메모리에 저장한다면 당연하게도 CPU는 메모리에 자주 접근하게 되고, 이는 CPU가 프로그램 실행 속도를 늦출 수 있습니다. 그래서 ALU의 결괏값을 메모리가 아닌 레지스터에 우선 저장하는 것 입니다. ALU는 계산 결과와 더불어 플래그를 내보냅니다. ALU는 결괏값뿐만 아니라 연산 결과에 대한 추가적인 정보를 내보내야 할 때가 있습니다. 연산 결과에 대한 추가적인 상태 정보를 플래그(flag) 라고 합니다. ALU가 내보내는 대표적인 플래그는 아래와 같습니다. 이러한 플래그는 CPU가 프로그램을 실행하는 도중 반드시 기억해야 하는 일종의 참고 정보입니다. 플래그들은 플래그 레지스터 라는 레지스터에 저장됩니다. 플래그 값들을 저장하는 레지스터입니다. 이 레지스터를 읽으면 연산 결과에 대한 추가적인 정보, 참고 정보를 얻을 수 있습니다. 플레그 레지스터 예시와 설명. 예를 들어 플래그 레지스터가 아래와 같은 구조를 가지고 있고, ALU가 연산을 수행한 직후 부호 플래그가 1이 되었다면 연산 결과는 음수임을 알 수 있습니다. 또한 만약 ALU가 연산을 수행한 직후 플래그 레지스터가 아래와 같다면 제로 플래그가 1이 되었으니 연산 결과는 0임을 알 수 있습니다. 이 밖에도 ALU 내부에는 여러 계산을 위한 회로들이 있습니다. 대표적으로 덧셈을 위한 가산기 뺄셈을 위한 보수기 시프트 연산을 수행해 주는 시프터 오버플로우를 대비한 오버플로우 검출기 등등 제어장치. 제어장치: 제어 신호를 내보내고, 해석하는 부품 제어 신호: 컴퓨터 부품들을 관리하고 작동시키기 위한 일종의 전기 신호 제어장치가 받아들이는 정보. 첫째. 제어장치는 클럭 신호를 받아들입니다. 클럭(Clock): 컴퓨터의 모든 부품을 일사분란하게 움직일 수 있게하는 시간 단위 클럭의 주기에 맞춰 한 레지스터에서 다른 레지스터로 데이터가 이동되거나, ALU에서 연산이 수행되거나, CPU가 메모리에 저장된 명령어를 읽어 들어는 것 입니다. 다만, “컴퓨터의 모든 부품이 클럭 신호에 맞춰 작동한다” 라는 말을 “컴퓨터의 모든 부품이 한 클럭마다 작동한다”라고 이해하면 안됩니다. 컴퓨터 부품들은 클럭이라는 박자에 맞춰 작동할 뿐 한 박자마다 작동하는 건 아닙니다. 가령 다음 그림처럼 하나의 명령어가 여러 클럭에 걸쳐 실행될 수 있습니다. 둘째, 제어장치는 ‘해석해야 할 명령어’를 받아들입니다. CPU가 해석해야 할 명령어는 명령어 레지스터 라는 특별한 레지스터에 저장됩니다. 제어장치는 이 명령어 레지스터로부터 해석할 명령어를 받아들이고 해석한 뒤, 제어 신호를 발생시켜 컴퓨터 부품들에 수행해야 할 내용을 알려줍니다. 셋째, 제어장치는 플래그 레지스터 속 플래그 값을 받아들입니다. 플래그는 ALU 연산에 대한 추가적인 상태 정보입니다. 제어장치는 플래그 값을 받아들이고 이를 참고하여 제어 신호를 발생 시킵니다. 넷째, 제어장치는 시스템 버스, 그중에서 제어 버스로 전달된 제어 신호를 받아들입니다. 제어 신호는 CPU뿐만 아니라 입출력장치를 비롯한 CPU 외부 장치도 발생시킬 수 있습니다. 제어장치는 제어 버스를 통해 외부로부터 전달된 제어 신호를 받아들이기도 합니다. 제어장치가 내보내는 정보. 여기에는 크게 CPU 외부에 전달하는 제어 신호와 CPU 내부에 전달하는 제어 신호가 있습니다. 제어장치가 CPU 외부에 제어 신호를 전달한다는 말은 곧, 제어 버스로 제어 신호를 내보낸다는 말과 같습니다. 이러한 제어 신호에는 크게 메모리에 전달하는 제어 신호와 입출력장치에 전달하는 제어 신호가 있습니다. 제어장치가 메모리에 저장된 값을 읽거나 메모리에 새로운 값을 쓰고 싶다면 메모리로 제어 신호를 내보냅니다. 그리고 제어장치가 입출력장치의 값을 읽거나 입출력장치에 새로운 값을 쓰고 싶을 때는 입출력장치로 제어 신호를 내보냅니다. 제어장치가 CPU 내부에 전달하는 제어 신호에는 크게 ALU에 전달하는 제어 신호와 레지스터에 전달하는 제어 신호가 있습니다. ALU에는 수행할 연산을 지시하기 위해, 레지스터에는 레지스터 간에 데이터를 이동시키거나 레지스터에 저장된 명령어를 해석하기 위해 제어 신호를 내보냅니다. 키워드로 정리하는 핵심 포인트 ALU는 레지스터로부터 피연산자를 받아들이고, 제어장치로부터 제어 신호를 받아들입니다. ALU는 연산 결과와 플래그를 내보냅니다. 제어장치는 클럭, 현재 수행할 명령어, 플래그, 제어 신호를 받아들입니다. 제어장치는 CPU 내부와 외보루 제어 신호 를 내보냅니다. check point 이진수의 음수표현 2의 보수: 모든 0과 1을 뒤집고, 거기에 1을 더한 값 Q1. ALU가 소프트웨어 개발, 특히 iOS 개발에 어떻게 적용될 수 있는지 설명해 주세요. 예를 들어, 어떻게 ALU가 앱의 성능에 영향을 미칠 수 있는지 구체적인 사례를 들어주세요. iOS 앱 개발에서 ALU의 역할은 직접적으로 보이지 않지만, 앱의 성능 최적화에 중요합니다. 예를 들어, 이미지 처리나 데이터 암호화 같은 작업은 많은 산술 및 논리 연산을 필요로 하며, 이는 ALU에서 처리됩니다. 따라서, ALU의 효율적인 사용은 앱의 반응 속도와 전반적인 성능에 직접적인 영향을 미칩니다. Q2. ALU(산술 논리 장치)의 기본적인 기능은 무엇이며, 컴퓨터 프로세서 내에서 어떤 역할을 합니까? ALU는 컴퓨터의 프로세서 내에 있는 하드웨어 구성 요소로, 기본적인 산술 연산(덧셈, 뺄셈, 곱셈, 나눗셈)과 논리 연산(AND, OR, XOR, NOT)을 수행합니다. 이는 모든 종류의 컴퓨터 프로그램 실행에 기본이 되는 연산이며, 프로세서가 복잡한 계산과 데이터 처리 작업을 수행할 수 있게 해줍니다. Q3. Java 애플리케이션의 성능 최적화와 관련하여, ALU의 역할과 중요성에 대해 설명해 주세요. Java 애플리케이션의 성능 최적화에서 ALU의 역할은 중요합니다. ALU는 계산 작업의 실제 수행 장소이므로, ALU의 효율성은 애플리케이션의 처리 속도와 직접적인 관련이 있습니다. 특히, 고성능을 요구하는 애플리케이션에서는 ALU를 통해 수행되는 연산의 최적화가 애플리케이션 전체의 성능을 크게 향상시킬 수 있습니다. Q4. 멀티 쓰레딩 Java 애플리케이션에서 ALU의 처리 능력이 중요한 이유는 무엇이라고 생각하나요? 멀티 쓰레딩 애플리케이션에서는 여러 쓰레드가 동시에 연산을 수행할 수 있으므로, ALU의 처리 능력이 성능의 병목 현상을 방지하는 데 중요합니다. 효율적인 ALU 설계는 복수의 연산을 동시에 빠르게 처리할 수 있게 해주며, 이는 멀티 쓰레딩 환경에서 애플리케이션의 반응 속도와 처리량을 크게 향상시킬 수 있습니다. Q5. 현대의 CPU가 여러 ALU를 갖고 있는 경우, 이것이 Java 백엔드 시스템의 성능에 어떤 영향을 미칠 수 있나요? 여러 ALU를 갖는 프로세서는 동시에 여러 연산을 수행할 수 있으므로, Java 백엔드 시스템에서의 병렬 처리 능력을 크게 향상시킵니다. 이는 데이터베이스 쿼리 처리, 대규모 데이터 분석, 실시간 트랜잭션 처리 등 다양한 작업에서 성능 이점을 제공할 수 있습니다. Q6. Java 애플리케이션에서 복잡한 수학적 연산을 효율적으로 처리하기 위해 개발자가 고려해야 할 ALU와 관련된 측면은 무엇인가요? 개발자는 복잡한 수학적 연산을 효율적으로 처리하기 위해, ALU의 연산 처리 능력을 최대화하는 방법을 고려해야 합니다. 이는 알고리즘의 최적화, 복잡한 연산의 분할 및 정복 전략 적용, 필요한 경우 하드웨어 가속기(예: GPU) 사용 등을 포함할 수 있습니다. Q7. ALU의 한계를 넘어서서 Java 애플리케이션의 성능을 향상시키기 위해 사용할 수 있는 다른 하드웨어 기반 최적화 기술은 무엇이 있을까요? ALU의 한계를 넘어서 Java 애플리케이션의 성능을 향상시키기 위해, 다중 코어 프로세싱, 병렬 처리, GPU 가속, FPGA(필드 프로그래밍 게이트 어레이)를 활용한 커스텀 하드웨어 가속 등의 기술을 활용할 수 있습니다. 이러한 기술들은 특정 유형의 작업에 대해 상당한 성능 향상을 제공할 수 있습니다.
Archive
· 2024-04-08
💾 [CS] 명령어의 구조
명령어의 구조 연산코드와 오퍼랜드 아래 그림을 보면 색 배경 필드는 명령의 ‘작동’, 달리 말해 ‘연산’을 담고 있고 흰색 배경 필드는 ‘연산에 사용할 데이터’ 또는 ‘연산에 사용할 데이터가 저장된 위치’를 담고 있습니다. 명령어 : 연산 코드와 오퍼랜드로 구성되어 있습니다. 연산코드(Opreation Code): 색 배경 필드 값, 즉 ‘명령어가 수행할 연산’을 연산코드(Operation Code) 라 합니다. 오퍼랜드(Operand) : 흰색 배경 필드 값, 즉 ‘연산에 사용할 데이터’ 또는 ‘연산에 사용할 데이터가 저장된 위치’를 오퍼랜드라고 합니다. 연산 코드는 연산자, 오퍼랜드는 피연산자 라고도 부릅니다. 연산 코드 필드: 연산 코드가 담기는 영역(색칠된 부분) 오퍼랜드 필드: 오퍼랜드가 담기는 영역(색칠되지 않은 부분) 오퍼랜드 오퍼랜드는 ‘연산에 사용할 데이터’ 또는 ‘연산에 사용할 데이터가 저장된 위치’를 의미합니다. 그래서 오퍼랜드 필드에는 숫자와 문자 등을 나타내는 데이터 또는 메모리나 레지스터 주소가 올 수 있습니다. 다만 오퍼랜드 필드에는 숫자나 문자와 같이 연산에 사용할 데이터를 직접 명시하기보다는, 많은 경우 연산에 사용할 데이터가 저장된 위치, 즉 메모리 주소나 레지스터 이름이 담깁니다. 그래서 오퍼랜드 필드를 주소 필드 라고 부르기도 합니다. 오퍼랜드는 명령어 안에 하나도 없을 수도 있고, 한 개만 있을 수도 있고, 두 개 또는 세 개 등 여러개가 있을 수도 있습니다. 오퍼랜드가 하나도 없는 명령어 0-주소 명령어 오퍼랜드가 하나인 명령어 1-주소 명령어 오퍼랜드가 두 개인 명령어 2-주소 명령어 오퍼랜드가 세 개인 명령어 3-주소 명령어 연산 코드 연산 코드 종류는 매우 많지만, 가장 기본적인 연산 코드 유형은 크게 네 가지로 나눌 수 있습니다. 데이터 전송 산술/논리 연산 제어 흐름 변경 입출력 제어 주소 지정 방식 연산 코드에 사용할 데이터가 저장된 위치, 즉 연산의 대상이 되는 데이터가 저장된 위치를 유효 주소(effective address) 라고 합니다. 오퍼랜드 필드에 데이터가 저장된 위피를 명시 할 때 연산에 사용할 데이터 위치를 찾는 방법을 주소 지정 방식(addressing mode) 이라고 합니다 다시 말해, 주소 지정 방식은 유효 주소를 찾는 방법입니다. 즉시 주소 지정 방식 즉시 주소 지정 방식(immediate addressing mode): 연산에 사용할 데이터를 오퍼랜드 필드에 직접 명시하는 방식입니다. 이런 방식은 표현할 수 있는 데이터의 크기가 작아지는 단점이 있지만, 연산에 사용할 데이터를 메모리나 레지스터로부터 찾는 과정이 없기 때문에 이하 설명할 주소 지정 방식들보다 빠릅니다. 직접 주소 지정 방식 직접 주소 지정 방식(direct addressing mode): 오퍼랜드 필드에 유효 주소를 직접 명시하는 방식입니다. 오퍼랜드 필드에서 표현할 수 있는 데이터의 크기는 즉시 주소 지정 방식보다 더 커졌지만, 여전히 유효 주소를 표현할 수 있는 범위가 연산 코드의 비트 수만큼 줄어들었습니다. 다시 말해 표현할 수 있는 오퍼랜드 필드의 길이가 연산 코드의 길이만큼 짧아져 표현할 수 있는 유효 주소에 제한이 생길 수 있습니다. 간접 주소 지정 방식 간접 주소 지정 방식(indirect addressing mode): 유효 주소의 주소를 오퍼랜드 필드에 명시합니다. 직접 주소 지정 방식보다 표현할 수 있는 유효 주소의 범위가 더 넓습니다. 두 번의 메모리 접근이 필요하기 때문에 앞서 설명한 주소 지정 방식들보다 일반적으로 느린 방식입니다. 레지스터 주소 지정 방식 레지스터 주소 지정 방식(register addressing mode): 직접 주소 지정 방식과 비슷하게 연산에 사용할 데이터를 저장한 레지스터를 오퍼랜드 필드에 직접 명시하는 방법입니다. 일반적으로 CPU 외부에 있는 메모리에 접근하는 것보다 CPU 내부에 있는 레지스터에 접근하는 것이 더 빠릅니다. 그러므로 레지스터 주소 지정 방식은 직접 주소 지정 방식보다 빠르게 데이터에 접근할 수 있습니다. 다만, 레지스터 주소 지정 방식은 직접 주소 지정 방식과 비슷한 문제를 공유합니다. 표현할 수 있는 레지스터 크기에 제한이 생길 수 있다는 점입니다. 레지스터 간접 주소 지정 방식 레지스터 간접 주소 지정 방식(register indirect addressing mode): 연산에 사용할 데이터를 메모리에 저장하고, 그 주소(유효 주소)를 저장한 레지스터를 오퍼랜드 필드에 명시하는 방법입니다. 유효 주소를 찾는 과정이 간전 주소 지정 방식과 비슷하지만, 메모리에 접근하는 횟수가 한 번으로 줄어든다는 차이이자 장점이 있습니다. 레지스터 간접 주소 지장 방식은 간접 주소 지정 방식보다 빠릅니다. 정리 연산에 사용할 데이터를 찾는 방법을 주소 지정 방식 이라고 했습니다. 연산에 사용할 데이터가 저장된 위치를 유효 주소 라고 했습니다. 대표적인 주소 지정 방식으로 아래의 다섯 가지 방식을 소개했습니다. 각각의 방식이 오퍼랜드 필드에 명시하는 값을 정리해 보면 아래와 같습니다. 즉시 주소 지정 방식: 연산에 사용할 데이터 직접 주소 지정 방식: 유효 주소(메모리 주소) 간접 주소 지정 방식: 유효 주소의 주소 레지스터 주소 지정 방식: 유효 주소(레지스터 이름) 레지스터 간접 주소 지정 방식: 유효 주소를 저장한 레지스터 키워드로 정리하는 핵심 포인트 명령어 는 연산 코드와 오퍼랜드로 구성됩니다. 연산 코드는 명령어가 수행할 연산을 의미합니다. 오퍼랜드는 연산에 사용할 데이터 또는 연산에 사용할 데이터가 저장된 위치를 의미합니다. 주소 지정 방식은 연산에 사용할 데이터 위치를 찾는 방법입니다. Q1. Swift에서 메모리 주소에 접근하기 위해 어떤 타입을 사용할 수 있는지 설명해 주세요. 그리고 왜 이러한 접근 방식이 필요할까요? Swift에서 메모리 주소에 직접 접근하기 위해 UnsafePointer<T> 타입과 그 변형인 UnsafeMutablePointer<T>를 사용할 수 있습니다. 이러한 포인터들은 C 언어의 포인터와 유사하게 작동하며, 메모리의 특정 위치를 직접 가리키는 데 사용됩니다. 이러한 접근 방식은 일반적으로 Swift의 안전성 및 추상화 원칙에 어긋나지만, 성능 최적화, 기존 C 기반 코드와의 상호 작용, 혹은 저수준 시스템 인터페이스와의 직접적인 상호 작용이 필요한 경우에 필요할 수 있습니다. 예를 들어, 대량의 데이터 처리나 기존 C 라이브러리의 함수를 호출할 때 이러한 방식이 유용할 수 있습니다. 아래는 주니어 Java 백엔드 개발자 면접 질문에 대한 모범 답안 예시입니다. 이 답변들은 Java의 메모리 관리와 관련된 기본적인 지식을 보여주는 데 목적이 있습니다. Q2. Java에서는 일반적으로 개발자가 직접 메모리 주소를 다루지 않습니다. 이에 대한 이유를 설명해 주세요. 또한, 자동 메모리 관리는 어떤 장점을 제공하나요? 답변: Java에서 개발자가 직접 메모리 주소를 다루지 않는 주된 이유는 Java가 자동 메모리 관리 시스템인 가비지 컬렉션(Garbage Collection, GC)을 제공하기 때문입니다. 이로 인해 메모리 누수와 같은 오류를 방지하고, 개발자가 메모리 관리에 드는 시간과 노력을 줄일 수 있습니다. 자동 메모리 관리의 장점으로는 안정성의 향상, 메모리 관리 오류의 감소, 그리고 개발자의 생산성 향상 등이 있습니다. Q3. JVM의 메모리 모델을 설명해 주세요. Heap과 Stack 메모리 영역의 차이점은 무엇이며, 각각 어떤 종류의 데이터를 저장하나요? 답변: JVM의 메모리 모델은 크게 Heap 영역과 Stack 영역으로 나뉩니다. Heap 영역은 모든 스레드에 걸쳐 공유되며, 주로 객체와 클래스의 메타데이터가 저장됩니다. 가비지 컬렉션은 이 Heap 영역에서 주로 작동합니다. 반면, Stack 영역은 스레드 별로 별도로 할당되며, 메소드 호출과 관련된 지역 변수와 참조 변수를 저장합니다. Stack은 LIFO(Last In, First Out) 방식으로 데이터를 관리합니다. Q4. 대규모 데이터 처리 작업을 수행할 때 Java에서 메모리 효율을 최적화하는 방법에는 어떤 것들이 있나요? 답변: 대규모 데이터 처리 시 메모리 효율을 최적화하기 위해, 객체 재사용, 적절한 컬렉션 선택, 스트림 API 사용, 그리고 메모리 캐싱 전략 등을 적용할 수 있습니다. 예를 들어, 객체 풀링을 통해 빈번히 생성 및 파괴되는 객체의 생성 비용을 줄일 수 있습니다. 또한, 데이터 양에 따라 적절한 자료구조를 선택하여 메모리 사용량과 성능을 균형있게 관리할 수 있습니다. Q5. JNI(Java Native Interface)는 무엇이며, 왜 사용하나요? Java 애플리케이션에서 JNI를 사용하여 네이티브 코드와 상호 작용하는 예를 들 수 있나요? 답변: JNI(Java Native Interface)는 Java 코드 내에서 C나 C++과 같은 네이티브 코드를 호출하거나, 반대로 네이티브 코드에서 Java 코드를 호출할 수 있는 프로그래밍 프레임워크입니다. JNI는 시스템 레벨의 리소스나 레거시 라이브러리를 사용해야 할 때, 또는 성능상의 이유로 직접 하드웨어를 제어해야 할 때 사용됩니다. 예를 들어, 고성능 그래픽 처리나 특정 하드웨어 장치와의 직접적인 상호작용을 구현할 때 JNI를 사용할 수 있습니다. Q6. 가비지 컬렉션(Garbage Collection)의 기본 원리를 설명해 주세요. Java에서 가비지 컬렉터의 작동 방식에 영향을 미칠 수 있는 프로그래밍 관행에는 어떤 것들이 있나요? 답변: 가비지 컬렉션은 참조되지 않는 객체를 자동으로 검출하고, 이를 메모리에서 제거하여 메모리를 회수하는 프로세스입니다. Java에서 가비지 컬렉터의 효율성에 영향을 미칠 수 있는 프로그래밍 관행으로는, 객체 참조를 적절히 해제하는 것, 대용량 객체의 재사용, 그리고 적절한 컬렉션 사용 등이 있습니다. 불필요한 객체 참조를 남겨두지 않고, 메모리 사용량이 큰 객체는 풀링 기법을 사용하여 관리함으로써, 가비지 컬렉터의 부하를 줄이고 애플리케이션의 성능을 개선할 수 있습니다.
Archive
· 2024-04-04
💾 [CS] 소스코드와 명령어
소스코드와 명령어. ‘컴퓨터는 명령어를 처리하는 기계’ 명령어는 컴퓨터를 실질적으로 작동시키는 매우 중요한 정보 모든 소스 코드(C, C++, Java, Python 과 같은 프로그래밍 언어로 만든 소스 코드)는 컴퓨터 내부에서 명령어로 변환됩니다. 고급 언어와 저급 언어 프로그램을 만들 때 사용하는 프로그래밍 언어, 컴퓨터가 이해하는 언어가 아닌 사람이 이해하고 작성하기 쉽게 만들어진 언어 이렇게 ‘사람을 위한 언어’를 고급 언어(high-level programming language) 라고 합니다. 컴퓨터가 직접 이해하고 실행할 수 있는 언어 저급 언어(low-level programming language) 하고 합니다. 컴퓨터가 이해하고 실행할 수 있는 언어는 오직 저급 언어뿐입니다. 그래서 고급 언어로 작성된 소스 코드가 실행되려면 반드시 저급 언어, 즉 명령어로 변환되어야 합니다. 저급 언어에는 두 가지 종류가 있습니다. 기계어 0과 1의 명령어 비트로 이루어진 언어입니다. 다시 말해 0과 1로 이루어진 명령어 모음입니다. 어셈블리어 0과 1로 표현된 명령어(기계어)를 읽기 편한 형태로 번역한 언어 컴파일 언어와 인터프리터 언어 고급 언어는 저급 언어로 변환되는 방식으로는 크게 두 가지 방식이 있습니다. 컴파일 방식 컴파일 방식으로 작동하는 프로그래밍 언어를 컴파일 언어 인터프리트 방식 인터프리트 방식으로 작동하는 프로그래밍 언어를 인터프리터 언어 컴파일 언어 컴파일 언어 컴파일러에 의해 소스 코드 전체가 저급 언어로 변환되어 실행되는 고급 언어입니다. 컴파일(Compile) 컴파일 언어로 작성된 소스 ㅋ코드는 전체가 저급 언어로 변환되는 과정을 거치는데 이 과정을 “컴파일”이라고 합니다. 컴파일러(Compiler) 컴파일을 수행해 주는 도구 개발자가 작성한 소스 코드 전체를 쭉 훑어보며 소스 코드에 문법적인 오류는 없는지, 실행 가능한 코드인지, 실행 가능한 코드인지, 실행하는 데 불필요한 코드는 없는지 등을 따지며 소스 코드를 처음부터 끝까지 저급 언어로 컴파일합니다. 이때 컴파일러가 소스 코드 내에서 오류를 하나라도 발견하면 해당 소스 코드는 컴파일에 실패합니다. 목적 코드(Object Code) 컴파일이 성공적으로 수행되면 개발자가 작성한 소스 코드는 컴퓨터가 이해할 수 있는 저급 언어로 변환됩니다. 이렇게 컴파일러를 통해 저급 언어로 변환된 코드를 목적 코드(Object code) 라고 합니다. 인터프리어 언어 인터프리터 언어 인터프리터에 의해 소스 코드가 한 줄씩 실행되는 고급 언어입니다 대표적인 인터프리터 언어로 Python이 있습니다. 인터프리터 소스 코드를 한 줄씩 저급 언어로 변환하여 실행해 주는 도구 인터프리터 언어는 컴퓨터와 대화하듯 소스 코드를 한 줄씩 실행하기 때문에 소스 코드 전체를 저급 언어로 변환하는 시간을 기다릴 필요가 없습니다. 소스 코드 내에 오류가 하나라도 있으면 컴파일이 불가능했던 컴파일 언어와는 달리, 인터프리터 언어는 소스 코드를 한 줄씩 실행하기 때문에 소스 코드 N번째 줄에 문법 오류가 있더라도 N-1번째 줄까지는 올바르게 수행됩니다. 일반적으로 인터프리터 언어는 컴파일 언어보다 느립니다. 컴파일을 통해 나온 결과물, 즉 목적 코드는 컴퓨터가 이해하고 실행할 수 있는 저급 언어인 반면, 인터프리터 언어는 소스코드 마지막에 이를 때까지 한 줄 한 줄씩 저급언어로 실행해야 하기 때문입니다. 목적 파일 vs 실행 파일 목적 파일 목적 코드로 이루어진 파일입니다. 실행 파일 윈도우의 .exe 확장자를 가진 파일이 대표적인 실행 파일입니다. 목적 코드가 실행 파일이 되기 위해서는 링킹이라는 작업을 거쳐야 합니다. 링킹 여러 개의 오브젝트 파일이나 라이브러리를 하나의 실행 파일로 결합하는 과정을 의미합니다. 컴파일러가 소스 코드를 기계어로 번역한 후 링커(Linker)가 이러한 기계어 코드들을 모아 실행 가능한 프로그램을 만듭니다. 키워드로 정리하는 핵심 포인트 고급 언어는 사람이 이해하고 작성하기 쉽게 만들어진 언어입니다. 저급 언어는 컴퓨터가 직접 이해하고 실행할 수 있는 언어입니다. 저급 언어는 0과 1로 이루어진 명령어로 구성된 기계어와 기계어를 사람이 읽기 편한 형태로 번역한 어셈블리어가 있습니다. 컴파일 언어는 컴파일러에 의해 소스 코드 전체가 저급 언어로 변환되어 실행되는 언어입니다. 인터프리터 언어는 인터프리터에 의해 소스 코드가 한 줄씩 저급 언어로 변환되어 실행되는 언어 입니다. Q1. Swift는 일반적으로 고급 언어로 분류됩니다. Swift의 어떤 특징이 개발자에게 고급 언어의 장점을 제공한다고 생각하나요? Swift는 고급 언어의 특징으로 높은 수준의 추상화, 강력한 타입 시스템, 메모리 안전성, 그리고 빠른 개발 시간을 제공합니다. Swift의 옵셔널 타입과 같은 기능은 안전한 코드 작성을 돕고, ARC는 메모리 관리를 단순화합니다. Q2. 고급 언어와 저급 언어의 차이점은 무엇이라고 생각하나요? 고급 언어는 인간이 이해하기 쉬운 형태로 추상화된 언어로, 복잡한 작업을 간단하게 표현할 수 있게 해줍니다. Java와 같은 고급 언어는 메모리 관리, 객체 지향 프로그래밍, 에러 처리 등 복잡한 컴퓨팅 개념을 추상화하여 개발자가 더 쉽게 소프트웨어를 개발할 수 있도록 돕습니다. 저급 언어는 컴퓨터가 직접 이해할 수 있는 더 낮은 수준의 명령어로 구성됩니다. 이에 해당하는 언어는 어셈블리 언어나 기계어로, 이들은 하드웨어와 밀접한 작업을 수행하는 데 사용됩니다. 저급 언어를 사용하면 성능 최적화와 메모리 관리를 더 세밀하게 제어할 수 있지만, 개발과 디버깅 과정이 복잡해집니다. Q3. Java는 고급 언어 중 하나로 간주됩니다. Java에서 저급 언어의 특성을 활용할 수 있는 방법에는 어떤 것이 있나요? Java는 기본적으로 고급 언어의 특성을 많이 가지고 있지만, JNI(Java Native Interface)를 통해 저급 언어 코드와 상호 작용할 수 있습니다. JNI는 Java 애플리케이션 내에서 C나 C++과 같은 저급 언어로 작성된 코드를 호출하고 사용할 수 있는 방법을 제공합니다. 이를 통해 개발자는 특정 작업을 위해 시스템 호출이나 하드웨어 직접 제어와 같은 저급 언어의 성능과 효율성을 Java 애플리케이션에 통합할 수 있습니다. 또한, 고성능을 요구하는 애플리케이션의 특정 부분에서 성능을 최적화할 수 있습니다. Q4. Java에서 고급 언어의 특성이 백엔드 개발에 어떤 장점을 제공하나요? Java의 고급 언어 특성은 백엔드 개발에서 여러 가지 장점을 제공합니다. 첫째, 강력한 객체 지향 프로그래밍(OOP) 지원으로 코드의 재사용성, 확장성, 유지 보수성이 향상됩니다. 둘째, 자동 메모리 관리와 가비지 컬렉션으로 메모리 누수와 같은 문제를 방지하며 개발자가 메모리 관리에 덜 신경 쓰고 로직 개발에 더 집중할 수 있게 합니다. 셋째, 다양한 라이브러리와 프레임워크, 그리고 강력한 개발 도구와 커뮤니티 지원으로 개발 속도와 효율성이 증가합니다. 마지막으로, Java는 플랫폼 독립적인 특성을 가지고 있어, 다양한 운영 체제에서 실행될 수 있는 애플리케이션을 개발할 수 있습니다.
Archive
· 2024-04-01
💾 [CS] 0과 1로 문자를 표현하는 방법
0과 1로 문자를 표현하는 방법. 문자 집합과 인코딩. 반드시 알아야 할 세 가지 용어 문자 집합 인코딩 디코딩 컴퓨터가 인식하교 표현할 수 있는 문자의 모음을 “문자 집합(character set)” 이라고 합니다. 문자를 0과 1로 변환하는 과정을 “문자 인코딩(character encoding)” 이라고 합니다. 0과 1로 이루어진 문자 코드를 사람이 이해할 수 있는 문자로 변환하는 과정을 “문자 디코딩(character decoding)” 이라고 합니다. 아스키 코드. 아스키(ASCII: American Standard Code for Information Interchang) 초창기 문자 집합 중 하나 영어 알파벳과 아라비아 숫자, 그리고 일부 특수 문자를 포함합니다. 아스키 문자 각각 7비트로 표현되는데, 7비트로 표현할 수 있는 정보의 가짓수는 2⁷개로, 총 128개의 문자를 표현할 수 있습니다. 아스키 코드 표를 보면 알 수 있듯 아스키 문자들은 0부터 127까지 총 128개의 숫자 중 하나의 고유한 수에 일대일로 대응됩니다. 아스키 문자에 대응된 고유한 수를 “아스키 코드”라고 합니다. 아스키 코드로 인코딩 아스키 코드를 이진수로 표현함으로써 아스키 문자를 0과 1로 표현할 수 있습니다. 아스키 문자는 이렇게 아스키 코드로 인코딩됩니다. 아스키 코드의 장,단점. 장점 매우 간단하게 인코딩됩니다. 단점 한글을 표현할 수 없습니다. 한글뿐만 아니라 아스키 문자 집합 외의 문자, 특수문자도 표현할 수 없습니다. 그 이유는 근본적으로 아스키 문자 집합에 속한 문자들은 7비트로 표현하기에 128개보다 많은 문자를 표현하지 못하기 때문입니다. 확장 아스키(Extend ASCII). 더 다양한 문자 표현을 위해 아스키 코드에 1비트를 추가한 8비트의 아스키 코드. 그럼에도 표현 가능한 문자 수는 256개여서 턱없이 부족했습니다. EUC-KR. 한국을 포함한 영어권 외의 나라들은 자신들의 언어를 0과 1로 표현할 수 있는 고유한 문자 집합과 인코딩 방식이 필요하다고 생각했습니다. 이러한 이유로 등장한 한글 인코딩 방식 EUC-KR은 KS X 1001, KS X 1003이라는 문자 집합을 기반으로하는 대표적인 완성형 인코딩 방식입니다. 즉, 초성 중성, 종성이 모두 결합된 한글 단어에 2바이크 크기의 코드를 부여합니다. EUC-KR로 인코딩된 한글 한 글자를 표현하려면 16비트(한글 한 글자에 2바이트 코드 부여)가 필요합니다. 16비트는 네 자리 십육진수로 표현할 수 있습니다. 즉, EUC-KR로 인코딩된 한글은 네 자리 십육진수로 나타낼 수 있습니다. 한글 인코딩의 두 가지 방식. 완성형 인코딩. 조합형 인코딩. “완성형 인코딩” 초성, 중성, 종성의 조합으로 이루어진 하나의 글자에 고유한 코드를 부여하는 인코딩 방식입니다. 조합형 인코딩 초성을 위한 비트열, 중성을 위한 비트열, 종성을 위한 비트열을 할당하여 그것들의 조합으로 하나의 글자 코드를 완성하는 인코딩 방식입니다. 다시 말해 초성, 중성, 종성에 해당하는 코드를 합하여 하나의 글자 코드를 만드는 인코딩 방식입니다. EUC-KR의 문제점. 아스키 코드보다 표현할 수 있는 문자가 많아졌지만(총 2,350여개), 이는 모든 한글 조합을 표현할 수 있을 정도로 많은 양은 아닙니다. 그래서 문자 집합에 정의되지 않은 ‘쀍’, ‘쀓’, ‘믜’같은 글자는 EUC-KR로 표현할 수 없습니다. “모든 한글을 표현할 수 없다는 사실은 때때로 크고 작은 문제를 유발합니다.” EUC-KR 인코딩을 사용하는 웹사이트의 한글이 깨지는 현상. EUC-KR 방식으로는 표현할 수 없는 이름으로 인해 은행, 학교 등에서 피해를 받는 사람이 생김. 이러한 문제를 조금이나마 해결하기 위해 등장한 것이 MS사의 “CP929(Code Page 949)” 입니다. CP949는 EUC-KR의 확장된 버전 EUC-KR로는 표현할 수 없는 더욱 다양한 문자를 표현 할 수 있습니다. 다만, 이마저도 한글 전체를 표현하기에 넉넉한 양은 아닙니다. 유니코드와 UTF-8. 모든 나라 언어의 문자 집합과 인코딩 방식이 통일되어 있다면, 다시 말해 모든 언어를 아우르는 문자 집합과 통일된 표준 인코딩 방식이 있다면 언어별로 인코딩하는 수고로움을 덜 수 있을 겁니다. 그래서 등장한 것이 “유니코드(Unicode)” 문자 집합입니다. 유니코드. EUC-KR보다 훨씬 다양한 한글을 포함하며 대부분 나라의 문자, 특수문자, 화살표나 이모티콘까지도 코드로 표현할 수 있는 통일된 문자집합힙니다. 현대 문자를 표현할 때 가장 많이 사용되는 표준 문자 집합이며, 문자 인코딩 세계에서 매우 중요한 역할을 맡고 있습니다. UTF-8, 16, 32 유니코드는 글자에 부여된 값 자체를 인코딩된 값으로 삼지 않고 이 값을 다양한 방법으로 인코딩합니다. 이런 인코딩 방법에는 크게 UTF-8, 16, 32 등이 있습니다. 요컨데 UTF-8, 16, 32는 유니코드 문자에 부여된 값을 인코딩하는 방식입니다. UTF-8 통상 1바이트부터 4바이트까지의 인코딩 결과를 만들어 냅니다. UTF-8로 인코딩한 값의 결과는 1바니크가 될 수도 2바이트, 3바이트, 4바이트가 될 수도 있습니다. UTF-8로 인코딩한 결과가 몇 바이트가 될지는 유니코드 문자에 부여된 값의 범위에 따라 결정됩니다. 4가지 키워드로 정리하는 핵심 포인트 문자 집합은 컴퓨터가 인식할 수 있는 문자의 모음으로, 문자 집합에 속한 문자를 인코딩하여 0과 1로 표현할 수 있습니다. 아스키 문자 집합에 0부터 127까지의 수가 할당되어 아스키 코드로 인코딩됩니다. EUC-KR은 한글을 2바이트 크기로 인코딩할 수 있는 완성형 인코딩 방식입니다. 유니코드는 여러 나라의 문자들을 광범위하게 표현할 수 있는 통일된 문자 집합이며, UTF-8, 16, 32는 유니코드 문자의 인코딩 방식입니다. Q1. iOS 개발에서 문자열을 다루는 것은 매우 흔한 작업입니다. 모든 문자는 컴퓨터 내부에서 0과 1의 이진 코드로 표현됩니다. 예를 들어, 유니코드 인코딩 방식 중 하나인 UTF-8을 사용하여 문자를 이진 코드로 변환할 수 있습니다. ‘안녕하세요’라는 문자열을 UTF-8 인코딩을 사용하여 이진 코드로 어떻게 변환할지 설명해 주세요. 또한, 이 과정에서 iOS 개발에 사용되는 Swift 언어에서 이러한 변환을 수행하는 코드 예시를 작성해 보세요. UTF-8 인코딩 변환 과정 설명 ‘안녕하세요’라는 문자열은 한글 문자로 구성되어 있으며, UTF-8 인코딩에서 한글은 보통 3바이트(24비트)로 인코딩됩니다. UTF-8은 가변 길이 인코딩 방식으로, 각 문자를 1바이트에서 4바이트까지 다양한 길이의 바이트로 인코딩합니다. 예를 들어, ASCII 코드의 경우 1바이트만 사용하지만, 한글과 같은 문자는 더 많은 바이트를 사용합니다. 예시로 ‘안녕하세요’ 중 ‘안’이라는 문자의 유니코드 코드 포인트는 U+548C입니다. 이를 UTF-8로 인코딩하면 다음과 같은 이진수로 표현될 수 있습니다: 1110xxxx 10xxxxxx 10xxxxxx. 실제 이진 코드로 변환하면 특정 이진값을 갖게 됩니다. (‘안’의 경우 실제 이진 변환 결과는 여기서 직접 계산하지 않았으나, 각 문자를 해당 방식으로 변환할 수 있습니다.) 안녕하세요’를 UTF-8로 인코딩하면 다음과 같은 이진 코드로 표현됩니다: 11101100 10010101 10001000 11101011 10000101 10010101 11101101 10010101 10011000 11101100 10000100 10111000 11101100 10011010 10010100 이진 코드는 각 바이트를 8비트 이진수로 표현한 것입니다. UTF-8 인코딩에서 한글 문자는 대체로 3바이트로 인코딩되므로, 위의 이진 코드는 ‘안녕하세요’의 각 글자를 UTF-8 인코딩으로 변환한 결과를 보여줍니다. 각 부분이 한글 문자 하나를 나타내며, 각 문자는 3개의 바이트(24비트)로 이루어져 있습니다 Swift에서의 구현 예시 Swift에서 문자열을 UTF-8 이진 코드로 변환하는 것은 간단합니다. Swift의 String 타입은 utf8 프로퍼티를 통해 UTF-8 인코딩을 쉽게 접근할 수 있게 해줍니다. let message = "안녕하세요" var binaryString = "" for codeUnit in message.utf8 { binaryString += String(codeUnit, radix: 2) + " " } print(binaryString) 이 코드는 각 문자를 UTF-8 인코딩으로 변환한 후, 각 바이트를 이진수로 변환하여 출력합니다. 출력 결과는 각 UTF-8 인코딩된 바이트를 이진수 형태로 나타낸 것으로, 각 바이트 사이에는 공백이 있습니다. Q2. Java에서는 문자와 문자열을 다루는 일이 자주 발생합니다. 특히 백엔드 시스템을 개발할 때, 다양한 인코딩 방식을 이해하고 이를 적절히 처리할 수 있는 능력이 중요합니다. UTF-8 인코딩 방식은 국제적으로 널리 사용되며, 다양한 언어와 특수 문자를 지원하는 강력한 인코딩 방식입니다. Java에서 문자열 ‘Java 백엔드 개발자’를 UTF-8 인코딩을 사용하여 이진 코드로 변환하는 과정을 설명해 주세요. 또한, 이 과정을 구현하는 Java 코드를 작성해 보세요. 주어진 질문에 대한 답변은 크게 두 부분으로 나눌 수 있습니다: 첫 번째는 UTF-8 인코딩 방식에 대한 이해와 설명이며, 두 번째는 ‘Java 백엔드 개발자’ 문자열을 UTF-8로 인코딩하여 이진 코드로 변환하는 Java 코드의 구현입니다. 1. UTF-8 인코딩 방식에 대한 이해 UTF-8은 유니코드 문자 집합을 인코딩하는 가장 널리 사용되는 방식 중 하나로, 1바이트에서 4바이트까지 다양한 길이의 바이트를 사용하여 전 세계의 거의 모든 문자를 표현할 수 있습니다. UTF-8은 영문 알파벳과 숫자 같은 기본적인 문자들을 1바이트로 표현하고, 그 외의 문자들은 2바이트 이상을 사용합니다. 예를 들어, 한글은 3바이트를 사용하여 표현됩니다. 이러한 특성 때문에, UTF-8은 다국어 처리가 필요한 웹 및 백엔드 시스템 개발에 널리 사용됩니다. 2. Java 코드 구현 ‘Java 백엔드 개발자’ 문자열을 UTF-8로 인코딩하여 이진 코드로 변환하는 과정은 다음 Java 코드를 통해 구현할 수 있습니다: public class Main { public static void main(String[] args) { String text = "Java 백엔드 개발자"; byte[] bytes = text.getBytes(java.nio.charset.StandardCharsets.UTF_8); StringBuilder binaryString = new StringBuilder(); for (byte b : bytes) { // 각 바이트를 이진수로 변환하고, 8자리 이진수 형태를 유지하기 위해 앞에 0을 채움 String binary = String.format("%8s", Integer.toBinaryString(b & 0xFF)).replace(' ', '0'); binaryString.append(binary).append(" "); } System.out.println(binaryString.toString().trim()); } } 이 코드는 다음과 같은 과정을 거칩니다: 문자열 “Java 백엔드 개발자”를 UTF-8 인코딩을 사용하여 바이트 배열로 변환합니다. 변환된 바이트 배열을 순회하면서, 각 바이트를 8비트 이진수로 변환합니다. 이 때, & 0xFF 연산을 사용하여 부호 없는 정수로 처리하고, String.format을 사용하여 이진수를 8자리로 맞춥니다. 변환된 이진수 문자열을 콘솔에 출력합니다. 이 구현을 통해 후보자는 UTF-8 인코딩 방식의 이해, Java에서의 문자열 처리, 그리고 바이트 및 이진수 처리에 대한 자신의 지식과 기술을 면접관에게 보여줄 수 있습니다. 이는 Java 백엔드 개발자로서 갖추어야 할 중요한 기술 중 하나입니다.
Archive
· 2024-03-25
💾 [CS] 컴퓨터 메모리를 16진수로 표시하는 이유
컴퓨터 메모리를 16진수로 표시하는 이유. 이진수와의 호환성 : 컴퓨터는 모든 데이터를 이진수, 즉 0과 1로 처리합니다. 이진수는 매우 기본적이지만, 긴 이진수를 읽고 이해하기는 어렵습니다. 16진수는 이진수를 좀 더 읽기 쉽게 만들어 줍니다. 4비트 이진수 한 덩어리가 16진수 한 자리와 정확히 대응되기 때문에, 이진수를 16진수로 변환하는 것은 자연스럽고 효율적입니다. 예를 들어, 이진수 1111은 16진수 F로 표현됩니다. 효율적인 표현 : 16진수를 사용하면 매우 큰 수나 메모리 주소를 훨씬 짧고, 관리하기 쉬운 형태로 표현할 수 있습니다. 예를 들어, 8비트 이진수인 10011011은 16진수로는 단 두 자리 9B로 표현할 수 있습니다. 이는 프로그래머와 기술자가 메모리 주소나 데이터 값을 빠르게 인식하고 작업하기 용이합니다. 표준화와 호환성 : 16진수는 컴퓨터 과학과 전자공학에서 널리 표준화되어 사용됩니다. 소프트웨어 개발, 디버깅, 하드웨어 설계 등 다양한 분야에서 16진수 사용은 정보를 일관되게 표현하고 전달하는 데 도움을 줍니다. 이는 서로 다른 시스템과 기술 간의 호환성을 증진시키는 역할을 합니다. 디버깅과 분석 용이 : 개발자와 엔지니어가 시스템의 문제를 진단하거나 메모리의 내용을 분석할 때, 16진수 표현은 이진 데이터를 빠르게 읽고 해석할 수 있게 해줍니다. 이는 소프트웨어와 하드웨어의 오류를 찾고 해결하는 과정을 간소화합니다. 이렇게 16진수는 이진수의 복잡성을 줄이면서도 정보를 효과적으로 표현하고 처리할 수 있는 효율적인 방법을 제공합니다. 컴퓨터 공학에서 이러한 방식을 사용함으로써, 우리는 컴퓨터 시스템과 소프트웨어를 보다 쉽게 이해하고, 효율적으로 작업할 수 있게 됩니다.
Archive
· 2024-03-21
💾 [CS] 0과 1로 숫자를 표현하는 방법
0과 1로 숫자를 표현하는 방법. 정보 단위. 컴퓨터는 0 또는 1밖에 이해하지 못합니다. 0과 1을 나타내는 나타내는 가장 작은 정보 단위를 “비트(bit)” 라고 합니다. 비트는 0 또는 1, 두 가지 정보를 표현할 수 있습니다. “n비트는 2ⁿ가지 정보를 표현할 수 있습니다.” 바이트(byte) : 여덟 개의 비트를 묶은 단위로, 비트보다 한 단계 큰 단위. 1바이트는 8비트와 같습니다. 2⁸(256)개의 정보를 표현할 수 있습니다. 킬로바이트(kB: kilobyte) : 1바이트 1,000개를 묶은 단위입니다. 메가바이트(MB: megabyte) : 1킬로바이트 1,000개를 묶은 단위입니다. 기가바이트(GB: gigabyte) : 1메가바이트 1,000개를 묶은 단위입니다. 테라바이트(TB: terabyte) : 1기가바이트 1,000개를 묶은 단위입니다. 더 큰 단위도 있습니다. 워드(word) : CPU가 한 번에 처리할 수 있는 데이터 크기를 의미합니다. 만약 CPU가 한 번에 16비트를 처리할 수 있다면 1워드는 16비트가 되고, 한 번에 32비트를 처리할 수 있다면 1워드는 32비트가 되는 것입니다. 워드의 절반 크기를 하프 워드(half word), 1배 크기를 풀 워드(full word), 2배 크기를 더블 워드(double word) 라고 부릅니다. 컴퓨터의 워트 크기는 대부분 32비트 또는 64비트 입니다. 가령 인텔의 x86 CPU는 32비트 워드, x64 CPU는 64비트 워드 CPU입니다. 이진법 0과 1만드로 모든 숫자를 표현하는 방법을 “이진법(binary)” 라고 합니다. 우리가 일상적으로 사용하는 방법은 십진법(decimal) 라고 합니다. 이진법으로 표현한 수를 “이진수” 십진법으로 표현한 수를 “십진수” 숫자만으로 어떤 수가 어떤 진법으로 표현된 수인지 알 수 없습니다. 이런 혼동을 예방하기 위해 이진수 끝에 아래첨자 (2)를 붙이거나 이진수 앞에 0b를 붙입니다. 전자는 주로 이진수를 수학적으로 표기할 때, 후자는 주로 코드 상에서 이진수를 표기할 때 사용합니다. 이진수의 음수 표현 음수를 표현하는 방법 중 가장 널리 사용되는 방법은 2의 보수(two;s complement) 를 구해 이 값을 음수로 간주하는 방법입니다. 2의 보수의 사전적 의미: ‘어떤 수를 그보다 큰 2ⁿ에서 뺀 값’을 의미합니다. 예를 들어 11₍₂₎의 2의 보수는 11₍₂₎보다 큰 2ⁿ, 즉 100₍₂₎에서 11₍₂₎을 뺀 01₍₂₎이 되는 것 입니다. “굳이 이렇게 사전적 의미로 어렵게 이해할 필요는 없습니다. 2의 보수를 매우 쉽게 표현하자면 다음과 같습니다.” ‘모든 0과 1을 뒤집고, 거기에 1을 더한 값’으로 이해하면 됩니다. 예를 들어 11₍₂₎의 모든 0과 1을 뒤집으면 00₍₂₎이고, 거기에 1을 더한 값은 01₍₂₎입니다. 즉, 11₍₂₎의 2의 보수(음수 표현)는 01₍₂₎이 됩니다. “실제로 이진수만 봐서는 이게 음수인지 양수인지 구분하기 어렵습니다. 그래서 컴퓨터 내부에서 어떤 수를 다룰 때는 이 수가 양수인지 음수인지를 구분하기 위해 ‘플래그(flag)’를 사용합니다.” 플래그는 쉽게 말해 부가 정보입니다. 십육진법(hexadecimal) 수가 15를 넘어가는 시점에 자리 올림을 하는 숫자 표현 방식입니다. 그리고 십진수 10, 11, 12, 13, 14, 15를 십육진법 체계에서는 각각 A, B, C, D, E, F로 표기합니다. 십육진수도 이진수와 마찬가지로 숫자 뒤에 아래첨자 ₍₁₆₎를 븉아고너 숫자 앞에 0x룰 븉여 구분합니다. 전자는 주로 수학적으로 표기할 때 사용되는 방식 후자는 주로 코드상에서 십육진수를 표기할 때 사용되는 방식 십육진법을 사용하는 주된 이유 중 하나는 이진수를 십육진수로, 십육진수를 이진수로 변환하기 쉽기 때문입니다. 십육진수를 이진수로 변환하기. 십육진수는 한 글자당 열여섯 종류(0~9, A~F)의 숫자를 표현할 수 있습니다. 십육진수를 이루는 숫자 하나를 이진수로 표현할 때는 4비트가 필요합니다.(2⁴ = 16) 십육진수를 이준수로 변환하는 간편한 방법 중 하나는 십육진수 한 글자를 4비트의 이진수로 간주하는 것 입니다. 즉, 십육진수를 이루고 있는 각 글자를 따로따로(4개의 숫자로 구성된) 이진수로 변환하고, 그것을 이어 붙이면 십육진수가 이진수로 변환됩니다. 이진수를 십육진수로 변환하기 이진수를 십육진수로 변환할 때는 이진수 숫자를 네 개씩 끊고, 끊어 준 네 개의 숫자를 하나의 십육진수로 변환한 뒤 그대로 이어 붙이면 됩니다. 키워드로 정리하는 핵심 포인트 비트는 0과 1로 표현할 수 있는 가장 작은 정보 단위입니다. 바이트, 킬로바이트, 메가바이트, 기가바이트, 테라바이트는 비트보다 더 큰 정보 단위입니다. 이진법은 1을 넘어가는 시점에 자리 올림을 하여 0과 1만으로 수를 표현하는 방법입니다. 이진법에서 음수는 2의 보수로 표현할 수 있습니다. 십육진법은 15를 넘어가는 시점에 자리 올림하여 수를 표현하는 방법입니다. Q1.현대의 컴퓨터와 디지털 기기들은 데이터를 처리하고 저장할 때 기본적으로 0과 1, 즉 이진수를 사용합니다. iOS 개발 과정에서도 이러한 이진수의 원리를 이해하는 것이 중요한데요, 여러분은 이러한 이진수 시스템이 왜 필요하고, 어떻게 우리가 개발하는 앱과 관련이 있는지 설명해주실 수 있나요? 특히, 이진수의 개념이 iOS 앱 개발에서 어떤 실질적인 적용 사례를 가지는지 구체적인 예를 들어 주세요. 이진수 시스템은 컴퓨터와 디지털 기기들이 데이터를 처리하고 저장하는 기본적인 방법입니다. 이 시스템은 0과 1, 두 가지 상태만을 사용하여 정보를 표현하는 방법으로, 컴퓨터 하드웨어는 이러한 이진 상태들을 전기적 신호의 켜짐과 꺼짐으로 해석합니다. 이는 컴퓨터 기술에서 가장 기본이 되는 원리로, 모든 프로그래밍 언어와 운영 체제, 애플리케이션 개발에 깊이 관련되어 있습니다. iOS 앱 개발에 있어 이진수의 이해는 몇 가지 중요한 측면에서 의미를 가집니다: 데이터 저장과 처리: 앱 내에서 사용자 데이터, 설정, 상태 정보 등을 저장하고 처리할 때, 이진 형식이 기본적으로 사용됩니다. 예를 들어, 사용자가 앱 내에서 사진을 찍거나 파일을 다운로드할 때, 이러한 데이터는 이진 형태로 디바이스에 저장됩니다. 통신: 앱이 서버와 데이터를 주고받을 때, 이진 데이터 형식이 널리 사용됩니다. 예를 들어, REST API를 통해 JSON 형식으로 데이터를 교환하더라도, 실제 네트워크를 통한 전송 과정에서는 이진 데이터로 변환되어 처리됩니다. 성능 최적화: 이진수를 직접 다루는 지식은 앱의 성능 최적화에 큰 도움이 될 수 있습니다. 예를 들어, 이미지나 동영상 처리, 암호화, 데이터 압축 등 고성능을 요구하는 작업에서는 낮은 수준의 이진 처리가 필요할 수 있습니다. 하드웨어 접근과 제어: iOS 앱 개발에서 때로는 하드웨어의 낮은 수준의 기능에 접근하거나 제어해야 할 필요가 있습니다. 이 경우, 이진수 처리 방식을 이해하는 것이 필수적입니다. 예를 들어, Bluetooth 통신이나 기타 특수한 하드웨어 기능을 사용하는 앱을 개발할 때 이진 데이터의 처리가 필요합니다. 이진수 시스템의 이해는 따라서, 기본적인 데이터의 표현부터 앱의 성능 최적화, 하드웨어 제어에 이르기까지 iOS 앱 개발의 여러 단계에 걸쳐 중요한 역할을 합니다. 이러한 지식은 개발자로서 문제 해결 능력을 향상시키고, 더 효율적이고 강력한 앱을 만드는 데 기여합니다. Q2. 우리가 컴퓨터 과학에서 배우는 가장 기본적인 개념 중 하나는 모든 디지털 데이터가 궁극적으로 0과 1, 즉 이진수로 표현된다는 것입니다. 이러한 이진수 체계를 이해하는 것이 왜 Java 백엔드 개발에 있어 중요한지에 대해 설명해 주세요. 또한, 이 개념이 실제 백엔드 시스템 개발과 운영에 어떻게 적용될 수 있는지 구체적인 예를 들어 설명해주실 수 있나요? 이진수 체계의 이해는 Java 백엔드 개발에 있어 여러 가지 이유로 중요합니다: 데이터 표현 및 처리의 기본: 컴퓨터는 모든 정보를 이진수로 처리하고 저장합니다. Java 백엔드 개발자로서 데이터를 저장, 검색, 변환하는 다양한 작업을 수행할 때 이진 데이터의 이해는 필수적입니다. 예를 들어, 파일 시스템에서 데이터를 읽고 쓰거나, 네트워크 통신을 통해 데이터를 송수신할 때 이진 데이터 형식에 대한 지식이 필요합니다. 성능 최적화: 이진수에 대한 이해는 데이터 압축, 암호화, 데이터 전송 최적화와 같은 고급 개발 작업에서 성능을 향상시키는 데 도움이 됩니다. 예를 들어, 대용량 데이터를 효율적으로 처리하기 위해 비트 연산을 사용할 수 있으며, 이는 이진수의 원리를 이해할 때 가능해집니다. 암호화 및 보안: 현대의 암호화 알고리즘은 대부분 이진수 기반의 복잡한 수학적 연산을 사용합니다. 백엔드 시스템에서 사용자 데이터의 보안을 유지하기 위해 데이터를 암호화하고 해시 함수를 적용할 때, 이진수 원리의 이해는 필수적입니다. 하드웨어 및 시스템 인터페이스: 백엔드 시스템은 때로 특정 하드웨어나 시스템과 직접적으로 상호작용해야 할 수 있습니다. 이러한 상호작용은 종종 낮은 수준의 데이터 표현에 대한 깊은 이해를 요구하며, 이는 이진수 체계의 지식이 있을 때 효율적으로 수행될 수 있습니다. 이진수 체계의 이해는 Java 백엔드 개발자가 효율적이고 안전한 시스템을 설계하고 구현하는 데 필수적인 기초를 제공합니다. 데이터의 기본적인 표현 방식을 이해함으로써 개발자는 보다 깊은 수준에서 시스템을 이해하고, 성능과 보안 문제를 더 잘 해결할 수 있게 됩니다.
Archive
· 2024-03-21
💾 [CS] 컴퓨터 구조의 큰 그림
컴퓨터 구조의 큰 그림 우리가 알아야 할 컴퓨터 구조 지식은 크게 두 가지 입니다. 컴퓨터가 이해하는 정보 컴퓨터의 네 가지 핵심 부품 컴퓨터가 이해하는 정보 데이터 컴퓨터가 이해하는 숫자, 문자, 이미지, 동영상과 같은 정적인 정보 명령어 컴퓨터를 실직적으로 작동 시키는 중요한 정보 데이터 없이는 아무것도 할 수 없는 정보 덩어리 “데이터를 움직이고 컴퓨터를 작동 시키는 장보” “즉, 명령어는 컴퓨터를 작동시키는 정보이고, 데이터는 명령어를 위해 존재하는 일종의 재료입니다.” 컴퓨터 프로그램은 ‘명령어들의 모음’으로 정의되기도 합니다. 그래서 명령어는 컴퓨터 구조를 학습하는 데 있어 데이터보다 더 중요한 개념. 컴퓨터의 4가지 핵심 부품. 중앙처리장치(Central Programming Unit, CPU) 컴퓨터의 두뇌 메모리에 저장된 명령어를 읽어 들이고, 읽어 들인 명령어를 해석하고, 실행하는 부품입니다. CPU 내부 구성 요소 중 가장 중요한 세 가지는 산술논리연산장치(ALU: Arithmetic Logic Unit), 레지스터(register), 제어장치(CU: Control Unit) 입니다. ALU: 계산기, 계산만을 위해 존재하는 부품, 컴퓨터 내부에서 수행되는 대부분의 계산은 ALU가 도맡아 수행 레지스터: CPU 내부의 작은 임시 저장 장치, 프로그램을 실행하는 데 필요한 값들을 임시로 저장, CPU 안에는 여러 개의 레지스터가 존재하고 각기 다른 이름과 역할을 가짐 제어장치: 제어 신호(Control Signal)라는 전기 신호를 내보내고 명령어를 해석하는 장치. 제어 신호란 컴퓨터 부품들을 관리하고 작동시키기 위한 일종의 전기 신호 CPU가 메모리에 저장된 값을 읽고 싶을 땐 메모리를 향해 “메모리 읽기”라는 제어 신호를 보낸다. CPU가 메모리에 어떤 값을 저장하고 싶을 땐 메모리를 향해 “메모리 쓰기”라는 제어 신호를 보낸다. 주기억장치(Main memory, 메모리) 현재 실행되는 프로그램의 명령어와 데이터를 저장하는 부품. 즉, 프로그램이 실행되려면 반드시 메모리에 저장되어 있어야 합니다. 메모리에 저장된 값의 위치는 주소로 알 수 있습니다. 보조기억장치(secondary storage) 메모리보다 크기가 크고 전원이 꺼져도 저장된 내용을 잃지 않는 메모리를 보조할 저장 장치 보조기억장치는 ‘보관할’ 프로그램을 저장한다고 생각해도 좋다. 입출력장치(input/output(I/O) device) 마이크, 스피커, 프린터, 마우스, 키보드처럼 컴퓨터 외부에 연결되어 컴퓨터 내부와 정보를 교환하는 장치를 의미. ‘컴퓨터 주변에 붙어 있는 장치’라는 의미에서 “주변장치(peripheral device)”라 통칭하기도 함. “주소” 컴퓨터가 빠르게 작동하기 위해서는 메모리 속 명령어와 데이터가 정돈된 위치에 저장되어 있어야 합니다. 그래서 메모리에는 저장된 값에 빠르게 효율적으로 접근하기 위해 주소(address)라는 개념이 사용됩니다. 주소로 메모리 내 원하는 위치에 접근할 수 있습니다. 메인보드와 시스템 버스 메인보드 마더보드(mother board)라고도 부름 메인보드에는 앞에서 소개한 부품을 비롯한 여러 컴퓨터 부품을 부착할 수 있는 슬록과 연결 단자가 있습니다. 메인 보드에 연력된 부품들은 서로 정보를 주고 받을수 있습니다. 이는 메인보드 내부에 “버스(bus)”라는 통로가 있기 때문입니다. 시스템 버스(system bus) 여러 버스 가운데 컴퓨터의 네 가지 핵심 부품을 연결하는 가장 중요한 버스입니다. 주소 버스, 데이터 버스, 제어 버스로 구성되어 있습니다. 주소 버스(address bus): 주소를 주고받는 통로 데이터 버스(data bus): 명령어롸 데이터를 주고 받는 통로 제어 버스(control bus): 제어 신호를 주고 받는 통로 키워드로 정리하는 핵심 포인트 컴퓨터가 이해하는 정보에는 “데이터” 와 “명령어” 가 있습니다. “메모리” 는 현재 실행되는 프로그램의 명령어와 데이터를 저장하는 부품입니다. “CPU” 는 메모리에 저장된 명령어를 읽어 들이고, 해석하고, 실행하는 부품입니다. “보조기억장치” 는 전원이 꺼져도 보관할 프로그램을 저장하는 부품입니다. “입출력장치” 는 컴퓨터 외부에 연결되어 컴퓨터 내부와 정보를 교환할 수 있는 부품입니다. “시스템 버스” 는 컴퓨터의 네 가지 핵심 부품들이 서로 정보를 주고받는 통로입니다. Q1. “메모리 주소가 무엇이며, iOS 시스템 내에서 어떤 역할을 수행한다고 생각하나요?” 메모리 주소는 컴퓨터 메모리 내에서 데이터나 명령어의 위치를 식별하는 데 사용되는 고유한 식별자입니다. 각 바이트 또는 워드에는 메모리 내의 위치를 나타내는 고유한 주소가 있으며, 이를 통해 CPU와 다른 시스템 구성 요소가 필요한 데이터를 정확히 찾아 읽고 쓸 수 있습니다. iOS 시스템 내에서 메모리 주소의 역할은 특히 중요합니다. iOS는 메모리 관리에 자동 참조 카운팅(ARC)를 사용하여 객체의 생명 주기를 관리합니다. ARC는 객체에 대한 참조가 더 이상 필요하지 않게 되면 자동으로 메모리를 해제합니다. 이 과정에서 메모리 주소를 사용하여 각 객체의 위치를 파악하고 관리합니다. 따라서, 개발자로서 메모리 주소의 이해는 메모리 누수를 방지하고 앱의 성능을 최적화하는 데 필수적입니다. 또한, 메모리 주소를 이해하는 것은 포인터를 사용한 프로그래밍, 메모리 접근 최적화, 그리고 다양한 메모리 관리 기법을 적용하는 데 중요합니다. 예를 들어, 효율적인 데이터 구조 설계, 대규모 데이터 처리, 멀티스레딩 환경에서의 데이터 공유와 동기화 문제 해결 등은 메모리 주소와 밀접한 관련이 있습니다. iOS 시스템 내에서 메모리 주소의 관리와 최적화는 앱의 반응 속도, 안정성, 그리고 사용자 경험에 직접적인 영향을 미치기 때문에, 이를 정확히 이해하고 효과적으로 활용하는 능력은 iOS 개발자에게 매우 중요한 자질입니다. Q2. “메모리 주소가 무엇이며, Java 시스템 내에서 어떤 역할을 수행한다고 생각하나요?” “메모리 주소는 컴퓨터 메모리 내의 특정 위치를 식별하는 데 사용되는 고유한 식별자입니다. 이 주소를 통해, 컴퓨터 시스템은 메모리 내에서 데이터나 명령어를 정확히 찾아내어 읽고 쓸 수 있습니다. 간단히 말해, 메모리 주소는 컴퓨터 메모리 내의 ‘우편 주소’와 유사한 역할을 수행합니다. Java 시스템 내에서, 메모리 주소의 역할은 Java 가상 머신(JVM)에 의해 추상화되어 다루어집니다. Java 개발자들은 직접적으로 메모리 주소를 다루지 않으며, 대신 Java가 제공하는 추상화된 메모리 모델을 사용하여 프로그래밍합니다. Java에서는 객체와 배열 등이 힙 메모리에 할당되며, 개발자는 이러한 객체에 대한 참조를 통해 메모리를 접근하게 됩니다. 여기서 ‘참조’는 실제 메모리 주소를 직접적으로 나타내지는 않지만, 특정 객체를 가리키는 역할을 합니다. JVM은 가비지 컬렉션(Garbage Collection)을 통해 메모리 관리를 자동화합니다. 가비지 컬렉터는 더 이상 사용되지 않는 객체를 자동으로 검출하고, 그 메모리를 회수하여 재사용 가능하게 만듭니다. 이 과정에서 JVM은 내부적으로 메모리 주소를 관리하여, 효율적인 메모리 할당과 해제를 수행합니다. 따라서, Java 시스템 내에서 메모리 주소는 주로 메모리 할당, 객체 참조, 그리고 가비지 컬렉션과 같은 메모리 관리 작업에 중요한 역할을 수행합니다. Java 개발자로서 우리의 역할은 주로 안전하고 효율적인 코드 작성에 초점을 맞추며, JVM이 메모리 관리의 세부 사항을 추상화하고 처리하도록 합니다. 이렇게 함으로써, 개발자는 메모리 관리의 복잡성으로부터 벗어나 비즈니스 로직 구현에 더 집중할 수 있습니다.”
Archive
· 2024-03-18
💾 [CS] 패턴 매칭(Pattern Matching)과 표현 매칭(Expression Matching)
패턴 매칭(Pattern Matching)과 표현 매칭(Expression Matching). 패턴 매칭과 표현 매칭은 프로그래밍 언어나 소프트웨어 개발에서 사용되는 두 가지 다른 개념입니다. 둘 다 데이터나 표현식의 구조를 분석하고 일치 여부를 판단하는 방법이지만, 적용되는 맥락과 목적에서 차이가 있습니다. 패턴 매칭(Pattern Matching). 데이터의 구조와 그 내용을 기반으로 한 매칭 방식입니다. 입력된 데이터가 특정 패턴이나 구조와 일치하는지를 검사합니다. 이를 통해 데이터의 타입, 값, 구조 등을 확인하고 , 그에 따른 처리를 분기하는 데 사용됩니다. 표현 매칭(Expression Matching) 특정 표현식이나 문자열이 주어진 패턴이나 규칙과 일치하는지를 확인하는 방법입니다. 주로 문자열 처리, 정규 표현식 사용, 텍스트 분석에서 널리 사용됩니다. 표현 매칭은 특정 패턴(예: 정규 표현식)을 정의하고, 대상 문자열이 이 패턴과 일치하는지 여부를 판단합니다. 이는 검색, 데이터 검증, 파싱 등 다양한 분야에서 활용됩니다. 차이점 요약 적용 분야 패턴 매칭은 주로 데이터의 구조와 타입을 다루는 함수형 프로그래밍에서 사용됩니다. 반면, 표현 매칭은 문자열이나 텍스트 데이터를 처리할 때 사용되는 패턴(예: 정규 표현식)과의 일치 여부를 확인하는 데 쓰입니다. 목적 패턴 매칭은 데이터의 구조를 통해 복잡한 데이터 타입을 효율적으로 분해하고 처리하는 데 중점을 둡니다. 표현 매칭은 문자열 내에서 특정 패턴의 존재 여부를 검사하고, 데이터를 검증하거나 추출하는 데 주로 사용됩니다. 사용 사례 패턴 매칭은 데이터 타입 분해, 조건 분기 처리 등에 사용되며, 함수형 프로그래밍 언어에서 자주 볼 수 있습니다. 표현 매칭은 로그 분석, 웹 페이지 파싱, 사용자 입력 검증 등 문자열 처리에 널리 사용됩니다. 두 방법은 각각의 사용 사례와 목적에 맞게 선택하여 사용되며, 프로그래밍에서의 다양한 문제를 해결하는 데 중요한 역할을 합니다.
Archive
· 2024-03-14
💾 [CS] 컴퓨터 구조를 알아야 하는 이유
컴퓨터 구조를 알아야 하는 이유. 컴퓨터 구조는 실력 있는 개발자가 되려면 반드시 알아야 할 기본 지식입니다 why? 문제 해결 컴퓨터 구조를 이해하고 있다면 문제 상황을 빠르게 진단할 수 있고, 문제 해결의 실마리를 다양하게 찾을 수 있습니다. 컴퓨터 구조 지식은 다양한 문제를 스스로 해결할 줄 아는 개발자로 만들어 줍니다. 성능, 용량, 비용 “컴퓨터 구조애서 배우는 내용은 결국 성능, 용량, 비용과 직결됩니다.” 즉, 컴퓨터 구조를 이해하면 입력과 출력에만 집중하는 개발을 넘어 성능, 용량, 비용까지 고려하며 개발하는 개발자가 될 수 있습니다, 문제 해결 컴퓨터 구조를 이해하고 있다면 문제 상황을 빠르게 진단할 수 있고, 문제 해결의 실마리를 다양하게 찾을 수 있습니다. 컴퓨터 내부를 거리낌 없이 들여다보면 더 좋은 해결책을 고민할 수 있습니다. 이러한 사고가 가능한 이들에게 컴퓨터란 ‘미지의 대상’이 아닌 ‘분석의 대상’이기 때문입니다. 컴퓨터 구조 지식은 다양한 문제를 스스로 해결할 줄 아는 개발자로 만들어 줍니다. 성능, 용량, 비용 성능, 용량, 비용 문제는 프로그래밍 언어의 문법만 알아서는 해결하기 어렵습니다. 혼자만 사용하는 프로그램을 만들 떄는 이러한 문제를 생각조차 해 본 적이 없을 수도 있습니다. 하지만 유튜브, 워드, 포토샵과 같이 사용자가 많은 프로그램은 필연적으로 성능, 용량, 비용이 고려됩니다. 그래서 컴퓨터 구조를 아는 것은 매우 중요합니다. “컴퓨터 구조애서 배우는 내용은 결국 성능, 용량, 비용과 직결됩니다.” 즉, 컴퓨터 구조를 이해하면 입력과 출력에만 집중하는 개발을 넘어 성능, 용량, 비용까지 고려하며 개발하는 개발자가 될 수 있습니다, 핵심 포인트 컴퓨터 구조를 이해하면 “문제 해결” 능력이 향상 됩니다. 컴퓨터 구조를 이해하면 문법만으로는 알기 어려운 “성능/용량/비용” 을 고려하며 개발할 수 있습니다. Q1. iOS 애플리케이션 개발에서 고효율적이고 성능이 우수한 앱을 만들기 위해 컴퓨터 구조에 대한 이해가 왜 중요한지 설명해주세요. 구체적인 예를 들어서 설명해주시기 바랍니다. 답변. iOS 애플리케이션 개발에서 컴퓨터 구조에 대한 이해는 여러 면에서 중요합니다. 첫째, 성능 최적와레 있어서 핵심적인 역할을 합니다. 예를 들어, CPU의 멀티코어 구조를 이해함으로써, 병렬 처리와 동시성을 통해 애플리케이션의 성능을 효과적으로 향상시킬 수 있습니다. 이는 앱이 사용자의 입력에 빠르게 반응하고, 더 복잡한 작업을 빠른 시간 안에 처리할 수 있게 만들어 줍니다. 둘째, 메모리 관리에 대한 이해를 통해, 애플리케이션의 효율성을 높일 수 있습니다. 예를 들어, RAM과 캐시의 작동 방식을 이해하면, 데이터를 효율적으로 저장하고 접근하는 방법을 개선할 수 있으며, 이는 애플리케이션의 반응 속도와 전반적인 성능에 긍정적인 영향을 미칩니다. 셋째, 하드웨어와 소프트웨어의 상호작용에 대한 이해는 에너지 효율성을 최적화하는 데 도움이 됩니다. iOS 장치의 배터리 수명은 사용자 경험의 중요한 부분이며, 컴퓨터 구조에 대한 이해는 개발자가 배터리 소모를 최소화하면서 성능을 극대화할 수 있는 애플리케이션을 설계할 수 있게 돕습니다. 마지막으로 컴퓨터 구조에 대한 깊은 이해는 개발자가 효과적인 코드를 작성하고, 시스템 리소스를 효율적으로 관리하며, 최종 사용자에게 더 나은 경험을 제공할 수 있는 애플리케이션을 만들 수 있도록 합니다. Q2. 서버 개발자가 컴퓨터 구조에 대한 기본적인 지식을 갖추어야 하는 이유에 대해 설명해주시고, 그 지식이 어떻게 서버 애플리케이션의 성능과 안정성에 영향을 미칠 수 있는지 구체적인 예시를 들어 설명해주세요. 답변. 서버 개발자에게 컴퓨터 구조에 대한 이해는 애플리케이션의 성능 최적화와 안정성 보장에 필수적입니다. 첫 번째 이유는 성능 최적화와 관련이 있습니다. 예를 들어, CPU의 멀티코어 아키텍처를 이해함으로써 서버 애플리케이션에서 멀티 스레딩과 병렬 처리를 효율적으로 구현할 수 있습니다. 이는 요청 처리량을 증가시키고 응답 시간을 단축시킬 수 있으며, 고객에게 더 나은 서비스 경험을 제공할 수 있습니다. 두 번째 이유는 자원 관리와 관련이 있습니다. 서버 애플리케이션은 종종 대량의 데이터를 처리하고, 메모리 및 CPU 자원을 집중적으로 사용합니다. 메모리 계층 구조(예: 캐시, 주 메모리)와 이에 대한 이해는 데이터 접근 시간을 최적화하고, 메모리 사용 효율을 극대화하는 방법을 개발자에게 제공합니다. 이를 통해 시스템의 전반적인 효율성을 향상시킬 수 있습니다. 세 번째 이유는 안정성과 가용성에 있습니다. 서버 개발자는 컴퓨터 구조에 대한 이해를 통해 시스템의 잠재적 한계와 병목 현상을 더 잘 파악할 수 있으며, 이를 바탕으로 더 견고하고 오류에 강한 시스템을 설계할 수 있습니다. 예를 들어, 서버 하드웨어의 장애 지점을 이해하고, 이에 대비한 높은 가용성을 보장하는 소프트웨어 설계를 할 수 있습니다. 종합하면, 컴퓨터 구조에 대한 깊은 이해는 서버 개발자가 성능, 자원 관리, 안정성을 고려한 효율적이고 안정적인 서버 애플리케이션을 설계하고 구현하는 데 있어 필수적입니다. 이는 최종적으로 사용자 경험을 개선하고, 비즈니스 목표 달성에 기여합니다.
Archive
· 2024-03-14
💾 [CS] 컴퓨터의 구성
컴퓨터의 구성. 1. 컴퓨터가 시스템은 크게 어떻게 나누어 지나요? 컴퓨터 시스템은 크게 하드웨어와 소프트웨어로 나누어집니다. 하드웨어는 컴퓨터를 구성하는 기계적 장치입니다. 중앙처리장치(CPU) 기억장치: RAM, HDD 입출력 장치: 마우스, 프린터 소프트웨어는 하드웨어의 동작을 지시하고 제어하는 명령어 집합입니다. 시스템 소프트웨어: 운영체제, 컴파일러 응용 소프트웨어: 워드프로세서, 스프레드시트 1.1 명령어란 무엇일까요? 명령어는 컴퓨터에게 무엇을, 어떻게 해야 하는지를 알려주는 지시사항입니다. 콤퓨터는 이러한 명령어들을 해석하고 실행하여 다양한 작업을 수행합니다. 명령어 구성 요소 연산자(Operation Code, Opcode) : 수행해야 할 기본적은 작업의 유형을 나타냅니다. 예를 들어, 데이터를 더하거나 빼거나, 저장하는 등의 작업이 이에 해당합니다. 피연산자(Operand) : 연산자가 작용할 데이터나, 그 데이터가 위치한 메모리 주소를 가리킵니다. 즉, 연산자가 어떤 데이터에 대한 작업을 수행할지를 지정합니다. 결과(Result) : 연산의 결과를 저장할 위치입니다. 이는 명령어에 따라 명시적으로 주어지거나, 특정 규칙에 따라 암시적으로 결정될 수 있습니다. 명령어들은 프로그래밍 언어로 작성되며, 고급 프로그래밍 언어에서 작성된 코드는 컴파일러나 인터프리터를 통해 기계어로 번역되어 컴퓨터가 이해할 수 있는 형태로 변환됩니다. 기계어는 컴퓨터의 프로세서가 직접 실행할 수 있는 매우 기본적이고 낮은 수준의 명령어 집합입니다. 2. 하드웨어란 무엇인가요? 하드웨어는 중앙처리장치(CPU), 기억장치, 입출력장치로 구성되어 있으며 이들은 시스템 버스로 연결되어 있습니다. 시스템 버스는 데이터와 명령 제어 신호를 각 장치로 실어나르는 역할을 합니다. 2.2 중앙처리장치(CPU)란 무엇인가요? 인간으로 따지면 두뇌에 해당하는 부분입니다. 주기억장치에서 프로그램 명령어와 데이터를 읽어와 처리하고 명령어의 수행 순서를 제어합니다. 중앙처리장치는 비교와 연산을 담당하는 산술논리연산장치(ALU)와 명령어의 해석과 실행을 담당하는 제어장치, 속도가 빠른 데이터 기억장소인 레지스터로 구성되어 있습니다. 개인용 컴퓨터와 같은 소형 컴퓨터에서는 CPU를 마이크로프로세서라고도 부릅니다. 2.3 기억장치란 무엇인가요? 프로그램, 데이터, 연산의 중간 결과를 저장하는 장치입니다. 기억장치는 주기억장치와 보조기억 장치로 나누어집니다. RAM과 ROM도 이곳에 해당합니다. 실행중인 프로그램과 같은 프로그램에 필요한 데이터를 일시적으로 저장합니다. 보조기억장치는 하드디스크 등을 말하며, 주기억장치에 비해 속도는 느리지만 많은 자료를 영구적으로 보관할 수 있는 장점이 있습니다. 2.4 입출력장치란 무엇인가요? 먼저 입출력장치는 입력과 출력 장치로 나뉘어집니다. 입력 장치는 컴퓨터 내부로 자료를 입력하는 장치인 키보드, 마우스등이 이에 속합니다. 출력 장치는 컴퓨터에서 외부로 표현하는 장치인 프린터, 모니터, 스피커등이 이에 속합니다. 3. 시스템 버스란 무엇인가요? 시스템 버스는 하드웨어 구성 요소를 물리적으로 연결하는 선을 말합니다. 시스템 버스는 각 구성요소가 다른 구성요소로 데이터를 보낼 수 있도록 통로가 되어줍니다. 시스템 버스는 용도에 따라 데이터 버스, 주소 버스, 제어 버스로 나뉘어집니다. 3.1 데이터 버스란 무엇인가요? 데이터 버스란 중앙처리장치와 기타 장치 사이에서 데이터를 전달하는 통로를 말합니다. 기억장치와 입출력장치의 명령어와 데이터를 중앙처리장치로 보내거나, 중앙처리장치의 연산 결과를 기억장치와 입출력장치로 보내는 ‘양방향’ 버스입니다. 3.2 주소 버스란 무엇인가요? 주소 버스는 중앙처리장치가 주기억장치나 입출력장치로 기억장치 주소를 전달하는 통로입니다. 주소버스는 그렇기 때문에 ‘단방향’ 버스입니다. 데이터를 정확히 실어나르기 위해서는 기억장치’주소’를 정해주어야 합니다. 3.3 제어 버스 제어 버스는 중앙처리장치가 기억장치나 입출력장치에 제어 신호를 전달하는 통로입니다. 제어 신호의 종류에는 기억장치 읽기 및 쓰기, 버스 요청 및 승인, 인터럽트 요청 및 승인, 클락, 리셋 등이 있습니다. 제어 버스는 읽기 동작과 쓰기 동작을 모두 수행하기 때문에 ‘양방향’ 버스입니다. 제어 버스가 필요한 이유는 주소 버스와 데이터 버스는 모든 장치에 공유되는데 이때 이를 제어할 수단이 필요하기 때문입니다. 4. 컴퓨터의 데이터 처리과정 컴퓨터는 기본적으로 읽고 처리한 뒤 저장하는 과정으로 이루어집니다. (READ -> PROCESS -> WRITE) 이 과정을 진행하면서 끊임없이 주기억장치(RAM)과 소통합니다. 이때 운영체제가 64bit라면, CPU는 RAM으로부터 데이터를 한번에 64bit씩 읽어옵니다.
Archive
· 2024-02-29
<
>
Touch background to close