본 글은 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

 

+ Recent posts