Linux设备驱动之Ioctl控制

 大部分驱动除了需要具备读写设备的能力之外,还需要具备对硬件控制的能力。

 一、在用户空间,使用ioctl系统调用来控制设备,原型如下

int ioctl(int fd,unsigned long cmd,...);
/*
fd:文件描述符
cmd:控制命令
...:可选参数:插入*argp,具体内容依赖于cmd
*/

 用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。

二、驱动ioctl方法:

int (*ioctl) (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);
/*
inode与filp两个指针对应于应用程序传递的文件描述符fd,这和传递open方法的参数一样。
cmd 由用户空间直接不经修改的传递给驱动程序
arg 可选。
*/

 在驱动程序中实现的ioctl函数体内,实际上是有一个switch {case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情,因为设备都是特定的。关键在于怎么样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。

  在Linux核心中是这样定义一个命令码的:

____________________________________

| 设备类型  | 序列号 |  方向 | 数据尺寸 |

|----------|--------|------|-------- |

| 8 bit   |  8 bit   | 2 bit |8~14 bit|

|----------|--------|------|-------- |

 这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。

1、定义命令:

  内核提供了一些宏来帮助定义命令:

//nr为序号,datatype为数据类型,如int
_IO(type, nr ) //没有参数的命令
_IOR(type, nr, datatype) //从驱动中读数据
_IOW(type, nr, datatype) //写数据到驱动
_IOWR(type,nr, datatype) //双向传送

 定义命令例子:

#define MEM_IOC_MAGIC ‘m‘ //定义类型
#define MEM_IOCSET _IOW(MEM_IOC_MAGIC,0,int)
#define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC, 1, int)

2、实现命令:

  定义好了命令,下一步就是要实现ioctl函数了,ioctl的实现包括三个技术环节:

1)返回值;

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

2)参数使用;

  用户使用  int ioctl(int fd,unsinged long cmd,...)  时,...就是要传递的参数;

  再通过  int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)  中的arg传递;

  如果arg是一个整数,可以直接使用;

  如果是指针,我们必须确保这个用户地址是有效的,因此,使用之前需要进行正确检查。

  内部有检查的,不需要检测的:

copy_from_user
copy_to_user
get_user
put_user

  需要检测的:

__get_user
__put_user

 检测函数access_ok():

static inline int access_ok(int type, const void *addr, unsigned long size)
/*
type :是VERIFY_READ 或者VERIFY_WRITE用来表明是读用户内存还是写用户内存;
addr:是要操作的用户内存地址;
size:是操作的长度。如果ioctl需要从用户空间读一个整数,那么size参数就等于sizeof(int);

返回值:Access_ok返回一个布尔值:1,是成功(存取没问题);0,是失败,ioctl返回-EFAULT;

*/

3)命令操作;

switch(cmd)
{
     case:
     ... ...
}

三、ioctl实例分析:

(1)memdev.h:

#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_ */

(2)memdev.c:(驱动程序)

#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 int mem_major = MEMDEV_MAJOR;

module_param(mem_major, int, S_IRUGO);

struct mem_dev *mem_devp; /*设备结构体指针*/

struct cdev cdev;

/*文件打开函数*/
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;
    
    return 0; 
}

/*文件释放函数*/
int mem_release(struct inode *inode, struct file *filp)
{
  return 0;
}

/*IO操作*/
int memdev_ioctl(struct inode *inode, struct file *filp,
                 unsigned int cmd, unsigned long 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));
    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);
        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;

}

/*文件操作结构体*/
static const struct file_operations mem_fops =
{
  .owner = THIS_MODULE,
  .open = mem_open,
  .release = mem_release,
  .ioctl = memdev_ioctl,
};

/*设备驱动模块加载函数*/
static int memdev_init(void)
{
  int result;
  int i;

dev_t devno = MKDEV(mem_major, 0);

/* 静态申请设备号*/
  if (mem_major)
    result = register_chrdev_region(devno, 2, "memdev");
  else  /* 动态分配设备号 */
  {
    result = alloc_chrdev_region(&devno, 0, 2, "memdev");
    mem_major = MAJOR(devno);
  }  
  
  if (result < 0)
    return result;

/*初始化cdev结构*/
  cdev_init(&cdev, &mem_fops);
  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;
        mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
        memset(mem_devp[i].data, 0, MEMDEV_SIZE);
  }
    
  return 0;

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("David Xie");
MODULE_LICENSE("GPL");

module_init(memdev_init);
module_exit(memdev_exit);

(3)app-ioctl.c(应用程序)

#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;

}

时间: 2024-08-10 15:11:45

Linux设备驱动之Ioctl控制的相关文章

Linux设备驱动之Ioctl控制【转】

转自:http://www.cnblogs.com/geneil/archive/2011/12/04/2275372.html 大部分驱动除了需要具备读写设备的能力之外,还需要具备对硬件控制的能力. 一.在用户空间,使用ioctl系统调用来控制设备,原型如下: int ioctl(int fd,unsigned long cmd,...);/*fd:文件描述符cmd:控制命令...:可选参数:插入*argp,具体内容依赖于cmd*/ 用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么

【转】Linux设备驱动之Ioctl控制

原文网址:http://www.cnblogs.com/geneil/archive/2011/12/04/2275372.html 大部分驱动除了需要具备读写设备的能力之外,还需要具备对硬件控制的能力. 一.在用户空间,使用ioctl系统调用来控制设备,原型如下: int ioctl(int fd,unsigned long cmd,...);/*fd:文件描述符cmd:控制命令...:可选参数:插入*argp,具体内容依赖于cmd*/ 用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于

(笔记)linux设备驱动--LED驱动

linux设备驱动--LED驱动 最近正在学习设备驱动开发,因此打算写一个系列博客,即是对自己学习的一个总结,也是对自己的一个督促,有不对,不足,需要改正的地方还望大家指出,而且希望结识志同道合的朋友一起学习技术,共同进步. 作者:liufei_learning(转载请注明出处) email:[email protected] IT学习交流群:160855096 转至:http://blog.csdn.net/liufei_learning/article/details/7025246 开发环

Linux设备驱动第六篇:高级字符驱动操作之iotcl

在之前我们介绍了怎样实现一个简单的字符设备驱动.并介绍了简单的open,close,read,write等驱动提供的基本功能.可是一个真正的设备驱动往往提供了比简单读写更高级的功能. 这一篇我们就来介绍一些驱动动中使用的一些高级的操作的实现. 大部分驱动除了提供对设备的读写操作外,还须要提供对硬件控制的接口,比方查询一个framebuffer设备能提供多大的分辨率,读取一个RTC设备的时间,设置一个gpio的高低电平等等.而这些对硬件操作能力的实现一般都是通过ioctl方法来实现的 1. 原型介

Linux设备驱动核心理论(一)

4.Linux内核模块 4.1 Linux内核模块简介 如果把所有需要的功能都编译到Linux内核.这回导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,将不得不重新编译内核. 现在我们需要的是一种机制使得编译出的内核本身并不需要包含所有功能,而在这些功能需要被使用的时候,其对应的代码被动态地加载到内核中. Linux提供了这样的一种机制,这种机制被称为模块(Module).模块具有这样的特点: 模块本身不被编译如内核映像,从而控制内核的大小. 模块一旦被加载,它就

Linux 设备驱动 Edition 3

原文网址:http://oss.org.cn/kernel-book/ldd3/index.html Linux 设备驱动 Edition 3 By Jonathan Corbet, Alessandro Rubini, and Greg Kroah-Hartman 由 quickwhale 翻译的简体中文版 V0.1.0 2006-6-2 遵循原版的版权声明. 还在完善中. 欢迎任何意见, 请给我邮件. 请发信至 quickwhale 的邮箱 <[email protected]> 版权 ©

Linux设备驱动核心理论(三)

10.中断与时钟 10.1 中断与定时器 所谓中断是指CPU在执行程序的过程中,出现了某些突发事件急待处理,CPU必须暂停执行当前程序,转去处理突发事件,处理完毕后CPU又返回原程序被中断的位置并继续执行. 根据中断的来源,中断可分为内部中断和外部中断,内部中断的中断来源来自CPU内部(软件中断.溢出.除法错误等,例如,操作系统从用户态切换到内核态需借助CPU内部的软件中断),外部中断的中断来源来自CPU外部,由外设提出请求. 根据中断是否可以屏蔽分为可屏蔽中断与不屏蔽中断(NMI),可屏蔽中断

Linux设备驱动中的阻塞和非阻塞I/O

[基本概念] 1.阻塞 阻塞操作是指在执行设备操作时,托不能获得资源,则挂起进程直到满足操作所需的条件后再进行操作.被挂起的进程进入休眠状态(不占用cpu资源),从调度器的运行队列转移到等待队列,直到条件满足. 2.非阻塞 非阻塞操作是指在进行设备操作是,若操作条件不满足并不会挂起,而是直接返回或重新查询(一直占用CPU资源)直到操作条件满足为止. 当用户空间的应用程序调用read(),write()等方法时,若设备的资源不能被获取,而用户又希望以阻塞的方式来访问设备,驱动程序应当在设备驱动层的

linux设备驱动第三篇:写一个简单的字符设备驱动

在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存. 下面就开始学习如何写一个简单的字符设备驱动.首先我们来分解一下字符设备驱动都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作. 1.主设备号和次设备号 对于字符设备的访问是通过文件系统中的设备名称进行的.他们通常位于/dev目录下.如下: [plain] vie