2012年1月1日 星期日

基礎 Linux Device Driver 驅動程式#6 (character device driver基礎_讀寫)

既然各位對最基本的驅動程式已有概念了,
那當然我們得再去一步的探討囉。

在上一個驅動程式,我們實作了open及release,
但,實際上只有這些handler了嗎?
當然不只,還有以下實作:
是定義在 include/include/linux/fs.h 裡。
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 *);
        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 *, 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 (*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 **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,
                          loff_t len);
};

但隨著之後的範例會愈來愈多,非常建議各位有獨立的機器或者跑虛擬機比較保險喔^^
回到主題,既然有這麼多的 handler的實作,但並不是完全都用得到,那其它沒用到的函式呢?
最基本的驅動程式,都會實作open與release(close),其他沒實作的handler就會被定義成NULL。

像open 這個handler,也就是user process的程式在操作裝置檔做的開啟動作,
結束當然就是release囉。
而其中的 inode 引數,是一個內含inode資訊的結構指標,有幾個成員我們會用到的,

bdev_t i_rdev    Major/Minor Number
void *i_private  驅動程式私有指標

既然如此,我們就可以透過 i_rdev這個成員去取得 major及minor number了。
怎麼做呢? Linux已提供了以下方法提供實作:
unsigned int iminor(const struct inode *inode);
unsigned int imajor(const struct inode *inode);

i_private 成員則是驅動程式可以自由使用的指標,不設定也沒關係。

file 也是很大的一個結構,以下是常用的幾個成員:
struct file_operations *fops    系統呼叫 handlers
unsigned int f_flags            open函式第二個引數傳入的旗標
void *private_data              驅動程式私有資料指標

通常 fops 不需修改,但有時假設需要對不同裝置提供不同的處理的話,就可自已更新fops的成員。
open handler傳回值 0為成功,非0為失敗。

在上篇教學,我們只示範了open與release這兩個 handler,再來,我們將實作read及write 這兩個handler。

ssize_t *read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
read handler 是指 當user process的程式想要向驅動程式讀取資料時用的。
file 這個結構指標是指向開啟裝置檔 kernel建立的file 結構,和open收到的指標是同一個。
因此,當open handler設給 filp->private成員的指標,在read handler也能拿來用。

buf引數是 user process呼叫read()時指定的緩衝區指標,但驅動程式不能直接取用buf指標,
必須透過 copy_to_user 這個kernel提供的函式將資料複製過去。

count引數是 user process呼叫read()時提供的緩衝區空間。
f_pos引數是offset。

read handler傳回值 0為什麼都沒做,正為寫入緩衝區的byte數,負為發生錯誤。

ssize_t *write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);
write handler和read handler差不多,主要差在,write handler是當user process要傳資料給驅動時用到的,
所以 buf引數就必須用 copy_from_user 這個kernel提供的函式將資料從緩衝區讀入。

read handler傳回值 0為什麼都沒做,正為從緩衝區讀入的byte數,負為發生錯誤。

相信各位已對read 與write這兩個 handler有了初步的了解,就來試一下囉^^
給我點時間,寫一下code喔。
時間一點一滴的流失.....
....................
好囉,sorry, 久等了,那我們就來run一下囉^^

chrdev_rw.c 原始碼如下:
/*****************************************************************************/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

#define DRIVER_NAME "chrdev_rw"

static unsigned int num_of_dev = 1;
static unsigned int chrdev_rw_major = 0;
static struct cdev chrdev_rw_cdev;

struct chrdev_rw_data {
unsigned char val;
rwlock_t lock;
};

static int chrdev_rw_open(struct inode *inode, struct file *filp)
{
struct chrdev_rw_data *data_p;

data_p = (struct chrdev_rw_data *)kmalloc(sizeof(struct chrdev_rw_data), GFP_KERNEL);

if (data_p == NULL) {
printk(KERN_ALERT "malloc error!!!\n");
return -ENOMEM;
}

        data_p->val = 0xff;
rwlock_init(&data_p->lock);
filp->private_data = data_p;

return 0;
}

static int chrdev_rw_close(struct inode *inode, struct file *filp)
{
if (filp->private_data) {
kfree(filp->private_data);
filp->private_data = NULL;
}

        return 0;
}

ssize_t chrdev_rw_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct chrdev_rw_data *data_p = filp->private_data;
unsigned char val;
int val_ret = 0;
int i;

printk(KERN_ALERT "%s\n", __func__);
read_lock(&data_p->lock);
val = data_p->val;
read_unlock(&data_p->lock);

for (i = 0; i < count; i++) {
if (copy_to_user(&buf[i], &val, 1)) {
val_ret = -EFAULT;
goto out;
}
}

val_ret = count;
out:
return val_ret;
}

ssize_t chrdev_rw_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
struct chrdev_rw_data *data_p = filp->private_data;
unsigned char val;
int val_ret = 0;

printk(KERN_ALERT "%s\n", __func__);
if (count >= 1) {
if (copy_from_user(&val, &buf[0], 1)) {
val_ret = -EFAULT;
goto out;
}
}

write_lock(&data_p->lock);
data_p->val = val;
write_unlock(&data_p->lock);

out:
return val_ret;
}

struct file_operations fops = {
.owner = THIS_MODULE,
.open = chrdev_rw_open,
.release = chrdev_rw_close,
.read = chrdev_rw_read,
.write = chrdev_rw_write,
};

static int chrdev_rw_init(void)
{
dev_t dev = MKDEV(chrdev_rw_major, 0);
int alloc_ret = 0;
int cdev_ret = 0;

alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
if (alloc_ret)
goto error;
chrdev_rw_major = MAJOR(dev);

cdev_init(&chrdev_rw_cdev, &fops);
cdev_ret = cdev_add(&chrdev_rw_cdev, dev, num_of_dev);
if (cdev_ret)
goto error;

printk(KERN_ALERT "%s driver(major number %d) installed.\n", DRIVER_NAME, chrdev_rw_major);
return 0;
error:
if (cdev_ret == 0)
cdev_del(&chrdev_rw_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, num_of_dev);
return -1;
}

static void chrdev_rw_exit(void)
{
dev_t dev = MKDEV(chrdev_rw_major, 0);

cdev_del(&chrdev_rw_cdev);
unregister_chrdev_region(dev, num_of_dev);

printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME);
}

module_init(chrdev_rw_init);
module_exit(chrdev_rw_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Wang Chen Shu");
MODULE_DESCRIPTION("This is chrdev_rw module.");

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

Makefile 如下:
/*****************************************************************************/

KDIR="/opt/linux-source-2.6.38"
PWD=$(shell pwd)

obj-m := chrdev_rw.o

all:
$(MAKE) -C ${KDIR} M=${PWD} modules
clean:
$(MAKE) -C ${KDIR} M=${PWD} clean

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

有驅動程式原始碼和Makefile,當然也少不了測試程式囉,如下:

test_rw.c
/*****************************************************************************/

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>

#define DEV_FILE "/dev/ch_rw"

int main(void)
{
int fd;
unsigned char buf;
ssize_t ret;

fd = open(DEV_FILE, O_RDWR);
if (fd == -1)
perror("open");

read(fd, &buf, 1);
printf("Before write, char is %x\n", buf);

buf = 0xCC;
ret = write(fd, &buf, 1);
if (ret <= 0)
perror("write");

read(fd, &buf, 1);
printf("After write, char is %x\n", buf);

if (close(fd) != 0)
perror("close");

return 0;
}

/*****************************************************************************/
那我們就來run一下囉。

# ls
chrdev_rw.c  Makefile  test_code
# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/chrdev_rw modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CC [M]  /opt/test_driver/my_driver/chrdev_rw/chrdev_rw.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /opt/test_driver/my_driver/chrdev_rw/chrdev_rw.mod.o
  LD [M]  /opt/test_driver/my_driver/chrdev_rw/chrdev_rw.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'
# ls
chrdev_rw.c  chrdev_rw.ko  chrdev_rw.mod.c  chrdev_rw.mod.o  chrdev_rw.o  Makefile  modules.order  Module.symvers  test_code
# cd test_code/
# ls
test_rw.c
# gcc test_rw.c -o test_rw
# ls
test_rw  test_rw.c

OK, 模組和測試程式都編好了後,就來測試一下。
# cd ..
# insmod ./chrdev_rw.ko
chrdev_rw driver(major number 250) installed.
# mknod /dev/ch_rw c 250 0
# cd test_code/
# ./test_rw
Before write, char is ff
write: Success
After write, char is cc

成功~~~~~

我們目前是用 read_lock 和 write_lock做資料讀寫的鎖定,
但實際上還有很多種方式,到時,在後面的教學,我將會為各位說明更多種的實作方法。


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

沒有留言:

張貼留言