Chinaunix首页 | 论坛 | 博客
  • 博客访问: 602245
  • 博文数量: 165
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1554
  • 用 户 组: 普通用户
  • 注册时间: 2013-10-23 22:57
个人简介

我本仁慈,奈何苍天不许

文章分类

全部博文(165)

文章存档

2018年(1)

2016年(33)

2015年(5)

2014年(34)

2013年(92)

分类: LINUX

2013-12-26 16:46:43

字符设备驱动

{//?什么是字符设备

 从设备数据传递的特点看, 如果是按字节流顺序传递过来的,就像生产流水线,如串口,键盘,鼠标等。

 我们把它们抽象为字符设备。

}

{//?为何要提出字符设备概念

  //?多种多样的硬件设备需要控制,我们如何去管理控制它们

  统一形式   ---    抽象为文件 

    

  分类       ---    不同类的设备对应不同的框架

  从数据通信的角度:

                 |--- 字符设备    字节流顺序传输            (类似流水线生产,按先后顺序进行传递)

     数据顺序 ---|

                 |---  块设备     按块传输,块内部可随机访问(类似卡车送货,一车一车的送,车内的货可任意拿)

     

                 |---- UART 

       协议   ---|---- I2C  

                 |---- SPI

                 |---- USB

                 |---- 网络设备                

  从其它角度:

       平台设备(Platform)  显示设备  视频设备等          

}

{//?重点在那

掌握如何去创建和使用一个简单的字符设备

}

{//?如何实现和使用一个字符设备 

{1. 注册设备号

  {//---hello_char/step1/hello.c

#include   

#include 

#include 

  #include    //for MKDEV register_chrdev_region

MODULE_LICENSE ("GPL");    

#define DEVICE_MAJOR 250  //主设备号 用于区分不同种类的设备  

                          //某些主设备号已经静态地分配给了大部分公用设备。见Documentation/devices.txt 。 这里我们常使用250

#define DEVICE_MINOR 0    //次设备号 用于区分同一类型的多个设备

#define DEVICE_NUM 1     //有多少个设备

#define DEVICE_NAME "hello"  //设备名称

static int __init hello_init (void)

{     

  int ret;

  dev_t dev_id = 0;  //设备号(由主次设备号合并而成)

  if(DEVICE_MAJOR){ //当主设备号为零时,用动态分配设备号

    dev_id = MKDEV (DEVICE_MAJOR , DEVICE_MINOR); //MKDEV 用于合并主次设备号生成设备号   见 include/linux/kdev_t.h

    ret = register_chrdev_region (dev_id, DEVICE_NUM, DEVICE_NAME); /*注册字符设备号(静态分配)

                                            为一个字符驱动获取一个或多个设备编号

                                            dev_id:       分配的起始设备编号(常常是0

                                            DEVICE_NUM:  请求的连续设备编号的总数(不能太大,避免别的主设备号冲突)

                                            DEVICE_NAME: 是应当连接到这个编号范围的设备的名字 

                                                                     */

    if (ret<0) {

      printk (" can't get major number %d\n", DEVICE_MAJOR);

      return ret;

    }       

  }else{

    ret = alloc_chrdev_region(&dev_id,0, DEVICE_NUM, DEVICE_NAME); //注册字符设备(动态分配),当不清楚主设备号时,常用该方式,让系统为它自动分配 

                                                                   //因设备号是随机的,采用从cat /proc/devices中获取设备号后,再mknod 创建设备文件

    if(ret<0)

    {

      printk("alloc_chrdev  fail\n");

      return ret;

    }

  }

  printk("char device register ok\n");     

  return 0;

}

static void __exit hello_exit (void)    

{

 dev_t dev_id = 0;

 

 dev_id = MKDEV (DEVICE_MAJOR , DEVICE_MINOR);

   unregister_chrdev_region (dev_id, DEVICE_NUM);

   printk("char unregister ok\n");   

}

module_init (hello_init);

module_exit (hello_exit);

}  

//---Makefile    和内核模块Makefile一样

//---使用

insmod hello.ko

cat /proc/devices //hello 表示该注册成功

}

   

{2. 初始化字符设备

//---hello_char/step2/hello.c

      #include   

#include 

#include 

#include 

#include   //字符设备头文件

MODULE_LICENSE ("GPL");    

#define DEVICE_MAJOR 250

#define DEVICE_MINOR 0

#define DEVICE_NUM 1

#define DEVICE_NAME "hello"

struct cdev *cdev;  //定义字符设备指针

struct file_operations hello_fops = {  //文件操作

 .owner = THIS_MODULE

};

static int __init hello_init (void)

{     

  int ret;

  dev_t dev_id = 0;

  dev_id = MKDEV (DEVICE_MAJOR , DEVICE_MINOR);

  ret = register_chrdev_region (dev_id, DEVICE_NUM, DEVICE_NAME);  

  if (ret<0) {

    printk (" can't get major number %d\n", DEVICE_MAJOR);

    goto err_reg_cdev;

  }        

  cdev = cdev_alloc();  //分配空间给字符设备

  if(NULL == cdev)

  {

    printk (" cdev_alloc fail\n");

    goto err_cdev_alloc;

  }

  cdev_init(cdev,&hello_fops); //字符设备初始化

  cdev->owner = THIS_MODULE;   //THIS_MODULE 相当于c++this ,表示该字符设备为本模块所有

  cdev->ops = &hello_fops;     //字符设备相关的文件操作

  ret = cdev_add(cdev,dev_id,DEVICE_NUM); //添加字符设备到系统中

  if(ret)

  {

      printk("cdev_add fail %d\n",ret);

      goto err_cdev_add;

  }

  printk("char device register ok\n");     

  return 0;

//---goto 出错处理, 顺序申请,逆序释放,避免资源回收不完全(如内存泄露)

err_cdev_add:

  cdev_del(cdev);

err_cdev_alloc:

  unregister_chrdev_region(dev_id,DEVICE_NUM);

err_reg_cdev:

  return ret;

}

static void __exit hello_exit (void)    

{

  dev_t dev_id = 0;

  dev_id = MKDEV (DEVICE_MAJOR , DEVICE_MINOR);

  if (cdev)

    cdev_del (cdev);

   unregister_chrdev_region (dev_id, DEVICE_NUM);

   printk("char unregister ok\n");   

}

module_init (hello_init);

module_exit (hello_exit);

}

{3. 最简方式操作设备

{//---hello_char/hello.c

      #include   

#include 

#include 

#include 

#include 

#include   // for copy_to_user

MODULE_LICENSE ("GPL");    

#define DEVICE_MAJOR 250

#define DEVICE_MINOR 0

#define DEVICE_NUM 1

#define DEVICE_NAME "hello"

struct cdev *cdev;

static int hello_open (struct inode *inode, struct file *file)  //打开文件

{

  printk ("device opened\n");

  return 0;

}

static int hello_release (struct inode *inode, struct file *file) //关闭文件

{

  printk ("device closed\n");

  return 0;

}

// file_operations 中 定义了针对文件的一系列操作方法,实现你需要的

struct file_operations hello_fops = { 

 .owner = THIS_MODULE,

 .open  = hello_open,

 .release = hello_release,

};

static int __init hello_init (void)

{     

  int ret;

  dev_t dev_id = 0;

  

  dev_id = MKDEV (DEVICE_MAJOR , DEVICE_MINOR);

  ret = register_chrdev_region (dev_id, DEVICE_NUM, DEVICE_NAME);  

  if (ret<0) {

    printk (" can't get major number %d\n", DEVICE_MAJOR);

    goto err_reg_cdev;

  }        

  cdev = cdev_alloc();

  if(NULL == cdev)

  {

    printk (" cdev_alloc fail\n");

    goto err_cdev_alloc;

  }

  cdev_init(cdev,&hello_fops);

  cdev->owner = THIS_MODULE;

  cdev->ops = &hello_fops;

  ret = cdev_add(cdev,dev_id,DEVICE_NUM);

  if(ret)

  {

      printk("cdev_add fail %d\n",ret);

      goto err_cdev_add;

  }

  printk("char device register ok\n");     

  return 0;

err_cdev_add:

  cdev_del(cdev);

err_cdev_alloc:

  unregister_chrdev_region(dev_id,DEVICE_NUM);

err_reg_cdev:

  return ret;

}

static void __exit hello_exit (void)    

{

  dev_t dev_id = 0;

  dev_id = MKDEV (DEVICE_MAJOR , DEVICE_MINOR);

  if (cdev)

    cdev_del (cdev);

   unregister_chrdev_region (dev_id, DEVICE_NUM);

   printk("char unregister ok\n");   

}

module_init (hello_init);

module_exit (hello_exit);

     

  }

{//---使用  注意:使用root 用户登录,否则可能出现open /dev/hello fail 的错误

  #mknod /dev/hello c 250 0  //创建设备文件,应用才能访问它. ( ls -l /dev 可以看到很多其它设备文件

                             // crw-------  1 root root    250,   0 2013-01-15 16:00 farsight_dev 

                             //    c表示是字符设备  rw表示可读可写  250是主设备号  0是次设备号  farsight_dev是设备名

  //---test.c

  #include 

#include 

#include 

#include 

#include 

#include 

      

int main (void) 

{

  int fd;

      fd = open ("/dev/hello",O_RDWR);  /* 应用open是如何调用到驱动的open

                                                 open      ->      sys_open     ->         inode           ->  driver .open 

                                                 应用       |      系统调用     |         文件系统         |   驱动      

                                        */

  if (fd < 0) {

     printf("open /dev/hello fail\n");

     return -1;

  }

  close (fd);

  printf("app end\n");

  return 0;

}   

  }

--------------------------------------  

{4. 实现需要的文件操作 //重点 难点

{//---hello_char/hello.c

      #include   

#include 

#include 

#include 

#include 

#include   // for copy_to_user

#include "hello.h"

MODULE_LICENSE ("GPL");    

#define DEVICE_MAJOR 250

#define DEVICE_MINOR 0

#define DEVICE_NUM 1

#define DEVICE_NAME "hello"

struct cdev *cdev;

#define C_BUF_LEN 64

static char c_buf[C_BUF_LEN];  //用于存储读写数据

//打开文件

static int hello_open (struct inode *inode, struct file *file)

{

  printk ("device opened\n");

  return 0;

}

//关闭文件

static int hello_release (struct inode *inode, struct file *file)

{

  printk ("device closed\n");

  return 0;

}

//读文件

static ssize_t hello_read (struct file *filp, char *buff, size_t count, loff_t *offp)

{

  ssize_t result = 0;

  if(count > C_BUF_LEN -1 )   count = C_BUF_LEN -1;

  if(count < 0) return -EINVAL;

  

  if (copy_to_user(buff,c_buf, count)) 

    result = -EFAULT;

  else 

    printk ("read %d bytes\n", count);

  result = count;

  return result;

}

//写文件

static ssize_t hello_write (struct file *filp, const char  *buf, size_t count, loff_t *f_pos)

{

  ssize_t ret = 0;

  printk ("Writing %d bytes\n", count);

  if (count > C_BUF_LEN -1) return -ENOMEM;

  if (count<0) return -EINVAL;

  if (copy_from_user (c_buf, buf, count)) {

    ret = -EFAULT;

  }

  else {

    c_buf[63]='\0';

    printk ("Received: %s\n", c_buf);

    ret = count;

  }

  return ret;

}

//ioctl 用于完成特殊操作

static int hello_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)

{

  int ret=0;

  switch (cmd) {

  case HELLO_ONE:

    printk ("HELLO_ONE cmd  called\n");

    break;

  case HELLO_TWO:

    printk ("HELLO_TWO cmd called\n");

    break;

  default:

    printk ("cmd other called\n");

    break;

  }

  return ret;

}  

/* file_operations 中 定义了针对文件的一系列操作方法

        不是每个都需实现

*/

struct file_operations hello_fops = { 

 .owner = THIS_MODULE,

 .open  = hello_open,

 .release = hello_release,

 .read  = hello_read,

 .write = hello_write,

 .ioctl = hello_ioctl

};

static int __init hello_init (void)

{     

  int ret;

  dev_t dev_id = 0;

  

  dev_id = MKDEV (DEVICE_MAJOR , DEVICE_MINOR);

  ret = register_chrdev_region (dev_id, DEVICE_NUM, DEVICE_NAME);  

  if (ret<0) {

    printk (" can't get major number %d\n", DEVICE_MAJOR);

    goto err_reg_cdev;

  }        

  cdev = cdev_alloc();

  if(NULL == cdev)

  {

    printk (" cdev_alloc fail\n");

    goto err_cdev_alloc;

  }

  cdev_init(cdev,&hello_fops);

  cdev->owner = THIS_MODULE;

  cdev->ops = &hello_fops;

  ret = cdev_add(cdev,dev_id,DEVICE_NUM);

  if(ret)

  {

      printk("cdev_add fail %d\n",ret);

      goto err_cdev_add;

  }

  printk("char device register ok\n");     

  return 0;

err_cdev_add:

  cdev_del(cdev);

err_cdev_alloc:

  unregister_chrdev_region(dev_id,DEVICE_NUM);

err_reg_cdev:

  return ret;

}

static void __exit hello_exit (void)    

{

  dev_t dev_id = 0;

  dev_id = MKDEV (DEVICE_MAJOR , DEVICE_MINOR);

  if (cdev)

    cdev_del (cdev);

   unregister_chrdev_region (dev_id, DEVICE_NUM);

   printk("char unregister ok\n");   

}

module_init (hello_init);

module_exit (hello_exit);

     

  }

  {//---hello_char/hello.h

   #define HELLO_MAGIC 'x'  //幻数:0~0xff的数。用于区分不同的驱动Documentation/ioctl/ioctl-number.txt

      #define HELLO_ONE _IO (HELLO_MAGIC, 1)   //加幻数方式来定义命令,防止不同驱动间命令错乱

      #define HELLO_TWO _IO (HELLO_MAGIC, 2)

  }

{//---使用  注意:使用root 用户登录,否则可能出现open /dev/hello fail 的错误

  #mknod /dev/hello c 250 0  //创建设备文件,应用才能访问它. ( ls -l /dev 可以看到很多其它设备文件

                             // crw-------  1 root root    250,   0 2013-01-15 16:00 farsight_dev 

                             //    c表示是字符设备  rw表示可读可写  250是主设备号  0是次设备号  farsight_dev是设备名

  //---test.c

  #include 

#include 

#include 

#include 

#include 

#include 

      #include 

      #include "hello.h"

      

int main (void) 

{

  int fd;

  char buff[]=" let's go ";

      fd = open ("/dev/hello",O_RDWR);  /* 应用open是如何调用到驱动的open

                                                 open      ->      sys_open     ->         inode           ->  driver .open 

                                                 应用       |      系统调用     |         文件系统         |   驱动      

                                        */

  if (fd < 0) {

     printf("open /dev/hello fail\n");

     return -1;

  }

  write (fd, buff, sizeof(buff));

  memset(buf,'\0',sizeof(buf));

  read (fd, buff, sizeof(buff) - 1);

  printf("read buf is %s\n",buf);

  

  ioctl (fd, HELLO_ONE);

  close (fd);

  printf("app end\n");

  return 0;

}   

  }

}

{//方法

  {//linux中有许多函数的功能,参数不知道怎么办

   1. 跳转到函数定义的地方,有些函数内核中有功能和参数的说明。 

      有的仅根据函数或参数的命名就能猜出来。

      

   2. 内核中有许多使用它的例子,根据这些例子进行推测

  }

}

阅读(615) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~