Kernel의 Block Layer 내용 정리
Storage IO Framework

- File System Layer
- Disk와 Memory 사이의 Mapping 작업 수행
- Search 및 Access를 위한 Data 저장 未 구성
- Block Layer
- File System 요청에 따라 Data를 전달
- IO Scheduling 기능을 이용하여 요청을 병합/분할
- SCSI Layer
- Data 전송 기능을 수행하며 SCSI Disk 식별
- 일반적인 임베디드 환경에서는 SCSI Layer 도움 없이 Device로 바로 연결
- LLDD (Low Level Device Dirver)
- Data 전송 기능을 수행하며 특정 Device 구동 기능 제공
- IO submit/complete
- 각 Layer는 하위에 요청을 전달할때 그에 상응하는 Call Back 함수를 등록
- 일반적인 경우 Hardware의 Interrupt에 의하여 Call Back 시작 (Timer, Polling 방식을 통해서도 구현)
- 예시
- SCSI Layer : 요청 = scsi_queue_rq() ↔ 완료 = scsi_done()
- libSAS Layer : 요청 = sas_queuecommand() ↔ 완료 = sas_scsi_tak_done()
bio data structure

- bio
- bi_iter 및 bi_vec 포함
- bio_vec
- IO Data의 Memory 內 위치 설명
- bv_page : Memory 上 Page를 가리킴
- bv_len : 연속된 Memory의 길이
- bv_offset : Page 內 Offer
- bi_iter
- IO Data의 Disk 內 위치 설명
- 이전 까지 완료된 IO 상태 설명
- bi_sector : 해당하는 Disk의 Sector 번호
- bi_size : 완료되지 않은 IO 크기
- bi_idx : 현재 처리중인 bio_vec의 index 번호
- bi_bvec_done : 처리 완료한 bio_vec의 Byte 크기
- bi_next
- 다음번에 처리할 bio
- bi_pool
- bio 구조체의 mempool
- bi_inline_vecs[]
- BIOSET_NEED_BVECS 설정 時 활용됨
- 연속적인 IO Data의 갯수가 많지 않다고 예상될 경우, bio_vec 할당 필요가 없으므로 성능 개선
bio_set & biovec_slab

- bio_set
- bio 구조체 할당을 위한 Cache(SLAB)
- 할당 크기 : front_pad + sizeof(struct bio) + back_pad
- back_pad : BIOSET_NEED_BVECS 설정 시, bio_vec에서 활용
- biovec_slab
- bio_vec 구조체 할당을 위한 Cache(SLAB)
- 4 종류의 크기 제공
- biovec-16 : 16개의 bio_vec, 16개 이하의 연속 Memory IO 필요 時 사용
- biovec-64 : 16개의 bio_vec, 64개 이하의 연속 Memory IO 필요 時 사용
- biovec-128 : 16개의 bio_vec, 128개 이하의 연속 Memory IO 필요 時 사용
- biovec-BIO_MAX_VECS : 256개의 bio_vec, 256개 이하의 연속 Memory IO 필요 時 사용
- 만약 IO 요청된 연속 물리 Memory 갯수가 4개 이하인 경우 bi_inline_vecs[] 사용
blk-mq

- Kernel v5.0부터 Multi-Queue를 Default 사용
- File System Layer에서 요청된 bio → request에 포함되어 하위 Layer로 전달
- Software Queue (struct blk_mq_ctx)
- 각 CPU 마다 존재
- bio는 1차적으로 해당 queue에 request 형태로 삽입
- Hardware Dispatch Queue (struct blk_mq_hw_ctx)
- 각 Block Device마다 존재
- Software Queue에 삽입된 request를 해당하는 Hardware Queue에 Mapping
IO 전달 개요

- current->bio_list : 本 task가 요청한 bio
- 요청된 bio가 Device까지 전달에는 크게 3가지 경로 존재
- plug기능 사용 時, plug pool 내부에 충분한 갯수의 bio 누적후 IO scheduler에게 전달
- plug기능 미사용 時, IO Scheduler에게 전달 (혹은, block mq context에게 전달)
- IO Scheduler 미사용 時, Device Driver에게 바로 전달

- 만약 이미 blk_mq_submit_bio() 수행중인 경우, bio를 current->bio_list에 삽입
- current->bio_list의 bio를 순회하면서 하나씩 처리
- 만약 IO요청된 Page가 HighMem인 경우 LowMem 영역의 하나 할당하여 Bounce Mechanism 사용
- IO 요청된 크기가 너무 큰 경우 분할
- blk_plug->mq_list 및 blk_mq_ctx->rq_lists 혹은 IO Scheduler list에 병합 시도
- 모든 병합시도 실패 時, 새로운 request 할당
- plug 기능 사용 時, fulsh를 위한 갯수조건 충족 시 request flush
- IO Scheduler 사용 時, Scheduler lists에게 request 전달
- IO Scheduler 미사용 時, Device Driver에게 request 전달
- 일반적으로 빠른 Disk 사용 시, IO Scheduler를 사용하지 않는것이 성능상 이득
- Scheduler Logic에 의한 약간의 부하 존재
request & bio

- 하나의 request는 여러개의 bio로 구성
- 동일한 request내의 bio는 물리적으로 연속된 Disk의 위치를 가르킴
- __sector 변수 : request中 disk의 시작위치
bio split
각 block device request_queue의 limitation 존재
- request_queue->limits.max_segments
- 하나의 request 內 segment의 최댓값
- blk_queue_max_segments() 함수를 통해 접근
- Default : BLK_MAX_SEGMENTS = 128
- request_queue->limits.max_segment_size
- 하나의 segment 최대크기
- blk_queue_max_segment_size() 함수를 통해 접근
- Default : BLK_MAX_SEGMENT_SIZZ = 65536 = 64 * 4K
- request_queue->limits.max_sectors
- 한번에 전송하는 request 內 sector의 최댓수. 즉, IO request의 최대 크기
- blk_queue_max_hw_sectors() 함수를 통해 접근
- Default : BLK_DEF_MAX_SECTORS = 2560

만약 상기의 limitation 초과하는 request 발생 時, bio_split()에 의하여 bio 분할.
bio_split()을 통해 첫번째 sector의 bio를 분할하여 반환.

만약, 아래 그림과 같이 split 이전에 bi_idx가 0을 가르키고 있는 상황이라면

새로 생성된 bio의 크기는 bio_vec 0번의 크기와 동일
bio_vec는 이전 bio와 공유하며, bvc_iter를 통해서 구분
bio merge
- Disk 상에서 인접한 bio는 merge 가능
- 일반적으로 bio merge를 통해서 request 횟수를 줄여 성능 향상
- 만약 random access가 훨씬 많은 상황 時, merge 자주 시도하는 것 자체가 성능에 불이익

- Sector의 연결방향에 따라서 2가지 종류의 merge 가능
- Front Merge
- Back Merge

- 먼저 blk_mq_ctx->rq_list 내부에서 merge 시도
- 이후에 IO scheduler를 통한 merge를 시도
- IO Scheduler가 비활성화 상태이면 blk_mq_ctx->rq_lists 내부에서 merge 시도
bio bounce
- IO 대상 Memory가 HIGH_MEM인 경우, DMA 불가
- HIGH_MEM 대상으로 IO를 수행하기 위해 bounce buffer 활용
- bounce buffer 向 Data Copy 後 IO 수행

- bio의 segment 中 HIGH_MEM 존재 시, Bounce Buffer 필요
- HIGH_MEM Page가 존재하고 segment의 갯수가 최대를 초과하면, bio_split() 後 sbumit_bio() 수행
- new bio할당 후, 이전의 관련정보 복사
- bio의 segment에 대하여 각 HIGH_MEM에 대응하는 새로운 bounce page 할당, 만약 write 동작이라면 page 내부의 data 복사 필요
- bio->bi_end_io 등록, read operation 종료 후 원래의 page로 data 복사

get request
- block layer에서 request는 sbitmap을 통한 tag로 관리
- 하나의 tag는 하나의 request에 대응

( sbitmap의 구조 및 원리에 대해서는 다른 글을 통해서 기술하도록 한다. 여기서는, 성능 향상을 위해서 request 할당에 bitmap이 아닌 sbitmap을 사용했다는 내용만 말한다.)
- request를 위한 Memory는 부팅 초반에 할당되며 sbitmap으로 관리
- blk_mq_alloc_tag_set() 함수를 통해서 부팅 초반에 sbitmap 할당
- 해당 함수는 request를 할당하는것 외에도 hctx와 CPU를 mapping하는 기능도 수행
- 각 hctx마다 nr_tags 와 nr_reserved_tags를 이용해서 tag의 get/put 관리

- blk_mq_tag_set : 전역 변수, 각 hctx에 대응되는 blk_mq_tags 관리
- blk_mq_tags->static_rqs : 부팅초반에 준비된 request 관리
- SCSI Command와 각 driver가 사용하게될 private data 포함
- 미리 할당된 request 갯수 = nr_hw_queues * queue_depth
plug & unplug
- blk_plug->mq_list를 통해서 IO 전달되는 경우 PLUG/UNPLUG 방법 활용
- plug -> IO 요청을 수집 -> unplug -> 수집한 IO를 한번에 전달
- plug 시점
- blk_start_plug() : task_struct->blk_plug 초기화
- blk_add_rq_to_plug() : request를 blk_plug->mq_list에 삽입
- 만약 mq_list가 여러개의 request_queue로 구성된 경우 multiple_queues 설정
- unplug 시점
- 3가지 종류의 조건에 의하여 unplug
- 누적된 IO갯수가 BLK_MAX_REQUEST_COUNT를 초과하거나, IO 요청된 크기가 BLK_PLUG_FLUSH_SIZE를 초과하는 경우
- blk_finish_plug()를 호출하는 경우
- scheduler에 의하여 sche_submit_work()가 호출되어 비동기적으로 flush 하는 경우
- unplug core function : blk_flush_plug_list()
- 만약 multiple_queues 설정된 경우, mq_ctx & mq_hctx & sector_number 기준으로 정렬 後 flush
- 동일한 ctx 및 hctx 속하는 IO는 묶어서 blk_mq_sched_insert_requests() 호출
- 3가지 종류의 조건에 의하여 unplug
동기 및 비동기 IO
- 동기 IO
- queue_rq() 함수를 바로 호출하여 IO 수행
- IO 완료까지 대기 후, 다음 IO를 이어서 수행
- 비동기 IO
- blk_mq_hw_ctx->run_work를 통해서 수행
- 여러개의 work를 각 CPU 나누어서 수행
- 각 work는 blk_mq_hw_ctx->cpumask 中 polling 방식으로 선택
- 각 CPU에서 수행되는 work의 갯수 : BLK_MQ_CPU_WORK_BATCH
- run_work는 최종적으로 queue_rq() 호출
- blk_mq_hw_ctx->run_work를 통해서 수행
Direct IO

- q->mq_ops->queue_rq()를 통해서 IO 전달
- System Resource 부족 時 (ex. tag 부족), hctx->dispatch에 삽입
- blk_mq_run_hw_queue() 함수를 통해서 동기식 IO 수행
IO with scheduler

- IO scheduler를 통합 정렬 및 병합이 필요한 경우, blk_mq_sched_insert_request() ghcnf
- 만약, IO scheduler가 없으면 IO를 blk_mq_ctx->rq_lists에 삽입 (각 CPU마다 존재하는 list)
- 최종적으로는 blk_mq_run_hw_queue() 호출
blk_mq_run_hw_queue
- 동기 IO 및 비동기 IO 모두 최종적으로는 blk_mq_run_hw_queue() 함수 호출

- 만약 비동기 IO인 경우, workqueue->run_work 수행
- 동기 IO인 경우, hctx->dispatch 먼저 검사하여 완료되지 못한 IO 먼저 수행
- resource 부족인 경우 request는 dispatch list에 존재
- IO scheduler가 존재하는 경우 scheduler를 통해서 IO 수행
- 만약 scheduler가 없는 경우 ctx->rq_lists 통해서 IO 수행
Core Functions
- blk_mq_dispatch_rq_list(struct blk_mq_hw_ctx *hctx, struct list_head *list, unsigned int nr_budgets)
- 동일한 hctx 內 rq_list의 request 수행
- 자원부족 등으로 인한 실패 時, true return
- IO 완료 時, false return
- blk_mq_do_dispatch_sched(struct blk_mq_hw_ctx *hctx)
- IO scheduler로부터 max_dispatch 갯수의 IO 수행
- 수행되는 IO가 서로 다른 hctx에 속한다면 hctx idx 순서에 따라 IO 수행
- 동일한 hctx에 속한다면 blk_mq_dispatch_rq_list() 함수 호출
- blk_mq_do_dispatch_ctx(struct blk_mq_hw_ctx *hctx)
- Round Robin 방식에 따라서 여러개의 ctx에 속한 IO를 hctx에 전달

IO Complete
- 각각의 IO request에 대해서 완료 時, blk_mq_complete_request() 호출
- IO 완성의 3가지 경로
- 현재의 CPU에서 request 완료
- soft irq를 통한 request 완료
- ipi를 통해서 다른 CPU에서 request 완료
참고 및 출처
- https://blog.csdn.net/flyingnosky/article/details/121341392?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-121341392-blog-121301421.235%5Ev43%5Epc_blog_bottom_relevance_base8&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-121341392-blog-121301421.235%5Ev43%5Epc_blog_bottom_relevance_base8&utm_relevant_index=2
- https://blog.csdn.net/qq_43603125/article/details/136608174
- https://www.bilibili.com/read/cv17063262/
- https://zhuanlan.zhihu.com/p/61123802
- https://blog.csdn.net/weixin_53344209/article/details/130728084
- https://blog.csdn.net/marlos/article/details/133897323
- https://zhuanlan.zhihu.com/p/164884780
- https://zhuanlan.zhihu.com/p/668017683?utm_id=0
- https://blog.csdn.net/flyingnosky/article/details/121301421
- https://kkikyul.tistory.com/44
- https://blog.csdn.net/geshifei/article/details/120590183
- https://blog.csdn.net/zhuzongpeng/article/details/132678480
- https://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/jammy
.
'Kernel > 理解' 카테고리의 다른 글
| Scatter Gather List (0) | 2024.04.10 |
|---|---|
| Device Tree 분석 (Kernel Source) (0) | 2024.02.02 |
| Device Tree 문법 (0) | 2024.02.02 |
| ZRAM 분석 (0) | 2024.02.02 |
| ELF 실행 & execve (0) | 2024.02.02 |