760 likes | 1.01k Views
리눅스 커널의 이해 중에서. 13 장 . 입출력 장치 관리. 장재필 시스템 소프트웨어 실험실. 목차. Part I : 입출력 아키텍처. Part II : 파일을 입출력 장치와 연관시키기. Part III : 장치 드라이버. Part IV : 문자 장치 처리. Part IV : 블록 장치 처리. Part V : 페이지 입출력 연산. Part I :. 입출력 아키텍쳐. 입출력 아키텍처. 버스
E N D
리눅스 커널의 이해 중에서 13장. 입출력 장치 관리 장재필 시스템 소프트웨어 실험실
목차 Part I : 입출력 아키텍처 Part II : 파일을 입출력 장치와 연관시키기 Part III : 장치 드라이버 Part IV : 문자 장치 처리 Part IV : 블록 장치 처리 Part V : 페이지 입출력 연산
Part I : 입출력 아키텍쳐
입출력 아키텍처 • 버스 CPU(들), 램 그리고 개인용 컴퓨터에 연결할 수 있는 여러 입출력 장 사이에 정보가 흘러갈 수 있는 경로 • 데이터 버스(data bus) • 데이터를 병렬로 전송하는 라인 모임 (펜티엄은 64비트 폭의 데이터 버스) • 주소버스 (address bus) • 주소를 병렬로 전송하는 라인 모임 (펜티엄은 32비트 폭의 주소 버스) • 제어버스 (control bus) • 연결된 회로에 제어 정보를 전송하는 라인 모임
입출력 아키텍처 • PC의 입출력 아키텍쳐
입출력 아키텍처 • 입출력 포트 • I/O 버스에 연결된 각 장치마다 자신만의 I/O 주소 집합 • in, ins, out, outs를 통해 CPU는 값을 읽거나 쓸 수 있다. • 요청한 입출력 포트를 선택하기 위해 주소버스 사용 • 데이터를 전송하기 위해 데이터 버스 사용 • 물리 주소 공간의 주소로 매핑 가능 • 어셈블리 명령으로 입출력 장치와 통신 • DMA와 결합할 수 있다
입출력 아키텍처 • 특수 입출력 포트
입출력 아키텍처 • 입출력 인터페이스 • 입출력 포트 그룹과 장치 제어기 사이에 들어가는 하드웨어 회로 • 입출력 포트 값을 장치용 명령과 데이터로 변환 • IRQ라인을 통해 프로그램 가능한 인터럽트 제어기(PIC)에 연결 • 장치를 대신해서 인터럽트 요청을 발생
입출력 아키텍처 • 전용 입출력 인터페이스 • 키보드 인터페이스 • 그래픽 인터페이스 • 디스크 인터페이스 • 버스 마우스 인터페이스 • 네트워크 인터페이스 • 범용 입출력 인터페이스 • 병렬포트 • 직렬포트 • 범용 직렬 버스 • PCMCIA 인터페이스 • SCSI 인터페이스
입출력 아키텍처 • 장치 제어기 • 입출력 인터페이스에서 받은 고수준 명령을 해석하고, 적절한 전기 신호를 장치에 보내 특정 작업 수행 • 장치에서 받은 전기 신호를 변환하고 적절히 해석한 다음, 상태 레지스터 값을 변경
입출력 아키텍처 • DMA • 램과 입출력 장치 사이에서 데이터를 전송 • CPU로 활성화한 후 DMAC는 스스로 데이터 전송 관리 • 데이터 전송 완료시 인터럽트 발생 • CPU와 DMAC 충돌시 중재자(arbiter)가 해결 • DMAC 초기 설정 시간이 길다 • 전송양이 많을경우 효과적
Part 2 : 파일을 입출력 장치와 연관시키기
파일을 입출력 장치와 연관시키기 • 장치 파일 • 유형 • 블록(block) 또는 문자(character) • 주 번호 • 장치 유형을 나타내는 1~255 범위 숫자 • 부 번호 • 같은 주 번호를 공유하는 장치 그룹에서 특정 장치를 나타냄
파일을 입출력 장치와 연관시키기 • 장치 파일 예
파일을 입출력 장치와 연관시키기 • 일반적으로 장치파일은 하드웨어 장치 또는 디스크 파티션과 같은 하드웨어 장치의 물리적 또는 논리적인 부분과 관련 • 가상적인 논리적 장치를 나타내는 경우도 있다 • /dev/null은 ‘블랙홀’에 대응하는 장치파일 • 기록되는 모든 데이터는 사라진다 • 파일은 언제나 비어있는 것처럼 보인다(읽는 경우 언제나 NULL값) • /tmp/disk, ‘블록’유형의 주번호 3, 부번호 0인 장치파일 • /dev/had와 같은 파일
파일을 입출력 장치와 연관시키기 • 블록과 문자 장치 비교 • 블록장치 • 입출력 연산 한 번으로 고정된 크기의 블록 데이터 전송 • 장치에 저장한 블록은 임의대로 참조 • 하드디스크, 플로피디스크, CD-ROM, 램 디스크 • 문자 장치 • 입출력 연산 한 번으로 임의의 크기만큼 데이터 전송 • 프린터 : 1바이트, 테이프 : 가변적인 크기의 데이터 블록 • 문자를 순서대로 참조
파일을 입출력 장치와 연관시키기 • 네트워크 카드 • 대응하는 장치 파일이 없다 • 서로 다른 기호 이름(symbolic name)을 할당한다 • 장치명과 네트워크 주소 사이관계를 설정 • Socket(), bind(), listen(), accept(), connect() 시스템 콜 기반
파일을 입출력 장치와 연관시키기 • 장치 파일의 VFS 처리 • 장치 파일 클래스 티스크립터 • 장치 클래스명인 name과 파일 연산 테이블에 대한 포인터인 fops 필드를 포함 • 모든 문자 장치 파일에 대한 device_struct 디스크립터는 chrdevs 테이블에 있다 • 테이블은 사용할 수 있는 주 번호에 해당하는 255개 항목을 포함한다 • 블록 장치 파일에 대한 255개 디스크립터 모두 blkdevs 테이블에 있다 • 주번호가 0일 수 없으므로, 두 테이블의 첫째 항목은 언제나 비어있다 • chrdevs와 blkdevs 테이블은 최초 비어있는 상태 • register_chrdev()와 register_blkdev() 함수를 사용하여 각 테이블에 새로운 항목을 삽입 • unregister_chrdev()와 unregister_blkdev()를 사용하여 항목을 삭제 • 만약 장치 드라이버를 커널에 정적으로 포함시켰다면 대응하는 장치 파일 클래스를 시스템 초기화 과정동안 등록하지만 장치 드라이버를 모듈로 동적으로 로드한다면 대응하는 장치 파일 클래스를 모듈을 로드할 때 등록하고, 모듈을 언로드 할때 등록을 해지한다
Part III : 장치 드라이버
장치 드라이버 • 커널 지원 수준 • 지원하지 않음 • 애플리케이션 프로그램은 적절한 in, out 어셈블리어 명령을 통해서 장치의 입출력 포트와 직접 상호 작용한다 • X 윈도우 시스템 • 입출력 장치에서 만드는 하드웨어 인터럽트를 활용하지 못하게 막는다 • iopl()과 ioperm() 시스템 콜은 프로세스에게 입출력 포트에 접근할 수 있는 권한 • 실행 파일의 fsuid 필드를 수퍼유저 UID인 0으로 설정하면 일반사용자도 가능
장치 드라이버 • 최소 지원 • 커널은 하드웨어 장치를 인식하지 않고 단지 그 입출력 인터페이스만 인식 • 사용자 프로그램은 일련의 문자열을 읽고 쓸 수 있는 순차적인 장치로 다룬다 • 범용 입출력 인터페이스에 연결된 외부 하드웨어 장치에 주로 쓰임 • 커널은 장치파일을 제공하여 입출력 인터페이스 관리 • 애플리케이션 프로그램은 장치 파일을 읽고 써서 외부 하드웨어 장치를 처리 • 커널 크기를 작게 유지할 수 있다(확장지원 방법에 비해 선호) • 범용 입출력 인터페이스중 직렬 포트만 이 접근 방법으로 처리 • 확장 지원 • 커널은 하드웨어 장치를 인식한다 • 입출력 인터페이스를 직접 처리 • 내부 하드디스크같이 입출력 버스에 직접 연결한 모든 하드웨어 • 직렬포트 이외의 모든 범용 입출력 인터페이스와 연결하는 외부장치(병렬, USB, PCMCIA, SCSI 인터페이스등)
장치 드라이버 • 입출력 연산 모니터링 • 풀링 모드(polling mode) • 상태 레지스터 값이 연산 종료 시그널을 담을 때까지 계속 검사 • 기다리는 시간이 너무 길고, 드라이버가 타임아웃 시간도 기억해야한다 • 인터럽트 모드(interrupt mode) • 입출력 제어기가 IRQ 선을 통해 입출력 연산이 끝났음을 알려줄 수 있을때 사용 • 입출력 장치 대기 큐에 대한 포인터를 매개변수로 넘겨서 interruptibla_sleep_on()또는 sleep_on()을 호출 • 인터럽트가 발생하면 인터럽트 핸들러는 wake_up()을 호출 • 깨어난 장치 드라이버는 입출력 연산 결과를 검사 • 타임아웃 제어는 정적/동적 타이머(5장 참조)로 구형
장치 드라이버 • 입출력 포트 접근 • in, out, ins, outs 어셈블리 언어에 보조 함수를 포함한다 • inb(), inw(), inl() • 입출력 포트에서 각각 연속하는 1, 2, 4바이트를 읽는다 • byte(8비트), word(16비트), long(32비트)를 나타낸다 • inb_p(), inw_p(), inl_p() • 위 작업 수행 후, 잠깐 쉬기(pause) 실행 • outb(), outw(), outl() • 각각 연속하는 1, 2, 4바이트를 입출력 포트에 쓴다 • outb_p(), outw_p(), outl_p() • 위 작업 수행 후, 잠깐 쉬기(pause)실행 • insb(), insw(), insl() • 연속하는 바이트 열을 1, 2, 4바이트 단위로 입출력 포트에서 읽는다 • 열 길이는 함수의 매개변수로 지정 • outsb(), outsw(), outsl() • 연속하는 바이트 열을 1, 2, 4바이트 단위로 입출력 포트에 쓴다
장치 드라이버 • request_region() • 주어진 입출력 포트 범위를 입출력 장치에 할당 • check_region() • 주어진 입출력 포트의 범위가 비어있는지, 그 중 일부를 이미 입출력 장치에 할당했는지 검사 • release_region() • 이전에 입출력 장치에 할당했던 입출력 포트 범위를 해제 ※ 현재 입출력 장치에 할당한 입출력 주소는 /proc/ioports 파일에 서 얻을 수 있다
장치 드라이버 • IRQ요청 • 사용 카운터가 현재 장치 파일에 접근하고 있는 프로세스 개수를 기억한다 • 카운터는 장치 파일의 open 메소드에서 증가하고, release 메소드에서 감소 • open 메소드는 값이 증가하기 전에 사용 카운터 값을 검사 • 카운터가 0이면, 장치 드라이버는 IRQ를 할당하고, 하드웨어 장치에 대한 인터럽트를 활성화 해야 한다. • request_irq()를 호출하고, 입출력 제어기를 설정 • Release 메소드는 값을 감소시킨 후 카운터 값을 검사 • 카운터가 0이면, 하드웨어 장치를 사용하는 프로세스가 하나도 없음 • free_irq()를 호출하여 IRQ 선을 해제하고, 제어기에 대한 인트럽트 비활성
장치 드라이버 • DMA 가동 • 연산 속도를 높이기 위해 활용 • 데이터 전송을 위해 장치의 입출력 제어기와 상호 작용 • 커널은 선형 주소를 버스 주소로 또는 그 반대로 변환 • Virt_to_bus, bus_to_virt 매크로 제공 • IRQ와 마찬가지로 DMAC도 요청하는 드라이버에 동적으로 할당
장치 드라이버 • ISA버스에 대한 DMA • 장치 파일의 open() 메소드에서 장치의 사용 카운터를 증가 • 증가시키기 전의 값이 0이면, 드라이버는 다음과 같이 동작 • request_irq()를 호출하여 ISA DMAC가 사용할 IRQ 선을 할당 • request_dma()를 호출하여 DMA 채널을 할당 • 하드웨어 장치에 DMA를 사용해야 하며, 인터럽트 발생사실을 알린다 • DMA 버퍼를 위한 저장 공간을 할당
장치 드라이버 • DMA 연산을 시작할 때 다음과 같은 연산을 수행 • set_dma_mode()를 호출하여 채널을 읽기/쓰기 모드로 설정 • set_dma_addr()을 호출하여 DMA 버퍼의 버스 주소를 설정 • set_dma_count()를 호출하여 전송할 바이트 수를 설정 • enable_dma()를 호출하여 DMA 채널을 활성화 한다 • 현재 프로세스를 장치의 대기 큐에 넣고, 이를 보류한다. DMA가 전송연산을 종료하며, 장치의 입출력 제어기가 인터럽트를 발생시키고, 해당 인터럽트 핸들러가 잠든 프로세스를 깨운다 • disable_dma()를 호출하여 DMA 채널을 비활성화 한다 • get_dma_residue()를 호출하여 모든 바이트를 전송했는지 검사 • 카운터가 0이 되면, 다음 연산을 수행한다 • DMA와 하드웨어 장치에서 해당 인터럽트를 비활성화한다 • free_dma()를 호출하여 DMA 채널을 해제한다 • free_irq()를 호출하여 DMA에 사용한 IRQ 선을 해제한다
장치 드라이버 • PCI버스에 대한 DMA • DMA 연산의 종료를 알리기 위해 사용할 IRQ선을 할당해야 한다 • DMA 채널을 할당할 필요는 없다 • DMA 버퍼의 버스주소, 전송방향, 데이터 크기 등을 기록 • release 메소드가 IRQ선을 해제
장치 드라이버 • 장치 제어기의 지역 메모리 • 주소 매핑 • ISA 버스에 연결된 장치의 대부분 • 물리주소 범위 0xa0000에서 0xfffff에 대응한다. 이 범위는 2장의 ‘예약된 페이지 프레임’에서 언급한 640KB에서 1MB 사이의 ‘구멍’에 해당 • VESA 지역버스(VLB)를 사용하는 오래된 장치 • 입출력 공유 메모리 주소 범위 0xe00000에서 0xffffff에 대응한다. 즉 14MB에서 16MB 범위이다. 페이징 테이블의 초기화를 복자바게 만듦 • PCI 버스에 연결된 장치 • 입출력 공유 메모리는 램의 물리 주소 범위를 훨씬 초과한 큰 물리 주소 영역에 대응
장치 드라이버 • 입출력 공유 메모리 접근 • PAGE_OFFSET보다 큰 주소로 나타내야 한다 • PAGE_OFFSET이 0xc0000000이라고 가정한다(3GB~4GB) • t1에 물리적 주소 0xc000b0fe4인 입출력 위치값을 저장 • t2에 물리적 주소 0xfc000000인 입출력 위치 값을 저장해야 한다고 가정 t1=*((unsigned char *)(0xc00b0fe4)); t2=*((unsigned char *)(0xfc000000)); • t1의 주소는 640KB에서 1MB사이의 ISA 구멍부분에 해당하므로 잘 동작 • t2는 시스템 램의 마지막 물리 주소보다 크므로 물리 주소를 대응시키는 선형 주소를 포함하도록 바꿔야 함 • ioremap() 함수를 호출하여 처리/매핑 제거시 iounmap() io_mem = ioremap(0xfb000000, 0x200000); t2 = *((unsigned char *)(io_mem + 0x100000)); • 0xfb000000에서 시작하는 새로운 2MB 선형 주소 범위를 생성 • 0xfc000000 주소로 메모리 위치를 읽는다
장치 드라이버 • Readb, readw, readl • 입출력 공유 메모리 위치에서 각각 1, 2, 4바이트를 읽는다 • Writeb, writew, writel • 입출력 공유 메모리 위치에 각각 1, 2, 4바이트를 쓴다 • Memcpy_fromio, memcpy_toio • 입출력 공유 메모리 위치에서 동적 메모리로 혹은 그 반대로 데이터 블록을 복사 • Memset_io • 입출력 공유 메모리 영역을 특정 값으로 채운다 0xfc000000 입출력 위치에 접근하는 방법은 io_mem = ioremap(0xfb000000, 0x200000); t2 = readb(io_mem + 0x100000); ※ 이 매크로로 플랫폼별 호환성을 높일수 있다
Part IV : 문자 장치 처리
문자 장치 처리 • 데이터 버퍼링, 디스크 캐시 불필요 • 복잡한 통신 프로토콜을 구현한다 • 하드웨어 장치의 입출력 포트에서 값을 읽는다
문자 장치 처리 • 로지텍 버스 마우스의 드라이버 동작 • 장치 드라이버는 /dev/logibm 문자 장치 파일에 대응 • 주 번호 10, 부 번호 0을 갖는다 • 파일 객체의 f_op필드는 bus_mouse_fops 테이블을 가리키고,open_mouse() 함수를 호출 • 버스 마우스를 열결했는지 검사 • 버스 마우스가 사용하는 IRQ 선, 즉 IRQ 5를 요청하고, mouse_interrupt() 등록 • Mouse_status 유형의 mouse라는 작은 자료 구조를 초기화버튼 클릭/포인트 이동 정보저장 • 0x23e제어 레지스터에 0을 쓴다 • 마우스 사용시 마다 mouse_interrupt() 함수를 활성화한다. • 버스 마우스 상태 확인 0x23e제어 레지스터에 적절한 명령 기록0x23c입력 레지스터에서 해당 값을 읽는다 • Mouse 자료 구조를 갱신 • 0x23e 제어 레지스터에 0을 기록
문자 장치 처리 • /dev/logibm 파일을 읽어 마수으 상태를 얻는다read() 시스템 콜은 파일 연산의 read 메소드와 관련한 read_mouse() 호출 • 프로세스가 적어도 3바이트를 요청했는지 검사하고, 아니라면 –EINVAL을 반환 • /dev/logibm에 대한 마지막 읽기 연산 이후 마우스 상태가 바뀌었는지 검사그렇지 않으면 –EAGAIN을 반환 • disable_riq()를 호출하여 IRQ5의 인터럽트 처리를 비활성, mouse자료구조의 값enalbe_irq()를 호출하여 IRQ5의 인터럽트 처리를 다시 활성화 • 마지막 읽기 연산 이후에 마우스 상태를 나타내는 3바이트를 사용자 모드 버퍼에 기록 • 프로세스가 3바이트 이상을 요청했다면, 나머지 사용자 모드 버퍼를 0으로 채운다 • 기록한 바이트 수를 반환
Part V : 블록 장치 처리
블록 장치 처리 • VFS를 통한 단일화된 인터페이스 제공 • 디스크 데이터에 대한 효율적인 미리 읽기 구현 • 데이터에 대한 디스크 캐싱 제공
블록 장치 처리 • 버퍼 입출력 연산 • 전송한 데이터는 버퍼에 남아있다 • 각 버퍼를 장치 번화와 블록 번호로 표현할 수 있는 특수한 블록에 대응 • 실제로 버퍼 입출력 연산은 비동기적으로 이루어진다 • 프로세스가 블록 장치 파일을 직접 읽거나 커널이 파일 시스템의 특정 유형 블록을 읽을 때 주로 사용 • 디스크 기반의 일반 파일을 기록할 때 사용(리눅스 2.2)
블록 장치 처리 • 페이지 입출력 연산 • 전송된 데이터는 페이지 프레임에 남아있다 • 각 페이지 프레임에는 일반 파일에 속한 데이터가 있다 • 파일의 아이노드와 파일 내 오프셋으로 표현 • ‘비동기적 입출력 연산’이라 한다 • 일반 파일을 읽거나 파일 메모리 매핑, 스와핑을 위해 사용 ※ 두 종류의 입출력 데이터 전송 모두 블록 장치에 접근하기 위해 동일한 드라이버를 사용하지만, 커널은 이들에 대해 서로 다른 알고리즘과 버퍼링 기법을 사용한다
블록 장치 처리 • 섹터, 블록, 버퍼 • 블록 장차에 대한 각 데이터 전송 연산을 섹터라는 연속 바이트 그룹에 따라 수행 • 대개 디스크 장치의 섹터 크기는 512바이트 • 여러 인접 섹터를 한 번에 전송할 수 있지만, 한 섹터보다 작은 자료는 결코 전송할 수 없다 • hardsect_size[3][2]는 /dev/hda2를 나타낸다 • hardsect_size[M]이 NULL인 경우, 주 번호 M을 공유하는 모든 블록 장치는 섹터 크기가 표준인 512바이트이다. • 섹터는 하드웨어 장치에 대한 데이터 전송의 기본 단위 • 블록은 장치 드라이버가 요청하는 입출력 연산과 관련한 인접한 바이트 그룹 • 리눅스 내 블록 크기는 반드시 2의 제곱, 페이지 프레임보다 작다
블록 장치 처리 • 블록에는 섹터가 정수 개 있기 때문에, 반드시 섹터 크기 배수여야 한다 • 각 블록 장치는 자신만의 고유한 크기로 블록을 제한 • 커널은 blksize_size라는 테이블에 블록 크기를 저장 • blksize_size[M]이 NULL이면, 주 번호 M을 공유하는 모든 블록 장치는 표준 블록 크기가 1024바이트 이다 • 커널이 블록의 내용을 저장하기 위해 사용하는 램 메모리 영역을 요청 • 장치 드라이버가 디스크에서 블록을 읽을 때, 하드웨어 장치에서 얻은 값을 사용하여 해당 버퍼를 채운다 • 장치 드라이버가 디스크에 블록을 기록할 때, 버퍼의 실제 값을 사용하여 하드웨어 장치의 연속 바이트를 갱신한다 • 버퍼 크기는 언제나 대응하는 블록 크기와 일치
블록 장치 처리 • 버퍼 입출력 연산에 대한 블록 장치 핸들러 아키텍처
블록 장치 처리 • 버퍼 입출력 연산 개요 • 자신만을 위한 고 수준 장치 드라이버를 요구한다 • 플로피디스크의 장치 드라이버 • 마지막으로 디스크에 접근한 다음에 사용자에 의해 드라이브 내부에 있는 디스크가 변경되지 않았는지 검사 • 새로운 디스크가 삽입했다면, 장치 드라이버는 이전 디스크의 데이터로 채워져 있는 모든 버퍼를 무효화해야 한다 • 고 수준 장치 드라이버는 자신의 read와 write 메소드가 있는 경우에도, 일반적으로 block_read()와 block_wirte()를 호출 • getblk() 함수를 호출하여 블록을 이미 읽었는지, 남아있는지 검사 • 캐쉬에 없을경우 getblk()는 ll_rw_block()을 호출 • VFS가 블록 장치의 특정 블록에 직접 접근할 때도 발생 • 언제나 비동기적으로 이루어 진다 • 저 수준 장치 드라이버는 DMAC와 디스크 제어기를 프로그래밍하고 종료 • 전송을 마치면 인터럽트가 발생하여 저 수준 장치 드라이버를 두 번째로 활성화하여 입출력 연산과 관련한 자료 구조를 지운다
블록 장치 처리 • 미리 읽기의 역할 • 블록 장치의 여러 인접 블록을 요청하기 전에 미리 익어두는 기법 • 적은 명령어로 인접한 섹터를 좀더 큰 섹터 그룹을 읽도록 한다 • 시스템의 응답성(responsiveness)도 좋아진다 • 블록 장치에 대한 임의(random) 접근의 경우, 쓸모없는 정보를 저장하여 공간을 낭비할 수도 있다 • 가장 최근에 요청한 입출력 접근이 이전과 비교해 순차적이지 않다면 미리 읽기를 중단한다 • 파일 객체의 f_reada필드는 파일에 대해 미리 읽기를 활성화 하면 1로 설정하고 그렇지 않으면 0인 플래그 이다. • 미리 읽을 바이트 수인 read_ahead테이블에 저장한다 • 0값은 기본 값 값인 512바이트 섹터 8개(4KB)
블록 장치 처리 • block_read()와 black_write()함수 • 프로세스가 장치 파일에 대해 읽기나 쓰기 연산을 요청할 때 • 고 수준 장치 드라이버에서 호출 • 매개변수 • Flip : 장치 파일과 관련한 파일 객체 주소 • Buf : 사용자 모드 주소 공간에 있는 메모리 영역 주소 • block_read()는 블록 장치에서 읽은 데이터를 이 메모리 영역에 쓴다 • block_write()는 반대로 이 메모리 영역에서 블록 장치에 쓸 데이터를 읽는다 • count : 전송할 바이트 수
블록 장치 처리 • ppos : 장치 파일 오프셋을 포함한 변수 주소(flip->f_pos) • flip->f_dentry->d_inode->i_rdev로 부터 블록 장치의 주 번호와 부 번호 획득 • blksize_size에서 장치 파일의 블록 크기를 얻는다. • *ppos와 블록 크기를 사용하여 장치에서 읽어올 첫째 블록의 연속 번호를 계산이 블록 내부에서 읽어야 하는 첫째 바이트 오프셋도 계산 • 블록 하드웨어 장치 크기를 얻는다(blk_size에 값 저장)장치 파일의 주 번호와 부 번호로 참조하며, 1024바이트 단위필요시 count를 변경하여 장치의 끝을 지나면 읽기 연산을 수행하지 못하게한다 • 장치에서 읽을 블록 수를 count, 블록 크기, 첫째 블록 안의 오프셋 등으로 계산filp->f_reada를 설정했다면, read_ahead 테이블에 지정된 미리 읽을 블록 수도 함께 고려 • 읽고자 하는 각 블록에 대해 다음 연산을 수행 • getblk()함수를 사용하여 버퍼 캐시에서 블록을 찾아본다그렇지 않으면 새로운 버퍼가 할당하여 캐시에 삽입 • 버퍼에 유요한 데이터를 포함하지 않으면 ll_rw_block() 함수로 읽기 연산 시작현재 프로세스는 데이터를 버퍼에 전송할때까지 보류 • 프로세스가 블록을 요청했다면, 즉 미리읽기로 읽지 않았다면, 버퍼 내용을 buf가 가리키는 사용자 메모리 영역에 복사 • *ppos에 사용자 메모리 영역에 복사한 바이트 수를 더한다
블록 장치 처리 8. Flip->f_reada 플래그를 1로 설정하여 다음 번에 미리 읽기 메커니즘을 사용할 수 있게 한다 9. 사용자 메모리 영역에 복사한 바이트 수를 반환 • block_write()함수와 block_read()함수의 차이 • Write연산을 시작하기 전에 block_write()함수는 블록 하드웨어 장치가 읽기 전용이 아닌지 반드시 검사하고 읽기 전용인 경우 에러코드를 반환 • block_write() 함수는 첫째 블록 안에 기록할 첫째 바이트 오프셋을 검사해야 한다. 오프셋이 0이 아니고, 이미 버퍼 캐시에 첫째 블록에 대한 유효 데이터가 없다면, 함수는 기록하기 전에 디스크에서 블록을 읽어야 한다 • Block_write() 함수는 디스크에 기록하기 위해 반드시 ll_rw_block()을 호출하지는 않는다
블록 장치 처리 • bread()와 breada()함수 • bread() • 특정한 블록이 버퍼 캐시에 있는지 검사하고, 없다면 블록 장치에서 블록을 읽는다 • 파일시스템은 디스크에서 비트맵, 아이노드, 다른 블록 기반의 자료 구조를 읽기 위해 광범위하게 사용(프로세스가 블록 장치 파일을 읽을경우 block_read()사용) • 장치 식별자, 블록 번호, 블록 크기를 매개변수로 받아들여 다음 연산을 수행 • getblk()함수를 호출하여 버퍼 캐시에서 블록을 찾고, 없다면 getblk()는 새로운 버퍼를 할당 • 버퍼에 최신 데이터가 있으면, 함수를 마친다 • ll_rw_block()을 호출하여 읽기 연산을 시작 • wait_on_buffer()라는 함수를 호출하여 데이터 전송이 끝날 때까지 기다린다current 프로세스를 b_wait 대기큐에 넣고, 버퍼가 락에서 풀릴 때까지 보류 • breada() • 요청한 블록에 추가하여 블록 몇 개를 미리 읽는다 • 디스크에 어떤 블록을 직접 쓰는 함수는 없다(쓰기연산은 언제나 뒤로 연기)
블록 장치 처리 • 버퍼 헤드 • 각 버퍼와 관련한 buffer_head 유형 디스크립터 • 커널은 각 버퍼에 대해 처리하기 전에 버퍼헤드를 검사 • b_data 필드에는 해당 버퍼의 시작 주소를 저장 • b_this_page는 페이지 내 다음 버퍼의 버퍼 헤드를 가리킨다 • 전체 페이지 프레임을 저장하고 검색하는데 쓰인다 • b_blocknr는 논리적 블록 번호, 즉 디스크 파티션 내의 블록 인덱스를 저장 • b_dev필드가 가상 장치를 나타내는 반면 b_rdev 필드는 실제 장치를 나타낸다. RAID 스토리지를 나타내기 위해 도입 • b_blocknr로 논리적인 블록 번호를, b_rdev로 특정 디스크 유닛을, b_rsector로 대응하는 섹터 번호를 나타낸다