linux 字符设备驱动开发详解

一、设备的分类及特点

    1、字符设备

    字符设备是面向数据流的设备,没有请求缓冲区,对设备的存取只能按顺序按字节的存取而不能随机访问。
    Linux下的大多设备都是字符设备。应用程序是通过字符设备节点来访问字符设备的。通常至少需要实现 open, close, read, 和 write 等系统调用。
    设备节点一般都由mknod命令都创建在/dev目录下,包含了设备的类型、主/次设备号以及设备的访问权限控制等,如:crw-rw----  1 root  root 4, 64 Feb 18 23:34 /dev/ttyS0
    常见的字符设备有鼠标、键盘、串口、控制台等。当然,也有可以随机访问的字符设备,比如磁带驱动器,但访问随机数据所需要的时间很大程度上依赖于数据在设备内的位置。

    2、块设备

    存储设备一般属于块设备,块设备有请求缓冲区,并且支持随机访问而不必按照顺序去存取数据,比如你可以先存取后面的数据,然后在存取前面的数据,这对字符设备来说是不可能的。
    尽管在Linux下有块设备节点,但应用程序一般是通过文件系统及其高速缓存来访问块设备的,而不是直接通过设备节点来读写块设备上的数据。
    每个块设备在/dev/目录下都有一个对应的设备文件,即设备节点,它们包含了设备的类型、主/次设备号以及设备的访问权限控制等 ,如brw-rw----  1 root  root  3, 1 Jul  5  2000 /dev/hda1
    块设备既可以作为普通的裸设备用来存放任意数据,也可以将块设备按某种文件系统类型的格式进行格式化,然后按照该文件系统类型的格式来读取块设备上的数据。常见的块设备有各种硬盘、flash磁盘、RAM磁盘等。

    3、网络设备

    不同于字符设备和块设备,它是面向报文的而不是面向流的,它不支持随机访问,也没有请求缓冲区。
    在Linux里一个网络设备也可以叫做一个网络接口,它没有像字符设备和块设备一样的设备号,只有一个唯一的名字如eth0、 eth1等,这个名字也不需要与设备文件节点对应,应用程序是通过Socket而不是设备节点来访问网络设备,在系统里根本就不存在网络设备节点。

二、字符设备驱动程序开发的一般步骤

    1、确定主设备号和从设备号。主设备号是内核识别一类设备的标识,从设备号用于内核确定一个具体的设备。主设备号和从设备号用一个32位的整型变量来表示,主设备号占12位,范围从0到4095,通常使用1到255,从设备号占20位,范围从0到1048575,通常使用0到255。

    2、实现file_operations结构体。

    3、调用相关函数对字符设备进行初始化及注册。

    4、调用销毁函数,释放字符设备。

    5、创建设备文件节点。


三、主要数据结构

    字符设备驱动中主要的结构体就这两个了。一个字符设备就对应一个cdev,当应用程序调用open()、write()、read()等函数时就会调用到文件操作结构体中相应的函数。


    1、设备结构体

     struct cdev
     {
         struct kobject kobj;              /* 内嵌的kobject 对象 */
         struct module *owner;         /*所属模块*/
         struct file_operations *ops;  /*文件操作结构体*/
         struct list_head list;
         dev_t dev;                           /*设备号*/
         unsigned int count;
     };

    2、文件操作结构体

     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 **);
 };

四、函数说明

    1、int register_chrdev_region( dev_t first,     unsigned int count,    char *name );

    功能:静态分配主设备号,找一个内核没有使用的主设备号来使用。

    参数说明:参数一,要分配的设备编号范围的起始值,次设备号通常为0;参数二,所请求的连续设备编号的个数;参数三,设备的名称。

    2、int  alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);

    功能:动态分配主设备号。

    参数说明:参数一,返回的设备编号;参数二,第一个次设备号;参数三,所请求的连续设备编号的个数;参数四,设备名称。

    3、void  unregister_chrdev_region(dev_t first, unsigned int count);

    功能:用于释放设备号,通常在模块清楚函数中调用。有申请就要有释放,要一一对应。

    4、void cdev_init( struct cdev *, struc t file_operations *);

    功能:用于初始化cdev的各个成员,并建立cdev和file_operations之间的连接。

    5、int cdev_add(struct cdev *, dev_t, unsigned) ;

    功能:把申请的设备号与设备绑定,然后i向系统添加一个cdev,完成字符设备的注册。

    6、void cdev_del(struct cdev *);

    功能:完成字符设备的注销


    注意:在调用cdv_add()函数向系统注册字符设备之前,应先调用register_chrdev_region()函数或alloc_chrdev_region函数向系统申请设备号。相反地,在调用cdev_del()函数从系统注销字符设备后,unregister_chrdev_region函数应该被调用以释放原先申请的设备号。


五、一个简单的例子(按键驱动)

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/major.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <asm/uaccess.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/gpio.h>
#include <asm/gpio.h>
 
#define BUTTON_NAME "poll_button"
#define OMAP_BUTTON_GPIO 29
 
static int button_major = 0;                               
static int button_minor = 0;
static struct cdev button_cdev;                                
static struct class *p_button_class = NULL;            
static struct device *p_button_device = NULL;    
 
static struct timer_list button_timer1;
static struct timer_list button_timer2;
static volatile int ev_press_poll = 0;
static volatile char down_times[] = {0};
static volatile int Button_Irq = 0;
static int flag_interrupt =1;

static DECLARE_WAIT_QUEUE_HEAD(button_waitq_poll);

 
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
    if(flag_interrupt)
    {
        flag_interrupt = 0;
        mod_timer(&button_timer2,jiffies + HZ/100);
    }
    return IRQ_RETVAL(IRQ_HANDLED);
}
 
static void button_timer_handle1(unsigned long arg)
{
    int key_value;
    
    down_times[0]++;
    key_value = gpio_get_value(OMAP_BUTTON_GPIO);
    
    if(key_value != 0)
    {
        ev_press_poll= 1;
        wake_up_interruptible(&button_waitq_poll);        
    }
    else
    {
        mod_timer(&button_timer1,jiffies + HZ);
    }
}
 
static void button_timer_handle2(unsigned long arg)
{
    int key_value;
    
    flag_interrupt = 1;
    key_value = gpio_get_value(OMAP_BUTTON_GPIO);
    if(key_value==0)
    {
        mod_timer(&button_timer1,jiffies + HZ);
    }
}
 
 
static int button_open(struct inode *inode,struct file *file)
{
    int err;
    
    init_timer(&button_timer1);
    init_timer(&button_timer2);
    button_timer1.function = &button_timer_handle1;
    button_timer2.function = &button_timer_handle2;
 
    Button_Irq = OMAP_GPIO_IRQ(OMAP_BUTTON_GPIO);
    err = request_irq(Button_Irq, buttons_interrupt, IRQ_TYPE_EDGE_FALLING, "BUTTON_IRQ", NULL);
    if(err)  
        {
              printk("fail to erquest irq : %d  err = %d\n",Button_Irq,err);
            
        disable_irq(Button_Irq);
        free_irq(Button_Irq, NULL);
        return -EBUSY;
        }
    
    ev_press_poll = 0;
    
    return 0;
}
 
static int button_close(struct inode *inode, struct file *file)
{
    free_irq(Button_Irq, NULL);
    return 0;
}
 
 
static int button_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
    unsigned long err;
 
    err = copy_to_user(buff, (const void *)down_times, min(sizeof(down_times), count));
    down_times[0] = 0;
    
    return err ? -EFAULT : min(sizeof(down_times), count);    
}
 
 
static unsigned int button_poll(struct file *file, struct poll_table_struct *wait)
 {
    unsigned int mask = 0;
 
    //í±í(poll_table)
    poll_wait(file, &button_waitq_poll, wait);
 
    if(ev_press_poll)
    {
        //±ê
        ev_press_poll = 0;
        mask |= POLLIN | POLLRDNORM;
    }
 
    return mask;
 }
 
 
static const struct file_operations button_fops = {
    .owner = THIS_MODULE,
    .open = button_open,
    .release = button_close,
    .read = button_read,
    .poll = button_poll,
    //.write = button_write,
    //.ioctl = button_ioctl
};
 
 
static int button_setup_cdev(struct cdev *cdev, dev_t devno)
{
    int ret = 0;
 
    cdev_init(cdev, &button_fops);
    cdev->owner = THIS_MODULE;
    ret = cdev_add(cdev, devno, 1);
 
    return ret;
}
 
static int __init button_init(void)
{
    int ret;
    dev_t devno;
    
    printk("button_init\n");
    
    if(button_major)
    {
        devno = MKDEV(button_major, button_minor);
        ret = register_chrdev_region(devno, 1, BUTTON_NAME);
    }
    else
    {
        ret = alloc_chrdev_region(&devno, button_minor, 1, BUTTON_NAME);
        button_major = MAJOR(devno);
        
    }
    
    if(ret < 0)
    {
        printk("get button_major fail\n");
        return ret;
    }
 
    ret = button_setup_cdev(&button_cdev, devno);
    if(ret)
    {
        printk("button_setup_cdev fail = %d\n",ret);
        goto cdev_add_fail;
    }
 
    p_button_class = class_create(THIS_MODULE, BUTTON_NAME);
    ret = IS_ERR(p_button_class);
    if(ret)
    {
        printk(KERN_WARNING "button class_create fail\n");
        goto class_create_fail;
    }
    p_button_device = device_create(p_button_class, NULL, devno, NULL, BUTTON_NAME);
    ret = IS_ERR(p_button_device);
    if (ret)
    {
        printk(KERN_WARNING "button device_create fail, error code %ld", PTR_ERR(p_button_device));
        goto device_create_fail;
    }
 
    return 0;
    
device_create_fail:
    class_destroy(p_button_class);
class_create_fail:
    cdev_del(&button_cdev);
cdev_add_fail:
    unregister_chrdev_region(devno, 1);
    return ret;
    
}
 
static void __exit button_exit(void)
{
    dev_t devno;
 
    printk("button_exit\n");
    
    devno = MKDEV(button_major, button_minor);
    del_timer_sync(&button_timer1);
    del_timer_sync(&button_timer2);
    device_destroy(p_button_class, devno);
    class_destroy(p_button_class);
    cdev_del(&button_cdev);
    unregister_chrdev_region(devno, 1);
}
 
module_init(button_init);
module_exit(button_exit);
 
MODULE_AUTHOR("Jimmy");
MODULE_DESCRIPTION("button Driver");
MODULE_LICENSE("GPL");

    

六、应用程序如何使用驱动程序

    以上驱动调用了class_create()函数和device_create()函数,这样一来,系统启动时udev就会在/dev下创建名为poll_button的设备节点。应用程序使用open函数打开该设备节点后,就可以调用read()、write()函数进行读写了。

    如果应用程序没有调用这两个函数,则要手动创建设备节点,如mknod /dev/poll_button c  250 0。

    

七、附上makefile文件

    

ifneq ($(KERNELRELEASE),)
obj-m := button.o
else
KERNELDIR ?= /home/share/linux-2.6.32-devkit8500
#KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
 
default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
 
endif
 
install:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
 
clean:
    rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.symvers *.order

你可能感兴趣的:(linux,字符设备驱动)