关于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 成员作为参数被传递。

定义命令的实例:

/* 定义幻数 */
#define MEMDEV_IOC_MAGIC  'k'

/* 定义命令 */
#define MEMDEV_IOCPRINT   _IO(MEMDEV_IOC_MAGIC, 1)
#define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int)
#define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int)

#define MEMDEV_IOC_MAXNR 3

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 。

参数检查的实例:

/* 根据命令类型,检测参数空间是否可以访问 */
   if (_IOC_DIR(cmd) & _IOC_READ)
       err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
       //关于这里的_IOC_READ和VERIFY_WRITE是因为读用户内存,然后写到设备空间。
   else if (_IOC_DIR(cmd) & _IOC_WRITE)
        err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
   if (err) 
      return -EFAULT;

关于Ioctl函数的实现实例:

/* 根据命令,执行相应的操作 */
    switch(cmd) 
    {

      /* 打印当前设备信息 */
      case MEMDEV_IOCPRINT:
      	printk("<--- CMD MEMDEV_IOCPRINT Done--->\n\n");
        break;
      
      /* 获取参数 */
      case MEMDEV_IOCGETDATA: 
        ioarg = 1101;
        ret = __put_user(ioarg, (int *)arg);
        break;
      
      /* 设置参数 */
      case MEMDEV_IOCSETDATA: 
        ret = __get_user(ioarg, (int *)arg);
        printk("<--- In Kernel MEMDEV_IOCSETDATA ioarg = %d --->\n\n",ioarg);
        break;

      default:  
        return -EINVAL;
    }
    return ret;
}

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

头文件部分:

#ifndef _MEMDEV_H_
#define _MEMDEV_H_

#include <linux/ioctl.h>

#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 0   /*预设的mem的主设备号*/
#endif

#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2    /*设备数*/
#endif

#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif

/*mem设备描述结构体*/
struct mem_dev                                     
{                                                        
  char *data;                      
  unsigned long size;       
};

/* 定义幻数 */
#define MEMDEV_IOC_MAGIC  'k'

/* 定义命令 */
#define MEMDEV_IOCPRINT   _IO(MEMDEV_IOC_MAGIC, 1)
#define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int)
#define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int)

#define MEMDEV_IOC_MAXNR 3

#endif /* _MEMDEV_H_ */

驱动程序部分:

/*
程序:字符设备驱动关于Ioctl函数的使用
整体思路:①初始化驱动函数
					②对模拟字符设备的内存进行分配
					③编写file_opetrions所对应的函数
日期:2013年5月22日12:41:32
作者:CYX
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>

#include "memdev.h"  //本地调用的头文件

static mem_major = MEMDEV_MAJOR; //定义了主设备号,为全局变量,整个程序都可以使用。
module_param(mem_major, int, S_IRUGO);//定义模块参数
struct mem_dev *mem_devp; //定义设备结构指针,在宏中有定义,主要定义了模拟的那块内存
                          //在分配内存部分会进行结构体具体补充。
struct cdev cdev; //定义cdev结构体来描述字符设备,定义成全局的,整个程序都可以使用。

/*文件打开的函数
参数含义:inode 存储文件物理信息的结构(包含设备号)
          filp  文件的指针
*/

int mem_open(struct inode *inode, struct file *filp)
{
	struct mem_dev *dev; //设备描述结构体。
	int num = MINOR(inode->i_rdev); //取次设备号。
	if (num >= MEMDEV_NR_DEVS) 
     return -ENODEV;
  
  dev = &mem_devp[num]; //这是一个设备号的指针数组。
  
  /*将设备描述结构指针赋值给文件私有数据指针*/
  filp->private_data = dev;
}



/*IO操作*/
int (*ioctl)(structinode*inode,struct file *filp,unsigned int cmd,unsignedlong arg)
{
	 int err = 0;
   int ret = 0;
   int ioarg = 0;
   
/* 检测命令的有效性 */
   if (_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC) 
       return -EINVAL;
   if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR) 
       return -EINVAL;
       
/* 根据命令类型,检测参数空间是否可以访问 */
   if (_IOC_DIR(cmd) & _IOC_READ)
       err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
       //关于这里的_IOC_READ和VERIFY_WRITE是因为读用户内存,然后写到设备空间。
   else if (_IOC_DIR(cmd) & _IOC_WRITE)
        err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
   if (err) 
      return -EFAULT;
        
/* 根据命令,执行相应的操作 */
    switch(cmd) 
    {

      /* 打印当前设备信息 */
      case MEMDEV_IOCPRINT:
      	printk("<--- CMD MEMDEV_IOCPRINT Done--->\n\n");
        break;
      
      /* 获取参数 */
      case MEMDEV_IOCGETDATA: 
        ioarg = 1101;
        ret = __put_user(ioarg, (int *)arg); //__put_user,所以这里要使用参数检查。
        break;
      
      /* 设置参数 */
      case MEMDEV_IOCSETDATA: 
        ret = __get_user(ioarg, (int *)arg);
        printk("<--- In Kernel MEMDEV_IOCSETDATA ioarg = %d --->\n\n",ioarg);
        break;

      default:  
        return -EINVAL;
    }
    return ret;
}



/*文件释放函数*/
int mem_release(struct inode *inode, struct file *filp)
//函数参数:inode存储文件物理信息的结构(包含设备号)   file结构
{
  return 0;
}

/*读函数
函数参数:   struct file *filp(open的时候系统产生的结构,根据inode来的)
			      char __user *buf  存储读取数据的空间
			      size_t size       读取的大小  size_t应该就是typedef  unsigned int的类型
			      loff_t *ppos      当前文件指针的位置
*/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
	unsigned long p =  *ppos;//保存下文件指针的当前位置
  unsigned int count = size;
  int ret = 0;
  
  struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/
  //这个在文件的打开中已经定义了。private_data是一个空指针用来接收各种数据的传递
  
  /*判断读位置是否有效*/
  if (p >= MEMDEV_SIZE)
  //MEMDEV_SIZE是设备的大小,所以读取的大小绝对不可以大于设备的大小
  return 0;
  if (count > MEMDEV_SIZE - p)
  //假如读到的内容比剩下的还多,那么只能让你读到剩下的大小。count = MEMDEV_SIZE - p;
  count = MEMDEV_SIZE - p;
  
  /*读数据到用户空间*/
  if (copy_to_user(buf, (void*)(dev->data + p), count))
  {
    ret =  - EFAULT;
  }
  else
  {
    *ppos += count; //ppos,读写指针的位置
    ret = count;    //返回实际读到的数据量
    printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
  }

  return ret;
}


//写函数
static ssize_t mem_write (struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
	unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  
  struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/
  //和读函数时一个道理,读函数和写函数都不能够自己活得设备号,所以都要借助于打开函数。
  
  /*分析和获取有效的写长度*/
  if (p >= MEMDEV_SIZE)
    return 0;
  if (count > MEMDEV_SIZE - p)
    count = MEMDEV_SIZE - p;
    
  *从用户空间写入数据*/
  if (copy_from_user(dev->data + p, buf, count))
    ret =  - EFAULT;
  else
  {
    *ppos += count;
    ret = count;
    
    printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
  }
  
  return ret;
}  

/* seek文件定位函数 */
//定位函数最主要的作用就是当写入这块内存的时候文件指针到达2的位置
//而当读取的时候就开始从2的位置开始读,所以如果那样就永远也读不到自己想要的东东了。
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
//whence初始位置,offset文件偏移。
{
	loff_t newpos; //老谢说loff_t其实就是一个整型,我没有去追。
	switch(whence)
	 {
      case 0: /* SEEK_SET */
      	//由于SEEK_SET实际上是一个整形的,所以0就是头了。
        newpos = offset;
        break;

      case 1: /* SEEK_CUR */
        newpos = filp->f_pos + offset;//filp->f_pos从这个地方可以取当前位置。
        break;

      case 2: /* SEEK_END */
        newpos = MEMDEV_SIZE -1 + offset;
        break;

      default: /* can't happen */
        return -EINVAL;
    }
    //需要进行一下整体的判断。
    if ((newpos<0) || (newpos>MEMDEV_SIZE))
    	return -EINVAL;
    filp->f_pos = newpos;
    return newpos;
}


/*文件操作结构体*/ 
//这个是很重点的地方
static const struct file_operations mem_fops =
{
  .owner = THIS_MODULE,//函数名都可以自己定义  都是函数指针
  .llseek = mem_llseek,
  .read = mem_read,
  .write = mem_write,
  .open = mem_open,
  .release = mem_release,
};

/*模块加载函数*/
static int memdev_init (void)
{
		int result;
	  int i;
	  
	  dev_t devno = MKDEV(mem_major, 0);
	  
	  /*静态申请设备号*/
	  if (mem_major) //判断mem_major是否大于零,但是在宏中已经定义了它就是大于0的。
	  {
		  result = int register_chrdev_region(devno,2, "memdev")
		  /*三个参数的含义:devno 希望申请使用的设备号
		                    2     希望申请适用的设备数目
		                    "memdev" 设备名(体现在/proc/devices)*/
	  }
	  else
	  	{
	  		result = int alloc_chrdev_region(&devno, 0, 2,"memdev")
			/*四个参数的含义:&devno 分配到的设备号
		                    0      起始的次设备号
		                    2      需要分配的设备数目
		                  "memdev" 设备名(体现在/proc/devices)*/

			mem_major = MAJOR(devno); //宏定义的函数,提取主设备号。
			/*由于这个时候是动态申请的设备号,所以要对主设备号进行提取。*/
	  	}
	  //申请失败。
	  if (result < 0)
    return result;
    
    //由于在最开始处已经分配了cdev空间,所以此处不必再分配cdev空间了。
    cdev_init(&cdev, &mem_fops);  //初始化cdev成员。
    
    cdev.owner = THIS_MODULE;
    cdev.ops = &mem_fops;
    
    //执行成功以后,驱动程序就在内核中注册好了。
    cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);//添加设备
    
    /*为所定义的内存分配空间*/
    mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
    
    if (!mem_devp)  //头文件中定义有设备描述结构体
  	{
  		result =  - ENOMEM;
      goto fail_malloc;
  	}
  	//分配空间结束后为所分配出来的描述结构进行清零。
    memset(mem_devp, 0, sizeof(struct mem_dev)); //清零的操作
    
    /*为设备分配内存*/
  for (i=0; i < MEMDEV_NR_DEVS; i++) 
   {
     mem_devp[i].size = MEMDEV_SIZE; //这是4K的大小,宏中有定义。使用4K的内存来模拟一个设备。
     mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);//分配出4K的内存来给data指针。
     memset(mem_devp[i].data, 0, MEMDEV_SIZE);
   }
   return 0;
//goto到达的位置
fail_malloc: 
  unregister_chrdev_region(devno, 1);
  return result; //鬼知道这玩意整哪去了。。
}

/*模块卸载函数*/
static void memdev_exit(void)
{
  cdev_del(&cdev);      /*注销设备*/
  kfree(mem_devp);     /*释放设备结构体内存*/
  unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
}

MODULE_AUTHOR("CYX");
MODULE_LICENSE("GPL");

module_init(memdev_init); //这里指定的就是整个程序的入口
module_exit(memdev_exit);

应用程序部分:

#include <stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#include "memdev.h"  /* 包含命令定义 */

int main()
{
	int fd = 0;
	int cmd;
	int arg = 0;
	char Buf[4096];
	
	
	/*打开设备文件*/
	fd = open("/dev/memdev0",O_RDWR);
	if (fd < 0)
	{
		printf("Open Dev Mem0 Error!\n");
		return -1;
	}
	
	/* 调用命令MEMDEV_IOCPRINT (打印当前设备信息)*/
	printf("<--- Call MEMDEV_IOCPRINT --->\n");
	cmd = MEMDEV_IOCPRINT;
	if (ioctl(fd, cmd, &arg) < 0) //向内核传递
        {
        	printf("Call cmd MEMDEV_IOCPRINT fail\n");
        	return -1;
	      }
	
	
	/* 调用命令MEMDEV_IOCSETDATA */
	printf("<--- Call MEMDEV_IOCSETDATA --->\n");
	cmd = MEMDEV_IOCSETDATA;
	arg = 2007;
	if (ioctl(fd, cmd, &arg) < 0)
        {
        	printf("Call cmd MEMDEV_IOCSETDATA fail\n");
        	return -1;
	      }

	
	/* 调用命令MEMDEV_IOCGETDATA */
	printf("<--- Call MEMDEV_IOCGETDATA --->\n");
	cmd = MEMDEV_IOCGETDATA;
	if (ioctl(fd, cmd, &arg) < 0)
        {
        	printf("Call cmd MEMDEV_IOCGETDATA fail\n");
        	return -1;
	      }
	printf("<--- In User Space MEMDEV_IOCGETDATA Get Data is %d --->\n\n",arg);	
	
	close(fd);
	return 0;	
}

Makefile:

ifneq ($(KERNELRELEASE),)

obj-m := memdev.o

else
	
KDIR := /home/guoqian/4-3-1/linux-2.6.29
all:
	make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux-
clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers  modul*

endif








 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值