[Linux] Device driver 동시성 관련 함수 – Semaphore & Mutex

동시성 관련 함수에 대해서 다시 한번 정리하고 가자. Linux device driver 3판, 5장. “동시성과 경쟁 상태” 를 기준으로 정리한다.

공유 자원 접근을 위한 Lock 을 위해서 다음과 같은 것들을 사용한다.

1. 세마포어와 뮤텍스
Critical Section을 정의하기 위해서 세마포어를 사용한다. 일반적으로 P와 V 함수 쌍을 사용하는데, linux 에서는 P함수는 “down”, V 함수를 “up”이라 부른다. 단일 세마포어(공유 자원 개수를 1개로 정의)로 사용할 때 뮤텍스(Mutual Exclusion)라 부른다.

1.1. 세마포어 초기화
<asm/semaphore.h> 를 포함하여야 한다. 관련 Type은 struct semaphore.
1.1.1. 세마포어로 사용할 때 초기화


void sema_init(struct semaphore *sem, int val);

val 은 세마포어에 할당할 초기값.

1.1.2. Mutex로 사용할 때 초기화
정적 초기화


DECLARE_MUTEX(name); // 1로 초기화.
DECLARE_MUTEX_LOCKED(name); // 0으로 초기화.

실행 중 초기화


void init_MUTEX(struct semaphore *sem); // 1로 초기화.
void init_MUTEX_LOCKED(struct semaphore *sem); // 0으로 초기화


1.2. 세마포어 획득하기
세마포어 값을 감소시키고 필요한 만큼 기다린다.


void down(struct semaphore *sem);


세마포어 값을 감소시키고 필요한 만큼 기다리지만, 인터럽트 가능하다. 인터럽트를 받으면 0이 아닌 값을 반환하고, 세마포어를 쥐고 있지 않는다. 때문에 항상 반환값을 확인하여야 한다.


int down_interuptible(struct semaphore *sem);


세마포어를 획득할 수 없다면 바로 0이 아닌 값을 반환한다.


int down_ttylock(struct semaphore *sem);


1.3. 세마포어 반환


void up(struct semaphore *sem);

2. 읽기/쓰기 세마포어
읽기만 수행하는 스레드라면 여럿이 함께 접근해도 된다. 이럴 때 rwsem 이라는 특수 세마포어를 이용한다.
rwsem 을 사용하면 쓰기 스레드 하나가 잡고 있던가 읽기 스레드 여럿이 잡고 있던가 둘 중에 하나가 되는데 우선순위는 쓰기 스레드에게 있다. 쓰기 스레드가 임계구역에 접근하는 순간, 읽기 스레드는 모든 쓰기 스레드가 작업을 끝낼 때까지 기다려야 한다. 그래서 쓰기 스레드가 많을 경우 읽기 스레드가 오랫동안 접근 권한을 얻지 못할 수 있다. 따라서 쓰기 접근이 매우 드물고, 짧은 시간 동안에만 필요한 경우에 적당하다.

2.1. 읽기/쓰기 세마포어 초기화
<linux/resem.h> 를 포함하여야 한다. 관련 타입은 struct rw_semaphore. 런타임에 명시적으로 초기화되어야 한다.


void init_rwsem(struct rw_semaphore *sem);

2.2. 읽기 전용 세마포어 사용
읽기 전용 접근 권한을 제공한다. 다른 읽기 스레드와 동시 참조가 가능하다. 호출 프로세스를 D 상태(인터럽트가 불가능한 잠자기 상태)로 빠뜨릴 수 있다는 사실에 주의한다.


void_down_read(struct rw_semaphore *sem);

읽기를 수행할 수 없을 경우 기다리지 않는다. 접근이 가능하다면 0 이 아닌 값을, 이외에는 0을 반환한다. 다른 커널 함수는 대부분 성공일 때 0을 반환하지만 down_read_ttylock은 반대다.


int down_read_ttylock(struct rw_semaphore *sem);

읽기 전용 세마포어 해제


void up_read(struct rw_semaphore *sem);


2.2. 쓰기 전용 세마포어 사용
down_read 와 동일


void down_write(struct rw_semaphore *sem);

down_read_ttylock 과 동일


int down_write(struct rw_semaphore *sem);

up_read 와 동일


void up_write(struct rw_semaphore *sem);

잠시만 쓰기 락을 걸어 수정하고 한동안은 읽기 권한만 필요하다면


void downgrade_write(struct rw_semaphore *sem);

 

register_chrdrv()를 리눅스 커널 2.6 버전용으로 바꾸기

Linux Device Driver 중 Character device 의 코드를 보면 기존의 2.6 기준으로 수정되지 않은 코드들은 register_chrdrv() 함수를 사용하여 Character device를 등록하도록 되어 있다. register_chrdrv 함수의 원형은 다음과 같다.

int register_chrdev(unsigned int major, const char *name,
      const struct file_operations *fops)

그러나 2.6의 디바이스 드라이버들은 이와 다른 방식을 사용한다. register_chrdrv를 사용하는 방식은 Linux device driver 개정 3판을 보면 3장의 “예전 방식”이라는 부분에서 이를 다룬다. 예전 방식이 아닌 새로운 방식으로 작성하려면 register_chrdev 대신 register_chrdrv_region/alloc_chrdev_region 과 cdev_init, cdev_add 로 작성하면 된다.

register_chrdrv_region 함수는 원하는 디바이스의 번호를 미리 알고 있을 때 사용하고, alloc_chrdev_region 함수는 디바이스의 번호를 동적으로 할당받아 파라미터로 받는 dev_t 구조체 포인터를 이용해 dev_t 구조체에 넣는다.
register_chrdrv 대신 register_chrdrv_region을 사용하는 것으로 혼동할 수 있는데 그게 아닌 cdev_add 함수까지 사용하여야 한다. 실제 커널 소스의 register_chrdrv 함수를 보면 이런 과정이 구현되어 있음을 볼 수 있다.
cdev_add 함수를 사용하기 위해서는 struct cdev 구조체를 사용하여야 하는데 이 구조체를 초기화 시켜주는 함수가 cdev_init 이다. struct cdev 구조체 등을 사용하려면 <linux/cdev.h> 를 include하여야 한다. 다음은 사용 예이다.

#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>


struct file_operations dasom_fops;


static struct cdev dasom_cdev = {
    .owner = THIS_MODULE,
    .ops = &dasom_fops,

};


int _init dasom_init(void)
{
    dev_t dev;
    int err = 0;


    if(major) {
        dev = MKDEV(major, minor);
        err = register_chrdev_region(dev, 1, “dasomoli”);
    } else {
        err = alloc_chrdev_region(&dev, mior, 1, “dasomoli”);
        major = MAJOR(dev); 
    }
    if(err < 0) {
        err = -ENODEV;
        return err;
    }

    …
   
    cdev_init(&dasom_cdev, &dasom_fops);
    dasom_cdev.owner = THIS_MODULE;
    dasom_cdev.ops  = &dasom_fops;


    if(cdev_add(&dasom_cdev, dev, 1)) {
        printk(KERN_INFO”dasom: cdev creation failed.\n”);
        err = -ENODEV;
        goto error_label;
    }
   
    …
   
    return 0;
   
error_label:
    return err;
}