关于Ioctl

本文介绍了Ioctl机制在驱动开发中的作用及其实现方式。包括用户空间和驱动空间的Ioctl调用,命令定义方法,参数检查及函数实现示例。

一、Ioctl的作用:大部分驱动除了需要具备读写设备的能力外,还需要具备对硬件控制的
能力
。例如,要求设备报告错误信息,改变波特率,这些操作常常通过 ioctl 方法来实现。

二、Ioctl有用户空间的Ioctl和驱动空间的Ioctl

1.用户空间的Ioctl:使用ioctl 系统调用来控制设备,原型如下:

                               int ioctl(int fd,unsignedlong cmd,...)

原型中的点表示这是一个可选的参数,存在与否依赖于控制命令(第 2 个参数 )是否涉及
到与设备的数据交互。

2.驱动的Ioctl:函数原型如下:

int (*ioctl)(structinode*inode,struct file *filp,unsigned int cmd,unsignedlong arg)

cmd参数从用户空间传下来,可选的参数 arg 以一个unsignedlong 的形式传递,不管它是一个整数或一个指针。如果cmd命令不涉及数据传输,则第 3 个参数arg的值无任何意义。

三、Ioctl的实现

1、定义命令:

在编写ioctl代码之前,首先需要定义命令。为了防止对错误的设备使用正确的命令,命令号应该在系统范围内是唯一的。ioctl 命令编码被划分为几个位段,include/asm/ioctl.h中定义了这些位字段:类型(幻数),序号,传送方向,参数的大小Documentation/ioctl-number.txt文件中罗列了在内核中已经使用了的幻数。

定义 ioctl 命令的正确方法是使用 4 个位段, 这个列表中介绍的符号定义在<linux/ioctl.h>中:

①Type 幻数(类型): 表明哪个设备的命令,在参考了 ioctl-number.txt之后选出,8 位宽。

②Number 序号,表明设备命令中的第几个,8 位宽。

③Direction 数据传送的方向,可能的值是 _IOC_NONE(没有数据传输), _IOC_READ, _IOC_WRITE。 数据传送是从应用程序的观点来看待的,_IOC_READ 意思是从设备读。

④Size 用户数据的大小。(13/14位宽,视处理器而定)

老谢说要是一位一位在那定义太烦,所以就给了我们几个宏来定义。

内核提供了下列宏来帮助定义命令:
v _IO(type,nr)   没有参数的命令
v _IOR(type,nr,datatype)   从驱动中读数据
v _IOW(type,nr,datatype)   写数据到驱动
v _IOWR(type,nr,datatype)  双向传送,type 和 number 成员作为参数被传递。

定义命令的实例:

  1. <span style="font-size:18px;">/* 定义幻数 */  
  2. #define MEMDEV_IOC_MAGIC  'k'  
  3.   
  4. /* 定义命令 */  
  5. #define MEMDEV_IOCPRINT   _IO(MEMDEV_IOC_MAGIC, 1)  
  6. #define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int)  
  7. #define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int)  
  8.   
  9. #define MEMDEV_IOC_MAXNR 3</span>  

2.Ioctl定义结束后开始进行使用这个命令

①返回值:Ioctl函数的实现通常是根据命令执行的一个switch语句。但是,当命令号不能
匹配任何一个设备所支持的命令时,通常返回-EINVAL(“非法参数”)。

②参数:在Ioctl的参数中,如果是一个整数,可以直接使用。如果是指针,我们必须确保这个用户地址是有效的,因此使用前需进行正确的检查。

③参数的检查:

不需要检测:
v copy_from_user
v copy_to_user
v get_user
v put_user
需要检测:
v __get_user
v __put_user

参数检查的函数原型:int access_ok(int type, const void *addr, unsigned long size)

第一个参数是 VERIFY_READ 或者 VERIFY_WRITE,用来表明是读用户内存还是写用户内存。addr 参数是要操作的用户内存地址,size 是操作的长度。如果 ioctl 需要从用户空间读一个整数,那么size参数等于 sizeof(int)。access_ok 返回一个布尔值: 1 是成功(存取没问题)和 0 是失败(存取有问题),如果该函数返回失败, 则Ioctl应当返回

–EFAULT 。

参数检查的实例:

  1. <span style="font-size:18px;">/* 根据命令类型,检测参数空间是否可以访问 */  
  2.    if (_IOC_DIR(cmd) & _IOC_READ)  
  3.        err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));  
  4.        //关于这里的_IOC_READ和VERIFY_WRITE是因为读用户内存,然后写到设备空间。  
  5.    else if (_IOC_DIR(cmd) & _IOC_WRITE)  
  6.         err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));  
  7.    if (err)   
  8.       return -EFAULT;</span>  

关于Ioctl函数的实现实例:

  1. <span style="font-size:18px;">/* 根据命令,执行相应的操作 */  
  2.     switch(cmd)   
  3.     {  
  4.   
  5.       /* 打印当前设备信息 */  
  6.       case MEMDEV_IOCPRINT:  
  7.         printk("<--- CMD MEMDEV_IOCPRINT Done--->\n\n");  
  8.         break;  
  9.         
  10.       /* 获取参数 */  
  11.       case MEMDEV_IOCGETDATA:   
  12.         ioarg = 1101;  
  13.         ret = __put_user(ioarg, (int *)arg);  
  14.         break;  
  15.         
  16.       /* 设置参数 */  
  17.       case MEMDEV_IOCSETDATA:   
  18.         ret = __get_user(ioarg, (int *)arg);  
  19.         printk("<--- In Kernel MEMDEV_IOCSETDATA ioarg = %d --->\n\n",ioarg);  
  20.         break;  
  21.   
  22.       default:    
  23.         return -EINVAL;  
  24.     }  
  25.     return ret;  
  26. }</span>  

下面附上整个程序的源代码(来自例程)。

头文件部分:

  1. <span style="font-size:18px;">#ifndef _MEMDEV_H_  
  2. #define _MEMDEV_H_  
  3.   
  4. #include <linux/ioctl.h>  
  5.   
  6. #ifndef MEMDEV_MAJOR  
  7. #define MEMDEV_MAJOR 0   /*预设的mem的主设备号*/  
  8. #endif  
  9.   
  10. #ifndef MEMDEV_NR_DEVS  
  11. #define MEMDEV_NR_DEVS 2    /*设备数*/  
  12. #endif  
  13.   
  14. #ifndef MEMDEV_SIZE  
  15. #define MEMDEV_SIZE 4096  
  16. #endif  
  17.   
  18. /*mem设备描述结构体*/  
  19. struct mem_dev                                       
  20. {                                                          
  21.   char *data;                        
  22.   unsigned long size;         
  23. };  
  24.   
  25. /* 定义幻数 */  
  26. #define MEMDEV_IOC_MAGIC  'k'  
  27.   
  28. /* 定义命令 */  
  29. #define MEMDEV_IOCPRINT   _IO(MEMDEV_IOC_MAGIC, 1)  
  30. #define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int)  
  31. #define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int)  
  32.   
  33. #define MEMDEV_IOC_MAXNR 3  
  34.   
  35. #endif /* _MEMDEV_H_ */  
  36. </span>  

驱动程序部分:

  1. <span style="font-size:18px;">/* 
  2. 程序:字符设备驱动关于Ioctl函数的使用 
  3. 整体思路:①初始化驱动函数 
  4.                     ②对模拟字符设备的内存进行分配 
  5.                     ③编写file_opetrions所对应的函数 
  6. 日期:2013年5月22日12:41:32 
  7. 作者:CYX 
  8. */  
  9. #include <linux/module.h>  
  10. #include <linux/types.h>  
  11. #include <linux/fs.h>  
  12. #include <linux/errno.h>  
  13. #include <linux/mm.h>  
  14. #include <linux/sched.h>  
  15. #include <linux/init.h>  
  16. #include <linux/cdev.h>  
  17. #include <asm/io.h>  
  18. #include <asm/system.h>  
  19. #include <asm/uaccess.h>  
  20.   
  21. #include "memdev.h"  //本地调用的头文件  
  22.   
  23. static mem_major = MEMDEV_MAJOR; //定义了主设备号,为全局变量,整个程序都可以使用。  
  24. module_param(mem_major, int, S_IRUGO);//定义模块参数  
  25. struct mem_dev *mem_devp; //定义设备结构指针,在宏中有定义,主要定义了模拟的那块内存  
  26.                           //在分配内存部分会进行结构体具体补充。  
  27. struct cdev cdev; //定义cdev结构体来描述字符设备,定义成全局的,整个程序都可以使用。  
  28.   
  29. /*文件打开的函数 
  30. 参数含义:inode 存储文件物理信息的结构(包含设备号) 
  31.           filp  文件的指针 
  32. */  
  33.   
  34. int mem_open(struct inode *inode, struct file *filp)  
  35. {  
  36.     struct mem_dev *dev; //设备描述结构体。  
  37.     int num = MINOR(inode->i_rdev); //取次设备号。  
  38.     if (num >= MEMDEV_NR_DEVS)   
  39.      return -ENODEV;  
  40.     
  41.   dev = &mem_devp[num]; //这是一个设备号的指针数组。  
  42.     
  43.   /*将设备描述结构指针赋值给文件私有数据指针*/  
  44.   filp->private_data = dev;  
  45. }  
  46.   
  47.   
  48.   
  49. /*IO操作*/  
  50. int (*ioctl)(structinode*inode,struct file *filp,unsigned int cmd,unsignedlong arg)  
  51. {  
  52.      int err = 0;  
  53.    int ret = 0;  
  54.    int ioarg = 0;  
  55.      
  56. /* 检测命令的有效性 */  
  57.    if (_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC)   
  58.        return -EINVAL;  
  59.    if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR)   
  60.        return -EINVAL;  
  61.          
  62. /* 根据命令类型,检测参数空间是否可以访问 */  
  63.    if (_IOC_DIR(cmd) & _IOC_READ)  
  64.        err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));  
  65.        //关于这里的_IOC_READ和VERIFY_WRITE是因为读用户内存,然后写到设备空间。  
  66.    else if (_IOC_DIR(cmd) & _IOC_WRITE)  
  67.         err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));  
  68.    if (err)   
  69.       return -EFAULT;  
  70.           
  71. /* 根据命令,执行相应的操作 */  
  72.     switch(cmd)   
  73.     {  
  74.   
  75.       /* 打印当前设备信息 */  
  76.       case MEMDEV_IOCPRINT:  
  77.         printk("<--- CMD MEMDEV_IOCPRINT Done--->\n\n");  
  78.         break;  
  79.         
  80.       /* 获取参数 */  
  81.       case MEMDEV_IOCGETDATA:   
  82.         ioarg = 1101;  
  83.         ret = __put_user(ioarg, (int *)arg); //__put_user,所以这里要使用参数检查。  
  84.         break;  
  85.         
  86.       /* 设置参数 */  
  87.       case MEMDEV_IOCSETDATA:   
  88.         ret = __get_user(ioarg, (int *)arg);  
  89.         printk("<--- In Kernel MEMDEV_IOCSETDATA ioarg = %d --->\n\n",ioarg);  
  90.         break;  
  91.   
  92.       default:    
  93.         return -EINVAL;  
  94.     }  
  95.     return ret;  
  96. }  
  97.   
  98.   
  99.   
  100. /*文件释放函数*/  
  101. int mem_release(struct inode *inode, struct file *filp)  
  102. //函数参数:inode存储文件物理信息的结构(包含设备号)   file结构  
  103. {  
  104.   return 0;  
  105. }  
  106.   
  107. /*读函数 
  108. 函数参数:   struct file *filp(open的时候系统产生的结构,根据inode来的) 
  109.                   char __user *buf  存储读取数据的空间 
  110.                   size_t size       读取的大小  size_t应该就是typedef  unsigned int的类型 
  111.                   loff_t *ppos      当前文件指针的位置 
  112. */  
  113. static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)  
  114. {  
  115.     unsigned long p =  *ppos;//保存下文件指针的当前位置  
  116.   unsigned int count = size;  
  117.   int ret = 0;  
  118.     
  119.   struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/  
  120.   //这个在文件的打开中已经定义了。private_data是一个空指针用来接收各种数据的传递  
  121.     
  122.   /*判断读位置是否有效*/  
  123.   if (p >= MEMDEV_SIZE)  
  124.   //MEMDEV_SIZE是设备的大小,所以读取的大小绝对不可以大于设备的大小  
  125.   return 0;  
  126.   if (count > MEMDEV_SIZE - p)  
  127.   //假如读到的内容比剩下的还多,那么只能让你读到剩下的大小。count = MEMDEV_SIZE - p;  
  128.   count = MEMDEV_SIZE - p;  
  129.     
  130.   /*读数据到用户空间*/  
  131.   if (copy_to_user(buf, (void*)(dev->data + p), count))  
  132.   {  
  133.     ret =  - EFAULT;  
  134.   }  
  135.   else  
  136.   {  
  137.     *ppos += count; //ppos,读写指针的位置  
  138.     ret = count;    //返回实际读到的数据量  
  139.     printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);  
  140.   }  
  141.   
  142.   return ret;  
  143. }  
  144.   
  145.   
  146. //写函数  
  147. static ssize_t mem_write (struct file *filp, const char __user *buf, size_t size, loff_t *ppos)  
  148. {  
  149.     unsigned long p =  *ppos;  
  150.   unsigned int count = size;  
  151.   int ret = 0;  
  152.     
  153.   struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/  
  154.   //和读函数时一个道理,读函数和写函数都不能够自己活得设备号,所以都要借助于打开函数。  
  155.     
  156.   /*分析和获取有效的写长度*/  
  157.   if (p >= MEMDEV_SIZE)  
  158.     return 0;  
  159.   if (count > MEMDEV_SIZE - p)  
  160.     count = MEMDEV_SIZE - p;  
  161.       
  162.   *从用户空间写入数据*/  
  163.   if (copy_from_user(dev->data + p, buf, count))  
  164.     ret =  - EFAULT;  
  165.   else  
  166.   {  
  167.     *ppos += count;  
  168.     ret = count;  
  169.       
  170.     printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);  
  171.   }  
  172.     
  173.   return ret;  
  174. }    
  175.   
  176. /* seek文件定位函数 */  
  177. //定位函数最主要的作用就是当写入这块内存的时候文件指针到达2的位置  
  178. //而当读取的时候就开始从2的位置开始读,所以如果那样就永远也读不到自己想要的东东了。  
  179. static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)  
  180. //whence初始位置,offset文件偏移。  
  181. {  
  182.     loff_t newpos; //老谢说loff_t其实就是一个整型,我没有去追。  
  183.     switch(whence)  
  184.      {  
  185.       case 0: /* SEEK_SET */  
  186.         //由于SEEK_SET实际上是一个整形的,所以0就是头了。  
  187.         newpos = offset;  
  188.         break;  
  189.   
  190.       case 1: /* SEEK_CUR */  
  191.         newpos = filp->f_pos + offset;//filp->f_pos从这个地方可以取当前位置。  
  192.         break;  
  193.   
  194.       case 2: /* SEEK_END */  
  195.         newpos = MEMDEV_SIZE -1 + offset;  
  196.         break;  
  197.   
  198.       default/* can't happen */  
  199.         return -EINVAL;  
  200.     }  
  201.     //需要进行一下整体的判断。  
  202.     if ((newpos<0) || (newpos>MEMDEV_SIZE))  
  203.         return -EINVAL;  
  204.     filp->f_pos = newpos;  
  205.     return newpos;  
  206. }  
  207.   
  208.   
  209. /*文件操作结构体*/   
  210. //这个是很重点的地方  
  211. static const struct file_operations mem_fops =  
  212. {  
  213.   .owner = THIS_MODULE,//函数名都可以自己定义  都是函数指针  
  214.   .llseek = mem_llseek,  
  215.   .read = mem_read,  
  216.   .write = mem_write,  
  217.   .open = mem_open,  
  218.   .release = mem_release,  
  219. };  
  220.   
  221. /*模块加载函数*/  
  222. static int memdev_init (void)  
  223. {  
  224.         int result;  
  225.       int i;  
  226.         
  227.       dev_t devno = MKDEV(mem_major, 0);  
  228.         
  229.       /*静态申请设备号*/  
  230.       if (mem_major) //判断mem_major是否大于零,但是在宏中已经定义了它就是大于0的。  
  231.       {  
  232.           result = int register_chrdev_region(devno,2, "memdev")  
  233.           /*三个参数的含义:devno 希望申请使用的设备号 
  234.                             2     希望申请适用的设备数目 
  235.                             "memdev" 设备名(体现在/proc/devices)*/  
  236.       }  
  237.       else  
  238.         {  
  239.             result = int alloc_chrdev_region(&devno, 0, 2,"memdev")  
  240.             /*四个参数的含义:&devno 分配到的设备号 
  241.                             0      起始的次设备号 
  242.                             2      需要分配的设备数目 
  243.                           "memdev" 设备名(体现在/proc/devices)*/  
  244.   
  245.             mem_major = MAJOR(devno); //宏定义的函数,提取主设备号。  
  246.             /*由于这个时候是动态申请的设备号,所以要对主设备号进行提取。*/  
  247.         }  
  248.       //申请失败。  
  249.       if (result < 0)  
  250.     return result;  
  251.       
  252.     //由于在最开始处已经分配了cdev空间,所以此处不必再分配cdev空间了。  
  253.     cdev_init(&cdev, &mem_fops);  //初始化cdev成员。  
  254.       
  255.     cdev.owner = THIS_MODULE;  
  256.     cdev.ops = &mem_fops;  
  257.       
  258.     //执行成功以后,驱动程序就在内核中注册好了。  
  259.     cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);//添加设备  
  260.       
  261.     /*为所定义的内存分配空间*/  
  262.     mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);  
  263.       
  264.     if (!mem_devp)  //头文件中定义有设备描述结构体  
  265.     {  
  266.         result =  - ENOMEM;  
  267.       goto fail_malloc;  
  268.     }  
  269.     //分配空间结束后为所分配出来的描述结构进行清零。  
  270.     memset(mem_devp, 0, sizeof(struct mem_dev)); //清零的操作  
  271.       
  272.     /*为设备分配内存*/  
  273.   for (i=0; i < MEMDEV_NR_DEVS; i++)   
  274.    {  
  275.      mem_devp[i].size = MEMDEV_SIZE; //这是4K的大小,宏中有定义。使用4K的内存来模拟一个设备。  
  276.      mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);//分配出4K的内存来给data指针。  
  277.      memset(mem_devp[i].data, 0, MEMDEV_SIZE);  
  278.    }  
  279.    return 0;  
  280. //goto到达的位置  
  281. fail_malloc:   
  282.   unregister_chrdev_region(devno, 1);  
  283.   return result; //鬼知道这玩意整哪去了。。  
  284. }  
  285.   
  286. /*模块卸载函数*/  
  287. static void memdev_exit(void)  
  288. {  
  289.   cdev_del(&cdev);      /*注销设备*/  
  290.   kfree(mem_devp);     /*释放设备结构体内存*/  
  291.   unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/  
  292. }  
  293.   
  294. MODULE_AUTHOR("CYX");  
  295. MODULE_LICENSE("GPL");  
  296.   
  297. module_init(memdev_init); //这里指定的就是整个程序的入口  
  298. module_exit(memdev_exit);</span>  

应用程序部分:

  1. <span style="font-size:18px;">#include <stdio.h>  
  2. #include<sys/types.h>  
  3. #include<sys/stat.h>  
  4. #include<fcntl.h>  
  5.   
  6. #include "memdev.h"  /* 包含命令定义 */  
  7.   
  8. int main()  
  9. {  
  10.     int fd = 0;  
  11.     int cmd;  
  12.     int arg = 0;  
  13.     char Buf[4096];  
  14.       
  15.       
  16.     /*打开设备文件*/  
  17.     fd = open("/dev/memdev0",O_RDWR);  
  18.     if (fd < 0)  
  19.     {  
  20.         printf("Open Dev Mem0 Error!\n");  
  21.         return -1;  
  22.     }  
  23.       
  24.     /* 调用命令MEMDEV_IOCPRINT (打印当前设备信息)*/  
  25.     printf("<--- Call MEMDEV_IOCPRINT --->\n");  
  26.     cmd = MEMDEV_IOCPRINT;  
  27.     if (ioctl(fd, cmd, &arg) < 0) //向内核传递  
  28.         {  
  29.             printf("Call cmd MEMDEV_IOCPRINT fail\n");  
  30.             return -1;  
  31.           }  
  32.       
  33.       
  34.     /* 调用命令MEMDEV_IOCSETDATA */  
  35.     printf("<--- Call MEMDEV_IOCSETDATA --->\n");  
  36.     cmd = MEMDEV_IOCSETDATA;  
  37.     arg = 2007;  
  38.     if (ioctl(fd, cmd, &arg) < 0)  
  39.         {  
  40.             printf("Call cmd MEMDEV_IOCSETDATA fail\n");  
  41.             return -1;  
  42.           }  
  43.   
  44.       
  45.     /* 调用命令MEMDEV_IOCGETDATA */  
  46.     printf("<--- Call MEMDEV_IOCGETDATA --->\n");  
  47.     cmd = MEMDEV_IOCGETDATA;  
  48.     if (ioctl(fd, cmd, &arg) < 0)  
  49.         {  
  50.             printf("Call cmd MEMDEV_IOCGETDATA fail\n");  
  51.             return -1;  
  52.           }  
  53.     printf("<--- In User Space MEMDEV_IOCGETDATA Get Data is %d --->\n\n",arg);     
  54.       
  55.     close(fd);  
  56.     return 0;     
  57. }  
  58. </span>  

Makefile:

  1. <span style="font-size:18px;">ifneq ($(KERNELRELEASE),)  
  2.   
  3. obj-m := memdev.o  
  4.   
  5. else  
  6.       
  7. KDIR := /home/guoqian/4-3-1/linux-2.6.29  
  8. all:  
  9.     make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux-  
  10. clean:  
  11.     rm -f *.ko *.o *.mod.o *.mod.c *.symvers  modul*  
  12.   
  13. endif</span>  




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值