본 글은 The Linux Kernel 을 정리한 것이며, 출처를 밝히지 않은 모든 이미지는 원글에 속한 것입니다.


하드 디스크

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=capemay&logNo=220221154613

  • 하드 디스크는 작동기(actuator)로 원반 표면 위에서 헤드를 움직이게 하며, 이 원반(platter)은 가운데 축(spindle)에 연결되어 일정한 속도로 회전하는데 이 원반은 하나 이상 존재
    • 회전 속도는 3000RPM ~ 10000RPM 사이 다양함
  • 읽기/쓰기 헤드(read/write head)는 원반 표면에 있는 미세한 알갱이에 자성을 띄워서 디스크에 자료를 기록하고, 이 자성을 감지하여 자료를 읽음
  • 각 원반의 윗면과 아랫면에 각각 헤드가 존재하고, 읽기/쓰기 헤드는 물리적으로 원반 표면을 건드리지 않고 아주 얕게 원반 위에 떠있으며, 모든 읽기/쓰기 헤드는 원반 표면에서 동일하게 움직임
  • 원반의 각 표면은 트랙(track)이라는 작은 동심원으로 나누어지며, 바깥쪽에 있을 수록 작은 번호, 중심에 가까울수록 큰 번호의 트랙
  • 실린더(cylinder)는 동일한 번호를 가진 트랙의 집합으로, 원반 양면의 k번 트랙은 모두 k번 실린더
  • 각 트랙은 섹터(sector)로 나뉘며, 섹터는 자료를 디스크에 저장하고 읽어들이는 최소 단위로, 디스크 블럭 크기와 동일 (보통 512바이트로, 이 크기는 디스크 제작 후 포맷 시 지정됨)
    • 터미널에서 출력했을 때 보여지는 디스크 용량이 더 작은 이유는 섹터 중 일부는 디스크 파티션 정보를 저장하므로 제외되기 때문
  • 디스크는 보통 기하학 구조로 표현되며, 부팅 시 IDE 디스크는 다음과 같이 나타낼 수 있음
    • hdb: Conner Peripherals 540MB-CFS540A, 516MB w/64kB Cache, CHS=1050/16/63
    • 디스크 1050개의 실린더(트랙), 16개의 헤드(8개의 원반), 각 트랙에는 63개의 섹터가 있음을 의미
    • 디스크 저장 용량은 516MB
  • 어떤 디스크들은 자동으로 배드 섹터(bad sector)를 찾아내서 디스크가 제대로 작동하도록 인덱스를 다시 부여하기도 함
  • 하드 디스크는 특별한 목적을 위해 할당된 섹터들의 그룹인 파티션(partition) 단위로 쪼개질 수 있고, 파티셔닝은 디스크를 여러 OS에게 나눠주는 등의 이유로 사용
  • 많은 리눅스 시스템은 하나의 디스크에 3개의 파티션을 포함: DOS 파일 시스템, EXT2 파일 시스템, 스왑 파티션
    • 이러한 파티션 정보는 파티션 테이블에 나와 있으며, 이 테이블의 각 엔트리는 파티션의 시작과 끝에 해당되는 헤드와 섹터, 실린더 번호를 포함
    • fdisk로 DOS로 포맷된 디스크는 4개의 1차 디스크 파티션(primary disk partition)을 가질 수 있고, 파티션 테이블의 4개 엔트리 모두 쓰일 필요는 없음
    • fdisk는 3가지 유형의 파티션을 지원: 1차(primary), 확장(ㄷxtended), 논리(logical) 파티션
    • 확장 파티션은 진짜 파티션이 아니며, 여러 논리 파티션을 가지고 있는 것
    • 다음은 2개의 1차 파티션을 갖는 디스크에 fdisk를 실행한 결과: 첫번째 파티션이 헤드 1, 섹터 1, 실린더 0에서 시작하며, 헤드 63, 섹터 32, 실린더 477 까지 있음을 의미. 두번째 파티션은 스왑 파티션으로 다음 실린더 478에서 시작하여 디스크 가장 안쪽 실린더까지 포함 (원반 표면의 바깥에서 안쪽 방향으로 각 파티션들이 위치함)

fdisk 결과

  • 리눅스는 초기화할 때 하드 디스크의 배치도를 메모리에 매핑하는데, 먼저 시스템에 디스크 개수와 각 디스크의 종류를 파악하여 각 디스크 파티션이 나누어지는 구조를 탐색
  • IDE 같은 개별 디스크 서브시스템은 초기화할 때 찾은 디스크를 gendisk 구조체로 기술하며, gendisk_head 포인터가 이 구조체들의 리스트를 참조
    • 각 gendisk 구조체는 블럭 특수 장치의 메이저 번호와 일치하는 고유한 번호를 가짐
    • 아래 그림의 맨 앞 gendisk는 SCSI 디스크 서브시스템의 것이고, 다음은 IDE 디스크 서브시스템의 것으로, 첫번째 IDE 컨트롤러는 "ide0"
    • gendisk 구조체는 리눅스가 파티션을 검사할 때만 쓰이며, 각 디스크 서브시스템은 장치의 메이저와 마이너 장치 번호를 물리 디스크에 있는 파티션과 매핑시킬 수 있도록 각자의 자료구조를 구축함
    • 블럭 장치가 버퍼 캐시나 파일 연산을 통해 읽혀지거나 쓰일 때, 커널은 이 연산을 블럭 장치 특수 파일(e.g., /dev/sda2)에서 발견한 메이저 장치 번호를 이용하여 올바른 장치로 보냄
    • 마이너 장치 번호를 물리 장치에 연결하는 것은 개별 디스크 드라이버나 서브시스템의 역할 (마이너 장치 번호로 장치를 구별)

디스크 커널 자료구조
https://www.twblogs.net/a/5ca59651bd9eee5b1a0720f4

  • IDE(Intergrated Disk Electronics) 디스크
    • IDE는 SCSI 같은 I/O 버스가 아닌 디스크 인터페이스
    • 각 IDE 컨트롤러는 2개의 디스크까지 지원: 주 디스크(master), 종속 디스크(slave)
    • IDE는 1초에 3.3 Mbytes의 데이터를 전송, 디스크 최대 크기는 538MB
    • 확장 IDE(Extended IDE, EIDE)는 디스크 크기를 최대 8.6GB로 가지며, 전송 속도를 초당 16.6MB로 올린 것
    • IDE/EIDE 디스크는 SCSI 디스크보다 저렴하며 대부분 PC는 IDE 컨트롤러를 포함
    • 리눅스는 IDE 디스크의 이름을 컨트롤러(최대 2개)를 발견한 순서에 따라 부여함
      • 1차 IDE 컨트롤러의 주 디스크는 /dev/hda 이고 종속 디스크는 /dev/hdb
      • 2차 IDE 컨트롤러의 주 디스크는 /dev/hdc
    • IDE 서브시스템은 커널에 IDE 컨트롤러를 등록하지만 디스크를 등록하지는 않음
    • 1차 IDE 컨트롤러의 메이저 장치 번호는 3이고, 2차 IDE 컨트롤러의 것은 22일 때, blk_dev와 blkdevs 배열의 3번과 22번 인덱스에 IDE 서브시스템 엔트리가 저장됨
    • 커널은 블럭 특수 파일을 관리하는 IDE 서브시스템에 대한 파일 연산이나 버퍼 캐시 연산을 메이저 장치 번호를 인덱스로 사용하여 알아낸 IDE 서브시스템으로 전달하며, 어떤 디스크에 대한 요청인지는 IDE 서브시스템이 마이너 장치 번호를 사용하여 판별
    • IDE 서브시스템 초기화
      • 커널은 시스템의 CMOS 메모리에 디스크 정보를 살펴보고, 발견한 디스크의 기하학 정보를 BIOS로부터 알아내 이 정보를 각자의 ide_hwif_t 구조체를 설정하는데 사용함
      • 최근 PCI EIDE 컨트롤러를 포함하는 경우 PCI 칩셋을 사용하는데, 여기서 PCI BIOS 콜백을 이용해서 컨트롤러를 찾음
      • IDE 컨트롤러가 발견되면, 연결된 디스크를 반영하여 ide_hwif_t가 설정됨
      • IDE 드라이버가 I/O 메모리 공간에 있는 IDE 명령 레지스터에 쓰면서 동작하는데, 각 컨트롤러는 리눅스 블럭 버퍼 캐시와 가상 파일 시스템에 자신을 등록 (blk_dev와 blkdevs 배열에 추가되는 것)
      • 추가로, 인터럽트에 대한 제어권을 요청하고, 부팅 시 발견된 컨트롤러마다 gnedisk 구조체를 생성해 gendisk_head 포인터가 참조하는 리스트에 추가 (이 리스트는 디스크의 파티션을 찾는데 사용됨)
  • SCSI(Small Computer System Interface) 디스크
    • SCSI 버스 하나 이상의 호스트를 포함하여 버스마다 8개까지의 장치를 지원할 수 있는 1:1 데이터 버스
    • 각 장치는 고유 식별자를 가지며, 대개 디스크에 있는 점퍼로 설정됨
    • 버스에 있는 두 장치 사이에는 동기적 또는 비동기적 데이터 전송이 가능하며, 32비트 크기로 초당 40MB까지 허용
    • SCSI 버스는 데이터 뿐만 아니라 상태 정보도 전송
    • 전송 시작(initator)과 전송 대상(target) 사이의 하나의 트랜잭션은 8개의 서로 다른 상태를 가지며, SCSI 버스의 현재 상태는 5개의 신호로부터 알 수 있음
      • BUS FREE 버스에 대한 제어권을 가진 장치 또는 트랜잭션이 없음
      • ARBITRATION 어떤 장치가 자신의 식별자를 보내 SCSI 버스에 대한 제어권을 얻는 중(중재), 동시에 발생할 경우 더 높은 번호에 제어권이 주어짐
      • SELECTION 장치가 중재를 통해 제어권을 얻으면, SCSI 요청을 받을 대상에게 명령을 보낼 준비라는 신호를 전달
      • COMMAND 6/10/12 바이트의 명령을 전송 시작자에서 전송 대상으로 전달
      • DATA IN, DATA OUT 데이터가 전달되는 상태
      • STATUS 모든 명령을 완료한 상태로, 성공과 실패를 나타내는 바이트를 응답해야 함
      • MESSAGE IN, MESSAGE OUT 추가적인 정보 전달
    • SCSI 서브시스템의 2가지 요소(호스트와 장치)는 각 자료구조로 표현됨
    • 호스트(host) SCSI 컨트롤러, 같은 종류의 컨트롤러가 하나 이상 존재하면 각각 별도의 호스트로 표현
      • SCSI 디바이스 드라이버는 컨트롤러가 여러개 일 때 제어 가능
      • SCSI 호스트는 대부분 SCSI 명령의 전송 시작자
    • 장치(device) 가장 일반적인 장치 유형으로 SCSI 디스크가 있으며, 테이프나 CD-ROM 같은 여러 종류를 지원하기도 함.
      • 각 장치는 서로 다르게 취급되며, SCSI 장치는 대부분 SCSI 명령의 전송 대상

SCSI 디스크 커널 자료구조

  • SCSI 서브시스템 초기화
    • 먼저 시스템에 있는 SCSI 컨트롤러(호스트)를 찾은 뒤, SCSI 버스를 검사하여 모든 장치를 탐색
    • 각 장치를 초기화: 일반 파일 연산과 버퍼 캐시 블럭 장치 연산을 매핑하여 나중에 사용될 수 있게 함
    • 커널을 빌드할 때 SCSI 호스트 어댑터(컨트롤러) 중 제어할 하드웨어의 것을 찾음
    • 커널에 포함된 호스트들은 builtin_scsi_hosts 배열에 Scsi_Host_Template 엔트리를 가지며, 이 구조체는 각 SCSI 호스트에 어떤 SCSI 장치가 연결되었는지 알기 위해 호스트마다 고유 루틴에 대한 포인터를 포함
    • SCSI 서브시스템은 자신을 설정하는 동안 이 루틴들을 호출하는데 루틴은 각 호스트에 대한 SCSI 디바이스 드라이버의 일부
    • 실제 장치가 연결된 호스트는 자신의 Scsi_Host_Template 구조체를 활성화된 호스트 목록인 scsi_hosts 리스트에 추가
    • 감지된 호스트 유형에 해당하는 호스트는 scsi_hostlist 리스트에 있는 Scsi_Host 구조체로 기술됨
    • 모든 호스트를 발견하면, SCSI 버스에 있는 장치로 TEST_UNIT_READY 명령을 전달해 버스에 연결된 장치들을 탐색
    • 장치가 응답하면 ENQUIRY 명령을 보내 신원 확인을 통해 커널에 제작자 이름과 장치 모델명 및 개정 이름을 전달
    • SCSI 명령은 Scsi_Cmnd 구조체로 기술되며, Scsi_Host_Template 구조체 있는 디바이스 드라이버 루틴을 호출할 때 Scsi_Cmnd 구조체가 전달됨
    • SCSI 장치는 Scsi_Device 구조체로 기술되며, 각각 부모 Scsi_Host 구조체를 참조하는데, 모든 Scsi_Device 구조체는 scsi_devices 리스트에 추가됨
    • 장치의 유형은 4가지가 존재: 디스크, 테이프, CD, 일반
      • 메이저 블럭 장치 유형으로 커널에 별도로 등록
      • 각 유형마다 장치 테이블을 관리하며, 이 테이블은 커널의 블럭 연산(파일, 버퍼 캐시)을 올바른 디바이스 드라이버나 SCSI 호스트로 보내는데 사용됨
      • 유형별로 Scsi_Device_Template 구조체를 생성하며, 이는 장치에 대한 정보 및 다양한 작업을 수행하는 루틴들의 주소를 포함. SCSI 서브시스템은 이 구조체를 이용해 장치의 유형별 루틴을 호출
      • 어떤 유형을 갖는 SCSI 장치가 하나 이상 발견되면 Scsi_Type_Template 구조체가 scsi_Devicelist 리스트에 추가됨
      • 초기화의 마지막 상태는 등록된 각 Scsi_Device_Template 구조체로부터 종료 함수를 부르는 것으로, SCSI 디스크 유형의 경우 발견한 모든 디스크를 회전시켜 각 디스크 구조를 기록한 뒤, 모든 SCSI 디스크를 나타내는 gendisk 구조체를 디스크 구조체 리스트에 추가
  • 블럭 장치 요청 전달
    • SCSI 서브시스템을 초기화하면 SCSI 장치들을 사용할 수 있으며, 정상 동작하는 장치 유형은 커널에 자신을 등록하여 블럭 장치 요청이 들어올 때마다 해당 장치 유형으로 전달되게 함
    • 요청은 blk_dev 배열을 인덱싱하여 버퍼 캐시 요청이나 blkdevs 배열을 인덱싱하여 파일 연산 요청으로 나뉨
    • SCSI 디스크에서 하나 이상의 EXT2 파일 시스템 파티션을 가지는 경우, 디바이스 드라이버는 파티션 중 하나를 마운트할 때 커널 버퍼 요청을 올바른 파일 시스템으로 전달
    • SCSI 디스크 파티션에서 한 블럭의 데이터를 읽고 쓰는 요청은 SCSI 디스크의 current_request 리스트에 새로운 request 구조체를 추가하여 처리됨. 처리중이면 버퍼 캐시는 다른 일을 할 필요가 없으며, 처리중이 아니면, 계속 요청 큐를 하나씩 처리
      • SCSI 디스크 파티션의 마이너 장치 번호 중 일부를 사용하여 blk_dev 배열을 인덱싱하며, 얻은 자료구조의 일부에 current_request 포인터가 존재
      • 각 Scsi_Disk 구조체는 이 장치를 나타내는 Scsi_Device 구조체에 대한 포인터를 가짐
      • Scsi_Device 구조체는 각자 소유한 Scsi_Host 구조체를 순서대로 참조
    • 버퍼 캐시로부터 온 request 구조체는 Scsi_Cmnd 구조체로 변환되고, 이 구조체는 Scsi_Host 구조체의 큐에 추가됨
    • 한 번 데이터 블럭을 읽거나 쓰고 나면, 이 요청들은 개별 SCSI 디바이스 드라이버에 의해 처리됨

네트워크 장치

  • 네트워크 디바이스 드라이버는 커널이 부팅하면서 초기화하는 동안 제어하는 장치를 커널에 등록하는데, 네트워크 장치는 device 구조체로 표현되며, 각 구조체는 장치 정보 및 리눅스에서 네트워크 프로토콜들이 장치의 서비스를 이용할 수 있는 (대부분 장치를 통한 데이터 전송과 관련된) 함수들의 주소를 포함
  • 네트워크 장치 특수 파일은 초기화 과정에서 차례로 생성되며, 이들 이름은 장치 유형을 나타내는 표준 이름이며 0부터 시작하는 번호가 뒤에 붙어서 식별됨
    • 이더넷 장치: /dev/eth0, /dev/eth1, /dev/eth2
    • SLIP 장치: /dev/sl0, /dev/sl1, /dev/sl2
    • PPP 장치: /dev/ppp0, /dev/ppp1, /dev/ppp2
    • 루프백 장치: /dev/lo
  • device 구조체가 포함한 장치 정보 등 모든 정보는 부팅 시 초기화될 때 설정됨
    • 버스 정보 디바이스 드라이버가 장치를 제어하기 위한 것
    • IRQ 번호 장치가 사용하는 인터럽트 번호
    • 베이스 주소 장치의 제어 레지스터 및 상태 레지스터가 있는 I/O 공간의 주소
    • DMA 채널 네트워크 장치가 사용하는 DMA 채널 번호
    • 인터페이스 플래그 네트워크 장치의 특징과 능력을 설명

인터페이스 플래그

  • 프로토콜 정보 네트워크 프로토콜 게층이 장치를 제어하는 방법을 나타냄
    • MTU 링크 계층에서 붙이는 헤더를 제외하고 전송 가능한 최대 패킷 크기
    • Family 장치가 지원할 수 있는 프로토콜 계열 e.g., AF_INET: 인터넷 주소
    • Type 하드웨어 인터페이스 유형으로, 장치에 연결된 매체 e.g., 이더넷
    • Address device 구조체는 IP 주소를 포함하여 여러 주소를 가짐
  • 패킷 큐(Packet Queue) 네트워크 장치가 전송하기를 기다리는 sk_buff 구조체의 리스트
    • 보내고 받는 모든 네트워크 데이터(패킷)은 sk_buff 구조체로 기술됨
  • 각 장치는 프로토콜 계층에서 호출 가능한 표준 함수 집합을 제공하여 전송받은 데이터를 올바른 프로토콜 계층으로 전달
  • 이는 셋업하고 프레임을 전송하는 루틴 외에 표준 프레임 헤더를 추가하고 통계 정보를 모으는 루틴도 포함되어 있으며, 통계 정보는 ifconfig 명령으로 출력 가능
  • 네트워크 장치 초기화
    • 네트워크 게층은 장치에 고유한 작업을 수행할 때 device 구조체에 있는 서비스 루틴을 호출
    • 단, device 구조체는 최초에 초기화나 장치를 탐사(probe)하는 루틴의 주소만 가짐
    • 장치의 초기화 루틴을 호출하면, 구동할 컨트롤러가 존재하는지 나타내는 상태값을 얻게 되고, 아무런 장치도 못찾으면 dev_base 포인터가 참조하는 device 리스트에 있는 엔트리가 제거됨
    • 장치를 찾게 되면, device 구조체의 나머지 부분을 장치 정보 및 드라이버가 지원하는 함수들로 설정
    • 장치 리스트에는 eth0 부터 eht7 까지 8개의 표준 엔트리가 있는데, 초기화 코드는 장치를 찾을 때까지 이더넷 디바이스 드라이버를 하나씩 시도
    • 이더넷 장치를 찾으면 device 구조체의 내용을 채우고, 제어할 하드웨어를 초기화한 뒤 사용할 IRQ 번호 및 DMA 채널 등을 알아냄. 8개 모두 할당되면 이더넷 장치를 더 이상 찾지 않음
  • 네트워크 장치 파일은 실제로 장치가 존재하는 경우에만 생성되나, 보통 문자 장치나 블럭 장치는 실제로 장치가 존재하지 않더라도 장치 특수 파일이 존재함

 

본 글은 The Linux Kernel 을 정리한 것이며, 출처를 밝히지 않은 모든 이미지는 원글에 속한 것입니다.


하드웨어 장치의 추상화

  • 운영체제는 하드웨어 장치별로 다른 특징을 보이지 않는, 일관된 인터페이스를 사용자에게 제공
  • 모든 물리 장치는 각자의 하드웨어 컨트롤러를 가지며, 모든 하드웨어 컨트롤러는 각자의 고유한 제어/상태 레지스터(Control and Status Registers, CSRs)를 포함
    • 키보드, 마우스, 직렬포트는 Super I/O 칩이 제어하며, IDE 디스크는 IDE 컨트롤러, SCSI 디스크는 SCSI 컨트롤러가 제어
    • CSRs는 장치를 시작 및 중단하고 초기화하며, 문제가 발생했을 때 진단하는 용도로 사용됨
  • 커널에는 하드웨어 컨트롤러를 제어하고 관리하기 위해 디바이스 드라이버(Device Driver)가 존재하는데, 이는 커널 모드에서 실행되고 메모리에 존재하며 저수준 하드웨어 처리 루틴을 포함한 공유 라이브러리
    • 각 디바이스 드라이버는 각자 관리하는 장치들의 특성들을 처리
  • 모든 물리 장치들은 일반 파일처럼 사용자에게 보여지며, 이를 장치 특수 파일(device special file)이라고 하는데, 파일을 다루는 표준 시스템 콜을 이용해서 열고, 닫고, 읽고, 쓸 수 있음
    • IDE 디스크는 /dev/hda 로 나타냄
    • 네트워크 장치들도 파일로 표시되지만 커널이 네트워크 컨트롤러를 초기화할 때 장치 특수 파일이 생성됨
    • 동일한 디바이스 드라이버로 제어되는 모든 장치는 동일한 메이저 장치 번호를 가지며, 각 장치나 컨트롤러를 구분하기 위해 마이너 장치 번호를 사용
  • 리눅스는 문자, 블럭, 네트워크 3가지 종류로 하드웨어 장치의 추상화를 제공
    • 문자 장치는 버퍼 없이 하나씩 바로 읽고 쓸 수 있음 e.g., 키보드
    • 블럭 장치는 일정한 크기(512 또는 1024바이트)의 배수로만 읽고 쓸 수 있음 e.g., 하드 디스크
    • 네트워크 장치는 BSD 소켓 인터페이스로 하위 네트워크 계층(TCP/IP)에 접근할 수 있음 e.g., 이더넷
  • 커널 디바이스 드라이버의 공통 특성
    • 커널 코드의 일부로, 잘못되면 시스템에 큰 피해를 줄 수 있음
    • 커널이나 자신이 속한 서브시스템에 표준 인터페이스를 제공
      • 터미널 디바이스 드라이버는 커널에 파일 I/O 인터페이스를 제공
      • SCSI 디바이스 드라이버는 커널에 파일 I/O와 버퍼 캐시 인터페이스를 제공
    • 커널 메커니즘 및 서비스 제공: 메모리 할당, 인터럽트 전달, 대기 큐 같은 커널 서비스 사용
    • 대부분 디바이스 드라이버는 필요 시 물리 메모리에 로드하고 필요없으면 언로드 가능
    • 커널에 포함된 상태로 함께 컴파일 될 수 있고, 장치는 컴파일 시 설정 가능
    • 부팅 후 디바이스 드라이버가 초기화 될 때, 시스템은 제어할 하드웨어 장치를 가지며, 없다하더라도 문제가 되지 않음

폴링(Polling)과 인터럽트(Interrupt)

  • 장치에 명려을 전달할 때, 그 명령이 언제 끝났는지 알 수 있는 방법
  • 폴링하는 디바이스 드라이버는 시스템 타이머를 이용하여 어느정도 시간이 지나면 커널이 디바이스 드라이버에 있는 한 루틴을 호출. 이 타이머 루틴은 명령이 수행되었는지 상태를 검사 (주기적으로 확인하는 작업)
  • 인터럽트를 이용하는 디바이스 드라이버는 제어하는 장치가 작업을 끝냈거나 서비스를 받아야할 때 인터럽트를 발생시킴. 커널은 이 인터럽트를 올바른 디바이스 드라이버로 전달함 (비동기적으로 요청하고 결과를 받음)
    • 이더넷 디바이스 드라이버는 네트워크에서 패킷을 받을 때마다 인터럽트를 발생시킴
  • 디바이스 드라이버는 인터럽트를 사용하기 위해 인터럽트 처리 루틴의 주소와 사용할 인터럽트 번호를 커널에 등록
    • /proc/interrupts 파일을 살펴보면, 디바이스 드라이버가 사용중인 인터럽트를 알 수 있음
    • 드라이버가 초기화될 때 인터럽트 자원(제어권)을 커널에 요청

/proc/interrupts, 중간 숫자가 인터럽트 번호

  • 시스템의 어떤 인터럽트들은 처음부터 고정되어 있으며, 플로피 디스크 컨트롤러의 경우 항상 6번을 사용. 또는 PCI 장치에서 발생하는 인터럽트들은 부팅 시 동적으로 할당됨
  • PCI 디바이스 드라이버는 제어할 장치의 인터럽트 번호(IRQ)를 먼저 알아낸 뒤(즉, 탐사 과정) 인터럽트의 제어권을 요청
    • 리눅스는 표준 PCI BIOS 콜백을 지원해서 장치 정보를 제공
  • 인터럽트가 PIC에서 CPU로 전달될 때는 인터럽트 모드에서 처리 루틴을 실행하는데, 다른 인터럽트가 발생하지 못하므로 되도록 처리 루틴에서는 적은 일을 하고 인터럽트 전으로 돌아갈 수 있도록 스택에 컨텍스트를 저장하고 복구함
    • 작업량이 많으면 나중에 해도 될 일을 커널의 하반부 핸들러나 작업큐에 넣어 처리 (인터럽트 모드를 빨리 벗어나려는 의도)

직접 메모리 접근(Direct Memory Access, DMA)

  • 인터럽트를 사용하는 디바이스 드라이버는 데이터의 양이 작을 때는 잘 동작하지만, 디스크 컨트롤러 및 이더넷 장치 같은 데이터 전송률이 높은 장치의 경우 CPU 이용률이 높을 수 밖에 없음
  • DMA 컨트롤러는 장치와 시스템 메모리 사이에 CPU 도움 없이 데이터 전송을 가능하게 함
  • ISA DMA 컨트롤러는 8개의 DMA 채널을 제공하고, 이 중 7개를 디바이스 드라이버가 사용하는데, 드라이버끼리는 채널을 공유할 수 없음
    • 각 DMA 채널은 16비트 주소 레지스터와 16비트 카운터 레지스터로 접근 가능
    • 데이터 전송을 초기화하기 위해, 디바이스 드라이버는 DMA 채널의 두 레지스터와 데이터 전송 방향(읽기/쓰기)을 함께 설정
  • DMA를 사용해도 좋다는 명령을 보내야 사용 가능하며, 데이터 전송이 완료되면 장치는 인터럽트를 발생시키고, 전송하는 동안 CPU는 다른 일을 할 수 있음
  • DMA 컨트롤러는 물리 메모리에 직접 접근하기 때문에, 프로세스의 가상 메모리로 DMA를 바로 사용할 수 없고, DMA에서 사용하는 메모리는 물리 메모리에서 연속된 블럭으로 할당되어야 함
  • 사용자는 시스템 콜로 프로세스가 사용하는 물리 페이지에 락을 걸어 DMA 작업 중 스왑 아웃되는 것을 방지할 수 있음
  • DMA가 사용하는 메모리는 하부 16MB 로 제한되어 있어 DMA 컨트롤러는 물리 메모리 전체에 접근 불가
  • 인터럽트처럼 어떤 장치가 사용하는 DMA 채널이 고정되어 있고, 이더넷 장치의 경우 하드웨어에 점퍼로 설정 가능
    • 또는 장치들이 CSRs를 통해 사용할 DMA 채널을 알려주고 디바이스 드라이버가 빈 채널을 사용할 수도 있음
  • 리눅스는 DMA 채널 하나당 dma_chan 구조체를 생성하며, 이 구조체의 배열을 이용하여 채널의 사용유무를 추적
    • 이 구조체는 2개의 항목을 포함: DMA 채널의 소유자를 나타내는 문자열 및 해당 채널의 할당 유무를 나타내는 플래그
    • cat /proc/dma 명령을 실행하면 나오는 것이 dma_chan 구조체의 배열
  • DMA가 없을 때와 있을 때의 차이
    • DMA 없을 때, CPU가 장치로부터 데이터 읽고 쓰는 작업을 반복해야 되서 CPU 이용률이 증가함
    • DMA 있을 때, CPU는 장치에게 DMA 시작 신호 및 DMA 완료 인터럽트만 수신하고, 데이터 전송에는 개입하지 않기 때문에 CPU 이용률이 낮음

http://jake.dothome.co.kr/dma-1/

디바이스 드라이버가 제공하는 인터페이스

  • 디바이스 드라이버의 메모리
    • 디바이스 드라이버는 커널 코드의 일부이기 때문에 물리 메모리만 사용 가능
    • 인터럽트를 받았거나 하반부 핸들러 또는 작업 큐 핸들러가 스케줄되었을 때, 현재 프로세스는 바뀔 수 있는데, 이는 디바이스 드라이버가 특정 프로세스가 실행될 때 독립적으로 다른 한 켠(백그라운드)에서 실행되기 때문 (커널 코드의 특성)
    • 부팅 시 커널에서 사용할 메모리가 할당되고 메모리를 해제하는 루틴을 전달받아, 디바이스 드라이버는 사용할 메모리를 2의 제곱승 단위로 할당 받거나 해제할 수 있음
    • 디바이스 드라이버가 할당 받은 메모리를 DMA로 입출력하려면, 그 메모리를 DMA 가능이라고 지정해야 함
  • 커널이 모든 종류의 디바이스 드라이버에게 서비스를 요청할 때 사용할 수 있는 공통적인 인터페이스가 존재
    • 서로 다른 장치들 또는 디바이스 드라이버들을 일관적이게 다루기 위한 인터페이스
  • 디바이스 드라이버는 장치가 없더라도 시스템이 동작할 수 있도록 커널에 의해 초기화될 때 스스로 커널에 등록
    • 커널은 등록된 디바이스 드라이버들을 테이블로 관리하며, 이 테이블은 해당 종류의 장치와 인터페이스를 제공하는 함수들의 포인터를 저장하고 있음
  • 문자 장치(Character Device)
    • 초기화될 때, 이 장치의 디바이스 드라이버는 device_struct 구조체를 생성하여 chrdevs 배열에 추가하는 것으로 커널에 자신을 등록
    • /proc/devices 에서 문자 장치에 대한 내용은 모두 chrdevs 배열에서 가져온 것
    • 메이저 장치 번호(tty 장치는 4번 같은)는 이 배열의 인덱스로 사용되며, 고정되어 있음
    • device_struct 구조체는 2가지 항목을 포함
      • 디바이스 드라이버의 등록 이름에 대한 문자열 포인터
      • 파일 연산 블럭에 대한 포인터
    • 프로세스가 문자 장치를 나타내는 문자 특수 파일(character specific file)을 열면, 커널은 올바른 문자 디바이스 드라이버의 파일 처리 루틴이 호출되도록 설정함
      • 각 장치 특수 파일은 VFS inode로 기술되며, VFS inode는 장치의 메이저 식별자와 마이너 식별자를 가짐
      • 각 VFS inode는 파일 연산 루틴에 대한 포인터를 가지는데, 생성 시 기본 문자 장치 연산으로 설정됨 (이 연산은 가리키는 파일 시스템 객체에 따라 다름)
      • 파일 연산 함수를 실행하면, 장치의 메이저 식별자로 chrdevs 배열에 인덱싱 --> 이 장치에 대한 파일 연산 블럭을 가져옴 --> file 구조체와 파일 연산 포인터가 디바이스 드라이버의 것을 참조하도록 device_struct 구조체를 설정
      • 이후 프로세스에서 호출하는 모든 파일 연산은 문자 장치의 파일 연산으로 매핑되어 호출됨
  • 블럭 장치(Block Device)
    • 초기화될 때, 이 장치의 디바이스 드라이버는 device_struct 구조체를 생성하여 blkdevs 배열에 추가하는 것으로 커널에 자신을 등록
    • 문자 장치와 마찬가지로, 메이저 장치 번호는 blkdevs 배열의 인덱스로 사용되며, 다른 점은 장치의 유형을 분류하는 클래스가 존재한다는 것. SCSI 장치와 IDE 장치를 클래스로 구분하며, 각 클래스는 커널에 파일 함수를 제공해야 함
    • 각 클래스의 블럭 장치들을 사용하는 디바이스 드라이버는 고유의 클래스 인터페이스를 제공하는데, 예를 들어, SCSI 디바이스 드라이버는 "SCSI 서브시스템이 커널에 파일 함수를 제공하기 위해 사용하는 인터페이스"를 서브시스템에 제공해야 함
    • 모든 블럭 디바이스 드라이버는 파일 연산과 함께 버퍼 캐시에 대한 인터페이스를 제공
    • 버퍼 캐시에 한 블럭의 데이터를 읽고 쓰기 위한 요청을 보내면, 각 드라이버는 all_requests (정적 리스트)에서 request 구조체를 할당받아 blk_dev_struct 구조체에 있는 request 리스크 (요청 큐)에 할당받은 것을 추가. 이후 요청 큐를 처리하기 위해 blk_dev_struct 구조체에 있는 요청 루틴의 주소로 점프해서 요청을 처리
      • blk_dev_struct 구조체는 blk_dev 배열의 원소이며, 메이저 장치 번호로 인덱싱됨
      • 각 request 구조체는 하나 이상의 buffer_head 구조체에 대한 포인터를 가지며, 이 구조체는 버퍼 캐시에 의해 락됨
      • 이 버퍼로 블럭 연산이 끝날 때까지 프로세스는 대기 상태가 됨
    • 요청이 처리되면 드라이버는 request 구조체에서 각 buffer_head 구조체를 제거하고 갱신되었음을 표시한 뒤 락을 해제. 대기 중인 프로세스는 다음 스케줄링에서 실행됨

blk_dev 구조체
https://lwn.net/Kernel/LDD2/ch12.lwn
https://lwn.net/Kernel/LDD2/ch12.lwn

 

본 글은 The Linux Kernel 을 정리한 것이며, 출처를 밝히지 않은 모든 이미지는 원글에 속한 것입니다.


인터럽트(Interrupt)

  • 커널은 장치에 요청을 한 뒤 기다리지 않고 다른 작업을 하면서 요청한 작업이 끝나면 장치로부터 인터럽트를 받음
    • 리눅스는 여러 하드웨어 장치를 사용하는데, 동기적으로 구동할 경우 요청을 완료할 때까지 기다려야하는 문제가 있음
  • 인터럽트를 사용할 경우 여러 장치에 동시에 작업을 요청하는 것이 가능
  • 커널이 인터럽트를 처리하는데 일반적인 메커니즘과 인터페이스 표준이 존재하지만 세부 내용은 아키텍처마다 상이함
    • 장치가 인터럽트를 전달하는 것은 하드웨어에서 지원해야 함
  • 보통 CPU의 특정한 핀의 전압이 바뀌면(예를 들어 +5볼트에서 -5볼트), CPU는 하던 일을 멈추고 인터럽트 처리 코드를 실행
    • 어떤 핀은 간격 타이머에 의해 1000분의 1초마다 인터럽트를 받기도 함

https://www.embien.com/blog/interrupt-handling-in-embedded-software/

  • 인터럽트 컨트롤러(Interrupt Controller)
    • 대체로 시스템은 CPU의 인터럽트 핀으로 1:1로 인터럽트를 전달하지 않고, 컨트롤러를 사용하여 장치 인터럽트들을 그룹화
    • 인터럽트 컨트롤러에는 인터럽트를 조정하는 마스크 레지스터와 상태 레지스터가 존재
    • 마스크 레지스터의 비트들을 1 또는 0으로 설정하여 인터럽트를 가능하게 하거나 불가능하게 만들 수 있음
    • 상태 레지스터는 시스템에 현재 발생한 인터럽트를 가짐
    • 시스템의 일부 인터럽트는 하드웨어적으로 연결되어 있는데, 실시간 간격 타이머가 이에 해당되며, 각 시스템은 독자적인 인터럽트 전달 방식을 제공
  • 인터럽트 모드(Interrupt Mode)
    • 인터럽트가 발생하면 CPU는 지금 실행중인 명령어를 잠시 중단하고 인터럽트 처리 코드(Interrupt Handler)가 있거나 해당 코드로 분기하는 명령어의 메모리 주소로 점프(jump)하는데, 여기서 인터럽트 모드에 진입한다고 함
    • 보통 이 모드에서는 다른 인터럽트가 발생할 수 없으며, 예외로 인터럽트에 우선순위를 매기는 CPU의 경우, 높은 인터럽트가 발생하는 것을 허용
    • 인터럽트 처리 코드는 인터럽트 처리하기 전에 CPU의 수행 상태(즉, CPU의 레지스터 등을 포함한 컨텍스트)를 자신의 스택에 저장하고, 인터럽트를 처리하고 나면 CPU의 수행 상태가 인터럽트 처리 전으로 복구되고 인터럽트는 해제됨
    • 이후 CPU는 인터럽트가 발생하기 전에 수행하던 명령어를 재개
  • 프로그램 가능 인터럽트 컨트롤러(Programmable Interrupt Controller, PIC)
    • ISA 주소 공간에 있는 컨트롤러의 레지스터를 이용해 프로그램할 수 있는 컨트롤러
      • 이 레지스터의 위치는 고정되어 이미 알려진 것
    • 인텔에 기반하지 않은 시스템은 위와 같은 구조적 제약에서 자유로우며, 대개 다른 인터럽트 컨트롤러를 사용
    • PIC는 마스크 레지스터와 상태 레지스터를 포함하는데, 마스크 레지스터는 특정 비트를 1 또는 0으로 설정하여 특정 인터럽트(N번 비트면 N번 인터럽트)의 사용 유무를 결정
    •  마스크 레지스터는 쓸 수만 있으며, 써 놓은 값을 읽어오려면 설정 값을 별도로 복사해야 함
    • 인터럽트 허용 상태의 루틴과 인터럽트 금지 상태의 루틴에서, 마스크 레지스터의 복사본을 변경하고 매번 레지스터에 변경된 마스크 값을 씀
    •  인터럽트가 발생하면 인터럽트 처리 코드는 2개의 인터럽트 상태 레지스터(Interrupt Status Register, ISR)를 읽어서 처리

(출처를 못찾겠..) 2개의 PIC 로 장치를 연결

인터럽트 관련 자료구조

인터럽트 처리 커널 자료구조

  • 디바이스 드라이버들이 시스템의 인터럽트에 대한 제어권을 요청하면서 자료구조를 초기화
  • 각 디바이스 드라이버는 인터럽트를 요청해서 켜거나 끄거나 하는 루틴들을 호출해 각자의 인터럽트 처리 루틴의 주소를 커널 자료구조에 저장(등록 과정)
  • PCI 디바이스 드라이버는 장치가 어떤 인터럽트를 사용하는지 알고 있으나, ISA 디바이스 드라이버는 알 방법이 없으므로 커널은 사용할 인터럽트를 탐사(probe)할 기회를 제공
    • 탐사 과정: 디바이스 드라이버는 장치에 인터럽트를 발생하게 한 다음, 다른 장치에 할당되지 않은 모든 인터럽트를 켜서, 처음에 발생시켰던 장치의 인터럽트가 PIC를 통해 커널로 전달되면, 커널은 ISR을 읽어 디바이스 드라이버에게 값을 알려줌. 만약 그 값이 0이 아니라면 탐사 중에 하나 이상의 인터럽트가 발생한 것을 의미. 탐사 종료 후 다른 장치에 할당되지 않은 인터럽트를 모두 끄기
  • ISA 장치가 사용하는 인터럽트 핀은 대개 장치 위에 있는 점퍼를 사용해 설정하고 디바이스 드라이버는 지정된 값을 사용하지만,  PCI 장치가 사용할 인터럽트는 시스템이 부팅 중 PCI 초기화 과정에서 PCI BIOS나 PCI 서브시스템에 의해 할당됨
  • 인텔 칩을 사용하는 PC에서는 시스템 부팅 시 BIOS 에서 셋업 코드를 실행하나, BIOS가 없는 경우 리눅스 커널이 셋업을 수행
    • PCI 셋업 코드는 각 장치별로 인터럽트 컨트롤러의 핀 번호를 PCI 설정 헤더에 쓰고, 장치가 사용하는 PCI 슬롯 번호와 PCI 인터럽트 핀 번호 및 PCI 인터럽트 전달 구조를 이용하여 인터럽트 핀(또는 IRQ)을 결정
    • 디바이스 드라이버는 셋업 코드가 위 정보를 저장한 인터럽트 라인 항목을 읽어서 인터럽트 제어권을 커널에 요청할 때 사용
  • PCI-PCI 브릿지를 사용할 때와 같이 시스템에 PCI 인터럽트를 일으키는 장치가 많은 경우, PCI 장치는 인터럽트를 공유하여 여러 장치의 인터럽트가 하나의 핀에 발생하게 함
    • 공유 인터럽트를 사용하려면 커널에게 인터럽트 제어권을 요청할 때 해당 디바이스 드라이버가 인터럽트 공유 유무를 전달
    • irq_action 배열에 irqaction 구조체를 여러 개 저장하여 인터럽트를 공유
  • 공유 인터럽트가 발생하면 커널은 그 인터럽트 핀을 사용하는 장치의 모든 인터럽트 핸들러를 호출하므로, 모든 PCI 디바이스 드라이버는 서비스할 인터럽트가 없더라도 무시 등의 동작으로 호출을 대비해야함

인터럽트 처리(Interrupt Handling)

https://www.embien.com/blog/interrupt-handling-in-embedded-software/

  • 리눅스에서 인터럽트 처리 서브시스템은 인터럽트를 올바른 핸들러로 전달하기 위해 인터럽트 처리 루틴의 주소를 저장하고 있는 구조체에 대한 포인터를 사용
  • 이 루틴들은 해당 디바이스 드라이버에 있는 것이며, 드라이버가 초기화 될 때 각자 사용할 인터럽트의 제어권을 커널에 요청하면서 루틴의 주소를 알려주게 됨
  • 각 irqaction 구조체는 인터럽트 처리 루틴의 주소를 포함하는데, 핸들러에 필요한 정보도 가짐
    • irq_action 배열의 크기는 인터럽트가 발생하는 장치의 수
  • 인터럽트의 수와 이들이 처리되는 방법은 아키텍처마다 다르기 때문에 리눅스의 인터럽트 핸들러는 아키텍처에 종속적
  • 인터럽트 처리 과정
    • 커널은 시스템에 있는 PIC의 인터럽트 상태 레지스터(ISR)을 읽어 어느 장치가 인터럽트가 발생했는지 파악하고 irq_action 배열의 오프셋을 계산
    • irqaction 구조체에 접근하여 저장된 핸들러가 없다면 커널은 오류를 기록하고, 핸들러가 있다면 이 인터럽트가 발생하는 모든 장치에 대해 irqaction 구조체에 있는 핸들러를 호출
    • 디바이스 드라이버는 인터럽트 원인(오류/요청 작업 완료)을 파악하기 위해 해당 장치의 상태 레지스터를 읽어 인터럽트를 처리
  • 디바이스 드라이버는 인터럽트를 처리할 때 작업량이 많을 수 있는데, 커널에서는 CPU가 오랫동안 인터럽트 모드에 있는 것을 피하기 위해 작업을 인터럽트 처리 후로 미루는 메커니즘을 지원
  • 예를 들어, 터미널에 출력하기 위해 인터럽트가 발생(어셈블리어 명령 int가 인터럽트 처리 루틴을 호출)

https://stackoverflow.com/questions/29656136/is-there-a-system-call-service-routine-in-the-interrupt-vector

 

본 글은 The Linux Kernel 을 정리한 것이며, 출처를 밝히지 않은 모든 이미지는 원글에 속한 것입니다.


PCI 주소 공간

  • PCI(Peripheral Component Interconnect) 시스템에 있는 여러 주변장치들을 구조화하고, 연결하여 효율적으로 관리하는 방법을 정의하는 표준

PCI 기반 시스템 예

  • 위 그림에서 CPU는 PCI 버스 0번에 연결되어 있고, PCI-PCI 브릿지는 PCI 버스 0번(1차 버스, primary)와 PCI 버스 1번(2차 버스, secondary)를 연결. 여기서 PCI-PCI 브릿지에서 CPU에 가깝게 연결된 버스를 upstream 이라 하며, CPU에 멀게 연결된 버스를 downstream 이라고 함. PCI-ISA 브릿지는 오래 전부터 사용되어온 ISA 장치(키보드, 마우스, 플로피 디스크를 제어하는 슈퍼 I/O 컨트롤러 칩 등)를 연결하기 위한 ISA 버스와 PCI 버스를 연결하는 장치
  • 디바이스 드라이버(커널 모듈)는 메인 보드에 있는 PCI 카드를 제어하고 PCI 장치와 서로 정보를 교환하기 위해 공유 메모리를 사용. 이 메모리에는 장치의 컨트롤러가 사용하는 제어 레지스터(control register)와 상태 레지스터(status register)가 존재
    • 예를 들어, PCI SCSI 디바이스 드라이버는 SCSI 디스크로 데이터를 쓰려고 할 때, 장치의 상태 레지스터를 읽어와 쓰기 작업이 가능한지 파악하며, 제어 레지스터에 값을 써서 장치를 동작시킴
  • 주변장치들은 각자 자신만의 메모리 공간(I/O 공간)을 가지며, CPU는 이 영역에 자유롭게 접근할 수 있으나, 장치가 시스템 메모리 공간에 접근하는 것은 직접 메모리 접근(Direct Memory Access, DMA) 채널을 이용해야 함
    • ISA 장치는 ISA I/O와 ISA 메모리를 가짐
    • PCI 장치는 PCI I/O, PCI 메모리, PCI 설정 공간(configuration space)를 가짐
    • 디바이스 드라이버는 PCI I/O와 PCI 메모리 주소공간을 사용하며, PCI 설정 공간의 경우 커널의 PCI 초기화 코드에서 사용
    • 장치들이 사용하는 메모리 공간은 가상 주소 공간에서 일부를 PCI 주소 공간으로 매핑하는 희소 주소 매핑(sparse address mapping) 방법으로 생성됨
  • PCI I/O와 PCI 메모리 주소
    • 장치가 리눅스 커널에서 실행되는 디바이스 드라이버와 통신하기 위해 사용하는 메모리 공간
    • PCI 시스템이 설정되고, PCI 설정 헤더에 있는 명령 항목에서 이들 주소 공간에 대한 접근을 허용할 때까지 아무도 이 공간에 접근할 수 없음.
    • 장치는 자신의 내부 레지스터를 PCI I/O 공간에 매핑하며,  해당 디바이스 드라이버는 장치를 제어하기 위해 레지스터를 읽고 씀
    • 비디오/오디오 드라이버는 데이터를 저장하기 위해 큰 용량의 PCI 메모리 공간을 사용

PCI 설정 헤더

PCI Configuration Header

  • 모든 PCI 장치(PCI-PCI 브릿지 포함)는 PCI 설정 공간에서 PCI 설정 헤더(모든 헤더의 크기는 256바이트)를 가지는데, 이 자료구조는 시스템이 장치를 구별하고 제어하는데 사용되며, 헤더가 있는 정확한 위치는 PCI 배치도에서 장치가 위치한 곳에 따라 결정됨
    • 메인 보드에 있는 여러 PCI 슬롯 중 하나에 꽂을 때, 어떤 슬롯에 꽂느냐에 따라 PCI 설정 공간에서 각기 다른 위치에 헤더가 위치
    • PCI 설정 헤더에 있는 설정 레지스터와 상태 레지스터를 이용해 장치를 설정
    • 첫번째 슬롯의 PCI 설정 헤더가 오프셋 0에 위치한다면, 두번째 슬롯의 헤더는 오프셋 256에 위치
  • 시스템별로 PCI 설정 공간에 접근하는 하드웨어 메커니즘이 다르게 정의되어 있으며, 이에 따라 PCI 설정 코드는 주어진 PCI 버스에 있어서 가능한 모든 PCI 설정 헤더를 검사하여 장치의 유무를 파악
  • 장치 식별자(Device Identification) 장치 자체를 나타내는 고유번호(고속 이더넷 장치의 경우 0x0009)
  • 제작자 식별자(Vendor Identification) PCI 장치의 제작자를 나타내는 고유번호(인텔의 경우 0x8086)
  • 상태(Status) 장치의 상태
  • 명령(Command) 시스템은 이 항목에 값을 써서 장치의 PCI I/O 공간에 접근할 수 있고, 이를 이용해 장치를 제어
  • 분류 코드(Class Code) 이 장치가 속한 장치의 유형을 구별
  • 베이스 주소 레지스터(Base Address Register) PCI I/O, PCI 메모리 공간의 유형과 크기, 위치를 지정하는데 사용
  • 인터럽트 핀(Interrupt Pin) PCI 장치가 사용하고 있는 인터럽트 핀
    • PCI 카드에 있는 4개의 핀(표준으로 각각 A, B, C, D라고 명명)은 PCI 카드로부터 PCI 버스로 인터럽트를 전달
    • 보통 특정 장치에 있어서 인터럽트 핀은 직접 배선되어 부팅 시 그 장치는 그 핀을 사용
  • 인터럽트 라인(Interrupt Line) PCI 초기화 코드와 디바이스 드라이버, 리눅스의 이넡럽트 처리 서브시스템 사이에 인터럽트 핸들을 전달하기 위해 사용되며, 이 핸들러가 PCI 장치로부터 전달된 인터럽트를 리눅스 커널에 있는 올바른 디바이스 드라이버의 인터럽트 핸들러로 전달

PCI-ISA 브릿지

  • PCI I/O와 PCI 메모리 공간으로의 접근을 ISA I/O와 ISA 메모리 공간으로의 접근으로 변환해주는 장치
    • 오래전부터 사용해온 ISA 장치를 지원하는 역할
  • PCI 규약에서는 PCI I/O와 PCI 메모리 공간에서 아래 영역을 ISA 시스템의 주변장치가 사용하도록 하며, 하나의 PCI-ISA 브릿지를 통해 PCI 메모리읮 주소로 접근하면 이 아래 영역의 주소로 변환

PCI-PCI 브릿지

  • 하나의 PCI 버스가 지원가능한 PCI 장치 개수는 전기적인 제한이 있어, 더 많은 PCI 장치를 지원하려면 PCI-PCI 브릿지를 이용하여 PCI 버스를 추가
  • PCI 설정 사이클(Configuration Cycle) PCI 초기화 전에 PCI 장치들을 설정하기 위해 사용하는 특별한 주소
  • PCI 초기화 코드가 메인 PCI 버스(0번)에 있지 않는 장치들에 접근하려면 브릿지가 자신의 1차 PCI 버스에서 2차 PCI 버스로 설정 사이클을 넘길지 결정하는 메커니즘이 필요
  • PCI 규약에서는 2가지 형식의 PCI 설정 주소를 정의
    • 0번 타입 PCI 설정 사이클 PCI 버스 번호가 없으며, 모든 장치는 같은 PCI 버스에 대한 PCI 설정 주소로 해석
    • 1번 타입 PCI 설정 사이클 PCI 버스 번호가 있으며, 이 주소는 PCI-PCI 브릿지만 사용

0번 타입 PCI 설정 사이클
1번 타입 PCI 설정 사이클

  • 각 PCI-PCI 브릿지는 종속 버스(subordinate bus) 번호를 가지는데, 이 번호는 2차 PCI 버스 너머 다운스트림으로 연결된 모든 PCI 버스들 중 최대 버스 번호를 의미
  • 모든 PCI-PCI 브릿지는 1번 타입 설정 사이클을 받으면, 다운스트림으로 보낼 지 결정
    • 지정된 버스 번호가 [브릿지의 2차 버스 번호, 종속 버스 번호] 사이에 있지 않으면, 1차 버스의 것으로 간주
    • 지정된 버스 번호가 2차 버스 번호와 일치하면 0번 타입 설정 명령으로 변환
    • 지정된 버스 번호가 (브릿지의 2차 버스 번호, 종속 버스 번호] 사이에 있으면, 주소를 바꾸지 않고 2차 버스로 전달
  • 예를 들어, 4개의 버스(0~3번)가 있고, 브릿지1은 0번과 1번, 브릿지2는 1번과 2번, 브릿지3은 1번과 3번 버스를 각각 연결한다고 가정. (2번과 3번 버스는 연결되어 있지 않은 상태) 이 때 3번 버스에 있는 장치A를 지정하려면
    • CPU로부터 1번 타입 설정 사이클을 계산해서 0번 버스를 이용해 브릿지1로 전달
    • 브릿지1은 이를 바꾸지 않고 1번 버스로 전달
    • 브릿지2는 해당되지 않으므로 무시
    • 브릿지3은 0번 타입 설정 사이클로 변환한 뒤 3번 버스로 전달 --> 장치 응답
  • PCI-PCI 브릿지 너머에 있는 모든 PCI 버스의 번호는 브릿지의 2차 버스 번호와 종속 버스 번호 사이에 있어야 함. 만약 그렇지 않다면, 브릿지는 1번 타입 설정 사이클을 변환하지 못하거나 제대로 통과시키지 못하게 되고, 시스템은 PCI 장치를 찾지 못하거나 초기화할 수 없음

PCI 초기화

  • PCI 디바이스 드라이버 0번 버스부터 PCI 시스템을 탐색하면서 시스템에 있는 브릿지 포함 모든 PCI 장치를 초기화
    • 찾은 모든 브릿지에는 순서대로 번호가 부여되며, 해당 자료구조의 연결 리스트를 만들어서 시스템의 배치도를 저장
    • 유사 디바이스 드라이버(pseudo device driver)로, 실제 디바이스 드라이버 프로그램이 아니라 초기화 과정에서 호출되는 리눅스 커널 내부의 한 함수
  • PCI BIOS CPU가 모든 PCI 주소 공간을 제어할 수 있도록 함 (커널 코드와 디바이스 드라이버만 사용)
    • BIOS 서비스가 없는 64 비트 프로세서의 경우 동일한 일을 하는 코드가 커널 내부에 존재
  • 리눅스 커널 PCI 자료구조
    • pci_dev 구조체 (브릿지 포함) 각 PCI 장치를 나타냄
    • pci_bus 구조체 PCI 버스를 나타냄
    • 최종 결과는 버스들의 트리 구조이며, 각 노드는 해당 버스에 연결된 여러 PCI 장치들을 가리킴
    • PCI 버스는 PCI-PCI 브릿지를 통해서만 도달 가능 (첫번째 PCI 버스인 0번 버스는 제외). 따라서, 각 pci_bus 구조체는 접근할 때 거쳐야 하는 PCI-PCI 브릿지에 대한 포인터를 포함
      • PCI-PCI 브릿지 구조체는 연결된 PCI 버스와 부모 PCI 버스의 자식 노드
    • pci_devices 변수 시스템에 있는 모든 PCI 장치 리스트를 가리키는 포인터
      • 시스템에 있는 모든 PCI 장치는 자신의 pci_dev 구조체를 이 리스트에 추가함

PCI 커널 자료구조

  • PCI 초기화 코드 깊이 우선 탐색(Depth First Search, DFS)으로 구현되어 있음
    • 시스템에 있는 모든 PCI 장치(브릿지 포함)들을 탐색하기 위해 PCI BIOS 코드를 이용하여 현재 조사중인 PCI 버스의 모든 가능한 슬롯 각각이 점유되었는지 파악
    • PCI 슬롯이 점유되었으면, 그 장치를 기술하는 pci_dev 구조체를 생성해서 pci_devices 가 가리키는 PCI 장치 리스트에 추가
    • 찾은 PCI 장치가 PCI-PCI 브릿지이면, pci_bus 구조체를 생성해서 브릿지에 대한 pci_dev 구조체를 참조하도록 하고, 브릿지에 연결된 다른 버스와도 연결
      • 장치 분류 코드(0x060400 고정)로 브릿지의 유무를 파악
    • 커널은 자신이 찾은 PCI-PCI 브릿지의 다운스트림 방향으로 PCI 버스를 설정, 깊이 우선 탐색이 끝나면 너비 우선 탐색으로 진행

PCI 시스템 설정: 1단계
PCI 시스템 설정: 2단계
PCI 시스템 설정: 3단계
PCI 시스템 설정: 4단계

  • PCI-PCI 브릿지가 PCI I/O와 PCI 메모리 공간 및 PCI 설정 공간에 읽고 쓰려는 작업을 통과시키려면 4가지 정보가 필요
    • 1차 버스(primary bus) 번호 PCI-PCI 브릿지에 upstream으로 연결된 버스 번호
    • 2차 버스(secondary bus) 번호 PCI-PCI 브릿지에 downstream으로 연결된 버스 번호
    • 종속 버스(subordinate bus) 번호 PCI-PCI 브릿지의 downstream 도달 가능한 버스들 중 최대 버스 번호
    • PCI I/O와 PCI 메모리 윈도우 PCI-PCI 브릿지의 모든 downstream으로 연결된 장치들이 사용하는 PCI I/O와 PCI 메모리 공간의 윈도우에 대한 베이스 주소 및 크기
  • PCI-PCI 브릿지 초기화 단계에서 종속 버스 번호는 바로 알 수 없기 때문에, 처음에는 DFS로 브릿지를 찾을 때마다 각 2차 버스 번호를 부여하고 종속 버스 번호에는 임시로 0xFF를 지정. CPU에서 가장 멀리 떨어진(downstream의 마지막) 브릿지로부터 되돌아올 때(백트래킹 과정에서) 종속 버스 번호에 도달가능한 최대 버스 번호를 부여

PCI Fixup

  • PCI 확정(Fixup) 시스템별로 다른 PCI 초기화의 종료 부분을 마무리짓는 작업
  • 인텔 기반 시스템은 부팅 시 실행되는 시스템 BIOS에서 이 작업을 진행하며 그 외 시스템에서는 커널에서 작업
  • 각 장치에 PCI I/O와 PCI 메모리 공간을 할당
    • 장치가 사용할 메모리 공간의 크기를 탐색하기 위해, 각 장치의 베이스 주소 레지스터에 1을 써서 전달, 장치는 무관한 주소 비트를 0으로 설정해서 응답하는데 커널이 받은 값은 주소공간의 크기를 나타냄
    • 모든 주소 공간의 크기는 2의 배수이며, 이에 맞춰 자연스럽게 메모리 공간은 정렬되어 장치에 할당됨

  • 커널은 어떤 주소 공간에 장치의 레지스터가 있어야 하는지를 알려주는데, 현재 버스의 모든 장치에 대해 각각 필요한 크기만큼 전역 PCI I/O와 메모리 베이스 주소를 이동시킴
    • 현재 버스의 모든 downstream 으로 연결된 버스들을 재귀적으로 찾아 공간을 할당
    • 전역 PCI I/O와 PCI 메모리 공간의 베이스 주소를 변경
    • 장치들의 주소 공간이 모든 upstream 으로 연결된 PCI-PCI 브릿지의 메모리 공간에 위치하게 됨
  • 시스템에 있는 각 PCI-PCI 브릿지의 PCI I/O와 PCI 메모리 주소 윈도우를 설정
    • 현재 있는 전역 PCI I/O와 PCI 메모리 베이스 주소를 4K 단위로 상대적 정렬 (1Mbytes 경계를 가짐)
    • 이 과정에서 현재 PCI-PCI 브릿지가 필요로 하는 윈도우의 베이스 주소 및 크기를 계산
    • 이 버스에 연결된 PCI-PCI 브릿지를 계속 탐색해서 베이스 주소와 크기를 지정
    • PCI-PCI 브릿지에 대한 PCI I/O와 PCI 메모리 공간에 접근할 수 있도록 브릿지 기능 설정
    • 브릿지의 1차 PCI 버스에서 보이는 PCI I/O와 PCI 메모리 주소 중에서 윈도우 안에 있는 PCI I/O와 PCI 메모리 주소는 2차 버스가 사용하는 것
  • 장치에 인터럽트 라인 값을 지정하여 그 장치의 인터럽트 처리를 제어할 수 있게 함

 

+ Recent posts