티스토리 뷰
안녕하세요 Pingu입니다!🐧
지난 글에 이어 OS에서의 Persistence(영속성)에 대해 알아볼 예정이에요. 지난 글에서는 I/O Device에 대한 일반적인 개념에 대해 알아봤었는데요, 이번 글에서는 그러한 I/O Device 중 하나인 Hard Disk Drive에 대해 알아보도록 하겠습니다. 이름에서 알 수 있듯 하드웨어를 다루는 장치일 것 같죠? 제가 공부할 때 참고하고 있는 책인 OSTEP에서는 Chapter 37 - Hard Disk Drives 부분입니다.
Hard Disk Drives
지난 글에서는 I/O Device에 대한 일반적인 개념을 알아보고 OS와 상호작용하는 방법을 알아봤었습니다. 이번글에서는 I/O Device들 중에서도 hard disk device에 대해 알아보려고 합니다. 하드디스크는 오랜 기간 컴퓨터의 영구 저장장치로 사용되었으며 이는 나중에 배울 파일 시스템과 이번 글에서 알아볼 하드디스크 드라이브에 의해 동작됩니다. 디스크를 관리하는 파일 시스템은 나중에 알아볼 예정인데요, 이를 알아보기 전에 이번 글에서 하드디스크 드라이브를 알아보며 디스크 작업의 세부 사항을 이해하는 것이 좋습니다! 따라서 하드 디스크 드라이브에 대해 알아보도록 하겠습니다.
The Interface
I/O 디바이스의 일반적인 개념에서 알아본 대로 I/O 디바이스는 Interface, Internal로 구성되어 있었습니다. 그렇다면 하드 디스크 드라이브는 어떤 인터페이스를 가지고있을까요? 하드 디스크 드라이브는 많은 수의 섹터라고 하는 512바이트 블록으로 구성되며 각 섹터는 읽고 쓰기가 가능합니다. 만약 섹터가 n개 있다면 디스크에는 0번 섹터부터 n-1 섹터까지 존재하게 됩니다. 즉 디스크는 하나의 섹터 배열이라고 볼 수도 있어요. 그리고 0~n-1은 address space(주소 공간)이라고 할 수 있죠.
멀티 섹터 작업도 가능합니다. 많은 파일 시스템에서는 한 번에 4KB 이상을 읽거나 쓰는데, 드라이브에서 보장하는 단위는 512 바이트 입니다. 이 말은 512바이트에 대한 읽고 쓰기가 원자적으로 발생한다는 말인데, 만약에 4KB를 쓰고 싶은데 중간에 전원이 차단되면 512바이트 단위로 쓰이다가 차단된 시점에서 쓰지 못한 데이터는 완료되지 못하는 것이죠. 이런 걸 torn write(조각난 쓰기)라고 합니다.
일반적으로 드라이브 주소공간 내에서 인접한 두 블록에 접근하는 것이 멀리 떨어진 블록에 접근하는 것보다 빠르다고 가정할 수 있습니다. 이를 활용하면 연속적인 읽고 쓰기가 가장 빠른 접근방법이며 Random 하게 블록에 접근하는 방법보다 빠르다고 가정할 수 있습니다. 하지만 이러한 가정들은 하드 디스크 드라이브에 지정되어 있진 않습니다.
Basic Geometry
인터페이스를 이해했으니 디스크의 구성 요소에 대해 알아보도록 하겠습니다. 일단 디스크에는 정보를 저장하는 platter라는 원판이 있습니다. Platter는 자기 변화를 유도하여 데이터를 영구적으로 저장해주는데 따라서 디스크에서는 반드시 하나 이상의 platter가 존재합니다. 물론 platter가 여러 개 있을 수도 있는고 platter 각각의 면을 surface라고 합니다.
이러한 platter를 일정한 속도로 회전시키는 moter가 존재하고 이는 하드웨어 회사에서 RPM(rotation per minute)이라고 광고하는 수치입니다. 예를 들어 10000RPM이라고 하면 1분에 만 번 회전할 수 있다는 것이고 1번 회전하는데 6ms가 걸린다는 것을 의미해요.
데이터는 platter의 surface에 저장되는데, 이 때 섹터들이 동심원으로 저장됩니다. 이러한 동심원을 track이라고 하며 platter의 surface에는 수천 개의 track이 존재합니다. 즉 track은 여러 개의 섹터들로 구성된다고 할 수 있어요.
이렇게 구성된 디스크를 읽고 쓰기 위해서는 디스크의 자기 패턴을 감지하고 변경을 유도하는, 쉽게 말하면 읽고 쓰는 메커니즘이 필요합니다. 이러한 작업은 disk head로 수행되며 하나의 surface당 하나의 헤드가 존재합니다.
이를 간단히 그림으로 그리면 위와 같은 모양이 됩니다.
A Simple Disk Drive
그럼 하나의 트랙이 읽고 쓰여지는 것을 보며 디스크가 어떻게 작동하는지 알아보겠습니다.
아까 말했듯 트랙은 섹터로 구성되어있으며 위의 그림에서는 하나의 트랙에 12개의 섹터가 존재하는 것을 볼 수 있습니다. 각 섹터는 512바이트 크기를 가지며 0~11의 숫자로 구분됩니다. 이러한 트랙만 있으면 데이터를 읽고 쓸 수 없겠죠? 따라서 읽고 쓰기를 위한 디스크 헤더가 하나 필요합니다.
이렇게 디스크 헤더를 추가하게 되면 데이터를 읽고 쓸 수 있습니다. 위의 그림에서는 6번 섹터에 디스크 헤더가 위치하고 있는데, platter의 surface가 회전하여 섹터를 바꿔줄 수 있습니다.
Single-track Latency: The Rotational Delay
아까 그림을 다시 가져와서 보겠습니다. 위의 그림에서 블록 0번을 읽고 싶다면 어떻게 해야 할까요?
이렇게 surface를 돌려버리면 되겠죠? 이때 디스크 헤드는 가만히 기다리고 있다가 작업을 해야 하는 블록이 자신에게 위치하면 그때부터 작업을 수행하면 됩니다. 이렇게 알맞은 블록을 헤드에게 위치할 때 발생하는 대기시간을 rataional delay(회전 지연)이라고 합니다. 위의 그림에서 처음에 6번에 위치한 헤더에게 가장 오래 기다리게 하는 방법은 무엇일까요? Surface가 시계 반대방향으로 도니까 5번 블록을 읽어달라고 하면 가장 오래 기다려야 할 거예요.
Multiple Tracks: Seek Time
방금 살펴본 것은 트랙이 하나 있을 때였고, 아까 하드디스크의 구성 요소에 대해 알아볼 때 언급한 대로 하나의 surface에는 엄청나게 많은 개수의 트랙이 존재하기 때문에 방금 예는 적절하지 않습니다. 따라서 트랙이 여러 개있을 때를 한 번 살펴보도록 하겠습니다.
위와 같이 트랙이 여러개 생기면 디스크 헤드는 가만히 기다리는 것이 아닌 스스로 트랙 정도는 찾아줘야 합니다. 위의 그림에서 볼 수 있듯 왼쪽 그림에서는 헤드가 제일 안쪽 트랙에 존재하는데 오른쪽 그림에는 제일 바깥쪽 트랙에 존재합니다. 이렇게 헤드를 이동하는 것을 seek라고 하며 이때 걸리는 시간을 seek time이라고 합니다.
그런데 헤드의 위치가 이동할 때 surface도 함께 돌아야 효율적이지 않을까요? 위의 그림에서도 이를 볼 수 있는데요, 헤드가 가장 안쪽에서 바깥쪽 트랙으로 이동하는 동안 surface도 함께 돌아가는 것을 볼 수 있습니다. 만약 11번 섹터를 읽고 싶었다면 헤드가 아까처럼 기다려야 하겠죠. 11번 섹터가 헤드 아래 위치할 때가 I/O의 최종단계입니다. 이때부터 헤드는 데이터를 읽거나 쓰기 시작합니다. 즉 seek 한 뒤 rotational delay를 기다리고 데이터를 전송하는 과정이 디스크의 작동과정이라고 볼 수 있습니다.
Some Other Details
이렇게 하드 디스크 드라이브 작동 방식을 알아봤는데 몇 가지 세부적인 부분들을 살펴보도록 하겠습니다.
첫 번째는 트랙 경계를 넘을 때에도 연속적인 읽기가 제대로 될 수 있도록 track skew라는 것을 사용합니다. Track skew는 섹터들의 시작 위치를 다르게 하는 방법인데요, track skew를 적용한 것과 적용하지 않은 그림으로 쉽게 구분할 수 있습니다.
읽고 싶은 섹터가 22, 23, 12 섹터와 34, 35, 24 섹터라고 해보겠습니다. 왼쪽의 track skew가 적용되지 않은 디스크에서는 두 개의 섹터를 접근하기 위해서는 surface가 2바퀴 돌아야 하는 상황이 발생합니다. 그런데 만약 오른쪽과 같이 트랙마다 시작점의 위치를 조절해주면 트랙을 한 바퀴만 돌려도 원하는 섹터에 모두 접근할 수 있습니다!
두 번째는 일반적으로 바깥쪽 트랙이 안쪽 트랙보다 더 많은 섹터를 갖는 것은 기하학적으로 당연한 사실입니다. 트랙의 크기가 커지기 때문이죠. 이러한 트랙이 엄청나게 많기 때문에 비슷한 위치의 트랙들을 모아서 연속적인 트랙 집합(multi zone이라고도 합니다.)을 만들기도 합니다.
마지막으로 디스크 드라이브에는 캐시가 존재하며 이는 track buffer라고 부릅니다. 이 캐시는 드라이브가 디스크에서 읽고, 쓰는 데이터를 보관하는데 쓰이는 메모리입니다. 예를 들어 특정 섹터를 읽을 때 섹터만 읽는 것이 아닌 해당 섹터의 트랙의 모든 섹터를 읽어서 메모리에 캐싱합니다. 이렇게 하면 동일한 트랙의 섹터 접근을 효율적으로 할 수 있습니다.
Read 말고 write의 경우엔 어떨까요? 하드 디스크 드라이브는 데이터를 메모리에 넣었을 때 write가 완료되었는지, write가 실제로 디스크에 기록된 후에 완료되었다고 해야 할지를 결정할 수 있습니다. 데이터를 메모리에 넣었을 때 write가 완료되었다고 처리하는 것은 write back caching이라고 합니다. 데이터가 디스크에 기록된 후에 완료되었다고 처리하는 것은 그냥 write라고 합니다. Write back caching은 더 빠르게 보일 수는 있지만 정확성을 위해 특정 순서로 데이터를 디스크에 저장해야 하는 경우 문제가 발생할 수 있습니다. 이에 대한 내용은 나중에 배울 파일 시스템 journaling에서 알아보도록 하겠습니다.
I/O Time: Doing The Math
그럼 디스크가 어떻게 작동하는지를 대략적으로 알았으니 이젠 디스크 요청에 소요되는 시간을 계산할 수 있는데요, 식은 다음과 같습니다.
위와 같은 디스크 드라이브가 존재할 때 각각 요청들이 어떻게 계산되는지를 한 번 살펴보겠습니다. Random 워크로드부터 살펴보도록 하겠습니다. 두 개의 디스크 드라이브에 4KB의 읽기 요청이 디스크의 랜덤 위치에서 발생한다고 가정한 뒤 읽기에 걸리는 시간을 계산해보겠습니다. 우선 Cheetah 디스크에서의 시간을 살펴보면..
평균 seek time으로 seek time은 계산해주면 되고 rotation time은 RPM으로 구하면 됩니다. Cheetah는 1초에 15000번 회전을 하기 때문에 1회전에 필요한 시간은 4ms이며 평균적으로 디스크는 반 바퀴 회전하기 때문에 2ms가 평균 rotation time입니다. Transfer time만 계산하면 되는데 Cheetah는 125MB/s 속도를 가지는데 지금 전송하려는 크기는 4KB이므로 이에 맞게 환산해주시면 됩니다.
대략적으로 30 마이크로 초가 나오게 됩니다. 1ms = 1000 마이크로 초이므로 대략적으로 계산하면 Cheetah에서는 4KB의 데이터를 읽는데 6ms가 필요하네요. 여기서 I/O 속도 계산을 위해 전송 크기를 평균 시간으로 나누면 0.66MB/s라는 값이 나오게 됩니다. 동일한 계산을 Barracuda에 하게 되면 0.31MB/s라는 값이 나오게 됩니다. Sequential Workload 계산도 진행하여 정리해보면 아래와 같은 결과가 나오게 됩니다.
위의 결과에서 알 수 있는 점은 Random과 Sequential 사이에는 성능 격차가 크다는 점입니다. 그리고 성능과 용량 사이에도 관계가 있다는 것을 알 수 있습니다.
Disk Scheduling
I/O는 비용이 높은 작업이기 때문에 OS는 디스크에 발생하는 I/O 순서들을 효율적인 순서로 결정하는 역할을 합니다. 이러한 역할을 하는 것은 디스크 스케줄러라고 하며 I/O 요청들이 들어오면 디스크 스케줄러가 이를 확인하고 작업들을 스케줄링합니다.
이러한 스케줄링은 예전에 프로세스 스케줄링에서도 한 번 다뤘던 적이 있는데요, 디스크 스케줄링은 프로세스 스케줄링과는 다르게 작업마다 걸리는 시간을 추측할 수 있습니다. 아까 알아본 대로 계산을 통해 알아볼 수 있기 때문이죠! 따라서 어떻게 처리하는 것이 가장 효율적인지를 결정할 수 있습니다. 이에 따라 디스크 스케줄러는 SJF(Shortest Job First) 원칙을 따르려고 합니다.
가장 간단한 방법으로는 FCFS(First Come First Serve) 방법이 있습니다. 이름만 봐도 알 수 있듯 요청이 들어오는 순서대로 그냥 처리하는 방법입니다. 이는 나중에 따로 다른 방법들과 비교를 할 때 다시 살펴보도록 하겠습니다.
SSTF: Shortest Seek Time First
디스크 스케줄링 방법 중 하나는 SSTF(Shortest Seek Time First)입니다. SSTF는 말 그대로 seek time이 가장 짧은 요청부터 처리한다는 아이디어입니다. Seek time에 의해 순서를 결정하기 때문에 현재 헤드의 위치에 따라 요청들의 순서가 달라지게 되겠죠?
예를 들어 트랙 번호로 98, 183, 37, 122, 14, 124, 65, 67의 순서 요청이 들어왔고 현재 헤드는 트랙 53에 있다고 가정해 보겠습니다. 이러한 상황에서 SSTF를 적용하면 헤더의 현재 트랙 위치에서 가장 가까운 요청부터 처리하게 되기 때문에 아래와 같이 처리됩니다.
비교를 위해 FCFS를 적용했을 때도 그려봤는데 확실히 SSTF가 FCFS에 비해 seek time을 줄일 수 있는 방법인 것을 알 수 있습니다.
하지만 SSTF는 문제점이 있는데요, 바로 starvation(기아문제)입니다. 즉 공평하지 않다는 것이죠. 아까의 예에서도 볼 수 있는데요, 트랙 183에 대한 요청이 두 번째로 먼저 들어왔음에도 불구하고 가장 마지막에 처리됩니다. 지금은 요청이 더 들어오지 않아서 곧 수행된 것처럼 보일 수 있지만 만약 요청들이 계속 들어오게 되면 오랜 기간 동안 처리가 되지 않을 수도 있습니다.
Elevator(a.k.a. SCAN or C-SCAN)
디스크 스케줄링 방법에는 SCAN이라는 방법도 있습니다. 이 방법은 한 방향으로 요청을 처리하는 것이죠. 즉 트랙 번호가 증가하는 방향이나 감소하는 방향 중 하나의 방향으로만 처리한다는 아이디어입니다. 아까의 예를 SCAN으로 처리하면 아래와 같이 처리됩니다.
위와 같이 한 방향으로만 가다가 어느 순간 디스크를 가로지르는 부분이 생기는데 이러한 부분을 sweep이라고 합니다. 이러한 SCAN은 여러 가지 변형 방법이 존재하는데 F-SCAN이라는 방법은 sweep 중 들어오는 요청을 나중에 처리하는 방법으로 이를 사용하면 기아 상태를 피할 수 있습니다.
또 다른 변형 방법은 C-SCAN(Circular SCAN)으로 아까 SCAN 방법은 언젠가 방향을 트는 경우가 발생했지만 C-SCAN은 무조건 한 방향으로만 이동합니다. 결과를 보면 이해가 빠르실 거예요.
위와 같이 한 방향으로만 처리합니다. 이렇게 하면 안쪽과 바깥쪽 트랙에 대해 좀 더 공평하게 처리할 수 있습니다. 이러한 SCAN 알고리즘들은 Elevator 알고리즘이라고도 합니다. 하지만 이러한 지금까지 알아본 SSTF, SCAN 알고리즘들은 seek time만 고려한 것으로 실제 디스크 작업에는 seek time, rotation time이 존재합니다. 따라서 이 두 가지를 모두 고려한 알고리즘이 필요합니다.
SPTF: Shortest Positioning Time First
그럼 seek time과 rotation time을 모두 고려하는 SPTF (Shortest Positioning Time First) 스케줄링 방법에 대해 알아보겠습니다. 이 방법은 seek time, ratation time을 합친 값이 가장 짧은 요청부터 처리하겠다 라는 말입니다.
예를 들어 현재 헤드가 섹터 30에 존재할 때 섹터 16, 섹터 8에 대한 요청이 들어왔을 때 무엇을 먼저 처리해야 할까요? Seek time만을 고려하던 방법들에서는 섹터 16 -> 섹터 8의 순서로 처리했을 텐데 이젠 rotation time도 고려해줘야 하기 때문에 잡을 쉽게 할 수 없습니다. 즉 상황에 따라 다르게 처리가 될 수 있겠죠? 물론 고려해야 하는 것이 늘어났기 때문에 구현은 더 어렵습니다. 또한 OS는 트랙 경계가 어디에 있는지 디스크 헤드가 지금 어디에 있는지에 대한 좋은 아이디어가 없기 때문에 SPTF는 하드 디스크 드라이브 내부에서 수행됩니다. 즉 디스크 스케줄링이 어디에서 수행되는지에 대한 문제가 발생합니다.
Other Scheduling Issues
아까 언급한 대로 디스크 스케줄링이 어디서 수행되는지에 대한 문제가 발생했습니다. 예전 시스템에서는 OS가 모든 스케줄링을 수행했었는데, 최근에는 디스크 내부에서 스케줄러를 가질 수 있고 아까 알아본 SPTF를 정확하게 구현할 수 있습니다.
또한 디스크 스케줄러가 수행될 때 중요한 것에는 I/O merge가 있습니다. 예를 들어 33번 블록, 3번 블록, 34번 블록을 읽으라는 I/O 요청이 발생하면 33, 34는 연속적으로 처리할 수 있기 때문에 이를 병합하여 처리하는 것이 효율적입니다.
또 다른 고려해야 할 점은 헤드를 이동하기 전에 현재 헤드가 존재하는 섹터에 대한 더 효율적인 요청이 들어올 수 있기 때문에 잠깐 기다렸다가 다음 요청을 처리하는 방법입니다. 보통 기다리게 되면 더 오래 걸리지 않느냐? 하실 수 있는데 실제 연구결과에 따르면 이렇게 잠깐 기다리는 것이 기다리지 않는 방법보다 효율적이라고 합니다.
Summary
이번 글에서는 디스크가 작동하는 방식에 대한 것을 간단하게 알아봤습니다. 물론 실제 드라이브 설계에는 물리학, 전자공학 등과 같은 개념들이 포함되지만 저는 그런 전공이 아니므로 그러한 부분은 생략했습니다. ㅎㅎ; 이번 글에서는 디스크 요청을 처리하는 시간을 계산하는 방법, 디스크 요청을 스케줄링하는 방법에 대해 알아봤습니다. 다음 글에서는 RAID(Redundant Array of Inexpensive Disk)에 대해 알아보도록 하겠습니다.
감사합니다!
'Computer > Operating System' 카테고리의 다른 글
[OS] File과 Directory - OS 공부 27 (1) | 2021.02.06 |
---|---|
[OS] Redundant Arrays of Inexpensive Disks(RAID) 알아보기 - OS 공부 26 (1) | 2021.01.30 |
[OS] I/O Device 알아보기 - OS 공부 24 (0) | 2021.01.19 |
[OS] Concurrency의 문제점 Deadlock - OS 공부 23 (0) | 2021.01.17 |
[OS] Semaphore(세마포어)를 사용하여 concurrency(동시성) 구현하기 - OS 공부 22 (0) | 2021.01.14 |
- Total
- Today
- Yesterday
- operating
- design
- 스위프트
- 동시성
- 백준
- Apple
- DP
- BFS
- 코딩테스트
- 프로그래밍
- mac
- document
- 알고리즘
- Xcode
- Publisher
- pattern
- System
- OSTEP
- 자료구조
- operator
- 문법
- Combine
- 아이폰
- Swift
- 앱개발
- OS
- IOS
- 코테
- 테이블뷰
- dfs
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |