天天看点

LDD3源码学习日记<五>

这篇是关于ioctl的,全篇就这一个知识点。

首先看函数原型:int (*ioctl)(struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg);

其中,cmd是预先定义好的一些命令编号,对应要求ioctl执行的命令。arg是与cmd配合使用的参数。ioctl的实现一般是通过一个大的switch语句,根据cmd参数执行不同的操作。所以,在实现ioctl函数之前,要先定义好cmd对应的命令编号。为了防止发生混淆,命令编号应该在系统范围内是唯一的。为此,Linux内核将命令编号分为4个部分,即4个位段,分别是:

   type:       幻数(magic number),它占8位。幻数就是一个标志,代表一个(类)对象,选好后在整个驱动程序中都用它,在                     Documentation/ioctl-number.text中罗列了内核使用的所有幻数。scull使用字符’k’作为幻数。

number:  序数,即顺序编号,它也占8位。

direction:如果相关命令涉及到数据的传输,则这个位段表示数据传输的方向,可用的值包括_IOC_NONE(没有数据传输),                        _IOC_READ(读)、_IOC_WRITE(写)、_IOC_READ | _IOC_WRITE(双向传输数据)。注意,数据传输方向是从应用程序的角    度看的,也就是说_IOC_READ意味着从设备中读数据,所以驱动程序必须向用户空间写数据。

size:所涉及的用户数据大小。这个位段的宽度与体系结构有关,通常是13或14位。

_IO(type, nr),用于构造无数据传输的命令编号。

_IOR(type, nr, datatype),用于构造从驱动程序中读取数据的命令编号。

_IOW(type, nr, datatype),用于构造向设备写入数据的命令编号。

_IOWR(type, nr, datatype),用于双向传输命令编号。

其中,type和number位段从以上宏的参数中传入,size位段通过对datatype参数取sizeof获得。

下面是scull.h中对各种指令的定义:

/* Use 'k' as magic number */
#define SCULL_IOC_MAGIC  'k'	//定义幻数是字符“k”
#define SCULL_IOCRESET    _IO(SCULL_IOC_MAGIC, 0)	

/*
 *      'S'表示通过arg指针来设置
 * 	'T'表示通过参数arg的值来设置	
 * 	’G’代表通过参数arg指向的地址返回请求的值
 *	’Q’代表通过ioctl函数的返回值返回请求的值。
 * 	’X’代表通过参数arg指向的内容设置,再把原来的值通过arg指向的地址返回。
 * 	’H’代表通过参数arg的值直接设置,再通过ioctl函数的返回值将原来的值返回。
 */
 
/*********************************************************/
/*********************************************************
定义命令SCULL_IOCSQUANTUM,该命令表示通过参数arg指向的内容设置quantum。
定义命令SCULL_IOCSQSET,该命令表示通过参数arg指向的内容设置qset。
定义命令SCULL_IOCTQUANTUM,该命令表示通过参数arg的值直接设置quantum。
定义命令SCULL_IOCTQSET,该命令表示通过参数arg的值直接设置qset。
定义命令SCULL_IOCGQUANTUM,该命令表示通过参数arg指向的地址返回quantum。
定义命令SCULL_IOCGQSET,该命令表示通过参数arg指向的地址返回qset。
定义命令SCULL_IOCQQUANTUM,该命令表示通过ioctl的返回值返回quantum。
定义命令SCULL_IOCQQSET,该命令表示通过ioctl的返回值返回qset。
定义命令SCULL_IOCXQUANTUM,该命令表示通过参数arg指向的内容设置quantum,然后,再把quantum原来的值写入arg指向的地址返回。
定义命令SCULL_IOCXQSET,该命令表示通过参数arg指向的内容设置qset,然后,再把qset原来的值写入arg指向的地址返回。
定义命令SCULL_IOCHQUANTUM,该命令表示通过参数arg的值直接设置quantum,然后,再通过ioctl的返回值返回quantum原来的值。
定义命令SCULL_IOCHQSET,该命令表示通过参数arg的值直接设置qset,然后,再通过ioctl的返回值返回qset原来的值。

定义命令SCULL_P_IOCTSIZE,该命令表示通过参数arg的值直接设置scull_p_buffer。
定义命令SCULL_P_IOCQSIZE,该命令表示通过ioctl的返回值返回scull_p_buffer。

定义SCULL_IOC_MAXNR为14,代表一共有14个命令。
/**********************************************************************/
#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC,  1, int)
#define SCULL_IOCSQSET    _IOW(SCULL_IOC_MAGIC,  2, int)
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC,   3)
#define SCULL_IOCTQSET    _IO(SCULL_IOC_MAGIC,   4)
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC,  5, int)
#define SCULL_IOCGQSET    _IOR(SCULL_IOC_MAGIC,  6, int)
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC,   7)
#define SCULL_IOCQQSET    _IO(SCULL_IOC_MAGIC,   8)
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)
#define SCULL_IOCXQSET    _IOWR(SCULL_IOC_MAGIC,10, int)
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC,  11)
#define SCULL_IOCHQSET    _IO(SCULL_IOC_MAGIC,  12)

#define SCULL_P_IOCTSIZE _IO(SCULL_IOC_MAGIC,   13)
#define SCULL_P_IOCQSIZE _IO(SCULL_IOC_MAGIC,   14)

#define SCULL_IOC_MAXNR 14
           

从调用方的角度来讲,传送和接受参数的途径可以归纳为6种:

通过指针设置,通过值设置;通过指针获取,通过返回值获取;通过指针交换,通过值交换;一般正常的驱动程序数据交换模式会保持统一,要么用指针,要么用数值。这里只是为了学习列举了所有的情况。

再看ioctl函数的代码:

int scull_ioctl(struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg)
{
	int err = 0, tmp;
	int retval = 0;
    	/*如果_IOC_TYPE(cmd) != SCULL_IOC_MAGIC,即cmd的幻数不是’k’,则退出*/
	if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
	/*如果cmd的序数大于14,则退出*/
	if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;

	/*如果要使用arg指向的地址进行数据的读或写,必须保证对该地址的访问是合法的*/
	/*这可通过access_ok函数来验证,如果访问不合法,则退出。*/
	if (_IOC_DIR(cmd) & _IOC_READ)
		err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
	else if (_IOC_DIR(cmd) & _IOC_WRITE)
		err =  !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
	if (err) return -EFAULT;
	/*进入switch判断*/
	switch(cmd) {

	  case SCULL_IOCRESET:		//赋默认值操作
		scull_quantum = SCULL_QUANTUM;
		scull_qset = SCULL_QSET;
		break;
        
	  case SCULL_IOCSQUANTUM: /* Set: arg points to the value */
		if (! capable (CAP_SYS_ADMIN))
			return -EPERM;
		retval = __get_user(scull_quantum, (int __user *)arg);
		break;

	  case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
		if (! capable (CAP_SYS_ADMIN))
			return -EPERM;
		scull_quantum = arg;
		break;

	  case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */
		retval = __put_user(scull_quantum, (int __user *)arg);
		break;

	  case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */
		return scull_quantum;

	  case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */
		if (! capable (CAP_SYS_ADMIN))
			return -EPERM;
		tmp = scull_quantum;
		retval = __get_user(scull_quantum, (int __user *)arg);
		if (retval == 0)
			retval = __put_user(tmp, (int __user *)arg);
		break;

	  case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */
		if (! capable (CAP_SYS_ADMIN))
			return -EPERM;
		tmp = scull_quantum;
		scull_quantum = arg;
		return tmp;
        
	  case SCULL_IOCSQSET:
		if (! capable (CAP_SYS_ADMIN))
			return -EPERM;
		retval = __get_user(scull_qset, (int __user *)arg);
		break;

	  case SCULL_IOCTQSET:
		if (! capable (CAP_SYS_ADMIN))
			return -EPERM;
		scull_qset = arg;
		break;

	  case SCULL_IOCGQSET:
		retval = __put_user(scull_qset, (int __user *)arg);
		break;

	  case SCULL_IOCQQSET:
		return scull_qset;

	  case SCULL_IOCXQSET:
		if (! capable (CAP_SYS_ADMIN))
			return -EPERM;
		tmp = scull_qset;
		retval = __get_user(scull_qset, (int __user *)arg);
		if (retval == 0)
			retval = put_user(tmp, (int __user *)arg);
		break;

	  case SCULL_IOCHQSET:
		if (! capable (CAP_SYS_ADMIN))
			return -EPERM;
		tmp = scull_qset;
		scull_qset = arg;
		return tmp;


	  case SCULL_P_IOCTSIZE:
		scull_p_buffer = arg;
		break;

	  case SCULL_P_IOCQSIZE:
		return scull_p_buffer;


	  default:  /* redundant, as cmd was checked against MAXNR */
		return -ENOTTY;
	}
	return retval;

}
           

驱动程序与用户空间传递数据,采用的是__put_user和__get_user函数,相比copy_to_user和copy_from_user来说,这些函数在处理1、2、4、8个字节的数据传输时,效率更高。另外,scull允许任何用户查询quantum和qset的大小,但只允许被授权的用户修改quantum和qset的值。这种权能的检查是通过capable()函数实现的。

然后是写测试程序,结合指令,代码如下:

#include <sys/types.h>  
#include <sys/stat.h>  
#include <sys/ioctl.h>  
#include <fcntl.h>  
#include <stdio.h>  
#include "scull_ioctl.h"  
  
#define SCULL_DEVICE "/dev/scull0"  
  
int main(int argc, char *argv[])  
{  
    int fd = 0;  
    int quantum = 8000;  
    int quantum_old = 0;  
    int qset = 2000;  
    int qset_old = 0;  
      
    fd = open(SCULL_DEVICE, O_RDWR);  
    if(fd < 0)  
    {  
        printf("open scull device error!\n");  
        return 0;  
    }  
  
    printf("SCULL_IOCSQUANTUM: quantum = %d\n", quantum);  
    ioctl(fd, SCULL_IOCSQUANTUM, &quantum); //通过指针来设置 
    quantum -= 500;  
    printf("SCULL_IOCTQUANTUM: quantum = %d\n", quantum);      
    ioctl(fd, SCULL_IOCTQUANTUM, quantum);  
      
  
    return 0;  
}  
           

再对ioctl函数进行修改,如下:

int scull_ioctl(struct inode *inode, struct file *filp,  
                 unsigned int cmd, unsigned long arg)  
{  
  
    int err = 0, tmp;  
    int retval = 0;  
   
    if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;  
    if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;  
  
    if (_IOC_DIR(cmd) & _IOC_READ)  
        err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));  
    else if (_IOC_DIR(cmd) & _IOC_WRITE)  
        err =  !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));  
    if (err) return -EFAULT;  
  
    switch(cmd) {  
  
      case SCULL_IOCRESET:  
        scull_quantum = SCULL_QUANTUM;  
        scull_qset = SCULL_QSET;  
        printk("SCULL_IOCRESET: scull_quantum = %d, scull_qset = %d\n", scull_quantum, scull_qset);  
        break;  
          
      case SCULL_IOCSQUANTUM: /* Set: arg points to the value */  
        if (! capable (CAP_SYS_ADMIN))  
            return -EPERM;  
        retval = __get_user(scull_quantum, (int __user *)arg);  
        printk("SCULL_IOCSQUANTUM: scull_quantum = %d\n", scull_quantum);  
        break;  
  
      case SCULL_IOCTQUANTUM: /* Tell: arg is the value */  
        if (! capable (CAP_SYS_ADMIN))  
            return -EPERM;  
        scull_quantum = arg;  
        printk("SCULL_IOCTQUANTUM: scull_quantum = %d\n", scull_quantum);  
        break;   
  
      default:  /* redundant, as cmd was checked against MAXNR */  
        return -ENOTTY;  
    }  
    return retval;  
  
}  
           

测试程序的头文件scull_ioctl.h:

#ifndef _SCULL_H_
#define _SCULL_H_

/* Use 'k' as magic number */
#define SCULL_IOC_MAGIC  'k'	//定义幻数是字符“k”
#define SCULL_IOCRESET    _IO(SCULL_IOC_MAGIC, 0)	

/*
 *      'S'表示通过arg指针来设置
 * 	'T'表示通过参数arg的值来设置	
 * 	’G’代表通过参数arg指向的地址返回请求的值
 *	’Q’代表通过ioctl函数的返回值返回请求的值。
 * 	’X’代表通过参数arg指向的内容设置,再把原来的值通过arg指向的地址返回。
 * 	’H’代表通过参数arg的值直接设置,再通过ioctl函数的返回值将原来的值返回。
 */
 
/*********************************************************/
/*********************************************************
定义命令SCULL_IOCSQUANTUM,该命令表示通过参数arg指向的内容设置quantum。
定义命令SCULL_IOCSQSET,该命令表示通过参数arg指向的内容设置qset。
定义命令SCULL_IOCTQUANTUM,该命令表示通过参数arg的值直接设置quantum。
定义命令SCULL_IOCTQSET,该命令表示通过参数arg的值直接设置qset。
定义命令SCULL_IOCGQUANTUM,该命令表示通过参数arg指向的地址返回quantum。
定义命令SCULL_IOCGQSET,该命令表示通过参数arg指向的地址返回qset。
定义命令SCULL_IOCQQUANTUM,该命令表示通过ioctl的返回值返回quantum。
定义命令SCULL_IOCQQSET,该命令表示通过ioctl的返回值返回qset。
定义命令SCULL_IOCXQUANTUM,该命令表示通过参数arg指向的内容设置quantum,然后,再把quantum原来的值写入arg指向的地址返回。
定义命令SCULL_IOCXQSET,该命令表示通过参数arg指向的内容设置qset,然后,再把qset原来的值写入arg指向的地址返回。
定义命令SCULL_IOCHQUANTUM,该命令表示通过参数arg的值直接设置quantum,然后,再通过ioctl的返回值返回quantum原来的值。
定义命令SCULL_IOCHQSET,该命令表示通过参数arg的值直接设置qset,然后,再通过ioctl的返回值返回qset原来的值。

定义命令SCULL_P_IOCTSIZE,该命令表示通过参数arg的值直接设置scull_p_buffer。
定义命令SCULL_P_IOCQSIZE,该命令表示通过ioctl的返回值返回scull_p_buffer。

定义SCULL_IOC_MAXNR为14,代表一共有14个命令。
/**********************************************************************/
#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC,  1, int)
#define SCULL_IOCSQSET    _IOW(SCULL_IOC_MAGIC,  2, int)
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC,   3)
#define SCULL_IOCTQSET    _IO(SCULL_IOC_MAGIC,   4)
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC,  5, int)
#define SCULL_IOCGQSET    _IOR(SCULL_IOC_MAGIC,  6, int)
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC,   7)
#define SCULL_IOCQQSET    _IO(SCULL_IOC_MAGIC,   8)
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)
#define SCULL_IOCXQSET    _IOWR(SCULL_IOC_MAGIC,10, int)
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC,  11)
#define SCULL_IOCHQSET    _IO(SCULL_IOC_MAGIC,  12)

#define SCULL_P_IOCTSIZE _IO(SCULL_IOC_MAGIC,   13)
#define SCULL_P_IOCQSIZE _IO(SCULL_IOC_MAGIC,   14)

#define SCULL_IOC_MAXNR 14

#endif /* _SCULL_H_ */
           

为了看得清楚,测试程序值测试了其中的两条指令,S和T,在电脑端的Linux端编译好后考到开发板上,结果如下:

LDD3源码学习日记&lt;五&gt;

每调用一次ioctl,都会执行一次内核空间的打印函数。

参考博客:http://blog.csdn.net/liuhaoyutz/article/details/7386254

继续阅读