字符设备是指在I/O传输过程中以字符为单位进行传输的设各,如键盘、打印机等。请注意,
以字符为单位并不一定意味着是以字节为单位,因为有的编码规则规定,1个字符占16比特,合2
个字节。字符设备驱动程序的结构如图5巧所示。
 

字符设备驱动简介_运维

在Linux系统中,字符设备以特别文件方式在文件目录树中占据位置并拥有相应的i结点。在i
结点中的文件类型指明该文件是字符设备文件。可以使用与普通文件相同的文件操作命令对字符设
备文件进行操作,如打开、关闭、读、写等。概括来说,字符设备驱动主要要做如下3件事。
 

  • 定义一个结构体staticstructfile_operations变量,在其中定义一些设备的打开、关闭、读、写、控制函数。
  • 在结构体外分别实现结构体中定义的这些函数。
  • 向内核中注册或删除驱动模块。

   由此可见,实现字符设备驱动的首要任务是定义一个结构体。字符设备提供给应用程序流的控
制接口有open、close、read、write和ioctl,添加一个字符设备驱动程序,实际上是给上述操作添加对应的代码,Linux对这些操作统一进行了抽象。

结构体fileoperaoons定义格式如下所示.

static struct file_operations myDriver_fops = {
       owner: THIS_MODULE,
       write: myDriver_write,
       read: myDriver_read,
       ioctl: myDriver_ioctl,
       open: myDriver_open,
       release: myDriver_release,
};

在此结构体中规定了驱动程序向应用程序提供的操作接口,主要有实现以下几个功能的接口。
(1)实现 write 操作,就是从应用程序接收数据送到硬件。例如下面的代码:
 

static ssize_t myDriver_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
       size_t fill_size = count;
       PRINTK("myDriver write called!\n");
       PRINTK("\tcount=%d, pos=%d\n", count, (int)*f_pos);
       if(*f_pos >= sizeof(myDriver_Buffer))
       {
              PRINTK("[myDriver write]Buffer Overlap\n");
              *f_pos = sizeof(myDriver_Buffer);
              return 0;
       }
       if((count + *f_pos) > sizeof(myDriver_Buffer))
       {
              PRINTK("count + f_pos > sizeof buffer\n");
              fill_size = sizeof(myDriver_Buffer) - *f_pos;
       }
       copy_from_user(&myDriver_Buffer[*f_pos], buf, fill_size);
       *f_pos += fill_size;
       return fill_size;
}

在上述代码中,函数u_long copy_from_user(void *to,const void *from,u_long len)用于把用户态的数据复制到内核态,实现数据的传送。

(2) 实现 read 操作, 即从硬件读取数据并交给应用程序,例如下面的代码:

static ssize_t myDriver_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
       size_t read_size = count;
       PRINTK("myDriver read called!\n");
       PRINTK("\tcount=%d, pos=%d\n", count, (int)*f_pos);
       if(*f_pos >= sizeof(myDriver_Buffer))
       {
              PRINTK("[myDriver read]Buffer Overlap\n");
              *f_pos = sizeof(myDriver_Buffer);
              return 0;
       }
       if((count + *f_pos) > sizeof(myDriver_Buffer))
       {
              PRINTK("count + f_pos > sizeof buffer\n");
              read_size = sizeof(myDriver_Buffer) - *f_pos;
       }
       copy_to_user(buf, &myDriver_Buffer[*f_pos], read_size);
       *f_pos += read_size;
       return read_size;
}

在上述代码中,函数u_long copy_to_user(void *to,const void *from,u_long len)用于实现把内核
态的数据复制到用户态下。

(3) 实现ioctl操作,即为应用程序提供对硬件行为的控制。例如下面的代码:
 

static int myDriver_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
    PRINTK("myDriver ioctl called(%d)!\n", cmd);
    if(_IOC_TYPE(cmd) != TSTDRV_MAGIC)
    {
        return -ENOTTY;
    }
    if(_IOC_NR(cmd) >= TSTDRV_MAXNR)
    {
        return -ENOTTY;
    }
    switch(cmd)
       {
         case MYDRV_IOCTL0:
              PRINTK("IOCTRL 0 called(0x%lx)!\n", arg);
              break;
         case MYDRV_IOCTL1:
              PRINTK("IOCTRL 1 called(0x%lx)!\n", arg);
              break;
         case MYDRV_IOCTL2:
              PRINTK("IOCTRL 2 called(0x%lx)!\n", arg);
              break;
         case MYDRV_IOCTL3:
              PRINTK("IOCTRL 3 called(0x%lx)!\n", arg);
              break;
       }
       return 0;
}

(4) 实现open操作。当应用程序打廾设备时,对设备进行初始化,使用MODINC_USECOUNT
增加驱动程序的使用次数。例如下面的代码:

static int myDriver_open(struct inode *inode, struct file *filp)
{
// int Minor = MINOR(inode->i_rdev);
// filp->private_data = 0;
       MOD_INC_USE_COUNT;
       PRINTK("myDriver open called!\n");
       return 0;
}

(5) 实现release操作。当应用程序关闭设备时,处理设各的关闭操作。使用MOD_DEC_USE_
COUNT来增加驱动程序的使用次数。例如下面的代码:

static int myDriver_release(struct inode *inode, struct file *filp)
{
// int Minor = MINOR(inode->i_rdev);
       MOD_DEC_USE_COUNT;
       PRINTK("myDriver release called!\n");
       return 0;
}

(6) 驱动程序初始化函数。Linux在加载内核模块时会调用初始化函数,初始化驱动程序本身使
用reyster_chrdev 向内核注册驱动程序,该函数的第三个指向包含驱动程序接口函数信息的
file_operations结构体。例如下面的代码:

#ifdef CONFIG_DEVFS_FS
devfs_handle_t devfs_myDriver_dir;
devfs_handle_t devfs_myDriver_raw;
#endif
static int __init myModule_init(void)
{
       /* Module init code */
       PRINTK("myModule_init\n");
       /* Driver register */
       myDriver_Major = register_chrdev(0, DRIVER_NAME, &myDriver_fops);
       if(myDriver_Major < 0)
       {
              PRINTK("register char device fail!\n");
              return myDriver_Major;
       }
       PRINTK("register myDriver OK! Major = %d\n", myDriver_Major);
#ifdef CONFIG_DEVFS_FS
       devfs_myDriver_dir = devfs_mk_dir(NULL, "myDriver", NULL);
       devfs_myDriver_raw = devfs_register(devfs_myDriver_dir, "raw0", DEVFS_FL_DEFAULT, myDriver_Major, 0, S_IFCHR | S_IRUSR | S_IWUSR, &myDriver_fops, NULL);
       PRINTK("add dev file to devfs OK!\n");
#endif
       return 0;
}

在上述代码中,丞数module_init()用于向内核声明当前模块的初始化函数。
(7) 驱动程序退出函数。Linux在卸载内核模块时会调用退出函数,释放驱动程序使用的资源,
使用unregister_chrdev从内核中卸载驱动程序。将驱动程序模块注册到内核,内核需要知道模块的
初始化函数和退出函数,才能将模块放入自己的管理队列中。例如下面的代码:

static void __exit myModule_exit(void)
{
       /* Module exit code */
       PRINTK("myModule_exit\n");
       /* Driver unregister */
       if(myDriver_Major > 0)
       {
#ifdef CONFIG_DEVFS_FS
        devfs_unregister(devfs_myDriver_raw);
        devfs_unregister(devfs_myDriver_dir);
#endif
        unregister_chrdev(myDriver_Major, DRIVER_NAME);
       }
       return;
}

在上述代码中,函数module_exit()用于向内核声明当前模块的退出函数。
经过上述分析,可以总结出开发字符设备驱动程序的基本步骤,如下所示:
a.确定主设备号和次设备号。
b.实现字符驱动程序,先实现file_operations 结构体,然后实现初始化函数并注册字符设备,
接下来实现销毁函数并释放字符设备。
c.创建设备文件结点。
接下来给出一个通用的字符设备驱动程序,此程序由如下两个文件构成,其中文件tst-driver.h
的实现代码如下所示:
 

tst-driver.h

#ifndef __TST_DRIVER_H__
#define __TST_DRIVER_H__

#define TSTDRV_MAGIC                0xd0

#define GPIO_IN                        0
#define GPIO_OUT                    1//_IO(TSTDRV_MAGIC, 1)

#define GPIO_SET_BIT                2//_IO(TSTDRV_MAGIC, 2)

#define GPIO_CLR_BIT                3//_IO(TSTDRV_MAGIC, 3)

#define TSTDRV_MAXNR                4

#endif    //#ifndef __TST_DRIVER_H__

tst-driver.c

#ifndef __KERNEL__
    #define __KERNEL__
#endif
#ifndef MODULE
    #define MODULE
#endif
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>  /* printk() */
#include <linux/init.h>    /* __init __exit */
#include <linux/types.h>   /* size_t */
#include <linux/fs.h>      /* file_operation */
//#include <linux/errno.h> /* Error number */
//#include <linux/delay.h> /* udelay */
#include <asm/uaccess.h>   /* copy_to_user, copy_from_user */
#include <asm/hardware.h>
#include "tst-driver.h"
#define DRIVER_NAME "myDriver"
//#undef CONFIG_DEVFS_FS
#ifdef DEBUG
#define PRINTK(fmt, arg...) printk(KERN_NOTICE fmt, ##arg)
#else
#define PRINTK(fmt, arg...)
#endif
/*     KERN_EMERG 用于紧急事件,一般是系统崩溃前的提示信息
       KERN_ALERT 用于需要立即采取动作的场合
       KERN_CRIT 临界状态,通常设计验证的硬件或软件操作失败
       KERN_ERR 用于报告错误状态.设备驱动程序通常会用它报告来自硬件的问题
       KERN_WARNING 就可能出现的问题提出警告.这些问题通常不会对系统造成严重破坏
       KERN_NOTICE 有必要提示的正常情况.许多安全相关的情况用这个级别汇报
       KERN_INFO 提示性信息.有很多驱动程序在启动时用这个级别打印相关信息
       KERN_DEBUG 用于调试的信息
*/
static int myDriver_Major = 0; /* Driver Major Number */
/* Vitual Driver Buffer */
static unsigned char myDriver_Buffer[1024*1024];
/* Driver Operation Functions */
static int myDriver_open(struct inode *inode, struct file *filp)
{
// int Minor = MINOR(inode->i_rdev);
// filp->private_data = 0;
       MOD_INC_USE_COUNT;
       PRINTK("myDriver open called!\n");
       return 0;
}
static int myDriver_release(struct inode *inode, struct file *filp)
{
// int Minor = MINOR(inode->i_rdev);
       MOD_DEC_USE_COUNT;
       PRINTK("myDriver release called!\n");
       return 0;
}
static ssize_t myDriver_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
       size_t read_size = count;
       PRINTK("myDriver read called!\n");
       PRINTK("\tcount=%d, pos=%d\n", count, (int)*f_pos);
       if(*f_pos >= sizeof(myDriver_Buffer))
       {
              PRINTK("[myDriver read]Buffer Overlap\n");
              *f_pos = sizeof(myDriver_Buffer);
              return 0;
       }
       if((count + *f_pos) > sizeof(myDriver_Buffer))
       {
              PRINTK("count + f_pos > sizeof buffer\n");
              read_size = sizeof(myDriver_Buffer) - *f_pos;
       }
       copy_to_user(buf, &myDriver_Buffer[*f_pos], read_size);
       *f_pos += read_size;
       return read_size;
}
static ssize_t myDriver_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
       size_t fill_size = count;
       PRINTK("myDriver write called!\n");
       PRINTK("\tcount=%d, pos=%d\n", count, (int)*f_pos);
       if(*f_pos >= sizeof(myDriver_Buffer))
       {
              PRINTK("[myDriver write]Buffer Overlap\n");
              *f_pos = sizeof(myDriver_Buffer);
              return 0;
       }
       if((count + *f_pos) > sizeof(myDriver_Buffer))
       {
              PRINTK("count + f_pos > sizeof buffer\n");
              fill_size = sizeof(myDriver_Buffer) - *f_pos;
       }
       copy_from_user(&myDriver_Buffer[*f_pos], buf, fill_size);
       *f_pos += fill_size;
       return fill_size;
}
static int myDriver_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
    PRINTK("myDriver ioctl called(%d)!\n", cmd);
    if(_IOC_TYPE(cmd) != TSTDRV_MAGIC)
    {
        return -ENOTTY;
    }
    if(_IOC_NR(cmd) >= TSTDRV_MAXNR)
    {
        return -ENOTTY;
    }
    switch(cmd)
       {
         case MYDRV_IOCTL0:
              PRINTK("IOCTRL 0 called(0x%lx)!\n", arg);
              break;
         case MYDRV_IOCTL1:
              PRINTK("IOCTRL 1 called(0x%lx)!\n", arg);
              break;
         case MYDRV_IOCTL2:
              PRINTK("IOCTRL 2 called(0x%lx)!\n", arg);
              break;
         case MYDRV_IOCTL3:
              PRINTK("IOCTRL 3 called(0x%lx)!\n", arg);
              break;
       }
       return 0;
}
/* Driver Operation structure */
static struct file_operations myDriver_fops = {
       owner: THIS_MODULE,
       write: myDriver_write,
       read: myDriver_read,
       ioctl: myDriver_ioctl,
       open: myDriver_open,
       release: myDriver_release,
};
/* Module Init & Exit function */
#ifdef CONFIG_DEVFS_FS
devfs_handle_t devfs_myDriver_dir;
devfs_handle_t devfs_myDriver_raw;
#endif
static int __init myModule_init(void)
{
       /* Module init code */
       PRINTK("myModule_init\n");
       /* Driver register */
       myDriver_Major = register_chrdev(0, DRIVER_NAME, &myDriver_fops);
       if(myDriver_Major < 0)
       {
              PRINTK("register char device fail!\n");
              return myDriver_Major;
       }
       PRINTK("register myDriver OK! Major = %d\n", myDriver_Major);
#ifdef CONFIG_DEVFS_FS
       devfs_myDriver_dir = devfs_mk_dir(NULL, "myDriver", NULL);
       devfs_myDriver_raw = devfs_register(devfs_myDriver_dir, "raw0", DEVFS_FL_DEFAULT, myDriver_Major, 0, S_IFCHR | S_IRUSR | S_IWUSR, &myDriver_fops, NULL);
       PRINTK("add dev file to devfs OK!\n");
#endif
       return 0;
}
static void __exit myModule_exit(void)
{
       /* Module exit code */
       PRINTK("myModule_exit\n");
       /* Driver unregister */
       if(myDriver_Major > 0)
       {
#ifdef CONFIG_DEVFS_FS
        devfs_unregister(devfs_myDriver_raw);
        devfs_unregister(devfs_myDriver_dir);
#endif
        unregister_chrdev(myDriver_Major, DRIVER_NAME);
       }
       return;
}
MODULE_AUTHOR("SXZ");
MODULE_LICENSE("Dual BSD/GPL");
module_init(myModule_init);
module_exit(myModule_exit);

文章摘自:

《Android 底层接口与驱动开发技术详解》