linux设备驱动程序-第六章1(ioctl操作)学习总结

一、前言
在第三章,我们已经构建了一个结构完整的可读写设备驱动程序。但一个实际可用的设备除了提供同步读取和写入之外,还会提供更多的功能。而现在我们拥有调试工具,掌握相关的调试方法,并且对并发问题有了坚实的理解。这样,构建更高级的驱动程序相对容易了。
在本节,我们实现ioctl系统调用,他用于设备控制的公共接口。
二、ioctl
除了读取和写入设备,大部分驱动程序还需要另外一种能力,即通过设备驱动程序执行各种类型的硬件控制。
在用户空间,ioctl系统调用具有如下原型:

int ioctl(int fd,unsigned long cmd,...);

由于使用一连串的“…”的缘故,这个模型在Unix系统调用中显得比较特别,通常这些点表示可变树木的参数表,代表可选参数,习惯上用char *arg定义。使用指针可以向ioctl调用传递任意数据,这样设备可以与用户空间交换任意数量的数据。
驱动程序的ioctl方法模型:

int (*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);

选择ioctl命令
在编写ioctl代码之前,需要选择对应不同命令的编号。为了防止对错误的设备使用正确的命令,命令号应该在系统范围内唯一。为方便程序员创建唯一的ioctl命令号,每个命令号被分成多个位字段。linux的第一个版本使用了一个16位整数:高八位是与设备相关的“幻数”,低八位是一个序列号吗,在设备内是唯一的。
type:幻数。选择一个号码,并在整个驱动程序中使用这个号码,这个字段有8位宽。
number:序数(顺序编号)。它也是8位宽。
derection:该字段定义数据传输方向。数据传输方向是由应用程序方向看的。
size:涉及的用户数据大小。与体系结构有关,通常是13位或14位。系统并不强制使用这个位字段。
包含定义一些构造命令编号的宏

_IO(type,nr)/*没有参数的命令*/
_IOR(type, nr, datatype)/*从驱动中读数据*/
_IOW(type,nr,datatype)/*写数据*/
_IOWR(type,nr,datatype)/*双向传送*/
/*type 和 number 成员作为参数被传递, 并且 size 成员通过应用 sizeof 到 datatype 参数而得到*/

/*获取对应数据命令*/
_IOC_DIR(nr)
_IOC_TYPE(nr)
_IOC_NR(nr)
_IOC_SIZE(nr)

使用ioctll参数
在分析scull驱动程序的ioctl程序之前,怎么使用附加参数。如果它是一个整数,那么很简单,直接使用即可。如果使用指针,就需要注意一些问题。
当一个指针指向用户空间时,必须确保指向的用户空间是合法的。对未验证的用户空间指针的访问,可能导致内核oops,系统崩溃和安全问题。驱动程序应该负责对每个用到的用户空间地址做适当的检查。
首先通过函数access_ok验证地址(不是传输数据),定义如下(面向内核的):

int access_ok(int type,const void *addr,unsigned long size);

关于access_ok,有两点有趣之处需要注意。第一,它并没有完成验证内存的全部工作,而只检查所引用的内存是否为进程对应访问权限的区域内,特别是确保访问地址没有指向内核空间的内存区。第二,大部分驱动程序代码中不需要真正调用access_ok,因为后面讲到的内存管理程序会处理它。

在调用access_ok之后,驱动程序可以安全的进行实际的数据传输。除了copy_from_user和 copy_to_user之外,程序员还可以使用最常用的数据大小(1,2,4和8)优化过的程序。

/*将datum写到用户空间,确保进程可以写入指定的内存地址,并在成功时返回0,出错时返回-EFAULT。需要使用access_ok后调用*/
put_user(datum,ptr);
__put_user(datum,ptr);

/*从用户空间接收一个数据。除了传输方向相反之外,它们与put_user差不多同样,__get_user应该在操作地址已被access_ok检查后使用*/
get_user(local,ptr);
__get_user(local,ptr);

三、实例-ioctl
scull_ioctl.h

ifndef SCULL_IOCTL_H                                                       
#define SCULL_IOCTL_H
 
//定义幻数
#define SCULL_IOC_MAGIC 'J'

#define SCULL_IOCRESET  _IO(SCULL_IOC_MAGIC,0)
 
  /*
  *  S means "Set" through a ptr
  *  T means "Tell" directly with the argument value
  *  G means "Get" reply by setting through a pointer
 *  Q menas "Query" response is on thereturn value
 *  X means "eXchange" switch G and S atomically
 *  H means "sHift" switch T and Q atomically
 */
#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_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)

/*
 * the other entities only have "Tell" and "Query",because they're
 * not printed in the book,and there's no need to have all six.
 * the previous stuff was only these to show different ways to do it.
 * */
#define SUCLL_P_IOCTSIZE _IO(SCULL_IOC_MAGIC,13)
#define SCULL_P_IOCQSIZE _IO(SCULL_IOC_MAGIC,14)

#define SCULL_IOC_MAXNR 14
 
#endif  

scull_ioctl.c

#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>

#include "scull_ioctl.h"

MODULE_LICENSE("Dual BSD/GPL");

//设备私有变量
struct scull_dev {
    int data;
    struct cdev cdev;
}dev;

#define SCULL_DEV_MAJOR 0
static int scull_major = SCULL_DEV_MAJOR;
module_param(scull_major,int,S_IRUGO);

struct class *scull_class;
struct cdev cdev;

long scull_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
{
    int err=0,retval=0;
    int temp;

    //判断命令幻数是否匹配
    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 *)arg,_IOC_SIZE(cmd));
    if(_IOC_DIR(cmd) & _IOC_WRITE)
        err = !access_ok(VERIFY_READ,(void *)arg,_IOC_SIZE(cmd));
    if(err)
        return -EFAULT;

    switch(cmd){
        case SCULL_IOCRESET://数据清零
            dev.data = 0;
            printk(KERN_EMERG "SCULL_IOCRESET\n");
            break;
        case SCULL_IOCSQUANTUM://指针赋值
            if(!capable(CAP_SYS_ADMIN))
                return -EPERM;
            retval=__get_user(dev.data,(int __user *)arg);
            printk(KERN_EMERG "SCULL_IOCSQUANTUM\n");
            break;
        case SCULL_IOCTQUANTUM://数值赋值
            if(!capable(CAP_SYS_ADMIN))
                return -EPERM;
            dev.data=arg;
            printk(KERN_EMERG "SCULL_IOCTQUANTUM\n");
            break;
        case SCULL_IOCGQUANTUM://读取指针
            retval=__put_user(dev.data,(int __user *)arg);
            printk(KERN_EMERG "SCULL_IOCGQUANTUM\n");
            break;
        case SCULL_IOCQQUANTUM://读取数值
            printk(KERN_EMERG "SCULL_IOCQQUANTUM\n");
            return dev.data;
        case SCULL_IOCXQUANTUM://交换“s”和“g”
            if(!capable(CAP_SYS_ADMIN))
                return -EPERM;
            temp = dev.data;
            retval = __get_user(dev.data,(int __user *)arg);
            if(0 == retval)//返回成功
                retval = __put_user(temp,(int __user *)arg);
            printk(KERN_EMERG "SCULL_IOCXQUANTUM\n");
            return retval;
        case SCULL_IOCHQUANTUM://交换"t"和“q”
            if(!capable(CAP_SYS_ADMIN))
                return -EPERM;
            temp = dev.data;
            dev.data = arg;
            arg = temp;
            printk(KERN_EMERG "SCULL_IOCHQUANTUM\n");
            return temp;
        default://其余暂时去除
            printk(KERN_EMERG "SCULL_default\n");
            return -ENOTTY;
    }
    return retval;
}

int scull_ioctl_open(struct inode *inode,struct file *filp)
{
    dev.data = 0;
    printk(KERN_EMERG "scull_ioctl_open success!\n");
    return 0;
}

int scull_ioctl_release(struct inode *inode,struct file *filp)
{
    printk(KERN_EMERG "scull_ioctl_release success!\n");
    return 0;
}

static const struct file_operations scull_fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = scull_ioctl,
    .open = scull_ioctl_open,
    .release = scull_ioctl_release,
};

static int scull_init(void)
{
    dev_t devno = MKDEV(scull_major,0);
    int result;

    if(scull_major)
        result = register_chrdev_region(devno,1,"scull_ioctl");
    else {
        result = alloc_chrdev_region(&devno,0,1,"scull_ioctl");
        scull_major = MAJOR(devno);
    }

    if(result < 0)
        return result;

    //用于udev/mdev自动创建节点
    scull_class = class_create(THIS_MODULE,"scull_ioctl");
    device_create(scull_class,NULL,devno,NULL,"scull_ioctl");

    //静态添加cdev
    cdev_init(&cdev,&scull_fops);
    cdev.owner = THIS_MODULE;
    cdev_add(&cdev,devno,1);
    printk(KERN_EMERG "scull_init success!\n");
    return 0;
}

static void scull_exit(void)
{
    cdev_del(&cdev);
    device_destroy(scull_class,MKDEV(scull_major,0));
    class_destroy(scull_class);
    unregister_chrdev_region(MKDEV(scull_major,0),1);
    printk(KERN_EMERG "scull_exit success!\n");
}

module_init(scull_init);
module_exit(scull_exit);

Makefile

# Comment/uncomment the following line to disable/enable debugging
#DEBUG = y


# Add your debugging flag (or not) to CFLAGS
#ifeq ($(DEBUG),y)
#  DEBFLAGS = -O -g -DSBULL_DEBUG # "-O" is needed to expand inlines
#else
#  DEBFLAGS = -O2
#endif

#CFLAGS += $(DEBFLAGS)
#CFLAGS += -I..

ifneq ($(KERNELRELEASE),)
# call from kernel build system
  obj-m := scull_ioctl.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD       := $(shell pwd)
default:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

#depend .depend dep:
#	$(CC) $(CFLAGS) -M *.c > .depend


#ifeq (.depend,$(wildcard .depend))
#include .depend
#endif

app_ioctl.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#include "scull_ioctl.h"

#define NAME "/dev/scull_ioctl"

int main(void)
{
    int fd;
    int data;

    //打开设备
    fd = open(NAME,O_RDWR);
    if(fd == -1)
    {
        printf("打开设备失败!\n");
        return -1;
    }
    
    //复位
    ioctl(fd,SCULL_IOCRESET);

    //读取
    data = ioctl(fd,SCULL_IOCQQUANTUM);
    printf("1:data is %d\n",data);

    //数值赋值
    data = 10;
    ioctl(fd,SCULL_IOCTQUANTUM,data);
    data = ioctl(fd,SCULL_IOCQQUANTUM);
    printf("2:data is %d\n",data);

    //指针赋值
    data = 20;
    ioctl(fd,SCULL_IOCSQUANTUM,&data);
    data = 1; //测试
    ioctl(fd,SCULL_IOCGQUANTUM,&data);//读取到内核值
    printf("3:data is %d\n",data);

    //数值交换
    data = 30;
    data = ioctl(fd,SCULL_IOCHQUANTUM,data);
    printf("4:data is %d\n",data);
    data = 40;
    ioctl(fd,SCULL_IOCXQUANTUM,&data);
    printf("5:data is %d\n",data);

    close(fd);
    return 0;
}

四、实验步骤
1、首先先在主目录下make,编译模块,生成.ko文件。
出现如下:

#make 
make -C /lib/modules/2.6.35.3-571-gcca29a0/build M=/root/Cdev_Driver/Ldd-6/IOCTL modules
make[1]: 正在进入目录 `/usr/src/linux-2.6.35.3'
  CC [M]  /root/Cdev_Driver/Ldd-6/IOCTL/scull_ioctl.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC     /root/Cdev_Driver/Ldd-6/IOCTL/scull_ioctl.mod.o
  LD [M] /root/Cdev_Driver/Ldd-6/IOCTL/scull_ioctl.ko
make[1]:正在离开目录 `/usr/src/linux-2.6.35.3'

2、编译模块到内核

insmod scull_ioctl.ko

3、查看/dev是否成功挂载设备

# find  /dev/ -name "scull*"
/dev/scull_ioctl

4、将用户程序app_ioctl.c编译,生产可执行文件app_ioctl.exe

gcc app_ioctl.c -o app_ioctl.exe

5、执行用户程序,在主界面上显示

./app_ioctl.exe
1:data is 0
2:data is 10
3:data is 20
4:data is 20
5:data is 30

以及在内核上打印数据

cat /proc/kmsg
<0>[12649.683393] scull_ioctl_open success!
<0>[12649.683533] SCULL_IOCRESET
<0>[12649.683537] SCULL_IOCQQUANTUM
<0>[12649.683627] SCULL_IOCTQUANTUM
<0>[12649.683631] SCULL_IOCQQUANTUM
<0>[12649.683640] SCULL_IOCSQUANTUM
<0>[12649.683643] SCULL_IOCGQUANTUM
<0>[12649.683650] SCULL_IOCHQUANTUM
<0>[12649.683658] SCULL_IOCXQUANTUM
<0>[12649.683667] scull_ioctl_release success!

五、总结
ioctl其实没有什么非常难的东西需要理解,关键是理解cmd命令码如何传入,并在内核对应是如何实现的,程序员最主要的工作量在switch结构中,因为对设备的I/O控制都是通过这一部分的代码实现的,在每个cmd下都有不同的操作函数入口。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值