• 设备 控制接口 —— ioctl 函数


    文件 操作 结构  struct file_operations(include/linux/fs.h)、

    非 字符       文件  即所有。

    用户cmd: 

     你的cmd不是定义在头文件中吗?
    把头文件包含进来直接用了

    http://blog.csdn.net/flyingdon/article/details/5096203

    http://hi.baidu.com/275156017/blog/item/6f6000199a421563dab4bdc2.html

    unsigned int 描述了 ioctl 的命令号。是这个函数中最重要的参数

    它 描述 ioctl要处理的命令

    它包含四个部分: dir type nr size

    提供宏   生成 —— 解码   相应的域值

    使用ioctl与内核交换数据

    1. 前言
     
    使用ioctl系统调用是用户空间向内核交换数据的常用方法之一,从ioctl这个名称上看,本意是针对I/O设备进行的控制操作,但实际并不限制是真正的I/O设备,可以是任何一个内核设备即可。

    2. 基本过程
     
    内核空间中ioctl是很多内核操作结构的一个成员函数,如
    文件操作结构struct file_operations(include/linux/fs.h)、
    协议操作结构struct proto_ops(include/linux/net.h)等、
    tty操作结构struct tty_driver(include/linux/tty_driver.h)等,
    而这些操作结构分别对应各种内核设备,只要在用户空间打开这些设备, 如I/O设备可用open(2)打开,网络协议可用socket(2)打开等,获取一个文件描述符后,就可以在这个描述符上   调用ioctl(2)    向内核 交换数据。
     
    3. ioctl(2)
     
    ioctl(2)函数的基本使用格式为:
    int ioctl(int fd, int cmd, void *data)     一定格式,是因为其要实现 很多功能,用格式来  多功能
    第一个参数是文件描述符;cmd是操作命令,一般分为GET、SET以及其他类型命令,GET是用户空间进程从内核读数据,SET是用户空间进程向内核写数据,cmd虽然是一个整数,但是有一定的参数格式的,下面再详细说明;第三个参数是数据起始位置指针,
    cmd命令参数是个32位整数,分为四部分:
    dir(2b)  size(14b)  type(8b) nr(8b)
    详细定义cmd要包括这4个部分时可使用宏_IOC(dir,type,nr,size)来定义,而最简单情况下使用_IO(type, nr)来定义就可以了,这些宏都在include/asm/ioctl.h中定义
    本文cmd定义为:
    #define NEWCHAR_IOC_MAGIC   'M'
    #define NEWCHAR_SET    _IO(NEWCHAR_IOC_MAGIC, 0)
    #define NEWCHAR_GET    _IO(NEWCHAR_IOC_MAGIC, 1)
    #define NEWCHAR_IOC_MAXNR   1

    要 定义自己的ioctl操作,可以有两个方式,一种是在现有的内核代码中直接添加相关代码进行支持,比如想通过socket描述符进行 ioctl操作,可在net/ipv4/af_inet.c中的inet_ioctl()函数中添加自己定义的命令和相关的处理函数,重新编译内核即可, 不过这种方法一般不推荐;第二种方法是定义自己的内核设备,通过设备的ioctl()来操作,可以编成模块,这样不影响原有的内核,这是最通常的做法。
     
    4. 内核设备
     
    为进行ioctl操作最通常是使用字符设备来进行,当然定义其他类型的设备也可以。在用户空间,可使用mknod命令建立一个字符类型设备文件,假设该设备的主设备号为123,次设备号为0:
    mknode /dev/newchar c 123 0
    如果是编程的话,可以用mknode(2)函数来建立设备文件。
     
    建立设备文件后再将该设备的内核模块文件插入内核,就可以使用open(2)打开/dev/newchar文件,然后调用ioctl(2)来传递数据,最后用close(2)关闭设备。而如果内核中还没有插入该设备的模块,open(2)时就会失败。
     
    由于内核内存空间和用户内存空间不同,要将内核数据拷贝到用户空间,要使用专用拷贝函数copy_to_user();要将用户空间数据拷贝到内核,要使用copy_from_user()。
    要最简单实现以上功能,内核模块只需要实现设备的open, ioctl和release三个函数即可,
    下面介绍程序片断:
    static int newchar_ioctl(struct inode *inode, struct file *filep,
    unsigned int cmd, unsigned long arg);
    static int newchar_open(struct inode *inode, struct file *filep);
    static int newchar_release(struct inode *inode, struct file *filep);
    // 定义文件操作结构,结构中其他元素为空
    struct file_operations newchar_fops =
    {
    owner:  THIS_MODULE,
    ioctl:  newchar_ioctl,
    open:  newchar_open,
    release: newchar_release,
    };
    // 定义要传输的数据块结构
    struct newchar{
    int a;
    int b;
    };
    #define MAJOR_DEV_NUM 123
    #define DEVICE_NAME "newchar"
     
    打开设备,非常简单,就是增加模块计数器,防止在打开设备的情况下删除模块,
    当然想搞得复杂的话可进行各种限制检查,如只允许指定的用户打开等:
    static int newchar_open(struct inode *inode, struct file *filep)
    {
    MOD_INC_USE_COUNT;
     return 0;
    }

    关闭设备,也很简单,减模块计数器:
    static int newchar_release(struct inode *inode, struct file *filep)
    {
    MOD_DEC_USE_COUNT;
     return 0;
    }

    进行ioctl调用的基本处理函数
    static int newchar_ioctl(struct inode *inode, struct file *filep,
    unsigned int cmd, unsigned long arg)
    {
    int  ret;
    // 首先检查cmd是否合法
    if (_IOC_
    TYPE(cmd) != NEWCHAR_IOC_MAGIC) return -EINVAL;
    • type
    描述了ioctl命令的类型,8位。每种设备或系统都可以指定自己的一个类型号,ioctl用这个类型    来表示ioctl命令所属的设备或驱动。一般用ASCII码字符来表示,如 'a'

    if (_IOC_NR(cmd) > NEWCHAR_IOC_MAXNR) return -EINVAL;
    ioctl命令序号,一般8位。对于一个指定的设备驱动,可以对它的ioctl命令做一个顺序编码,一般从零开始,这个编码就是ioctl命令的序号
    // 错误情况下的缺省返回值
    ret = EINVAL;
     switch(cmd)
    {
    case KNEWCHAR_SET:??????            编号0
    // 设置操作,将数据从用户空间拷贝到内核空间
    {
    struct  newchar  nc;
    if(copy_from_user(&nc, (const char*)arg, sizeof(nc)) != 0)
    return -EFAULT;
    ret = do_set_newchar(&nc);
    }
    break;
    case KNEWCHAR_GET:
    // GET操作通常会在数据缓冲区中先传递部分初始值作为数据查找条件,获取全部
    // 数据后重新写回缓冲区
    // 当然也可以根据具体情况什么也不传入直接向内核获取数据
    {
    struct  newchar  nc;
    if(copy_from_user(&nc, (const char*)arg, sizeof(nc)) != 0)
    return -EFAULT;
    ret = do_get_newchar(&nc);
    if(ret == 0){
    if(copy_to_user((unsigned char *)arg, &nc, sizeof(nc))!=0)
    return -EFAULT;
    }
      }
    break;
    }
    return ret;
    }
    模块初始化函数,登记字符设备
    static int __init _init(void)
    {
    int  result;
    // 登记该字符设备,这是2.4以前的基本方法,到2.6后有了些变化,
    // 是使用MKDEV和cdev_init()来进行,本文还是按老方法
    result = register_chrdev(MAJOR_DEV_NUM, DEVICE_NAME, &newchar_fops);
    if (result < 0) {
    printk(KERN_WARNING __FUNCTION__ ": failed register character device for /dev/newchar/n");
    return result;
    }
    return 0;
    }

    模块退出函数,登出字符设备
    static void __exit _cleanup(void)
    {
    int  result;
     result = unregister_chrdev(MAJOR_DEV_NUM, DEVICE_NAME);
    if (result < 0)
    printk(__FUNCTION__ ": failed unregister character device for /dev/newchar/n");
     return;
    }
    module_init(_init);
    module_exit(_cleanup);
     
    5. 结论
     
    用ioctl()在用户空间和内核空间传递数据是最常用方法之一,比较简单方便,而且可以在同一个ioctl中对不同的命令传送不同的数据结构,本文只是为描述方便而在不同命令中使用了相同的数据结构
  • 相关阅读:
    201504051930_《移动APP框架——MUI——HTML5》
    201503121644_《ios直播协议相关》
    2010502260926_《avolon》
    201502251333_《avolon作用域》
    201502251308_《fekit》
    使用Eclipse下载CRaSH源代码
    网络流量监控工具
    Map的putAll方法验证
    bat脚本中%~dp0含义解释
    CD管理和检索软件比较
  • 原文地址:https://www.cnblogs.com/ai616818/p/2444096.html
Copyright © 2020-2023  润新知