Introduction
컴퓨터 조립을 해봤다면 한 번쯤 PCIe에 대해서 들어봤을 것이다. 예를 들어서 "이 NVME SSD는 PCIe 4.0을 지원해서 높은 대역폭을 보여준다"던가 말이다. PCIe는 컴퓨터 시스템에 고성능 디바이스(GPU, 네트워크 카드 등)를 연결하기 위한 표준 인터페이스이다.
이 글은 PCIe가 무엇이고, 이전에 사용되던 PCI에서 어떻게 발전했는지를 다룬다. 또한 PCIe 시스템을 구성하는 루트 컴플렉스, 스위치, 브리지와 엔드포인트 등의 구성요소와 이것이 어떻게 소프트웨어에서 표현되는지를 다룬다.
PCI vs. PCI Express
PCIe (Peripheral Component Interconnect Express)는 프로세서와 디바이스, 혹은 디바이스와 디바이스가 통신하기 위해 정의된 인터페이스이다. PCIe는 기존에 사용하던 PCI와, PCI-X를 대체하기 위해 설계되었다.PCIe로 넘어오면서 크게 바뀐 부분은 1) 병렬 전송이 아닌 직렬 전송 방식을 채택했다는 점과 2) 여러 디바이스가 버스를 공유하여 사용하는 구조가 아닌 point-to-point 링크를 사용한다는 점, 그리고 3) 트랜잭션이 요청(Request)과 응답(Completion)으로 분리되었다는 점이다.
Parallel Transmission vs. Serial Transmission
병렬 전송은 한 번에 여러 비트를 보내는 방법이고, 직렬 전송은 한 번에 하나의 비트씩 보내는 방법이다. 병렬 전송은 한 번에 여러 비트를 보내기 때문에 빠를 것 같지만, 특성상 속도를 높이는 데에 본질적인 한계가 존재한다. 여러 개의 비트가 서로 다른 타이밍에 도착하거나 (clock skew), common clock을 사용하기 때문에 clock period가 flight time (신호가 전달되는 시간)보다 짧아질 수 없다.
PCIe는 이러한 병렬 전송의 본질적인 문제 때문에 직렬 전송을 채택하게 되었다. 직렬 전송은 병렬 전송과 달리 여러 와이어들의 동기화를 신경쓰지 않아도 되기 때문에 속도를 더 높일 수 있다. 또한, PCIe는 x1, x2, x4, x8, x12, x16, x32 등 하나 이상의 레인을 사용해서 직렬 전송을 동시에 여러 레인에서 함으로써 전송 속도를 높일 수 있다. 링크와 레인은 뒷부분에서 좀더 자세하게 다룬다.
Bus architecture vs Point-to-Point links
PCI(-X)에서는 버스 구조를 채택했기 때문에 하나의 버스에 여러 디바이스가 연결된다. 이때 데이터를 보내기 위해 트랜잭션을 시작하면 하나의 디바이스만 버스를 사용할 수 있고, 버스 중재자는 여러 디바이스가 버스를 사용하는 데 문제가 없도록 중재한다. 버스 구조에서 브리지는 버스와 버스를 연결하며, 만약 한 버스의 디바이스에서 다른 버스의 디바이스로의 트랜잭션을 시작한다면 이는 브리지를 거쳐가게 된다.
PCIe는 버스가 아닌 point-to-point 링크를 사용한다. 링크는 두 디바이스를 연결하는 통로이며, 버스처럼 불특정 다수의 디바이스가 공유할 수 없다.
Request and Completion
이 글에서 PCIe 트랜잭션에 관해 자세히 다루지 않지만, 대략적 프로세서가 메모리에 읽고 쓰는 것이 트랜잭션이라고 불리는 패킷의 형태(정확히는 Transaction Layer Packet)로 송수신된다고 생각할 수 있다.
PCI에서 트랜잭션은 버스 마스터 (프로세서, 혹은 트랜잭션을 시작할 수 있는 디바이스)가 트랜잭션을 시작한다. 트랜잭션을 진행하는 동안은 같은 버스 상의 다른 디바이스가 버스를 사용할 수 없다. 위 그림은 PCI의 읽기 트랜잭션을 예시로 든 것이다. 버스 마스터는 트랜잭션을 시작할때 타겟 디바이스의 번호를 버스로 보내는데, 타겟 디바이스는 자신이 목적지인 경우 데이터를 전송하기 시작한다. PCI 트랜잭션에는 데이터의 크기가 명시되지 않아서, 타겟 디바이스는 마스터가 트랜잭션을 종료할 때까지 계속 데이터를 보낸다.
PCI에서 트랜잭션을 처리하는 방식의 문제점은, 타겟 디바이스가 아직 데이터를 보낼 준비가 안됬을 수도 있다는 것이다. 이 경우 트랜잭션을 잠깐 멈추거나 잠시 후에 다시 시도해야 하는데, 이러한 방식은 효율적이지 않다.
이와 다르게 PCIe는 트랜잭션을 요청(Request)과 응답(Completion)으로 나눈다. 요청자가 요청 트랜잭션을 보내면 타겟 디바이스는 데이터가 준비되었을 때 응답 트랜잭션을 보낸다. PCIe의 요청과 응답은 각각 트랜잭션 계층 패킷 (TLP, Transaction Packet Layer)로 전송된다. 구체적으로는 트랜잭션에는 Posted (응답으로 Completion이 필요없는 트랜잭션), Non-Posted (응답으로 Completion이 필요한 트랜잭션), Completion (Non-Posted에 대한 응답 트랜잭션) 유형이 있다.
Link and Lanes
링크(Link)는 두 PCIe 디바이스를 이어주는 물리적 연결이다. 링크는 하나 이상의 레인(Lane)으로 구성되는데, PCIe 디바이스를 구매할 때 x1, x2, x4, x8, ..., x32 등으로 명시된 숫자가 레인의 수다. 링크에 레인이 하나만 있어도 정상적으로 동작하지만, 레인의 수를 늘리면 더 많은 대역폭을 사용할 수 있다. 대신 메인보드에서 더 많은 공간을 차지하기 때문에 무한정 많은 레인을 사용하기에는 현실적인 한계가 있다.
레인이 여러 개인데 왜 병렬 전송이 아니라 직렬 전송인가? 하는 궁금증이 들 수 있다. PCIe는 데이터를 레인 수에 따라서 여러 조각으로 쪼갠 다음, 각 조각을 레인마다 직렬로 보낸다. 데이터를 쪼갠 다음 동시에 직렬로 보낸 데이터를 다시 합치는 것은 PCIe의 Physical Layer에서 담당한다.
PCIe Topology
PCIe에는 크게 다음과 같은 구성 요소가 있다: Root Complex, Switch, Bridge, Endpoint 각 구성요소에 관해 알아보자. 참고로, PCIe의 디바이스는 루트 컴플렉스가 루트 노드가 되는 트리 구조를 구성한다. 이는 중요한 특성 중 하나인데, 트리 구조는 사이클이 없기 때문에 두 정점 간의 경로가 유일하다.
Root Complex and Root Port
루트 컴플렉스 (Root Complex)는 프로세서를 PCIe 서브시스템과 연결한다. 루트 컴플렉스의 구현은 벤더에 따라 다를 수 있다. 루트 컴플렉스에는 PCIe 디바이스를 연결할 수 있게 포트 (혹은 슬롯)이 있는데, 루트 컴플렉스의 포트는 루트 포트 (Root Port) 라고 부른다. 참고로 루트 포트는 다운스트림 포트 (Downstream Port)다. 위 그림에서 네모난 구멍들은 포트를 표현한 것이다. 아래 그림은 실제 메인보드에서 PCIe 슬롯이 어떻게 생겼는지 보여준다.
Switch and Bridge
자, 그런데 루트 포트가 4개면 PCIe 디바이스를 4개만 장착할 수 있을까? 그렇지는 않다. 앞서 언급했듯 PCIe는 공유 버스가 아니라 Point-to-Point 링크를 사용하기 때문에 포트에는 하나의 디바이스만 연결할 수 있다. 대신 PCIe는 스위치 (Switch)와 브리지 (Bridge) 디바이스로 하나의 PCIe 포트를 여러 개의 포트로 확장하거나 (스위치) PCI, PCI-X 버스로 확장할 수 있도록 한다. (브리지)
스위치에는 하나의 업스트림 포트 (Upstream Port)와 하나 이상의 다운스트림 포트(Downstream Port)가 있다. 업스트림 포트에는 루트 포트나 다른 스위치의 다운스트림 포트가 연결될 수 있다. 다운스트림 포트에는 스위치나 브리지의 업스트림 포트, 혹은 엔드포인트 (Endpoint) 디바이스의 업스트림 포트가 연결될 수 있다. 스위치는 하나의 포트를 여러 개의 포트로 (다운스트림 포트의 개수 만큼) 확장해준다.
Endpoints
스위치도, 브리지도 아닌 장치는 엔드포인트라고 한다. 네트워크 카드, 그래픽 카드 등 우리가 쓰는 장치들은 대부분 엔드포인트에 해당한다.
PCIe topology visible to Software
앞서 루트 컴플렉스, 스위치, 브리지, 엔드포인트 등 다양한 구성 요소에 관해 이야기했다. 그런데 소프트웨어(펌웨어, 운영체제, UEFI 드라이버)가 바라보는 관점은 조금 다르다. 조금 과장해서 말하면 소프트웨어가 바라보는 데이터 상에는 오직 브리지와 엔드포인트만 존재한다. 소프트웨어에 루트 컴플렉스와 스위치는 브리지와 엔드포인트로 표현된다.
이게 무슨 말인지 이해가 가지 않을 수 있다. 스위치를 예를 들어보자. 우리는 스위치에 업스트림 포트와 다운스트림 포트가 있고, 스위치가 PCIe 계층 구조를 확장하는 데에 사용됨을 알고 있다. 그럼 스위치 내부는 어떻게 생겼을까? 그건 스위치를 설계하는 사람에게 중요하지, PCIe 스펙에서 중요한 부분은 아니다. PCIe 표준만 지킨다면 스위치 내부는 어떻게 만들어도 상관이 없다. 비슷한 논리로 (...) 스위치가 실제로 어떻게 구현되냐와 관계 없이 스위치의 내부는 브리지를 갖는 것처럼 표현된다.
소프트웨어가 바라보는 스위치는 위 그림과 같이 생겼다. 스위치 업스트림 포트를 PCI 버스로 확장하는 PCI-PCI 브리지가 있고, 해당 브리지에 다운스트림 포트를 나타내는 PCI-PCI 브리지가 있다. 디바이스를 다운스트림 포트에 연결하면, 아래 세 개의 브리지의 버스 중 하나에 연결된 것과 같이 나타난다.
루트 컴플렉스도 비슷하게 소프트웨어 관점에서 브리지로써 표현된다. 스위치와 다르게 업스트림 포트는 없고, 프로세서를 PCIe 시스템과 연결하는 호스트 브리지, 그리고 루트 컴플렉스의 루트 포트들은 호스트 브리지에 연결된 PCI-to-PCI 브리지로 표현된다. 원래 호스트 브리지 (Host Bridge)는 PCIe의 루트 컴플렉스와 비슷하게 PCI에서 프로세서와 메모리를 PCI 시스템과 연결하는 브리지였는데, PCIe의 루트 컴플렉스도 소프트웨어 하위호환성을 위해 동일하게 표현된다.
마치며
이번 글에서는 PCI와 PCIe가 어떻게 다른지와 PCIe 디바이스에 어떤 컴포넌트들이 존재하며, 토폴로지가 어떻게 구성되는지를 알아보았다. PCIe에는 재미있는 주제가 더 있어서, 앞으로 글로 조금 더 정리해볼 생각이다.
PCIe를 공부할 자료로는 MindShare사의 "PCI Express Technology 3.0"을 적극 추천한다. PCIe 3.0 기준으로 작성된 오래된 책이지만 중요한 내용들을 매우 높은 퀄리티로 설명한다.
아쉽게도 공식 PCIe 스펙은 PCI-SIG 멤버만 접근할 수 있다. 아니, 멤버가 아니어도 구매할 수 있긴 한데 매우 비싸니 보고싶다면 PCI-SIG에 멤버로 가입한 회사에 취업하자 :)
지금 머릿속에 있는 몇 가지 주제들
- 운영체제가 어떻게 PCIe 디바이스와 소통하는가 (데이터 송수신, 디바이스 설정)
- 운영체제가 PCIe 디바이스를 어떻게 발견하고 순회하는가
- 디바이스의 레지스터와 버퍼에 접근할 때, 어떤 주소 공간을 사용하는가
- 트랜잭션이 어떻게 라우팅되는가
- PCIe의 Physical, Data Link, Transaction layers
- PCIe 트랜잭션 오더링
p.s. 어쩌다보니 PCIe를 주제로 글을 쓰게 되었다. MM을 버리고 디바이스 드라이버 개발자가 되겠다는 건 아니다 :) 최근에 CXL을 배우다 보니 PCIe도 자연스럽게 배우게 됐다. 한동안 공부한 것들을 노션으로만 정리했는데, 그 중 일부는 블로그에도 공개하려고 한다.
참고 문서
[1] https://powerdeng.tistory.com/11
[2] https://www.mindshare.com/Learn/PCI_Express/Books
[3] https://en.wikipedia.org/wiki/PCI_Express#/media/File:PCIeX16andX1OnAsusH81MKMB.jpg
'Computer Architecture' 카테고리의 다른 글
PCIe Enumeration (0) | 2023.11.30 |
---|---|
PCIe Configuration Space (0) | 2023.11.30 |
PIPT / VIVT / VIPT 캐시와 flush_dcache_folio() (0) | 2022.06.25 |
다시 정리하는 NUMA (1) | 2022.01.21 |
LEGv8 ISA - 특징과 명령어 포맷 (0) | 2021.12.02 |
댓글