ldd3笔记-第6章-高级字符程序操作part1

高级字符程序操作
    本章内容:
        和用户空间保持同步的几个途径。
        任何使用进程休眠及唤醒,如何实现非阻塞IO.
        在设备可读或者可写时通知用户空间。
        介绍几种访问设备的策略。

编者:张永辉 2012年10月19日
//--------------------------------------------------------------------------------------------------
6.1 ioctl
    除了读写,还要控制设备等,常使用ioctl()方法来支持,
    在用户空间, ioctl系统调用如下:
        int ioctl(int fd, unsigned long cmd, ...);
            ... : 表示函数有数目不定的参数。事实上会使用固定的参数,传统上为char *argp.
            cmd :可以是整形参数,或指针。

    驱动的原型:
        int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
            inode和filp 与open方法的参数相同。
            cmd:来自用户,
            arg:以unsigned long形式传递,不管它是整数还是指针.若用户调用不传递第3个参数,则arg值未定义。
                大部分ioctl使用switch语句根据cmd参数,选择正确的做法.
                cmd参数常通过预处理来定义符号名. scull.h为scull声明了要使用的符号。

    6.1.1 选择ioctl命令 --cmd
        1 当然可以选择从0或者1开始,但为了命令在系统唯一,使用下面的方法。
        2 linux统一如下:
            2.1 include/asm/ioctl.h 定义了type,序号,传输方向,和参数大小
            <linux/ioctl.h> 对字段的定义
                type    8 位宽(_IOC_TYPEBITS).
                    选择一个号码并在整个驱动中使用。(参考了ioctl-number.txt)
                number  8 位(_IOC_NRBITS)宽
                    序号(顺序编号)
                direction
                    定义数据传输方向
                        _IOC_NONE(没有数据传输)
                        _IOC_READ  读 (从设备读取数据)
                        _IOC_WRITE 写
                        _IOC_READ|_IOC_WRITE (数据在 2 个方向被传送).
                    _IOC_READ和_IOC_WRITE可使用一个逻辑AND分解出来.
                size 13 或14位 (_IOC_SIZEBITS)
                    用户数据大小,不强制使用该段。

            <asm/ioctl.h>定义宏来帮助建立命令号,它被包含在<linux/ioctl.h>中
                _IO(type,nr)                (给没有参数的命令)
                _IOR(type, nre, datatype)   (给从驱动中读数据的)
                _IOW(type,nr,datatype)      (给写数据)
                _IOWR(type,nr,datatype)     (给双向传送)

            可被用在驱动中来解码这的宏:
                _IOC_DIR(nr),
                _IOC_TYPE(nr),
                _IOC_NR(nr),
                _IOC_SIZE(nr).

            scull示例:
                #define SCULL_IOC_MAGIC 'k'                         //type字段 k作为幻数
                #define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)

                #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_IOC_MAXNR 14

                * S means "Set"   通过指针设置
                * T means "Tell"  直接使用参数值
                * G means "Get":  通过设置指针来应答
                * Q means "Query":    通过返回值应答
                * X means "eXchange": 原子的交换G和S
                * H means "sHift":    原子地交换T和Q

            2.2 Documentation/ioctl-number.txt.     列举了内核已经使用了的命令号
        3 返回值
            同样可以返回一个整数,或者一个代表地址的整数。
            若使用了非法的命令,可以返回 -ENIVAL("Invalid argument")或-ENOTTY

        4 预定义命令
            有一组命令,由系统定义,在调用ioctl方法之前被解释,如果自定义与此类命令相同,则ioctl行为无法预测。
            分三类:
                1 可对任何文件发出的(普通,设备,FIFO,或socket)
                2 只对常规文件的命令.
                3 针对文件系统的特殊命令.
            设备驱动只关注第一组,它的type是'T'
            下列ioctl命令是预定义给任何文件,包括设备特殊的文件:
                FIOCLEX
                FIONCLEX
                FIOQSIZE
                FIONBIO

    6.1.2 选择ioctl参数 --arg
        arg 参数:
        1 若是一个整数,直接使用即可。
        2 若是用户空间指针,必须是合法的,否则可能导致内核崩溃。驱动负责检查该指针,若非法则返回错误。
            方法一:使用copy_to_user()或者copy_from_user().
            方法二:其他更有效的方法:
                    #include <asm/uaccess.h>
                    int access_ok(int type,const void *addr,unsigned long size)
                        type:  VERIFY_READ     读
                                VERIFY_WRITE    读写
                        addr:  用户空间地址,
                        size    若是针对一个整数可以是sizeof(int).
                    返回:bool类型。0表示失败,驱动应该返回-EFAULT给调用者

            scull示例:
                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;}

            经过检查后,就可以使用数据传输函数了,除了copy_to_user()和copy_from_user(),可以使用如下宏:
            他们只针对 1、2、4、8字节的单个数据。
                定义 <asm/uaccess.h>:
                //向用户空间传输数据
                put_user(datum, ptr)        调用access_ok做类型检查
                __put_user(datum, ptr)      此宏展开不做类型检查
                    datum:将写到用户空间的数据;
                    ptr:用户空间指针。 根据此类型将传输相应的字节数。

                //从用户空间取得数据
                get_user(local, ptr)        调用access_ok做类型检查
                __get_user(local, ptr)      此宏展开不做类型检查
                    local 本地接收变量
                    ptr:用户空间指针。
            若编译报错'coversion to non-scalar type requested'应该使用copy_to_user  或者  copy_from_user.

    6.1.5 兼容性和受限操作
        对设备的访问由设备文件的权限控制,驱动程序通常不用权限检查。有时也只允许部分权限。
        内核在许可权管理上有2个系统调用capget()和capset(), 来允许它们被从用户空间管理.
        具体:暂略。

    6.1.6 ioctl 命令在scull上的实现

        驱动程序:
        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;

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

        用户空间:
        从用户空间看 有6种传递和接收参数的方法看来如下:
            int quantum;
                    ioctl(fd,SCULL_IOCSQUANTUM, &quantum);      指针设置
                    ioctl(fd,SCULL_IOCTQUANTUM, quantum);       值  设置
                    ioctl(fd,SCULL_IOCGQUANTUM, &quantum);      指针获取
            quantum=ioctl(fd,SCULL_IOCQQUANTUM);                值  获取
                    ioctl(fd,SCULL_IOCXQUANTUM, &quantum);      指针交换
            quantum=ioctl(fd,SCULL_IOCHQUANTUM, quantum);       值交换
        使用其中一种即可。

    6.1.7 非IO控制设备
        有时控制设备最好是通过写控制序列到设备自身来实现.
            例如:这个技术用在控制台驱动中,序列被用来移动光标,  改变缺省的颜色。
                  通过打印来控制的缺点是它给设备增加了策略限制
        对于不用传送数据而只是响应命令的设备,此方法是很好的方法,如控制机器人。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值