2011年12月30日 星期五

基礎 Linux Device Driver 驅動程式#5 (character device driver基礎)

交換資料
這邊要說明的是user process 與驅動程式交換資料的方法。
這邊是以character這類的驅動程式做說明。

Device file 裝置檔
device file 又可稱為 special file,
通常位於 /dev 目錄底下。

基本上,一個裝置就要有一個device file,
why device file also call special file?

# ls -l /dev/ttyS0
crw-rw---- 1 root dialout 4, 64 2011-12-30 13:20 /dev/ttyS0

# ls -l README
-rw-r--r-- 1 walter walter 1691 2011-12-30 11:00 README

看到前面多了一個c嗎?很明顯和一般檔案不一樣,所以又稱為special file,
而c指的是字元裝置。

當然還有其他裝置檔,如下:
b      : 區塊裝置 (block device)
c or u : 字元裝置 (character device/unbuffered device)
p      : 具名管線 或稱為FIFO(name pipe)

建立與刪除裝置檔
底下是使用範例
  mknod   裝置檔名  種類 主編號 副編號
# mknod /dev/sample c 254 0
# ls -l /dev/sample
crw-r--r-- 1 root root 254, 0 2011-12-30 14:35 /dev/sample

要刪除的話,用rm指令即可
# rm /dev/sample

目前Linux 的major number是12-bit, minor是 20-bit,
合起來一共為 32-bit。
而major/minor number 可以使用的bit數是定義在 include/linux/kdev_t.h

Major Number的豋記方法
如果要將裝置檔和驅動程式連結起來的話,
驅動程式就必須向kernel豋記 major number才行,
目前都採動態豋記為主,所以我們就以動態豋記來做說明。

動態豋記法
Linux 2.6開始建議採用動態豋記法,也就是用cdev結構豋記 major number的方法。
1. 以 alloc_chrdev_region()動態取得 major number。
2. 以 cdev_init() 豋記系統呼叫 handler。
3. 以 cdev_add() 向 kernel豋記驅動程式。

在卸載驅動程式的時候,必須以相反步驟解除豋記才行。
1. 以 cdev_del() 向 kernel釋放驅動程式。
2. 以 unregister_chrdev_region()釋放 major number。

我們就分別來看一下這些函式。
int alloc_chrdev_region(dev_t *dev,
                        unsigned int firstminor,
                        unsigned int count,
                        char *name);
函式如果成呼叫會回傳0, 失敗則會回傳負數。
dev引數就會放進新的 major number及minor number。
firstminor 則是minor number的第一個數字編號。
count      是 minor number的個數,也就是裝置的個數。
name       是驅動程式的名稱。

void cdev_init(struct cdev *cdev, const struct file_operations *fops);
cdev_init負責初使化 cdev結構,並豋記系統呼叫 handler(fops)。
因為 cdev結構變數,在卸載驅動程式的時候還會用到,所以要將它定義為全域變數。

int cdev_add(struct cdev *p, dev_t dev, unsigned int count);
cdev_add()會向 kernel豋記 cdev_init()設定好的裝置資訊,
p 引數可傳入 cdev_init()初使化完成的 cdev結構變數。
dev 則是豋記好的 major number 及 minor number 的起點。
count 則是 minor的個數。
同樣的,該函式如果成呼叫會回傳0, 失敗則會回傳負數。

void cdev_del(struct cdev *p);
會從 kernel釋放驅動程式。

void unregister_chrdev_alloc(dev_t from, unsigned int count);
則會釋放之前拿到的 major number。
from 是 major number 及 minor number 的起點。
count則是當初配置的minor number 個數。

OK, 既然稍作說明過後,我們就來run一下程式碼,才不會如此沉悶吧。

test_alloc_chrdev.c 程式碼如下:

/*****************************************************************************/

     1 #include <linux/init.h>
     2 #include <linux/module.h>
     3 #include <linux/fs.h>
     4 #include <linux/cdev.h>
     
     5 #define DRIVER_NAME "test_chrdev"
     6 static unsigned int test_chrdev_alloc_major = 0;
     7 static unsigned int num_of_dev = 1;
     8 static struct cdev test_chrdev_alloc_cdev;
     
     9 static int test_chrdev_alloc_open(struct inode *inode, struct file *filp)
    10 {
    11 printk(KERN_ALERT "test_chrdev_alloc is open.\n");
    12 return 0;
    13 }
     
    14 static int test_chrdev_alloc_close(struct inode *inode, struct file *filp)
    15 {
    16 printk(KERN_ALERT "test_chrdev_alloc is close.\n");
    17 return 0;
    18 }
     
    19 struct file_operations fops = {
    20 .owner = THIS_MODULE,
    21 .open = test_chrdev_alloc_open,
    22 .release = test_chrdev_alloc_close,
    23 };
     
    24 static int test_alloc_chrdev_init(void)
    25 {
    26 dev_t dev = MKDEV(test_chrdev_alloc_major, 0);
    27 int alloc_ret = 0;
    28 int cdev_ret = 0;
     
    29 alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
    30 if (alloc_ret)
    31 goto error;
    32 test_chrdev_alloc_major = MAJOR(dev);
     
    33 cdev_init(&test_chrdev_alloc_cdev, &fops);
    34 cdev_ret = cdev_add(&test_chrdev_alloc_cdev, dev, num_of_dev);
    35 if (cdev_ret)
    36 goto error;
    37
    38 printk(KERN_ALERT "%s driver(major number %d) installed.\n", DRIVER_NAME, test_chrdev_alloc_major);
    39 return 0;
     
    40 error:
    41 if (cdev_ret == 0)
    42 cdev_del(&test_chrdev_alloc_cdev);
    43 if (alloc_ret == 0)
    44 unregister_chrdev_region(dev, num_of_dev);
    45 return -1;
    46 }
     
    47 void test_alloc_chrdev_exit(void)
    48 {
    49 dev_t dev = MKDEV(test_chrdev_alloc_major, 0);
     
    50 cdev_del(&test_chrdev_alloc_cdev);
    51 unregister_chrdev_region(dev, num_of_dev);
    52 printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
    53 }
     
    54 module_init(test_alloc_chrdev_init);
    55 module_exit(test_alloc_chrdev_exit);
     
    56 MODULE_LICENSE("GPL") ;
    57 MODULE_AUTHOR("Wang Chen Shu");
    58 MODULE_DESCRIPTION("This is test_alloc_chrdev module.");
 
/*****************************************************************************/

Makefile 如下:

/*****************************************************************************/

     1 KDIR="/opt/linux-source-2.6.38"
     2 PWD=$(shell pwd)
     
     3 obj-m := test_alloc_chrdev.o
     
     4 all:
     5 $(MAKE) -C ${KDIR} M=${PWD} modules
     6 clean:
     7 $(MAKE) -C ${KDIR} M=${PWD} clean

/*****************************************************************************/

我們這裡又準備了一個名叫 test_chrdev.c 的測試程式。

/*****************************************************************************/

     1 #include <stdio.h>
     2 #include <errno.h>
     3 #include <fcntl.h>
     
     4 #define DEV_FILE_NAME "/dev/test_chrdev"
     
     5 int main(void)
     6 {
     7 int fd;
     8 fd = open(DEV_FILE_NAME, O_RDWR);
     9 if (fd == -1)
    10 perror("open");
     
    11 sleep(3);
     
    12 if (close(fd) != 0)
    13 perror("close");
     
    14 return 0;
    15 }

/*****************************************************************************/

有了程式碼,測試程式以及Makefile,當然直接給它build下去囉^^

# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/test_alloc_chrdev modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CC [M]  /opt/test_driver/my_driver/test_alloc_chrdev/test_alloc_chrdev.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /opt/test_driver/my_driver/test_alloc_chrdev/test_alloc_chrdev.mod.o
  LD [M]  /opt/test_driver/my_driver/test_alloc_chrdev/test_alloc_chrdev.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
這時就產生了ko檔。

# insmod test_alloc_chrdev.ko
test_chrdev driver(major number 250) installed.
看到了嗎,我們拿到了 250 這個 major number。
但此時我們並沒有 device file裝置檔,還記得怎麼建來嗎? 哈,想一下喔^^
這樣就行了啊。
# mknod /dev/test_chrdev c 250 0

編譯一下 test_chrdev.c
# gcc test_chrdev.c -o test_chrdev
# ./test_chrdev
經過3秒.....

這時候的訊息,我們就從 dmesg去看吧,走~~
# dmesg | tail
..........  // 以上略過 //
test_chrdev_alloc is open.
test_chrdev_alloc is close.

看到kernel 印出的資訊了嗎?
再來就是刪除裝置檔及卸載模組吧^^
# rm /dev/test_chrdev
# rmmod test_alloc_chrdev
test_chrdev driver removed.

這樣就完成了整個流程,包含豋記及反豋記驅動。


註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。

1 則留言: