Chinaunix首页 | 论坛 | 博客
  • 博客访问: 120527
  • 博文数量: 83
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 50
  • 用 户 组: 普通用户
  • 注册时间: 2013-10-31 11:07
个人简介

弃我去者,昨日之日不可留; 乱我心者,今日之日多烦忧。

文章分类

分类: LINUX

2014-07-25 09:22:57

通过前面我们对字符驱动的完善,已经可以完成基本的功能了,但是仍然有以下不足,这一节我们继续完善它。

(1)没有实现xxx_ioctl接口,没办法通过命令来控制dev_fifo 

改善:增加xxx_ioctl函数接口,应用层可以通过ioctl系统调用,根据不同的命令来操作dev_fifo。

(2)只能驱动一个dev_fifo设备,多个dev_fifo设备无法驱动

改善 :通过给模块传递参数,驱动多个dev_fifo设备

一   实现xxx_ioctl接口

(1)为什么要实现xxx_ioctl ? 

前面我们在驱动中已经实现了读写接口,通过这些接口我们可以完成对设备的读写。但是很多时候我们的应用层工程师除了要对设备进行读写数据之外,还希望还可以对设备进行控制。例如:对于串口设备,驱动层除了需要提供对串口的读写之外,还需提供对串口波特率的设置。

一句话总结一下了,实现xxx_ioctl函数接口,主要的目的是提供对设备的控制能力,增加驱动程序的灵活性。


(2)如何实现xxx_ioctl函数接口?

先来看看应用层的ioctl和驱动层的xxx_ioctl对应关系:


<1>应用层ioctl参数分析 

第一个参数:打开设备文件的时候获得文件描述符 
第二个参数:给驱动层传递的命令,需要注意的时候,驱动层的命令和应用层的命令一定要统一
第三个参数: "..."在C语言中,很多时候都被理解成可变参数。这里不是这个意思。

当我们通过ioctl调用驱动层xxx_ioctl的时候,有三种情况可供选择:

第一种情况:不传递数据给xxx_ioctl 
第二种情况:传递数据给xxx_ioctl,希望它最终能把数据写入设备(例如:设置串口的波特率)
第三种情况:调用xxxx_ioctl希望获取设备的硬件参数(例如:获取当前串口设备的波特率)

好了,这三种情况中,有些时候需要传递数据,有些时候不需要传递数据。在C语言中,是无法实现函数重载的 。
那怎么办?用"..."来欺骗编译器了,"..."本来的意思是传递多参数。在这里的意思是带一个参数还是不带参数。

哎!说起来真费劲,重在意会,希望你能理解。

<2>驱动层xxx_ioctl参数分析 

第一个参数和第二个参数就不说了,你懂得。

第三个参数 :用户空间传递的命令,可以根据不同的命令做不同的事情

第四个参数:   用户空间的数据,主要这个数据可能是一个地址值(用户空间传递的是一个地址),也可能是一个数值,也可能没值


<3>如何确定cmd 的值。

是不是都认为随便给cmd一个值就可以了,只要这个值驱动层能认识就可以。

这是不负责人的干的事情,最后只能引起一场悲剧。这种事情生活中太多了.....

想一想这种情况:
有两个个设备,你本来想操作第一个设备,但是你就是一个和"2"结了不解之缘,所以你打开了第二个设备。正好你传递的命令也是第二个设备驱动能识的,你很开心的操作它。本来你想通过这命令来打开磁盘驱动器(第一个设备)来看片的,由于"2",这个命令被电源设备(第二个设备)接收到,直接关机。疯了,熬了一晚上的代码没保存,本来想通过看片振奋一下精神的,现在全玩完....

你是不是想,就算我"2"打开了错误的设备,你也不应该识别我这个命令呀,返回一个错误给我,告诉我这是无效的命令(这个命令不能再这个设备上使用),为什么不让系统中每个驱动程序识别的命令都不一样呢?呜呜。。。

哎,说多了都是泪!所以我们在设计命令的时候,一定要做到唯一性哦,这样就算很"2"的人,也可以过的很幸福。。。

好了,扯了这么多,我想你应该明白了,命令不能随便指定,要做到规范。现在我就来看看,在Linux 内核中这个cmd是如何设计的吧!




貌似很麻烦,难道我设计一个命令的时候,还需要在不同的位域写不同的数字?

不需要的,Linux 系统已经给我们封装好了宏,我们只需要直接调用宏来设计命令即可。


通过Linux 系统给我们提供的宏,我们在设计命令的时候,只需要指定设备类型、命令序号,数据类型三个字段就可以了。

在这里需要注意的时候,我们不能随便指定的哦,我们最终设计的命令应该是Linux系统中没有的命令才符号规范。那怎么知道Linux   系统中已经设计了哪些命令呢?可以通过查阅Linux 源码中的Documentation/ioctl/ioctl-number.txt文件,看哪些命令已经被使用过了。

好了,还是举个例子来看看,如何设计一个唯一的命令吧。



代码实例:




二、驱动多个dev_fifo 设备



上附图,是最后的效果。我们在内核空间分配多个struct mycdev结构体,每个struct mycdev代表一个dev_fifo设备。由于这些设备都是属于dev_fifo类,所以我们在处理的时候,让这些设备的主设备号一样,次设备号不一样。

想一个问题:如果我打开/dev/dev_fifo0 进行操作的时候,我怎么知道dev_fifo0对应内存空间的地址呢?

以前我们只有一个dev_fifo设备好办,我们在分配好内存后用一个全局的指针变量保存它的地址。现在有多个dev_fifo设备难道是定义多个全局变量分别保存,就算这样干了,当我调到驱动层的 xxx_read和xxx_write函数,我怎么知道是对哪一个设备进行读写操作呢?

有问题是好的,我们带着问题出发,看看大牛们是怎么做的。


先分析contianer_of 接口,这简直就是一个神奇的实现呀!


举个例子,来简单分析一下container_of 内部实现机制。

例如:

struct   test
{
    int       a;
    short   b;
    char    c;
};

struct  test  *p = (struct test *)malloc(sizeof(struct  test));

test_function(&(p->b));



int  test_function(short *addr_b)
{
      //获取struct test结构体空间的首地址
     struct  test *addr;
     addr =   container_of(addr_b,struct test,b);
}

展开container_of宏,探究内部的实现:

(1)typeof (  ( (struct test   *)0 )->b ) : 获取成员变量b的类型 ,这里获取的就是short 类型。这是GNU_C的扩展语法。

(2)typeof (  ( (struct test   *)0 )->b )   *__mptr =  addr_b : 用获取的变量类型,定义了一个指针变量 __mptr ,并且将成员变量 b的首地址赋值给它 

(3)(struct test *)( (char *)__mptr  -  offsetof(struct test,b)) :  这里的offsetof(struct test,b)是用来计算成员b在这个struct test 结构体的偏移。__mptr 是成员b的首地址, 现在 减去成员b在结构体里面的偏移值,算出来的是不是这个结构体的首地址呀 。


现在能看懂上面xxx_open那段代码了吗?

每次我们从应用层调用到驱动层的函数接口时,VFS层都会把struct inode  和 struct file 结构体分配的内存空间首地址传递给对应的驱动层接口。

还记得,前面我分析应用层和驱动层是如何关联的吗?最终找到驱动层的字符设备,并且把内核为字符设备分配的内存首地址保存在了struct inode 的i_cdev指针变量中。


我们通过container_of获取了内核空间为dev_fifo设备分配的内存首地址,然后把它保存在了file->private_data成员变量中。

现在能回答"如果我打开/dev/dev_fifo0 进行操作的时候,我怎么知道dev_fifo0对应的dev_ffifo0所在的内存空间的首地址呢?"  这个问题了吗?

是的,我们通过open打开/dev/dev_fifo0这个设备文件时,Linux 内核最终找到了/dev/dev_fifo0对应在内核空间的dev_fifo0这个设备。在xxxx_open函数中我们把内核为这个设备分配的内存空间首地址保存在了file->private_data这成员变量中。下一次当我们通过read 或 write调用到驱动层的xxx_read或xxx_write时,VFS层都会把struct file *file 传递下来的哦。

三、完善上一次的字符设备驱动

  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. #include <linux/cdev.h>
  4. #include <linux/fs.h>
  5. #include <linux/device.h>
  6. #include <linux/slab.h>
  7. #include <asm/uaccess.h>
  8. #include "dev_fifo_head.h"

  9. //指定的主设备号
  10. #define MAJOR_NUM 250

  11. //自己的字符设备
  12. struct mycdev
  13. {
  14.     int len;
  15.     unsigned char buffer[50];
  16.     struct cdev cdev;
  17. };

  18. MODULE_LICENSE("GPL");

  19. //设备号
  20. static dev_t dev_num = {0};

  21. //全局gcd
  22. struct mycdev *gcd;

  23. //设备类
  24. struct class *cls;

  25. //获得用户传递的数据,根据它来决定注册的设备个数
  26. static int ndevices = 1;
  27. module_param(ndevices, int, 0644);
  28. MODULE_PARM_DESC(ndevices, "The number of devices for register.\n");


  29. //打开设备
  30. static int dev_fifo_open(struct inode *inode, struct file *file)
  31. {
  32.     struct mycdev *cd;
  33.     
  34.     printk("dev_fifo_open success!\n");
  35.     
  36.     //用struct file的文件私有数据指针保存struct mycdev结构体指针
  37.     cd = container_of(inode->i_cdev,struct mycdev,cdev);
  38.     file->private_data = cd;
  39.     
  40.     return 0;
  41. }

  42. //读设备
  43. static ssize_t dev_fifo_read(struct file *file, char __user *ubuf, size_t size, loff_t *ppos)
  44. {
  45.     int n;
  46.     int ret;
  47.     char *kbuf;
  48.     struct mycdev *mycd = file->private_data;
  49.     
  50.     printk("read *ppos : %lld\n",*ppos);

  51.     if(*ppos == mycd->len)
  52.         return 0;

  53.     //请求大大小 > buffer剩余的字节数 :读取实际记得字节数
  54.     if(size > mycd->len - *ppos)
  55.         n = mycd->len - *ppos;
  56.     else
  57.         n = size;
  58.     
  59.     printk("n = %d\n",n);
  60.     //从上一次文件位置指针的位置开始读取数据
  61.     kbuf = mycd->buffer + *ppos;

  62.     //拷贝数据到用户空间
  63.     ret = copy_to_user(ubuf,kbuf, n);
  64.     if(ret != 0)
  65.         return -EFAULT;
  66.     
  67.     //更新文件位置指针的值
  68.     *ppos += n;
  69.     
  70.     printk("dev_fifo_read success!\n");

  71.     return n;
  72. }

  73. //写设备
  74. static ssize_t dev_fifo_write(struct file *file, const char __user *ubuf, size_t size, loff_t *ppos)
  75. {
  76.     int n;
  77.     int ret;
  78.     char *kbuf;
  79.     struct mycdev *mycd = file->private_data;

  80.     printk("write *ppos : %lld\n",*ppos);
  81.     
  82.     //已经到达buffer尾部了
  83.     if(*ppos == sizeof(mycd->buffer))
  84.         return -1;

  85.     //请求大大小 > buffer剩余的字节数(有多少空间就写多少数据)
  86.     if(size > sizeof(mycd->buffer) - *ppos)
  87.         n = sizeof(mycd->buffer) - *ppos;
  88.     else
  89.         n = size;

  90.     //从上一次文件位置指针的位置开始写入数据
  91.     kbuf = mycd->buffer + *ppos;

  92.     //拷贝数据到内核空间
  93.     ret = copy_from_user(kbuf, ubuf, n);
  94.     if(ret != 0)
  95.         return -EFAULT;

  96.     //更新文件位置指针的值
  97.     *ppos += n;
  98.     
  99.     //更新dev_fifo.len
  100.     mycd->len += n;

  101.     printk("dev_fifo_write success!\n");
  102.     return n;
  103. }

  104. //linux 内核在2.6以后,已经废弃了ioctl函数指针结构,取而代之的是unlocked_ioctl
  105. long dev_fifo_unlocked_ioctl(struct file *file, unsigned int cmd,
  106.     unsigned long arg)
  107. {
  108.     int ret = 0;
  109.     struct mycdev *mycd = file->private_data;
  110.     
  111.     switch(cmd)
  112.     {
  113.         case DEV_FIFO_CLEAN:
  114.             printk("CMD:CLEAN\n");
  115.             memset(mycd->buffer, 0, sizeof(mycd->buffer));
  116.             break;

  117.         case DEV_FIFO_SETVALUE:
  118.             printk("CMD:SETVALUE\n");
  119.             mycd->len = arg;
  120.             break;

  121.         case DEV_FIFO_GETVALUE:
  122.             printk("CMD:GETVALUE\n");
  123.             ret = put_user(mycd->len, (int *)arg);
  124.             break;

  125.         default:
  126.             return -EFAULT;
  127.     }

  128.     return ret;
  129. }


  130. //设备操作函数接口
  131. static const struct file_operations fifo_operations = {
  132.     .owner = THIS_MODULE,
  133.     .open = dev_fifo_open,
  134.     .read = dev_fifo_read,
  135.     .write = dev_fifo_write,
  136.     .unlocked_ioctl = dev_fifo_unlocked_ioctl,
  137. };


  138. //模块入口
  139. int __init dev_fifo_init(void)
  140. {
  141.     int i = 0;
  142.     int n = 0;
  143.     int ret;
  144.     struct device *device;
  145.     
  146.     gcd = kzalloc(ndevices * sizeof(struct mycdev), GFP_KERNEL);
  147.     if(!gcd){
  148.         return -ENOMEM;
  149.     }

  150.     //设备号 : 主设备号(12bit) | 次设备号(20bit)
  151.     dev_num = MKDEV(MAJOR_NUM, 0);

  152.     //静态注册设备号
  153.     ret = register_chrdev_region(dev_num,ndevices,"dev_fifo");
  154.     if(ret < 0){

  155.         //静态注册失败,进行动态注册设备号
  156.         ret = alloc_chrdev_region(&dev_num,0,ndevices,"dev_fifo");
  157.         if(ret < 0){
  158.             printk("Fail to register_chrdev_region\n");
  159.             goto err_register_chrdev_region;
  160.         }
  161.     }
  162.     
  163.     //创建设备类
  164.     cls = class_create(THIS_MODULE, "dev_fifo");
  165.     if(IS_ERR(cls)){
  166.         ret = PTR_ERR(cls);
  167.         goto err_class_create;
  168.     }
  169.     
  170.     printk("ndevices : %d\n",ndevices);
  171.     
  172.     for(n = 0;n < ndevices;n ++)
  173.     {
  174.         //初始化字符设备
  175.         cdev_init(&gcd[n].cdev,&fifo_operations);

  176.         //添加设备到操作系统
  177.         ret = cdev_add(&gcd[n].cdev,dev_num + n,1);
  178.         if (ret < 0)
  179.         {
  180.             goto err_cdev_add;
  181.         }
  182.         //导出设备信息到用户空间(/sys/class/类名/设备名)
  183.         device = device_create(cls,NULL,dev_num + n,NULL,"dev_fifo%d",n);
  184.         if(IS_ERR(device)){
  185.             ret = PTR_ERR(device);
  186.             printk("Fail to device_create\n");
  187.             goto err_device_create;    
  188.         }
  189.     }
  190.     printk("Register dev_fito to system,ok!\n");

  191.     
  192.     return 0;

  193. err_device_create:
  194.     //将已经导出的设备信息除去
  195.     for(i = 0;i < n;i ++)
  196.     {
  197.         device_destroy(cls,dev_num + i);    
  198.     }

  199. err_cdev_add:
  200.     //将已经添加的全部除去
  201.     for(i = 0;i < n;i ++)
  202.     {
  203.         cdev_del(&gcd[i].cdev);
  204.     }

  205. err_class_create:
  206.     unregister_chrdev_region(dev_num, ndevices);

  207. err_register_chrdev_region:
  208.     return ret;

  209. }

  210. void __exit dev_fifo_exit(void)
  211. {
  212.     int i;

  213.     //删除sysfs文件系统中的设备
  214.     for(i = 0;i < ndevices;i ++)
  215.     {
  216.         device_destroy(cls,dev_num + i);    
  217.     }

  218.     //删除系统中的设备类
  219.     class_destroy(cls);
  220.  
  221.     //从系统中删除添加的字符设备
  222.     for(i = 0;i < ndevices;i ++)
  223.     {
  224.         cdev_del(&gcd[i].cdev);
  225.     }
  226.     
  227.     //释放申请的设备号
  228.     unregister_chrdev_region(dev_num, ndevices);

  229.     return;
  230. }


  231. module_init(dev_fifo_init);
  232. module_exit(dev_fifo_exit);
头文件内容:

  1. #ifndef _DEV_FIFO_HEAD_H
  2. #define _DEV_FIFO_HEAD_H

  3. #define DEV_FIFO_TYPE 'k'
  4. #define DEV_FIFO_CLEAN _IO(DEV_FIFO_TYPE,0x10)
  5. #define DEV_FIFO_GETVALUE _IOR(DEV_FIFO_TYPE,0x11,int)
  6. #define DEV_FIFO_SETVALUE _IOW(DEV_FIFO_TYPE,0x12,int)

  7. #endif
Makefile :


  1. ifeq ($(KERNELRELEASE),)

  2. KERNEL_DIR ?=/lib/modules/$(shell uname -r)/build
  3. PWD :=$(shell pwd)

  4. modules:
  5.     $(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules

  6. .PHONY:modules clean
  7. clean:
  8.     $(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean
  9. else

  10.     obj-m := dev_fifo.o
  11. endif

应用程序:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <string.h>
  5. #include <sys/stat.h>
  6. #include <fcntl.h>

  7. int main(int argc, const char *argv[])
  8. {
  9.     int fd ;
  10.     int n;
  11.     char buf[1024] = "hello word";

  12.     fd = open("/dev/dev_fifo0",O_RDWR);
  13.     if(fd < 0){
  14.         perror("Fail ot open");
  15.         return -1;
  16.     }

  17.     printf("open successful ,fd = %d\n",fd);
  18.     
  19.     n = write(fd,buf,strlen(buf));
  20.     if(n < 0){
  21.         perror("Fail to write");
  22.         return -1;
  23.     }

  24.     printf("write %d bytes!\n",n);

  25.     n = write(fd,buf,strlen(buf));
  26.     if(n < 0){
  27.         perror("Fail to write");
  28.         return -1;
  29.     }

  30.     printf("write %d bytes!\n",n);
  31.     return 0;
  32. }
阅读(639) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~