// 커널 2.4
struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, struct dentry *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
	ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*dmapi_map_event) (struct file *, struct vm_area_struct *, unsigned int);
};

// 커널 2.6
struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, struct dentry *, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*dir_notify)(struct file *filp, unsigned long arg);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **);
};

  주번호

리눅스에서 주번호(Major Number)는 디바이스를 구분하기 위해 사용된다. 첫 번째 IDE 하드디스크는 /dev/hdaXX로 표시되고, 주번호는 3번이다. tty 디바이스는 /dev/ttyXX로 표시되며, 주번호는 4번이다.

주번호가 다르다는 것은 물리적인 디바이스도 다르고, 사용되는 디바이스 드라이버도 다르다는 것을 의미한다.

커널 2.4에서는 주번호에 8 비트, 부번호에 8 비트를 사용하기 때문에 최대 256개의 주번호를 사용할 수 있다. 2.4 커널에서 주번호와 부번호는 kdev_t 형식에 저장된다.

typedef unsigned short kdev_t;

커널 2.6에서는 주번호 12 비트, 부번호 20 비트로 확장하여, 총 4096개의 주번호를 사용한다. 2.4 커널은 디바이스 번호에 16 비트 데이터형을 사용했으며, 2.6 커널은 디바이스 번호에 32비트 데이터형을 사용한다.

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;

참고로 문자 디바이스와 블록 디바이스의 주번호는 각각 독립되어 있다. 문자 디바이스와 블록 디바이스는 서로 다른 인터페이스와 API를 제공한다.

  부번호

부번호(Minor Number)는 동일한 디바이스가 여러 개 있을 때, 이들 디바이스를 구분하기 위한 용도로 사용한다. 예를 들어, 리눅스 터미널을 의미하는 /dev/ttyXX는 모든 주번호 4번를 갖습니다. 주번호가 4인 것은 모두 같은 종류의 디바이스라는 것을 의미하지만, 부번호를 사용해서 각 터미널을 구분해서 처리하게 된다.

부번호는 커널에서 사용하지 않으며, 각 디바이스 드라이버 내부에서 디바이스를 구분하기 위해 사용한다.

  misc 디바이스

Misc(miscellaneous) 디바이스는 다소 특별한 형태의 디바이스 드라이버이다. 디바이스를 구별하기 위해 주번호 외에 부번호까지 사용하는 디바이스로 부족한 디바이스 번호할당 문제를 해결하기 위해 제안된 것이다. 때문에, 별도의 함수와 자료구조를 갖는다.

커널에서 사용하는 주번호와 사용자가 사용할 수 있는 주번호는 커널 소스의 Documentation/device-list/에서 확인 할 수 있다.

현재 시스템에 장착되어 있는 디바이스들의 정보는 커널 내 정보들을 보여주는 profs에서(cat /proc/devices)로 확인할 수 있다.


디바이스를 사용하기 위해 open()을 호출하고, 사용을 마친 후에는 close()를 호출해서 사용을 끝낸다. 만약, 여러 사람이 동시에 open()을 호출해서 사용하고 있을 때, 누군가가 close()를 호출해서 사용을 끝낸 경우에는 어떻게 될까? 디바이스를 사용하던 다른 사람들은 이상한 문제를 겪게 될 것이다. 디바이스를 얼마나 많이 사용하는지 알 수 있는 방법으로 디바이스를 사용할 때 카운트를 증가 시키고, 디바이스 사용을 끝낼 때 카운트를 감소시키는 것이다. 이것을 참조 계수기(reference counter)라고 한다.

2.4 커널에서는 open()이 호출될 때, 디바이스 드라이버에서 MOD_INC_USE_COUNT를 호출해서 카운트를 증가시켜야 하고, close()가 호출될 때는 MOD_DEC_USE_COUNT를 호출해서 카운터를 감소시켜야 한다.

2.4 커널에서 카운트는 커널 내부에서 유지하며, 카운트의 증가/감소만 MOD_INC_USE_COUNT, MOD_DEC_USE_COUNT 매크로를 사용하여 변경할 수 있다.

이 방법의 문제점은 모듈을 제거하려 할 때, 오류가 발생해서 MOD_DEC_USE_COUNT가 수행되지 못한 경우에 해당 모듈을 제거할 수 없다. 디바이스 드라이버는 이미 제거되었는데, 카운트가 유지되고 있기 때문에 시스템을 다시 시작할 때까지 카운트를 초기화시킬 방법이 없기 때문이다.

2.6 커널에서는 MOD_INC_USE_COUNT, MOD_DEC_USE_COUNT 등을 사용하지 않고, 모듈의 참조 계수를 내부적으로 관리하며, 모듈을 강제로 제거할 수 있는 기능도 제공한다. 그러나 일부 디바이스 드라이버를 작성할 때 사용 횟수를 직접 관리해야 하는 경우도 있기 때문에 사용 횟수 증가를 위한 try_module_get()과 사용 횟수 감소를 위한 module_put() 함수를 제공한다.

 // 2.4
#define MODULE
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define NAME  "bsp"
#define MAJOR  240

int bsp_open(struct inode *inode, struct file *filp)
{
  MOD_INC_USE_COUNT;
  return 0;
}

ssize_t bsp_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
  return 1;
}

ssize_t bsp_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
  return 2;
}

int bsp_release(struct inode *inode, struct file *filp)
{
  MOD_DEC_USE_COUNT;
  return 0;
}

struct file_operations bsp_fops =
{
  read  : bsp_read,
  write  : bsp_write,
  open  : bsp_open,
  release  : bsp_release,
};

int init_module(void)
{
  int result;
  result = register_chrdev(MAJOR, NAME, &bsp_fops);
  if(result <0)
    return result;

  return 0;
}

void cleanup_module(void)
{
  unregister_chrdev(MAJOR, NAME);
}

MODULE_LICENSE("GPL");

 // 2.6
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/slab.h>

#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <asm/io.h>

#define NAME  "bsp"
#define MAJOR  240

int bsp_open(struct inode *inode, struct file *filp)
{
  try_module_get(THIS_MODULE);
  printk("open module\n");
  return 0;
}

ssize_t bsp_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
  printk("read call\n");
  return 1;
}

ssize_t bsp_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
  printk("write call\n");
  return 2;
}

int bsp_release(struct inode *inode, struct file *filp)
{
  module_put(THIS_MODULE);
  printk("release module\n");
  return 0;
}

struct file_operations bsp_fops =
{
  .owner = THIS_MODULE,
  .read = bsp_read,
  .write = bsp_write,
  .open = bsp_open,
  .release = bsp_release,
};

int init_test(void)
{
  int result;
  result = register_chrdev(MAJOR, NAME, &bsp_fops);
  if(result < 0)
    return result;
  return 0;
}

void exit_module(void)
{
  unregister_chrdev(MAJOR, NAME);
}

module_init(init_test);
module_exit(exit_module);
MODULE_LICENSE("GPL");




모듈은 커맨드 라인 인자를 받을 수 있다. 인자를 모듈로 넘기기 위해서는 커맨드 라인 인자의 값을 저장할 변수를 전역으로 선안한 후 메커니즘을 활성화 시키기 위해 MODULE_PARM() 매크로를 사용한다. 실행 시간에 insmod는 주어진 인자로 변수를 채울 것이다. 변수의 선언과 매크로들은 명확성을 위해 모듈 서두에 위치해야 한다.

MODULE_PARM() 매크로는 2개의 인자를 받는다. 변수의 이름과 타입이다.
타입 : byte "b" // short int "h" // integer "i" // long "l" // string "s"
문자열은 "char *"로 해야 하며, insmod는 그 문자열을 위한 메모리를 할당한다. 늘 변수를 초기화하는 습관을 갖길 권한다. 이것은 커널 코드이므로 반드시 방어적으로 프로그래밍 해야한다.

MODULE_PARM()은 배열 역시 지원된다. '-'에 의해 분리된 두 번호는 최대 최소값을 알려준다.

// 2.4
#include <linux/module.h>
#include <linux/kernel.h>

static int  onevalue = 1;
static char  *twostring = NULL;

MODULE_PARM(onevalue, "i");
MODULE_PARM(twostring, "s");

int init_module(void)
{
  printk("onevalue = [%d]\n", onevalue);
  printk("twostring = [%s]\n", twostring);

  return 0;
}

void cleanup_module(void)
{
  printk("Remove...\n");
}

MODULE_AUTHOR("장난감");
MODULE_DESCRIPTION("이 장난감은...");
MODULE_LICENSE("GPL")
/*
 * 실행
 * insmod test.o onevalue=0x64 twostring="test..."
 */


// 2.6
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>

static int onevalue = 1;
static char *twostring = NULL;

module_param(onevalue, int0);
module_param(twostring, charp, 0);

int init_test(void)
{
  printk("onevalue = [%d]\n", onevalue);
  printk("twostring = [%s]\n", twostring);

  return 0;
}

void exit_test(void)
{
  printk("Remove...\n");
}

module_init(init_test);
module_exit(exit_test);
MODULE_LICENSE("GPL");

/*
 * 실행
 * insmod test.ko onevalue=0x64 twostring="test..."
 */






1. 초기화와 종료 처리디바이스
1.1 드라이버가 동작하기 위한 초기화/종료에 필요한 처리 항목(대표적)
  • 디바이스 드라이버의 등록과 해제
  • 디바이스 드라이버에 내부 구조체의 메모리 할당과 해제
  • 여러 프로세스가 하나의 디바이스에 접근할 때 필요한 사전 처리 및 종료 시 처리
  • 주 번호에 종속된 부 번호를 관리하기 위한 사전 처리 및 종료 시 처리
  • 하드웨어 검출 처리 및 에러 처리
  • 하드웨어 초기화와 제거 가능한 하드웨어의 제거 처리
  • 응용 프로그램에서 디바이스 드라이버를 사용하는 경우의 초기 처리 및 사용 종료 처리
  • 부 번호에 관련된 프로세스별 처리
  • 프로세스별 메모리 할당과 해제
  • 사용하는 모듈 수의 관리

1.2 디바이스 드라이버의 초기화/종료 처리 시점
  • 모듈 적재와 커널 부팅 처리 과징 / 제거 과정
    - insmod 명령 : module_init : 모듈 적재과정
    - rmmod 명령 : module_exit : 모듈 제거 과정
  • 응용프로그램이 디바이스 파일을 여는 과정과 닫는 과정
    - open() 함수 : file_operations.open : 디바이스 파일을 여는 과정
    - close() 함수 : file_operations.release : 디바이스 파일을 닫는 과정

1.3 모듈 초기화와 종료
1.3.1 module_init의 초기화 처리
  • 디바이스 드라이버의 등록
  • 디바이스 드라이버에 내부 구조체의 메모리 할당
  • 여러 프로세스가 하나의 디바이스에 접근하는 경우에 필요한 사전 처리
  • 주 번호에 종속된 부 번호를 관리하기 위한 사전 처리
  • 하드웨어 검출 처리 및 에러 처리
  • 하드웨어 초기화

1.3.2 module_exit의 종료 처리
  • 디바이스 드라이버의 해제
  • 디바이스 드라이버에 할당된 모든 메모리의 해제

less(처리순서 예)..

(예) 처리순서
init()
{
   // 하드웨어 검출 처리 및 에러 처리
   // 하드웨어 초기화
   // 디바이스 드라이버의 등록
   // 디바이스 드라이버의 동작에 필요한 내부 구조체의 메모리 할당
   // 여러 프로세스가 디바이스 하나에 접근하는 경우에 필요한 사전 처리
   // 주 번호에 종속된 부 번호를 관리하기 위한 사전 천리
}

exit(){
   // 디바이스 드라이버에 할당된 모든 메모리 해제
   // 디바이스 드라이버의 해제
   // 하드웨어 제거에 따른 처리
}

less(처리순서 예)..


※ 실제로 디바이스 드라비어가 사용되는 시점 : 응용프로그램에서 open()함수로 열었을때 부터

1.3.3 open()함수 호출시 초기화 처리
  • 디바이스 드라이버가 처음 열렸을 때 하드웨어 초기화
  • 디바이스 드라이버의 동작에 필요한 에러 체크
  • 부 번호에 대한 처리가 필요한 경우 파일 오퍼레이션 구조체를 갱신
  • 프로세스별 메모리 할당과 초기화
  • 모듈의 사용 횟수 증가(kernel 2.4)

1.3.4 release()함수 호출시 종료 처리
  • 프로세스별 할당 메모리 해제
  • 모듈 사용 횟수 감소(kernel 2.4)

less..

커널 2.4에서는 디바이스 드라이버의 사용 횟수를 커널에서 관리하지 않고, 디바이스 드라이버 자체에서 처리하도록 하고 있음

less..



2. 모듈 사용 횟수 관리
디바이스 드라이버가 모듈 형태라면 커널에 추가하거나 삭제하는 행위가 발생.
프로세스가 드라이버를 사용하는 도중에 디바이스 드라이버 모듈을 커널에서 제거하면 커널 자체에 문제가 생기므로 커널을 디바이스 드라이버의 사용 횟수를 감시하고, 사용하지 않을 때만 삭제할 수 있게 해야한다.

커널 2.4 : 디바이스 드라이버에서 사용 횟수 관리
/linux/modules.h
  • MOD_INC_USE_COUNT : 모듈 사용 횟수를 증가시킨다.
  • MOD_DEC_USE_COUNT : 모듈 사용 횟수를 감소시킨다.
  • MOD_IN_USE : 모듈 사용 횟수가 0이 아니면 참값을 반환한다.
open()함수 : MOD_INC_COUNT 매크로를 이용하여 모듈 사용횟수 증가

less..

int xxx_open(struct inode * inode, struct file * flip)
{
    MOD_INC_USE_COUNT;
    return 0;
}

less..


release()함수 : MOD_DEC_USE_COUNT 매크로를 이용하여 모듈 사용횟수 감소

less..

 int xxx_release(struct inode * inode, struct file * flip)
{
   MOD_DEC_USE_COUNT;
   return 0;
}

less..



커널 2.6 : 커널에서 사용 횟수 관리
핫 플러그인 처리가 필요한 경우처럼 디바이스 드라이버의 사용횟수를 직접 관리해야할 경우에 
사용하는 함수
  • try_module_get(THIS_MODULE);: 모듈의 사용 횟수를 증가시킨다.

    less..

    반환값이 0이면 열기 실패 : 디바이스 드라이버 모듈이 적재되어 있지 않은 상태
    ※ 다른 프로세스가 커널에서 모듈을 제거하는 도중에 open()함수가 호출된 상태
        (선점형 커널의 특성 때문에 발생하는 상황)

    less..

  • module_put(THIS_MODULE);: 모듈의 사용 횟수를 감소시킨다.
open()함수 : try_module_get() 함수를 이용하여 모듈 사용횟수 증가

less..

 int xxx_open(struct inode * inode, struct file * flip)
{
    if(!try_module_get(THIS_MODULE)) return -ENODEV;
    return 0;
}

less..


release()함수 :     module)put() 함수를 이용하여 모듈 사용횟수 감소

less..

int xxx_release(struct inode * inode, struct file * flip)
{
    module_put(THIS_MODULE);
   return 0;
}

less..


3. I/O 영역의 경쟁 처리 함수

3.1 I/O 포트 영역의 경쟁 처리 함수
#include <linux/ioport.h>
  • check_region : 등록할수 있는 I/O 영역인지 확인한다.
    int check_region(unsigned long from, unsigned long extent);
  • request_region : I/O 영역을 등록한다.
    void request_region(unsigned long from, unsigned long extend, const char * name);
    ※ request_region 등록시 지정하는 문자열은 디바이스 드라이버의 이름을 사용
        cat /proc/ioports
  • release_region : 등록된 I/O 영역을 해제한다.
    void release_region(unsigned long from, unsigned long extent);

자원의 충돌을 해결하기 위해 함수 사용 순서
  1. 디바이스 드라이버 초기화 루틴에서 check_region() 함수를 사용해 해당 I/O영역의 사용 여부를 확인
  2. 해당 영역을 사용하지 않고 있다면 request_region() 함수를 사용해 점유 영역을 새로 등록
  3. 디바이스 드라이버를 제거 할 때는 release_region() 함수를 사용해 등록된 영역을 제거

less..

※ I/O메모리 포트 0x3FF0400번지부터 0x3FF040F까지의 영역을 사용하는 하드웨어의 예
 int xxx_open(struct inode *inode, struct file *filp){
    int err;
    if(err = check_region(0x400), 0x10)) return err;
       request_region(0x400, 0x10, "TEST");
    return 0;
}
 

int xxx_release(struct inode *inode, struct file * filp)
{
    release_region(0x400, 0x10);
      ....
    return 0;
}

less..


3.2 I/O 메모리 영역의 경쟁 처리 함수
 #include <linux/ioport.h>
  • check_mem_region : 등록된 자원인가를 확인한다.
    int check_mem_region(unsigned long from, unsigned long extent);
  • request_mem_region() : 자원을 등록한다.
    void request_mem_region(unsigned long from, unsigned long extent, const char * name);
  • release_mem_region() : 등록된 자원을 제거한다.
    void release_mem_reion(unsigned long from, unsinged long extent);

less..

   int xxx_open(struct inode *inode, struct file *filp){
    int err;
    if(err = check_mem_region(0x3FF0400), 0x10)) return err;
       request_mem_region(0x3FF0400, 0x10, "TEST");
    return 0;
}
 

int xxx_release(struct inode *inode, struct file * filp)
{
    release_mem_region(0x3FF0400, 0x10);
      ....
    return 0;
}

less..


* 디바이스 드라이버 초기화와 종료 함수 정리

 System.map 파일은 커널을 컴파일 할 때마다 새로 생성되는 파일로 커널에 들어 있는 심벌에 대한 정보를 담고 있다. 이 파일은 커널 부팅 과정에서 사용되지 않고, 부팅 이후 디버깅을 하는 프로그램 등에 의해 사용된다. 그렇더라도 부팅 과정에서 파일 버전이 틀리다고 불평하는 경우가 있다.

 우선 가장 손쉬운 해결 방법은 커널 컴파일을 하면 생기는 System.map 파일을 /boot/ 디렉토리에 복사를 하는 것이다. System.map 파일은 /usr/src/linux 디렉토리에 있다. /boot에는 System.map 파일이 있는데, 파일이 있는 경우도 있지만 버전별로 System.map-(version) 파일이 있고, 여기에 심벌릭 링크로 되어 있는 경우도 있다. 심벌릭 링크로 되어 있는 경우는 링크를 새로 만들어서 복사를 하면 된다.

 시스템에 여러 버전의 커널이 설치되어 있고, LILO를 이용하여 다른 버젼의 커널로 부팅할 수도 있다. 이 때는 부팅하는 버전별로 System.map 파일을 자동으로 바꿀 수 있다. 먼저 /boot에 버전별로 System.map 파일을 만들어 둔다. 예를 들어 2.2.16 버전의 System.map 파일은 /boot/System.map-2.2.16으로 만든다. 그리고 /etc/rc.d/rc.sysinit에 root filesystem을 Read/Write로 마운트를 한 부분 뒤에 다음 스크립트를 추가한다.

kernel_version='uname -r'
rm -f /boot/System.map
ln -s /boot/System.map-$(kernel_version) /boot/System.map

ln -sf /boot/System.map-$(uname -r) /boot/System.map

 이 스크립트는 부팅한 커널 버전에 따라서 자동으로 심벌릭 링크를 만들어 준다.


+ Recent posts