리눅스 커널의 이해 중에서 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/fdo 블록 2 플로피디스크 플로피디스크 /dev/had 3 첫째IDE디스크 /dev/hda2 첫째IDE디스크의 둘째 기본 파티션 /dev/hdb 64 둘째IDE디스크 /dev/hdb3 67 둘째IDE디스크의 셋째 기본 파티션 /dev/ttyp0 문자 터미널 /dev/console 5 1 콘솔 /dev/lp1 6 병렬 프린터 /dev/ttyS0 4 첫째 직렬 포트 /dev/rtc 10 135 실시간 클록 /dev/null 널 장치(블랙홀)
파일을 입출력 장치와 연관시키기 일반적으로 장치파일은 하드웨어 장치 또는 디스크 파티션과 같은 하드웨어 장치의 물리적 또는 논리적인 부분과 관련 가상적인 논리적 장치를 나타내는 경우도 있다 /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) 상태 레지스터 값이 연산 종료 시그널을 담을 때까지 계속 검사 기다리는 시간이 너무 길고, 드라이버가 타임아웃 시간도 기억해야한다 인터럽트 모드(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() 주어진 입출력 포트 범위를 입출력 장치에 할당 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 연산을 시작할 때 다음과 같은 연산을 수행 카운터가 0이 되면, 다음 연산을 수행한다 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선을 할당해야 한다 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 Writeb, writew, writel 입출력 공유 메모리 위치에서 각각 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에 사용자 메모리 영역에 복사한 바이트 수를 더한다
블록 장치 처리 block_write()함수와 block_read()함수의 차이 8. Flip->f_reada 플래그를 1로 설정하여 다음 번에 미리 읽기 메커니즘을 사용할 수 있게 한다 9. 사용자 메모리 영역에 복사한 바이트 수를 반환 block_write()함수와 block_read()함수의 차이 Write연산을 시작하기 전에 block_write()함수는 블록 하드웨어 장치가 읽기 전용이 아닌지 반드시 검사하고 읽기 전용인 경우 에러코드를 반환 block_write() 함수는 첫째 블록 안에 기록할 첫째 바이트 오프셋을 검사해야 한다. 오프셋이 0이 아니고, 이미 버퍼 캐시에 첫째 블록에 대한 유효 데이터가 없다면, 함수는 기록하기 전에 디스크에서 블록을 읽어야 한다 Block_write() 함수는 디스크에 기록하기 위해 반드시 ll_rw_block()을 호출하지는 않는다
블록 장치 처리 bread()와 breada()함수 bread() breada() 특정한 블록이 버퍼 캐시에 있는지 검사하고, 없다면 블록 장치에서 블록을 읽는다 파일시스템은 디스크에서 비트맵, 아이노드, 다른 블록 기반의 자료 구조를 읽기 위해 광범위하게 사용(프로세스가 블록 장치 파일을 읽을경우 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로 대응하는 섹터 번호를 나타낸다
블록 장치 처리 버퍼 헤드 필드 유형 필드 설명 unsigned long b_blocknr 논리적 블록 번호 b_size 블록 크기 kdev_t b_dev 가상 장치 식별자 b_rdev 실제 장치 식별자 b_rsector 실제 장치 안에서의 첫 섹터 번호 b_state 버퍼 상태 플래그 unsigned int b_count 블록 사용 카운터 Char* b_data 버퍼에 대한 포인터 b_flushtime 버퍼의 플래시(flush) 시간
블록 장치 처리 유형 필드 설명 struct wait_queue* b_wait 버퍼 대기큐 struct buffer_head* b_next 충돌 해시 리스트의 다음 항목 b_pprev 충돌 해시 리스트의 이전 항목 b_thit_page 페이지별 버퍼 리스트 b_next_free 리스트의 다음 항목 b_prev_free 리스트의 이전 항목 unsigned int b_list 버퍼를 포함하는 LRU 리스트 b_reqnext 요청 버퍼 리스트 void(*)() b_end_io 입출력 완료 메소드 void(*) b_dev_id 특수 장치 드라이버 데이터
블록 장치 처리 b_state는 다음 플래그를 저장 BH_Uptodate BH_Dirty BH_Lock BH_Req 버퍼에 유효한 데이터가 있으면 1로 설정 buffer_uptodate() 함수가 이 플래그 값을 반환 BH_Dirty 버퍼가 더티이면, 즉 블록 장치에 기록해야 하는 데이터가 있으면 1로 설정 buffer_dirty() 함수가 이 플래그 값을 반환 BH_Lock 버퍼에 락이 걸려 있으면 1로 설정 Buffer_locked() 함수가 이 플래그 값을 반환 BH_Req 대응하는 블록을 요청했으며, 유효한(최신) 자료를 포함하면 1로 설정 buffer_req()함수가 이 플래그 값을 반환 BH_Protected 버퍼가 보호 상태에 있으면 1로 설정 buffer_protected()함수가 이 플래그 값을 반환
블록 장치 처리 블록 장치 요청 블록 장치 드라이버는 한번에 블록 하나를 전송 커널은 디스크 위의 접근할 각 블록에 대해 입출력 연산을 별도로 수행하지 않는다 커널은 여러 블록을 묶어 하나로 처리하려고 시도 -> 헤드의 평균 이동 횟수를 줄일 수 있다 프로세스, VFS 계층 또는 다른 커널 구성 요소가 디스크 블록을 읽거나 쓰려 할 때 발생 요청이 들어오자마자 처리하지 않고, 실행 예정인 목록에 추가하고, 나중에 실행 작업을 가성적으로 연기하는 과정은 블록 장치 성능을 향상 시키는 핵심 새로운 요청을 탐색 연산을 수행하지 않고도 만족시킬 수 있는지 검사 디스크는 순차적으로 접근하는 경우가 많기 때문에 매우 효율적 보류를 막기 위해 비동기적으로 처리(매우 복잡한 작업형태로 되므로)
블록 장치 처리 각 블록 장치 드라이버 마다 자신 만의 요청큐가 있다 각 블록 장치 요청을 요청 디스크립터로 표현 요청 순서를 정렬하여 디스크 성능을 높일 수 있다 각 블록 장치 요청을 요청 디스크립터로 표현 데이터 전송 방향을 cmd필드에 저장(read, write) rq_status 필드는 요청 상태를 지정하기 위해 쓰임 블록 장치 대부분은 RQ_INACTIVE 또는 RQ_ACTIVE이다 요청은 동일한 장치에서 인접하는 여러 블록을 포함할 수 있다 rq_dev 필드는 블록 장치 sector 필드는 요청의 첫째 블록에 대응하는 첫째 섹터번호 nr_sector와 current_nr_sector는 전송할 섹터 수 sector, nr_sector, current_nr_sector필드는 요청을 처리하는 동안에 동적으로 바뀔 수 있다 bh와 bhtail은 처음과 마지막 항목
블록 장치 처리 유형 필드 설명 int rq_status 요청 상태 kdev_t rq_dev 장치 식별자 cmd 요청한 연산 errors 성공 또는 실패 코드 unsigned long sector 첫째 섹터 번호 nr_sector 요청한 섹터 수 current_nr_sector 현재 블록의 섹터 수 char* buffer 입출력 전송을 위한 메모리 영역 struct semaphore* sem 요청 세마포어 struct buffer_head* bh 첫째 버퍼 디스크립터 bhtail 마지막 버퍼 디스크립터 struct request* next 요청큐 링크
블록 장치 처리 요청 디스크립터와 디스크립터의 버퍼와 섹터
블록 장치 처리 요청큐와 블록 장치 드라이버 디스크립터 요청큐는 그 항목이 요청 디스크립터인 단순 연결 리스트 Next필드가 다음 항목을 가리키며, 마지막 항목일때 null 먼저 장치 식별자에 따라 정렬하고 최초 섹터 번호에 따라 정렬 요청큐를 하나만 포함하는 경우 전체적인 성능을 저하시킨다 디스크립터는 blk_dev_struct형 자료 구조 이다
블록 장치 처리 블록 장치 드라이버 디스크립터 필드 모든 블록 장치에 대한 디스크립터는 blk_dev 테이블에 저장하고, 블록 장치의 주 번호로 참조할 수 있다 유형 필드 설명 void*(*)(void) Request_fn 스트래티지 루턴 Void* data 드라이버의 고유한 데이터 공통 큐 struct request plug 더미 플러그 요청 struct request* current_request 단일 공통 큐에 들어 있는 현재 요청 struct request**(*)(kdev_t) queue 큐 중 하나에서 요청을 얻기 위한 메소드 struct tq_struct plug_tq 플러그 태스크 큐 항목
블록 장치 처리 요청큐 하나를 포함하는 경우, queue 필드는 null이고, current_request 필드는 큐 안에 있는 처리 중인 요청 디스크립터를 가리킨다. 큐가 비어있는 경우 current_request는 null 여러큐를 관리하는 경우 queue 필드는 전용 드라이버 메소드를 가리킨다 블록 장치 파일의 식별자를 받아 장치 번호에 따라 큐 하나를 선택하고, 서비스 요청이 있다면 그 요청의 디스크립터를 가리키는 포인터 주소를 반환 current_request 필드는 서비스 요청이 있다면 그 요청 디스크립터를 가리킴 request_fn() 필드는 드라이버의 스트래티지 루틴 주소를 포함 큐에 포함된 요청에 의해 지정된 데이터 전송을 시작하도록 실제로 물리적 블록 장치와 상호작용
블록 장치 처리 ll_rw_block()함수 블록 장치 요청을 담당 다음과 같은 매개변수를 받는다 Rw는 연산의 종류로 READ, WRITE, READA, WRITEA 중 하나를 선택할 수 있다. 끝에 A가 붙는 두 값이 나타내는 연산 유형은 사용 가능한 요청 디스크립터가 없을 때 블록되지 않고, 즉시 반환 nr은 전송할 블록 수를 나타낸다 bh는 nr개의 포인터 배열로 블록을 표현하는 버퍼 헤드를 가리킨다 버퍼 헤드를 이전에 초기화 시켰으며, 각각은 블록 번호, 블록 크기, 가상장치 식별자 등을 나타낸다
블록 장치 처리 함수는 bh 배열의 null이 아닌 모든 항목을 대상으로 하는 루프에 들어간다. 각 버퍼 헤드에 대해 다음과 같은 동작을 수행 블록 크기 b_size가 가상 장치 b_dev의 블록 크기와 일치하는지 검사 실제 장치의 식별자를 설정 섹터 번호 b_rsector를 블록 번호와 블록 크기에 따라 설정 연산이 WRITE 또는 WRITEA인 경우, 블록 장치가 읽기 전용이 아닌지 검사 버퍼 헤드의 BH_Req 플레그를 설정하여 다른 커널 제어 경로에게 이 블록을 요청 중이라는 사실을 알린다 make_request() 함수를 실제 장치의 주 번호, 입출력 연산 유형, 버퍼 헤드 주소를 매개변수로 호출한다
블록 장치 처리 make_request() 함수는 다음 연산을 수행 버퍼 헤드의 BH_Lock 플래그를 설정 B_rsector가 블록 장치의 섹터 수를 넘지 않는지 검사 블록을 읽을경우 그 블록이 이미 유효하지 않은지 검사 블록을 쓰고자 하는 경우, 실제로 더티 상태인지 검사 두 조건 모두 성립하지 않으면, 데이터 전송을 필요로 하지 않음-> 반환 지역 인터럽트를 금지하고, io_request_lock 스핀 락을 얻는다 정의되어 있다면 queue 메소드를 호출하고, 그렇지 않으면 블록 장치 디스크립터의 current_request 필드를 읽어서 실제 장치의 요청큐 주소를 얻는다 다음 둘 중 하나를 수행한다 요청큐가 비어 있다면, 새로운 요청 디스크립터를 삽입하고, 스트래티지 루틴을 나중에 활성화하도록 스케쥴링한다 요청큐가 비어있지 않다면, 새로운 요청 디스크립터를 삽입하고, 이미 큐에 들어있는 다른 요청과 새로운 요청을 합치는 과정을 시도
블록 장치 처리 스트래티지 루틴의 활성화 계획 연속하는 블록에 대한 요청을 묶을 수 있는 가능성을 높이기 위해 스트래티지 루틴을 활성화 하는 계획을 연기하는 편이 낫다 장치 플러깅과 언플러깅이라는 기법을 사용 실제 장치의 요청큐가 비어있고, 장치가 아직 플러그 되지 않을때 make_request()는 장치 플러깅을 수행 새로운 요청 디스크립터를 할당하고, 버퍼 헤드에서 읽은 정보를 사용하여 초기화 make_request()는 새로운 요청 디스크립터를 실제 장치의 요청큐에 삽입 make_request()는 plug_tq 태스크 큐 디스크립터를 tq_disk 태스크 큐 중에 삽입하고 활성화 한다 커널은 tq_disk 태스크 큐가 plug_tq 태스크 큐 항목을 하나라도 포함하고 있는지 주기적으로 검사 Tq_disk를 검사하는 동안, 커널은 큐에 있는 모든 항목을 제거하고, 이에 대응하는 unplug_device()함수를 호출 <- 장치 언플러깅
블록 장치 처리 요청큐 확장 요청큐가 비어 있지 않다면, 저 수준 블록 장치 드라이버는 큐가 빌 때까지 계속 요청을 처리한다 -> make_request()는 스트래티지 루틴의 활성화를 계획할 필요가 없다 make_request()는 새로운 항목을 추가하거나 블록 클러스터링 한다 블록 클러스터링은 특정한 블록 장치에 해당하는 블록에 대해서만 구현하며, 다음 조건을 만족해야 한다 삽입될 블록은 요청 안에 있는 다른 블록과 같은 블록 장치에 속해야 하며, 그들과 연속해야 한다 요청에 포함된 블록은 삽입될 블록과 같은 입출력 연산 유형이어야 한다 확장된 요청은 허용된 최대 섹터 수를 넘지 않아야 한다 요청을 현재 저 수준 장치 드라이버에서 처리 중이지 않아야 한다
블록 장치 처리 make_request() 함수는 큐의 모든 요청을 탐색 블록을 요청끝에 추가하면,요청을 큐의 다음 항목과 병합하려 한다 make_request()는 io_request_lock 스핀락을 해제하고 종료 기존의 어떤 요청도 새로운 블록을 포함할 수 없으면, make_request()는 새로운 요청 디스크립터를 할당하고 버퍼헤드에서 읽은 정보로 이를 초기화 make_request()는 add_request()를 호출하여 새로운 요청을 섹터 번호에 따라 요청큐에 삽입 io_request_lock 스핀 락이 해제되고, 실행 종료
블록 장치 처리 저 수준 요청 처리 저 수준 블록 장치 드라이버는 다음과 같은 기법을 채택 저수준 블록 장치 드라이버의 구분 스트래티지 루틴은 큐에 들어 있는 현재 요청을 처리하고, 데이터 전송을 완료하면 인터럽트가 발생 -> 스트래지 루틴 종료 블록 장치 제어기에서 인터럽트를 발생새키면, 인터럽트 처리기는 하반부를 활성화 하반부 처리기는 큐에서 요청을 제거하고, 스트래티지 루틴을 다시 실행하여 큐의 다음 요청을 처리 저수준 블록 장치 드라이버의 구분 요청을 개별적으로 각 블록별로 처리하는 드라이버 요청을 함께 묶어서 여러 블록을 처리하는 드라이버
Part V : 페이지 입출력 연산
페이지 입출력 연산 프로세스 주소 공간은 페이지 집합으로 정의 페이지 입출력 연산을 활성화 할 수 있는 경우 프로세스가 일반 파일에 대해 read() 또는 write() 시스템 콜을 호출했을 경우 프로세스가 파일을 메모리에 매핑하는 페이지 위치를 읽을 경우 커널이 파일 메모리 매핑에 관련된 일부 더티 페이지를 디스크로 몰아쓰는 경우 스왑 인 또는 스왑 아웃의 경우 커널이 페이지 프레임 전체 내용을 디스크에서 로드 하거나 디스크에 저장
페이지 입출력 연산 페이지 입출력 연산 시작 brw_page()함수를 호출할 때 시작 다음과 같은 매개변수를 갖는다 dev : 블록 장치 번호 b : 논리적 블록 번호 배열 size : 블록 크기 bmap : b의 블록 번호가 아이노드 연산의 bmap 메소드를 사용하여 계산 여부를 나타내는 플래그
페이지 입출력 연산 함수는 다음과 같은 연산을 수행 create_buffers()를 수행하여 페이지에 포함된 모든 버퍼를 위한 임시 버퍼헤드를 할당 페이지의 각 버퍼헤드에 대해 다음과 같은 단계 수행 버퍼 헤드 필드 초기화 bmap 매개변수 플래그가 NULL이 아니면, 버퍼 헤드가 0번 블록을 포함하는지 검사 find_buffer()를 호출하여 버퍼 헤드와 관련한 블록이 이미 메모리에 있는지 검사 메모리에 있다면 캐시에서 발견된 버퍼 헤드의 사용 카운터 증가 입출력 연산이 read이고, 캐시에 있는 버퍼가 최신 것이 아니면, ll_rw_block()을 호출하여 READ 요청을 보내고 wait_on_buffer()를 호출하여 입출력 작업을 종료할 때까지 기다린다 입출력 연산이 read이면, 데이터를 캐시에 있는 버퍼에서 페이지 버퍼로 복사 입출력 연산이 write이면, 데이터를 페이지 버퍼에서 캐시에 있는 버퍼로 복사하고, mark_buffer_dirty()를 호출하여 캐시에 있는 버퍼 헤드의 BH_Dirty 플래그를 1로설정 비동기 버퍼헤드의 BH_Uptodate 필드를 1로 설정 캐시에 있는 버퍼 헤드의 사용 카운터를 감소 비동기 버퍼 헤드에 대해 작업을 계속 한다
페이지 입출력 연산 요청한 블록이 캐시에 존재하지 않는다. 따라서 입출력 연산이 READ인 경우 비동기 버퍼 헤드의 BH_Uptodate 플래그를 지운다. WRITE인 경우 BH_Dirty 플래그를 1로 설정 비동기 버퍼 헤드에 대한 포인터를 지역 배열에 삽입하고, 다음 비동기 버퍼헤드에 대해 작업을 계속 3. 비동기 버퍼 헤드 포인터의 지역 배열이 비어 있다면 요청한 모든 블록이 버퍼 캐시에 있으면, 페이지 입출력 연산은 불필요 4. 비동기 버퍼 헤드 포인터의 지역 배열이 비어 있지 않은 상태이며, 페이지 입출력연산이 필수적 ll_rw_block()을 호출하여 지역 배열에 포함된 모든 버퍼 헤드에 대해 rw 요청을 요청하고, 즉시 0을 반환
페이지 입출력 연산 페이지 입출력 연산 종료 모든 비동기 버퍼 헤드의 b_end_io메소드를 호출 b_end_io 필드는 end_buffer_io_async()함수를 가리키고 다음 연산을 수행 mark_buffer_uptodate() 함수를 호출 입출력 연산 결과에 따라 비동기 버퍼 헤드의 BH_Uptodate 플래그를 1로 설정 BH_Uptodate 플래그를 1로 설정했다면, 페이지의 모든 다른 비동기 버퍼 헤드가 최신인지 검사, 그렇다면, 페이지 디스크립터의 PG_uptadate 플래그를 1로 설정 비동기 버퍼 헤드의 BH_Lock 플래그를 지운다 BH_Uptodate플래그가 0이면, 블록을 전송하는 중에 에러가 발생 페이지 디스크립터의 PG_error 플래그를 1로 설정 비동기적 버퍼 헤드의 사용 카운터를 감소
페이지 입출력 연산 페이지를 참조하는 모든 비동기 버퍼 헤드의 사용 카운터가 0인지 검사 그렇다면 페이지 버퍼에 대한 모든 데이터 전송을 완료한 시점임 free_async_buffers()호출 모든 비동기 버퍼 헤드 해제 페이지 디스크립터의 PG_locked 비트를 지워 페이지 프레임 락을 해제 페이지 디스크립터의 wait 대기 큐에서 잠들어 있는 모든 프로세스를 깨움 after_unlock_page() 함수 호출 페이지 디스크립터의 PG_free_after 플래그를 1로 설정했으면 이 함수는 페이지 프레임을 해제
리눅스 2.4 예상 플러그 앤 플레이 지원 리눅스 엔터프라이즈급 운영체제에 좀 더 가까이 다가감 원시 입출력 장치 도입으로 직접 디스크 접근 가능 지능형 입출력을 지원한다 devfs 가상 파일 시스탬 도입
Part VI : Q & A