Linux kernel v4.4에서 간단한 블록 장치 드라이버 만들어보기

블럭레이어에서 wait-queue 사용

사실 우리는 wait-queue가 사용되는 코드를 이미 봤었습니다. generic_make_request에서 mybrd 드라이버의 make_request_fn콜백함수를 호출하기전에 blk_queue_enter함수가 있습니다.

blk_qc_t generic_make_request(struct bio *bio)
{
    struct bio_list bio_list_on_stack;
	blk_qc_t ret = BLK_QC_T_NONE;

......
	do {
		struct request_queue *q = bdev_get_queue(bio->bi_bdev);

		if (likely(blk_queue_enter(q, __GFP_DIRECT_RECLAIM) == 0)) {

			ret = q->make_request_fn(q, bio);

			blk_queue_exit(q);
......

blk_queue_enter함수를 보면 다음과 같이 프로세스를 잠재우는 코드가 있습니다.


    	ret = wait_event_interruptible(q->mq_freeze_wq,
				!atomic_read(&q->mq_freeze_depth) ||
				blk_queue_dying(q));

wait_event_interruptible는 매크로함수인데 최종적으로 ___wait_event라는 매크로함수를 호출하게됩니다.

#define wait_event_interruptible(wq, condition)    			\
({									\
	int __ret = 0;							\
	might_sleep();							\
	if (!(condition))						\
		__ret = __wait_event_interruptible(wq, condition);	\
	__ret;								\
})

#define __wait_event_interruptible(wq, condition)    		\
	___wait_event(wq, condition, TASK_INTERRUPTIBLE, 0, 0,		\
		      schedule())

#define ___wait_event(wq, condition, state, exclusive, ret, cmd)    \
({									\
	__label__ __out;						\
	wait_queue_t __wait;						\
	long __ret = ret;	/* explicit shadow */			\
									\
	INIT_LIST_HEAD(&__wait.task_list);				\
	if (exclusive)							\
		__wait.flags = WQ_FLAG_EXCLUSIVE;			\
	else								\
		__wait.flags = 0;					\
									\
	for (;;) {							\
		long __int = prepare_to_wait_event(&wq, &__wait, state);\
									\
		if (condition)						\
			break;						\
									\
		if (___wait_is_interruptible(state) && __int) {		\
			__ret = __int;					\
			if (exclusive) {				\
				abort_exclusive_wait(&wq, &__wait,	\
						     state, NULL);	\
				goto __out;				\
			}						\
			break;						\
		}							\
									\
		cmd;							\
	}								\
	finish_wait(&wq, &__wait);					\
__out:	__ret;								\
})

___wait_event 매크로 함수의 인자는 다음과 같습니다.

  • wq: struct request_queue 구조체의 mq_freeze_wq필드
    • wait_queue_head_t 타입의 객체
  • condition: !atomic_read(&q->mq_freeze_depth) || blk_queue_dying(q)
    • mq_freeze_depth는 request-queue를 사용중인 프로세스의 갯수
    • blk_queue_dying: request-queue를 제거할때 참이됨
    • request-queue가 사용가능할 때 프로세스를 깨움
  • state: TASK_INTERRUPTIBLE
    • 프로세스가 잠들때의 상태
  • exclusive: 0
    • wait-queue의 기본 동작은 자원이 가용해지면 잠들어있는 모든 프로세스를 깨우는 것이지만, exclusive로 셋팅하면 하나의 프로세스만 깨움
  • ret: 0
    • 프로세스가 깨워나면서 반환하는 값
  • cmd: schedule()
    • 프로세스가 잠들때 호출할 함수
    • null_blk은 io_schedule함수를 호출했음

___wait_event코드는 null_blk드라이버에서 이미 본 코드입니다. prepare_to_wait을 호출하고 잠들어야할지 말아야할지 확인한다음 조건에 맞지 않으면 잠들기를 반복하는 것입니다.

request-queue에 mq_freeze_wq 필드는 언제 초기화된걸까요? mq_freeze_wq필드의 초기화도 이미 우리가 알아본 코드에 있습니다. blk_alloc_queue_node는 mybrd에서 request-queue를 초기화할 때 호출한 함수입니다. mq_freeze_wq필드도 당연히 request-queue가 초기화될때 같이 초기화되었을겁니다. blk_alloc_queue_node를 볼까요.


struct request_queue *blk_alloc_queue_node(gfp_t gfp_mask, int node_id)
{
    struct request_queue *q;
	int err;
    ......
    init_waitqueue_head(&q->mq_freeze_wq);

init_waitqueue_head가 호출되는걸 확인할 수 있습니다. init_wait_queue_head는 __init_waitqueue_head의 wrapper입니다.

void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *key)
{
    spin_lock_init(&q->lock);
	lockdep_set_class_and_name(&q->lock, key, name);
	INIT_LIST_HEAD(&q->task_list);
}

구현은 간단합니다. 스핀락을 초기화하고 대기할 프로세스의 리스트를 초기화합니다. lockdep_set_calss_and_name은 블럭장치와 거리가 머니까 생략하겠습니다.

그럼 마지막으로 잠든 프로세스를 깨우는 코드를 찾으면 되겠네요. wake_up등의 함수에서 mq_freeze_wq를 인자로 받는 코드를 추적하면 됩니다. blk_mq_wake_waiters라는 함수와 blk_mq_unfreeze_queue라는 함수 등에서 wake_up_all을 호출합니다.

댓글

댓글 본문
작성자
비밀번호
버전 관리
gurugio
현재 버전
선택 버전
graphittie 자세히 보기