FSTWikiKernel Api1
Login:
Password:
Join
E D R S I H P RSS
FrontPage|FindPage|TitleIndex|RecentChanges

Kernel API 1 - Core(2/2) #


본 장에서 다루는 내용은 다음과 같다.
  • 인터럽트
  • 동기화
  • 비동기
  • Block/Non-Block
  • IDR
  • 연결 리스트
  • 모듈
  • 심볼
  • 블록 디바이스 드라이버
  • 네트워크 드라이버

Contents

1 Kernel API 1 - Core(2/2)
1.1 인터럽트
1.1.1 HARD IRQ
1.1.1.1 자료 구조
1.1.1.2 request_irq
1.1.1.3 free_irq
1.1.1.4 enable_irq
1.1.1.5 disable_irq
1.1.1.6 local_irq_enable
1.1.1.7 local_irq_save
1.1.1.8 local_irq_disable
1.1.1.9 local_save_flags
1.1.1.10 local_irq_restore
1.1.2 SOFT IRQ
1.1.2.1 softirq
1.1.2.2 tasklet
1.1.3 bottom-half
1.2 동기화
1.2.1 Kernel-synchronization
1.2.2 스핀락 (spinlock)
1.2.2.1 spinlock_t
1.2.2.2 SPIN_LOCK_UNLOCKED
1.2.2.3 spin_lock_init
1.2.2.4 spin_lock/spin_unlock
1.2.2.5 spin_lock_irqsave/spin_unlock_irqrestore
1.2.2.6 spin_lock_irq/spin_unlock_irq
1.2.2.7 spin_lock/unlock_bh
1.2.2.8 spin_trylock
1.2.2.9 spin_trylock_bh
1.2.3 rwlock
1.2.3.1 RW_LOCK_UNLOCKED
1.2.3.2 rwlock_init
1.2.3.3 write_lock/write_unlock
1.2.3.4 read_lock/read_unlock
1.2.3.5 read_trylock
1.2.3.6 read_lock_irq/read_unlock_irq
1.2.3.7 write_lock_irq/write_unlok_irq
1.2.3.8 read_lock_irqsave/read_unlock_irqrestore
1.2.3.9 write_lock_irqsave/write_unlock_irqretore
1.2.4 seqlock
1.2.4.1 SEQLOCK_UNLOCKED
1.2.4.2 seqlock_init
1.2.4.3 write_seqlock/write_sequnlock
1.2.4.4 write_tryseqlock
1.2.4.5 read_seqbegin
1.2.4.6 read_seqretry
1.2.5 세마포어(semaphore)
1.2.5.1 세마포어
1.2.5.1.1 down/down_interruptible
1.2.5.1.2 down_trylock
1.2.5.1.3 up
1.2.5.2 이진 세마포어(MUTEX)
1.2.5.2.1 DECLARE_MUTEX
1.2.5.2.2 DECLARE_MUTEX_LOCKED
1.2.5.2.3 init_MUTEX
1.2.5.2.4 init_MUTEX_LOCKED
1.2.5.2.5 down/down_interruptible
1.2.5.2.6 down_trylock
1.2.5.2.7 up
1.2.5.3 rw_semaphore
1.2.5.3.1 init_rwsem
1.2.5.3.2 down_read (down_write)
1.2.5.3.3 up_read (up_write)
1.2.6 뮤텍스(mutext)
1.2.6.1 mutex_init
1.2.6.2 mutex_is_locked
1.2.6.3 mutex_lock
1.2.6.4 mutex_lock_interruptible
1.2.6.5 mutex_trylock
1.2.6.6 mutex_unlock
1.2.7 completion
1.2.7.1 completion 초기화
1.2.7.2 wait_for_completion
1.2.7.3 complete
1.3 비동기
1.3.1 커널 쓰레드
1.3.2 workqueue
1.3.3 시그널
1.3.3.1 signal_pending
1.4 Block/Non-Block
1.4.1 waitqueue
1.4.1.1 DECLARE_WAIT_QUEUE_HEAD
1.4.1.2 init_waitqueue_head
1.4.1.3 wait_event
1.4.1.4 wait_event_timeout
1.4.1.5 wait_event_interruptible
1.4.1.6 wait_event_interruptible_timeout
1.4.1.7 wake_up
1.4.1.8 wake_up_interruptible
1.4.2 async
1.4.2.1 async_helper
1.4.2.2 kill_fasync
1.4.2.3 poll_wait
1.5 IDR
1.5.1 DEFINE_IDR
1.5.2 idr_init
1.5.3 idr_find
1.5.4 idr_pre_get
1.5.5 idr_get_new
1.5.6 idr_remove
1.6 연결 리스트 (Linked List)
1.6.1 연결 리스트의 구조
1.6.2 연결 리스트 API
1.6.2.1 연결 리스트 초기화
1.6.2.1.1 LIST_HEAD_INIT
1.6.2.1.2 LIST_HEAD
1.6.2.1.3 INIT_LIST_HEAD
1.6.2.2 연결 리스트 처리
1.6.2.2.1 list_add_tail
1.6.2.2.2 list_del
1.6.2.2.3 list_empty
1.6.2.2.4 list_entry
1.6.2.2.5 list_for_each
1.6.2.2.6 list_for_each_entry
1.7 모듈
1.7.1 모듈 등록/해제 처리
1.7.1.1 module_init/module_exit
1.7.1.2 devexit_p/exit_p
1.7.2 모듈 파라미터
1.7.2.1 moudle_param
1.7.2.2 MODULE_PARAM_DESC
1.7.3 모듈 심볼
1.7.3.1 symbol_get/put
1.7.4 모듈 참조 카운터
1.7.4.1 try_module_get
1.7.4.2 module_put
1.7.5 THIS_MODULE
1.7.6 모듈 동작 메카니즘
1.8 심볼
1.8.1 EXPORT_SYMBOL
1.8.2 EXPORT_SYMBOL_GPL
1.8.3 EXPORT_NO_SYMBOLS
1.9 블록 디바이스 드라이버
1.9.1 alloc_disk
1.9.2 add_disk
1.9.3 del_disk
1.10 네트워크 드라이버

1.1 인터럽트 #

인터럽트에 대한 함수는 <linux/interrupt.h>에 선언되어 있다.

1.1.1 HARD IRQ #

1.1.1.1 자료 구조 #

인터럽트 핸들러는 irqreturn_t 형의 반환 값을 갖는다.

irqreturn_t형의 반환값설명
IRQ_NONE핸들러가 정상적으로 처리되지 않았다
IRQ_HANDLED핸들러가 정상적으로 처리되었다

1.1.1.2 request_irq #

int request_irq(unsigned int irq,
                irqreturn_t (*handler)(int, void *, struct pt_regs *),
                unsigned long irq_flags, const char *devname, void *dev_id);

인자설명
irq정수형 인터럽트 번호
handler인터럽트 서비스 루틴의 함수 포인터
irq_flags인터럽트 종류
devname인터럽트 이름을 문자열로 명시
dev_id인럽트를 공유하거나 인터럽트 서비스 루틴으로 데이터를 전달하고자 할 때 사용

irq_flags설명
SA_INTERRUPT인터럽트 처리시 다른 인터럽트의 접근을 막는다
SA_SHIRQ인터럽트는 공유한다
SA_SAMPLE_RANDOM랜덤 값을 위한 엔트로피를 증가시킨다

성공시엔 0을 반환하고 그렇지 않은 경우엔 음수를 반환한다.

관련함수: free_irq

1.1.1.3 free_irq #

irq와 dev_id를 통해 등록된 인터럽트 핸들러의 등록을 해제하고 자원을 반환한다.

void free_irq(unsigned int irq, void *dev_id)

1.1.1.4 enable_irq #

irq에 해당하는 인터럽트를 활성화 한다.

void enable_irq(unsigned int irq)

/!\ enable_irq와 이에 대응되는 disable_irq는 반복 실행이 가능한다. 단, disable_irq가 두번 실행이 되었다면 반드시 enable_irq도 두번 실행이 되어야만 인터럽트가 활성화 된다.

관련함수: disable_irq

1.1.1.5 disable_irq #

irq에 해당하는 인터럽트를 비활성화 시킨다.

void disable_irq(unsigned int irq)

/!\ enable_irq와 이에 대응되는 disable_irq는 반복 실행이 가능한다. 단, disable_irq가 두번 실행이 되었다면 반드시 enable_irq도 두번 실행이 되어야만 인터럽트가 활성화 된다.

관련함수: enable_irq

1.1.1.6 local_irq_enable #

해당 프로세서에 한정하여 모든 인터럽트를 활성화한다.

void local_irq_enable(void)

관련함수: local_irq_disable

1.1.1.7 local_irq_save #

flag에 현재 인터럽트의 상태 값을 저장한 후 인터럽트를 금지 시킨다.

void local_irq_save(unsigned long flags)

1.1.1.8 local_irq_disable #

해당 프로세서에 한정하여 모든 인터럽트를 비활성화한다.

void local_irq_disable(void)

관련함수: local_irq_enable

1.1.1.9 local_save_flags #

로컬 IRQ를 비활성화시키기 전에 현 상태를 저장하여 IRQ를 다시 활성화할 때 이를 복원할 필요가 있다. IRQ 관련 현 상태를 저장할 때 사용하는 것이 local_save_flags이다.

void local_save_flags(unsigned long flags)

앞서 설명한 바와 local_save_flags는 혼자 사용되지는 않는다. 보통은 다음과 같이 사용된다.
unsigned long flags;
local_save_flags(flags);
local_irq_disable();
 ...
local_irq_restore(flags);

/!\ 위 처럼 local_save_flags와 local_irq_disable을 사용해야 하는 경우 동일한 기능을 하는 것이 local_irq_save이다. 따라서 local_save_flags는 잘 사용되지 않는다.

관련함수: local_irq_disable, local_irq_restore

1.1.1.10 local_irq_restore #

로컬 인터럽트를 다시 활성화 시킬 때 비활성 시키기 전 상태로 복원할 수 있다. 물론, 그 때의 상태를 알고 있어야 한다. 이런 경우에 사용할 수 있는 것이 local_irq_restore이다. 일반적으로 local_irq_save를 이용한 경우 그 대응되는 함수가 바로 local_irq_restore이다.

void local_irq_restore(unsigned long flags)

관련함수: local_irq_save

1.1.2 SOFT IRQ #

1.1.2.1 softirq #

1.1.2.2 tasklet #

1.1.3 bottom-half #

1.2 동기화 #

1.2.1 Kernel-synchronization #

다음을 클릭한다.

1.2.2 스핀락 (spinlock) #

스핀락과 관련된 것은 <linux/spinlock.h>에 정의되어 있다.

커널 내에서 아주 짧은 기간 동안 다른 것에 방해받지 않고 실행되어야 할 것이 있다면 스핀락을 사용한다.

스핀락은 SMP가 지원되는 경우에 완전한 제 기능을 사용한다. SMP를 지원하지 않는 경우에는 preemtive 기능을 끄거나 irq를 마스킹 하는 정도로 스핀락을 처리한다. SMP를 지원하는 경우에는 앞서 말한 것에 덧붙여 lock이라는 변수를 두어 이 값을 1로 설정한다. SMP를 해제할 때는 0으로 설정하고 irq의 마스킹을 푼다. 마지막으로 preemtive 기능도 다시 활성화한다.

1.2.2.1 spinlock_t #

스핀락의 기본 자료형이다.

1.2.2.2 SPIN_LOCK_UNLOCKED #

스핀락은 반드시 초기화 되어야 한다. 이때 컴파일 시에 정적으로 초기화 되는 경우에 다음과 같이 한다.
spinlock_t xxx_lock = SPIN_LOCK_UNLOCKED;

관련 함수: spin_lock_init

1.2.2.3 spin_lock_init #

<linux/spinlock.h>에 선언되어 있다.

스핀락은 반드시 초기화 되어야 한다. 이때 실행시에 동적으로 초기화 되는 경우에는 spin_lock_init()를 사용한다.

void spin_lock_init(spinlock_t *lock);

관련 함수: SPIN_LOCK_UNLOCKED

1.2.2.4 spin_lock/spin_unlock #

스핀락을 걸 때는 spin_lock을 사용한다.
void spin_lock(spinlock_t *lock);

스핀락을 해제할 때는 spin_lock을 사용한다.
void spin_unlock(spinlock_t *lock);

스핀락은 매우 짧은 시간동안 락을 걸기 위한 것이다. 긴 시간동안 락을 걸 필요가 있다면 mutex나 semaphore와 같은 방법을 사용해야 한다. 단, 스핀락은 인터럽트 핸들러와 같이 휴면해서는 안되는 경우에도 사용할 수 있지만, mutex나 semaphore는 휴면할 수 있기에 인터럽트 핸들러와 같은 경우엔 사용할 수 없다.

1.2.2.5 spin_lock_irqsave/spin_unlock_irqrestore #

<linux/spinlock.h>에 선언되어 있다.

인터럽트를 마스킹하는 스핀락의 경우 스핀락을 걸기 전의 인터럽트 상태를 기억하지 않는다. 이럴 경우 스핀락을 해제하는 경우에 인터럽트를 무조건 활성화 시키는 결과를 가져와 스핀락을 걸기 전의 상태와 일치하지 않는 경우가 생길 수 있다.

이러한 문제를 해결하기 위해 커널에서는 스핀락을 거는 시점에 현 상태를 기억하여 스핀락을 해제시에 이를 다시 복원하기 위한 함수를 제공하고 있다.

void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);

void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);

다음은 사용에 대한 간단한 예이다.
unsigned long flags;

spin_lock_irqsave(&foo_lock, flags);

// critical jobs

spin_unlock_irqrestore(&foo_lock, flags);
foo_lock은 spin_lock_init()을 통해 초기화 되어 있거나 정적으로 SPIN_LOCK_UNLOCKED로 초기화 되어 있어야만 한다.

1.2.2.6 spin_lock_irq/spin_unlock_irq #

preemtive기능을 끄고 irq를 마스킹 시킨다. SMP를 지원한다면 lock을 설정한다.
void spin_lock_irq(spinlock_t *lock);

preemtive기능을 켜고 irq의 마스킹을 해제한다. SMP를 지원한다면 lock을 해제한다.
void spin_unlock_irq(spinlock_t *lock);

1.2.2.7 spin_lock/unlock_bh #

void spin_lock_bh(spinlock_t *lock);

void spin_unlock_bh(spinlock_t *lock);

1.2.2.8 spin_trylock #

int spin_trylock(spinlock_t *lock);

1.2.2.9 spin_trylock_bh #

int spin_trylock_bh(spinlock_t *lock);

1.2.3 rwlock #

<linux/spinlock.h>에 선언되어 있다.

1.2.3.1 RW_LOCK_UNLOCKED #

1.2.3.2 rwlock_init #

1.2.3.3 write_lock/write_unlock #

1.2.3.4 read_lock/read_unlock #

1.2.3.5 read_trylock #

1.2.3.6 read_lock_irq/read_unlock_irq #

1.2.3.7 write_lock_irq/write_unlok_irq #

1.2.3.8 read_lock_irqsave/read_unlock_irqrestore #

1.2.3.9 write_lock_irqsave/write_unlock_irqretore #

1.2.4 seqlock #

<linux/seqlock.h>에 선언되어 있다.

1.2.4.1 SEQLOCK_UNLOCKED #

1.2.4.2 seqlock_init #

1.2.4.3 write_seqlock/write_sequnlock #

1.2.4.4 write_tryseqlock #

1.2.4.5 read_seqbegin #

1.2.4.6 read_seqretry #

1.2.5 세마포어(semaphore) #

1.2.5.1 세마포어 #

<linux/asm/semaphore.h> ===== sema_init ====
1.2.5.1.1 down/down_interruptible #
1.2.5.1.2 down_trylock #
1.2.5.1.3 up #

1.2.5.2 이진 세마포어(MUTEX) #

<linux/asm/semaphore.h>
1.2.5.2.1 DECLARE_MUTEX #
1.2.5.2.2 DECLARE_MUTEX_LOCKED #
1.2.5.2.3 init_MUTEX #
1.2.5.2.4 init_MUTEX_LOCKED #
1.2.5.2.5 down/down_interruptible #
1.2.5.2.6 down_trylock #
1.2.5.2.7 up #

1.2.5.3 rw_semaphore #

1.2.5.3.1 init_rwsem #
<linux/rwsem.h>

void init_rwsem(struct rw_semaphore *sem);
1.2.5.3.2 down_read (down_write) #
읽기(쓰기) 락을 건다. <linux/rwsem.h>에 인라인 함수로 정의되어 있다.

inline void down_read(struct rw_sempahore *sem);

inline void down_write(struct rw_semaphore *sem);
1.2.5.3.3 up_read (up_write) #
읽기(쓰기) 락을 푼다. <linux/rwsem.h>에 인라인 함수로 정의되어 있다.

inline void up_read(struct rw_semaphore *sem);

inline void up_write(struct rw_semaphore *sem);

1.2.6 뮤텍스(mutext) #

/!\ 다음 내용은 커널 2.6.16부터 추가된 내용이다.

<linux/mutext.h>에 선언되어 있다.

뮤텍스를 사용하기 위해서는 struct mutex를 선언하고 mutex_init을 통해 초기화를 해야 한다.

1.2.6.1 mutex_init #

<linux/mutex.h>에 선언되어 있다.

뮤텍스를 사용하기 위해서는 struct mutex를 선언하고 mutex_init을 통해 초기화를 해야 한다.

mutex_init(struct mutex *lock);

1.2.6.2 mutex_is_locked #

<linux/mutex.h>

뮤텍스의 lock 여부를 여부를 판별한다.

int mutex_is_locked(struct mutex *lock);

리턴값이 1이면 뮤텍스는 locked 상태이며, 0이면 unlocked 상태이다.

1.2.6.3 mutex_lock #

1.2.6.4 mutex_lock_interruptible #

1.2.6.5 mutex_trylock #

1.2.6.6 mutex_unlock #

1.2.7 completion #

작업 중 어떤 작업이 완료 되기를 기다릴 필요가 있을 수 있다. 이런 경우에 간단히 사용할 수 있는 것이 completion 이다.

completion에 대한 내용은 <linux/completion.h>에 선언되어 있다. completion 관련 함수들에서 공통적으로 사용하는 구조체가 completion 구조체이다. 그 원형은 다음과 같다.
struct completion {
  unsigned int done;
  wait_queue_head_t wait;
};
위 구조체에 대한 내부 필드에 대해 알 필요는 없다. 필요한 것은 그 구조체 자체이다.

1.2.7.1 completion 초기화 #

completion 구조체를 초기화 하는 방법은 두 가지가 있다. 컴파일시에 정적으로 선언하여 초기화를 하는 경우와 실행시에 동적으로 선언하여 초기화하는 방법이다.

전자의 경우엔 DECLARE_COMPLETION 매크로를 사용한다. 사용 방법은 다음과 같다.
DELCARE_COMPLETION(xxx_completion);
xxx_completion은 completion 구조체로 이름만 넘겨주면 DECLARE_COMPLETION 내에서 구조체를 선언한다.

후자의 경우 동적으로 초기화 하는 경우엔 init_completion을 사용한다. 그 원형은 다음과 같다.
void init_completion(struct completion *x);
이 경우엔 completion 구조체를 선언하고 init_completion을 통해 초기화를 해 준다.
static void __init xxx_init(void)
{
  struct completion xxx_com;
  ...

  init_completion(&xxx_com);
  ...
}

1.2.7.2 wait_for_completion #

완료 이벤트를 받을 때까지 대기 상태로 있기 위하여 wait_for_completion 함수를 사용한다. 대기 중에 시그널을 받아 깨어날 수도 있고(interruptible), 만료 시간을 두어 그동만 대기하도록 할 수도 있다(timeout).

void wait_for_completion(struct completion *x);
void wait_for_completion_interruptible(struct completion *x);
void wait_for_completion_timeout(struct completion *x, unsigned long timeout);
void wait_for_completion_interruptible_tiemout(struct completion *x,
                                               unsigned long timeout);

대기는 중첩해서 할 수 있으며 모든 대기가 풀려야만 대기가 풀리게 된다.

1.2.7.3 complete #

완료 이벤트를 보내기 위해서는 complete 함수를 실행하면 된다. 만약, 현재 중첩된 모든 대기 상태를 풀려면 complete_all를 사용하면 된다.

void complete(struct completion *x);
void complete_all(struct completion *x);

1.3 비동기 #

1.3.1 커널 쓰레드 #

경우에 따라서는 현재 커널의 흐름과 달리 실행되야 할 것들이 있다. 일반 프로세스와 달리 커널 모드에서 실행되어야 하는 경우 커널 쓰레드를 이용할 수 있다.

커널 쓰레드는 <asm/processor.h>에 선언되어 있으며, 그 원형은 다음과 같다:
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags);

인자설명
fn커널 쓰레드를 정의하는 함수 포인터
arg커널 쓰레드를 정의하는 함수에 넘겨줄 인자
flags커널 쓰레드의 속성을 지정한다. 일반적으로 CLONE_KERNEL 을 지정한다

flags에 대한 더 자세한 정보는 <linux/sched.h>를 참고하라.

1.3.2 workqueue #

1.3.3 시그널 #

커널에서 호출한 프로세스로 시그널을 전달할 수 있다. 시그널 호출 시 전달되는 데이터는 siginfo 구조체로 정의되어 있다.


다음은 include/asm-generic/siginfo.h에 정의되어 있는 내용 중 일부이다.
typedef struct siginfo {
  int si_signo;
  int si_errno;
  int si_code;

  union {
    ...
  } _sifields;
} siginfo_t;

#define si_pid   _sifields._kill._pid
...
#define si_int   _sifields._rt._sigval.sival_int
...

사용할 수 있는 함수들은 다음과 같다:

recalc_sigpendign
dequeue_signal
flush_signals
force_sig
kill_pg
kill_proc호출한 프로세스(pid)에 signal을 보낸다
ptrace_notify
send_sig
send_sig_infosiginfo의 내용을 보내려면 이 함수를 이용한다
sigprocmask
block_all_signals
unblock_all_signals


시그널 함수 중 kill_proc()함수는 다음과 같이 이용할 수 있다.
kill_proc(cpid, SIGINT, 1);
프로세스(cpid)에 ?SIGUSR2 시그널을 보낸다. 보내는 것에 실패한다면 SIGINT를 보내게 된다.


send_sig_info()를 이용하는 경우는 다음과 같다:
struct siginfo info;

info.si_signo = SIGUSR2;
info.si_errno = 0;
info.si_code = SI_QUEUE;
info.si_int = flags;

if (send_sig_info(SIGUSR2, &info, p))
  send_sig_info(SIGINT, (struct siginfo *)1, p);
여기서 p는 struct task_struct형의 포인터이다.

1.3.3.1 signal_pending #

프로세스에 대해 발생한 시그널이 있는지 확인한다. 이는<linux/sched.h>에 인라인 함수로 정의되어 있다.
inline int signal_pending(struct task_struct *p)

시그널이 펜딩되어 있다면 1을 반환하고 그렇지 않다면 0을 반환한다.

1.4 Block/Non-Block #

1.4.1 waitqueue #


1.4.1.1 DECLARE_WAIT_QUEUE_HEAD #

1.4.1.2 init_waitqueue_head #

1.4.1.3 wait_event #

<linux/wait.h>

wait_event(wait_queue_head wq, condition);

1.4.1.4 wait_event_timeout #

<linux/wait.h>

signed long wait_event_timeout(wait_queue_head_t wq, condition,
                               signed long timeout);
timeout은 jiffies 값이다.

시간이 만기되면 0을 리턴하고 시간이 만기되기 전에 조건(condition)을 만족하면 남은 jiffies값을 리턴한다.

1.4.1.5 wait_event_interruptible #

<linux/wait.h>

int wait_event_interruptbile(wait_queue_head_t wq, condition);

시그널로 인해 중단되면 -ERESTARTSYS를 리턴하고 조건(condition)을 만족하면 0을 리턴한다.

1.4.1.6 wait_event_interruptible_timeout #

<linux/wait.h>

signed int wait_event_interruptible_timeout(wait_queue_head_t wq, condition,
                                            signed long timeout);

시간이 만기되면 0을 리턴하고, 시그널로 중단되면 -ERESTARTSYS를 반환한다. 조건을 만족하면 남은 jiffies 값을 반환한다.

1.4.1.7 wake_up #

<linux/wait.h>

wake_up(wait_queue_head_t *wq);

1.4.1.8 wake_up_interruptible #

<linux/wait.h>

wake_up_interruptible(wait_queue_head_t *wq);

1.4.2 async #

1.4.2.1 async_helper #

1.4.2.2 kill_fasync #

1.4.2.3 poll_wait #

1.5 IDR #

커널에서는 id를 관리하기 위한 함수를 제공한다.

<linux/idr.h>

1.5.1 DEFINE_IDR #

<linux/idr.h>에 선언되어 있다.

컴파일시에 정적으로 idr 구조체인 name을 선언한다.

DEFINE_IDR(name)

1.5.2 idr_init #

<linux/idr.h>에 선언되어 있다.

실행 중에 idr을 초기화하는 경우 idr_init을 사용한다.
void idr_init(struct idr *idp);

1.5.3 idr_find #

<linux/idr.h>에 선언되어 있다.

void *idr_find(struct idr *idp, int id);

1.5.4 idr_pre_get #

<linux/idr.h>에 선언되어 있다.

int idr_pre_get(struct idr *idp, gfp_t gfp_mask);

다음은 mmc 드라이버(drivers/mmc/mmc_sysfs.c)에서 사용한 예이다.
int err;

if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL))
  return -ENOMEM;

spin_lock(&mmc_host_lock);
err = idr_get_new(&mmc_host_idr, host, &host->index);
spin_unlock(&mmc_host_lock);

1.5.5 idr_get_new #

<linux/idr.h>에 선언되어 있다.

int idr_get_new(struct idr, *idp, void *ptr, int *id);

다음은 mmc 드라이버(drivers/mmc/mmc_sysfs.c)에서 사용한 예이다.
int err;

if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL))
  return -ENOMEM;

spin_lock(&mmc_host_lock);
err = idr_get_new(&mmc_host_idr, host, &host->index);
spin_unlock(&mmc_host_lock);

1.5.6 idr_remove #

<linux/idr.h>에 선언되어 있다.

void idr_remove(struct idr *idp, int id);

다음은 mmc 드라이버(drivers/mmc/mmc_sysfs.c)에서 사용한 예이다.
spin_lock(&mmc_host_lock);
idr_remove(&mmc_hsot_idr, host->index);
spin_unlock(&mmc_host_lock);

1.6 연결 리스트 (Linked List) #

리눅스 커널 소스 내에서 공통적으로 많이 사용되는 것 중 하나가 연결 리스트이다. 거의 모든 자료 구조를 표현 관리하는데, 있어서 연결 리스트를 빼놓고는 설명할 수 없다.

연결 리스트에 대한 것은 <linux/list.h> 파일에 정의되어 있다. 이를 기준으로 정리한다.

1.6.1 연결 리스트의 구조 #

리눅스 커널 내에서 사용하는 연결 리스트는 기본적으로 circular linked list 구조를 갖는다. 즉, 순환형 구조의 연결 리스트이다.

시작과 끝이 연결 되어 있으며, 이 순환 구조 상에 엔트리가 추가되는 식으로 되어 있다.

리스트의 기본 자료형은 struct list_head이다.
struct list_head {
  struct list_head *next, *prev;
};

1.6.2 연결 리스트 API #

함수설명
LIST_HEAD_INIT
LIST_HEAD
INIT_LIST_HEAD실행 시에 리스트 헤더를 초기화 한다.
list_add_tail
list_del
list_empty
list_entry리스트 엔트리의 포인터를 포함하는 container의 포인터를 가져온다.
list_for_each리스트 헤더에서 순차적으로 리스트 엔트리의 포인터를 가져온다.
list_for_each_entry

1.6.2.1 연결 리스트 초기화 #


1.6.2.1.1 LIST_HEAD_INIT #
LIST_HEAD_INIT(name);
1.6.2.1.2 LIST_HEAD #
<linux/list.h>

list_head 구조체형의 name을 선언하고 이를 초기화한다. 이는 컴파일 시에 정적으로 리스트 헤드를 만들 때 사용한다.

LIST_HEAD(name);
1.6.2.1.3 INIT_LIST_HEAD #
<linux/list.h>

실행시에 리스트 헤더를 초기화 한다.

INIT_LIST_HEAD(struct list_head *ptr);

1.6.2.2 연결 리스트 처리 #

1.6.2.2.1 list_add_tail #
head 리스트에 new entry를 추가한다.

void list_add(struct list_head *new, struct list_head *head);
1.6.2.2.2 list_del #
리스트에서 entry를 삭제한다.
void list_del(struct list_head *entry);
1.6.2.2.3 list_empty #
비어 있는 리스트인지 테스트한다. 만약 비어 있는 리스트라면 1을 반환하고 그렇지 않다면 0을 반환한다.
int list_empty(const struct list_head *head);
1.6.2.2.4 list_entry #
명시한 list_head 포함(ptr)하는 container에 해당하는 구조체를 가져온다.
list_entry(ptr, type, member);
ptr은 list_head 형의 포인터 값이며, 이를 포함하는 container의 type에서 list_struct의 이름이 member이다.

예시: 다음은 drivers/block/genhd.c의 part_start()함수의 일부 내용이다.
struct list_head *p;
loff_t l = *pos;
...
list_for_each(p, &block_subsys.kset.list)
  if (!l--)
    return list_entry(p, struct gendisk, kobj.entry);
...

리턴 값은 gendisk형에 대한 포인터이다.
1.6.2.2.5 list_for_each #
리스트를 순차적으로 탐색한다.

list_for_each(pos, head);

head는 탐색하고자 하는 리스트의 포인터이고 각 엔트리는 pos를 통해 반환받는다.
1.6.2.2.6 list_for_each_entry #
리스트에서 순차적으로 엔트리를 반환받는다.
list_for_each_entry(pos, head, member)

예시:
drivers/pci/bus.c의 pci_bus_devices함수의 일부 내용이다.
list_for_each_entry(dev, &bus->devices, bus_list) {
  if (!list_entry(&dev->global_list))
    continue;
  pci_bus_add_device(dev);
}

1.7 모듈 #

모듈에 관한 자료 구조 및 함수들의 정의하는 <linux/module.h>에 있다.

1.7.1 모듈 등록/해제 처리 #

1.7.1.1 module_init/module_exit #

모듈의 등록 및 해제 시에 사용하는 매크로는 <linux/init.h>에 선언되어 있다.

모듈을 등록할 때는 다음과 같이 선언 한 후 module_init 매크로를 통해 모듈을 등록하게 된다.
static int __init xxx_init(void)
{
  ...
}

모듈을 해제할 때는 다음과 같이 선언 한 후 module_exit 매크로를 통해 모듈을 해제하게 된다.
static void __exit xxx_exit(void)
{
  ...
}

이후 다음과 같이 매크로를 선언해 주면 된다.
module_init(xxx_init);
module_exit(xxx_exit);

모듈의 등록 함수 및 해제 함수를 보면 __init__exit라는 것이 있다. 전자는 함수가 호출된 이후에는 다시 사용할 필요가 없다. 따라서 함수에 대한 정보를 유지할 필요가 없음을 알려 메모리 사용의 효율성을 높이기 위한 것이다. 후자는 별 의미는 없고 단지 __init과 대응하기 위하여 선언한 것 뿐이다.

1.7.1.2 devexit_p/exit_p #

모듈시에만 그 선언이 유효하고 그렇지 않은 경우에는 NULL로 선언할 필요가 있다. 이런 경우를 위한 매크로가 바로 __devexit_p__exit_p이다. 일반적으로 이들은 다바이스의 등록을 해제하거나 드라이버의 등록을 해제할 경우에 모듈일 경우에만 그것이 유효하고 그렇지 않고 커널에 포함된 경우엔 해제될 경우가 없을 경우에 사용된다.

1.7.2 모듈 파라미터 #

1.7.2.1 moudle_param #

모듈 적재시 파라미터를 모듈에 넘길 수 있다. 이와 관련된 것들은 <linux/moduleparam.h>에 선언되어 있다.

module_param(name, type, perm)

type설명
byteunsigned char
shortshort
ushortunsigned short
intint
uintunsigned int
longlong
ulongunsigned long
charpchar *형으로 문자열을 갖는다.
boolint형으로 0(거짓)과 0이 아닌 값(참)을 취한다
invboolint형으로 Not(!)을 취한 값이다

perm은 8진수로 chmod에서 사용하는 퍼미션 포맷을 갖는다.

1.7.2.2 MODULE_PARAM_DESC #

MODULE_PARAM_DESC(name, const char * desc)

사용 예는 다음과 같다:
static int nrpacks = USX2Y_NRPACKS;

module_param(nrpacks, int , 0444);
MODULE_PRAM_DESC(nrpacks, "Number of packets per URB.");

1.7.3 모듈 심볼 #

1.7.3.1 symbol_get/put #

모듈에서 심볼을 가져오는데, 경우에 따라서는 가져오는 심볼이 다른 모듈에서 제공하는 것일 수 있다. 이런 경우 그 모듈이 등록 중이며 초기 루틴의 실행이 완료되지 않은 상태라면 해당 심볼을 사용하더라도 정상적으로 동작하지 않을 수 있다. 이러한 경우를 피하기 위하여 그 모듈이 완벽히 동작 가능한 상태임을 보장 받고 이때 심볼을 참조할 수 있도록 하기 위하여 커널은 특별한 API를 제공한다.

void *symbol_get(const char *symbol);
void symbol_put(void char *symbol);

정상적으로 심볼을 사용할 수 있다면 그 심볼의 주소 값을 리턴한다. 이때 내부적으로 해당 심볼이 정의된 모듈의 사용 회수를 하나 증가 시킨다. 만약, 심볼이 유효하지 않다면 NULL을 리턴한다.

반대로 symbol_put은 사용중인 심볼을 반환할 때 사용하는 것으로 해당 심볼이 정의된 모듈의 사용 회수를 하나 감소 시킨다.

사용예는 다음과 같다:
/* get the symbol */
int (*del_bus)(struct i2c_adapter *) = symbol_get(i2c_bit_del_bus);

/* put the symbol */
symbol_put(i2c_bit_del_bus);

1.7.4 모듈 참조 카운터 #

모듈의 사용 여부를 나타내는 방법 중 하나가 모듈의 참조 카운터를 이용하는 것이다. 일반적으로 커널에서 제공하는 드라이버 API 들은 모듈 참조 카운터를 내부적으로 처리한다.

그러나 직접 드라이버 모델을 만드는 경우 모듈의 참조 카운터를 처리할 필요가 있다. 다음은 이때 사용할 수 있는 API들이다.

1.7.4.1 try_module_get #

모듈의 참조 카운터 값을 하나 증가 시킨다.

함수 원형은 다음과 같다:
int try_module_get(struct module *module)

1.7.4.2 module_put #

모듈의 참조 카운터 값을 하나 감소 시킨다.

함수 원형은 다음과 같다:
void module_put(struct module *module)

1.7.5 THIS_MODULE #

struct module 형의 __this_module에 대한 매크로이다. 이에 대한 것은 모듈 컴파일 시에 생성되는 파일에서 정의가 된다.

1.7.6 모듈 동작 메카니즘 #

드라이버를 작성하는데는 별로 중요하지 않은 모듈 자체의 동작 방식에 대해 일부 기술하고자 한다.

하나의 모듈은 struct module이라는 구조체에 의해 기술된다. 이 구조체의 한 필드 중 state에 현 모듈의 상태 값을 갖고 있다. <linux/module.h>에 현 모듈의 상태를 정의하기 위한 값이 정의되어 있다.
enum module_state
{
   MODULE_STATE_LIVE,
   MODULE_STATE_COMING,
   MODULE_STATE_GOING,
};

모듈 상태값설명
MODULE_STATE_LIVE현재 모듈 초기화가 이뤄지고 정상적으로 동작하고 있는 상태
MODULE_STATE_COMING현재 모듈이 등록되는 과정중에 있는 상태
MODULE_STATE_GOING현재 모듈이 해제되는 과정중에 있는 상태

이들 모듈 상태는 내부적으로 사용된다. 특히 symbol_get과 같이 현재 모듈이 정상적으로 동작중인 경우를 확인하고 심볼 정보를 가져오자 할 때 유용하게 사용된다.

모듈 기술자에는 모듈에서 사용할 수 있는 노출된(exported) 심볼 정보를 갖고 있다. syms와 gpl_syms가 바로 심볼 정보를 갖고 있다. 특히 gpl_syms는 모듈 자신의 라이선스가 GPL 또는 그와 동등한 라이선스 일 때만 사용할 수 있는 심볼 정보를 갖고 있다. 만약, 자신의 라이선스가 GPL 또는 그와 동등한 라이선스가 아니라면 커널에서는 심볼 정보를 모듈에서 사용하는 것을 허용하지 않는다.

1.8 심볼 #

커널 내의 한 파일에서 정의한 심볼(함수 및 변수)은 외부에서 사용하기 위해서는 특별한 매크로를 사용하여 외부에 노출을 시켜야 한다. 특히 모듈 내의 심볼을 외부에서 사용하기 위해서는 꼭 매크로를 사용하여 노출을 시켜줘야만 한다. 이를 통해 커널 내의 다른 파일 또는 모듈에서 심볼을 사용할 수 있다.

1.8.1 EXPORT_SYMBOL #

명시적으로 지정하는 심볼을 외부에 노출하도록 한다. 이렇게 노출된 심볼은 커널(모듈) 어디에서도 사용할 수 있다.

선언은 <linux/module.h>에 있다.

foobar라는 심볼을 외부에 노출 시키고자 한다면, 그 사용 예는 다음과 같다.
EXPORT_SYMBOL(foobar);

1.8.2 EXPORT_SYMBOL_GPL #

리눅스 커널은 기본적으로 GPL v2 라이선스 하에서 배포가 이뤄지고 있다. 일부 코드는 아닐 수도 있지만 거의 대체로 그렇다는 것이다. 경우에 따라서는 더 엄격하게 GPL를 따를 것을 선언하고 있으며, 일부 심볼들은 GPL을 따르는 경우에만 사용할 수 있도록 강제하고 있다. 바로 이러한 것을 강제하는 것이 EXPORT_SYMBOL_GPL인 것이다.

선언은 <linux/module.h>에 있다.

사용법은 EXPORT_SYMBOL과 동일하다.

1.8.3 EXPORT_NO_SYMBOLS #

경우에 따라서는 모듈이 어떤 심볼도 공개하지 않을 수도 있다. 이러한 경우를 위하여 모듈 내의 어디에든 EXPORT_NO_SYMBOLS; 를 선언하기만 하면 된다.

선언은 <linux/module.h>에 있다.
EXPORT_NO_SYMBOLS;

1.9 블록 디바이스 드라이버 #

블록 디바이스 드라이버는 앞서 설명한 문자 디바이스 드라이버와 비교할 때 다소 다른 형태로 구현이 된다.

문자 디바이스 드라이버의 경우엔 데이터의 접근이 순차적으로 이뤄지는 반면에 블록 디바이스는 마운트라는 것을 이뤄진 후 데이터의 접근이 가능하며, 이때 데이터의 접근이 순차적으로 이뤄지는 것이 아니라 geo-metry 정보를 통해 비 순차적으로(랜덤하게) 이뤄진다.

이런 이유로 문자 디바이스 드라이버에 비해 내부 구현이 다소 복잡하고 이를 이용한 디바이스 드라이버 역시 다소 복잡하다.

블록 디바이스는 하나의 디스크가 있으며 이러한 디스크 상에는 하나 이상의 파티션을 갖고 있을 수 있다. 한 디스크에 속한 파티션은 동일한 주번호를 갖게 되며(결국 이 주번호가 디스크의 디바이스 번호가 된다), 각 파티션은 서로 다른 부번호를 갖게 된다. 이를 통해 각 파티션을 구분하게 된다.


블록 디바이스 드라이버의 주요 데이터 구조체 중 하나가 gendisk이다. 이는 <linux/genhd.h>에 정의되어 있다.
struct gendisk {
        int major;                      /* major number of driver */
        int first_minor;
        int minors;                     /* maximum number of minors, =1 for
                                         * disks that can't be partitioned. */
        char disk_name[32];             /* name of major driver */
        struct hd_struct **part;        /* [indexed by minor] */
        struct block_device_operations *fops;
        struct request_queue *queue;
        void *private_data;
        sector_t capacity;

        int flags;
        char devfs_name[64];            /* devfs crap */
        int number;                     /* more of the same */
        struct device *driverfs_dev;
        struct kobject kobj;
        ...
};

위 데이터 구조체는 그 내용의 일부만을 표현한 것이다. 여기서 중요한 것은 major, first_minor, minors, part 정도가 될 것이다. 하나의 디스크가 파티션을 갖고 있지 않다면(엄밀히 말하면 하나의 파티션만을 갖고 있는 것이다), minors는 1이 된다. 즉 minors는 부번호의 개수, 다른 말로 하면 파티션의 개수를 의미한다. major는 디스크 및 이에 속한 파티션의 주번호가 된다. first_minor는 부번호의 시작 값이다. 파티션이 존재한다면 각 파티션은 part를 통해 기술되게 된다.

그외 필드들도 중요하지만 여기서는 이정도만 설명하고 넘어가겠다.

블록 디바이스 드라이버에서 사용하는 대표적인 함수는 다음과 같다.
  • add_disk
  • del_gendisk
  • alloc_disk

1.9.1 alloc_disk #

struct gendisk *alloc_disk(int minors);

1.9.2 add_disk #

void add_disk(struct gendisk *disk);

1.9.3 del_disk #

void del_gendisk(struct gendisk *disk);

1.10 네트워크 드라이버 #


last modified 2006-05-09 11:06:10
EditText|FindPage|DeletePage|LikePages Valid XHTML 1.0! Valid CSS! powered by MoniWiki
0.2800 sec