zoukankan      html  css  js  c++  java
  • 第6章 高级字符驱动操作

    一、ioctl接口

    函数原型:

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

    ioctl驱动方法有和用户空间版本不同的原型:

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

    为帮助程序员创建唯一的ioctl命令代码,这些编码已被划分为几个段位,Linux的第一个版本使用16-位数:

    高8位是关联这个设备的“魔”数,低8位是一个顺序号,在设备内唯一。

    根据Linux内核管理来选择驱动ioctl号,在include/asm/ioctl.h和Documentation/ioctl-number.txt文件列举了在内核中使用的魔数。

    定义ioctl命令号的正确方法使用4个位段,新符号定义在<linux/ioctl.h>

    • type:魔数,只是一个选择数(参考ioctl-number.txt),并且使用它在整个驱动中,8位宽(_IOC_TYPEBITS)
    • number:序号,它是8位(_IOC_NRBITS)宽
    • direction:数据传送方向,如果这个特殊的命令设计数据传送。可能的值_IOC_NONE(没有数据传输),_IOC_READ, _IOC_WRITE, IOC_READ|_IOC_WRITE。
    • size:涉及到的用户数据的大小

    在<linux/ioctl.h>中包含的<asm/ioctl.h>头文件定义了一些构造命令编号的宏:

    • _IO(type, nr)用于构造无参数的命令编号
    • _IOR(type, nr, datatype)用于构造从驱动程序中读取数据的命令编号
    • _IOW(type, nr datatype)用于写入数据的命令
    • _IOWR(type, nr, datatype)用于双向传输
      • type和number位字段通过参数传入,而size位字段通过对datatype参数取sizeof获取

    还有头文件定义了用于解开位字段的宏:

    • _IOC_DIR(nr)、_IOC_TYPE(nr)、_IOC_NR(nr)和_IOC_SIZE(nr)

    scull的使用例子:

    /* 使用"k"作为幻数 */
    #define SCULL_IOC_MAGIC 'k'
    /* 在你自己的代码中,请使用不同的8位数字 */
    #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 means "Query":response is on the return 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_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
    scull define

     1.1 预定义的命令

    • 可对任何文件发出的(常规,设备,FIFO,或者socket)的那些
    • 只对常规文件发出的那些
    • 对文件系统类型特殊的那些

    ioctl命令是预定义给任何文件,包括设备特殊的文件:

    • FIOCLEX  :设置close-on-exec标志
    • FIONCLEX  :清除close-no-exec标志
    • FIOQSIZE  :返回一个文件或者目录的大小
    • FIONBIO  :在“阻塞和非阻塞操作”一节中描述

    头文件<asm/ioctl.h>,它包含在<linux/ioctl.h>中,定义宏来帮助建立命令号。

    1.2 使用ioctl参数

    ioctl调用常常包含小数据项,可通过其他方法更有效的操作。地址校验由函数access_ok,在头文件<asm/uaccess.h>:

    int access_ok(int type, const void *addr, unsigned long size);
    第一个参数应当是VERIFY_READ或者VERIFY_WRITE
    addr参数持有一个用户空间地址
    size是一个字节量

    头文件<asm/uaccess.h>

    put_user(datum, ptr)
    __put_user(datum, ptr)
    get_user(local, ptr)
    __get_user(local, ptr)

    1.3 兼容性和受限操作

    在头文件中<linux/capability.h>中找到

    • CAP_DAC_OVERRIDE  :这个能力来推翻在文件和目录上的存取的限制(数据存取控制, 或者 DAC).
    • CAP_NET_ADMIN  :进行网络管理任务的能力, 包括那些能够影响网络接口的
    • CAP_SYS_MODULE  :加载或去除内核模块的能力
    • CAP_SYS_RAWIO  :进行 "raw" I/O 操作的能力. 例子包括存取设备端口或者直接和 USB 设备通讯
    • CAP_SYS_ADMIN  :一个捕获-全部的能力, 提供对许多系统管理操作的存取
    • CAP_SYS_TTY_CONFIG  :进行 tty 配置任务的能力

     能力检查通过capable函数来进行的(定义在<linux/sched.h>):

    int capable(int capability);
    
    例子:
    if(!capable(CAP_SYS_ADMIN))
        return -EPERM;

     ioctl命令实现

    switch(cmd)
    {
    case SCULL_IOCRESET:
        scull_quantum = SCULL_QUANTUM;
        scull_qset = SCULL_QSET;
        break;
    
    case SCULL_IOSQUANTUM:    /* 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: user 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_qaumtum = arg;
        return tmp;
    
    default:
        return -ENOTTY;
    }
    return retval:
    ioctl命令实现

    二、阻塞I/O

    如何使进程睡眠并且之后再次唤醒他。一个驱动当它无法立刻满足请求应当如何响应?

    程序员只希望调用read和write并且使调用返回,在必要的工作已完成后。这样,你的驱动应当(缺省地)阻塞进程,使它进入睡眠直到请求可继续。

    2.1 睡眠的介绍

    当一个进程被置为睡眠,它被标识为处于一个特殊的状态并且从调度器的运行队列中去除。直到发生谋陷事情改变了那个状态,这个进程将不被在任何CPU上调度,并且将不会运行。一个睡着的进程被搁置系统的一边,直到以后发生事件。

    有几个规则必须记住,促使安全的方式的睡眠:

    1.当你运行在原子上下文时不能睡眠,这意味着驱动持有自旋锁,seqlock或者RCU锁时不能睡眠。

    2.当你进程醒来,不能关于醒后的系统状态做任何的假设,并且必须检查来确保你在等待的条件有效。

    3.你的进程不能睡眠除非确信其他人,某处将唤醒它。做唤醒工作的代码必须也能够找到你的进程来做它工作。

    一个等待队列由一个“等待队列头”来管理,定义在<linux/wait.h>,wait_queue_head_t类型,使用:

    DECLARE_WAIT_QUEUE_HEAD(name);

    或者动态的,如下:

    wait_queue_head_t my_queue;
    init_waitqueue_head(&my_queue);

    2.2 简单睡眠

    Linux内核中睡眠的最简单方式是一个宏定义。称为wait_event(有几个变体),形式是:

    wait_event(queue, condition)
    wait_event_interruptible(queue, condition)
    wait_event_timeout(queue, condition, timeout)
    wait_event_interruptible_timeout(queue, condition, timeout)

    queue是要用的等待队列头,条件是一个被这个宏在睡眠前后锁求值的任一的布尔表达式。

    如果你使用wait_event进程被置为不可中断地睡眠

     唤醒函数:

    void wake_up(wait_queue_head_t *queue);
    void wake_up_interruptible(wait_queue_head_t *queue);

    这个例子,任何试图从这个设备读取的进程都被置为睡眠。无论何时一个进程写这个设备,所有的睡眠进程被唤醒:

    static DECLARE_WAIT_QUEUE_HEAD(wq);
    static int flag = 0;
    
    ssize_t sleepy_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
    {
        printk(KERN_DEBUG "process %i (%s) going to sleep
    ",
            current-pid, current-comm);
        wait_event_interruptible(wq, flag != 0);
        flag = 0;
        printk(KERN_DEBUG "awoken %i (%s)
    ", current->pid, current->comm);
        return 0; /* EOF */
    }
    
    ssize_t sleepy_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
    {
        printk(KERN_DEBUG "process %i (%s) awakening the readers...
    ",
            current->pid, current->comm);
        flag = 1;
        wake_up_interruptible(&wq);
        return count;    /* succedd, to avoid retrial */
    }
    read和write方法实现

    注意这个例子里 flag 变量的使用. 因为 wait_event_interruptible 检查一个必须变为真的条件, 我们使用 flag 来创建那个条件。

    2.3 阻塞和非阻塞操作

    明确的非阻塞I/O由filp->f_flags中的O_NONBLOCK标志来指示。这个标志定义于<linux/fcntl.h>被<linux/fs.h>自动包含。

    struct scull_pipe
    {
        wait_queue_head_t inq, outq;    /* read and write queues */
        char *buffer, *end;    /* begin of buf, end of buf */
        int buffersize;    /* used in pointer arithmetic */
        char *rp, *wp;    /* where to read, where to write */
        int nreaders, nwriters;    /* number of openings for r/w */
        struct fasync_struct *async_queue;    /* asynchronous readers */
        struct semaphore sem;    /* mutual exclusion semaphore */
        struct cdev cdev;    /* Char devie structure */
    };

    read实现既管理阻塞也管理非阻塞输入:

    static ssize_t scull_p_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
    {
        struct scull_pipe *dev = filp->private_data;
        if(down_interruptible(&dev->sem))
            return -ERESTARTSYS;
    
        while(dev->rp == dev->wp) {
            /* nothing to read */
            up(&dev->sem);    /* release the lock */
            if(filp->f_flags & O_NONBLOCK)
                return -EAGAIN;
            PDEBUG(""%s" reading: going to sleep
    ", current->comm);
            if(wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))
                return -ERESTARTSYS;    /* signal: tell the fs layer to handle it */
            /* otherwise loop, but first reacquire the lock */
            if(down_interruptible(&dev->sem))
                return -ERESTARTSYS;
        }
        /* ok, data is there, return something */
        if(dev->wp > dev->rp)
            count = min(count, (size_t)(dev->wp - dev->rp));
        else /* the write pointer has wrapped, return data up to dev->end */
            count = min(count, (size_t)(dev->end - dev->rp));
        if(copy_to_user(buf, dev->rp, count)) {
            up(&dev->sem);
            return -EFAULT;
        }
        dev->rp += count;
        if(dev->rp == dev->end)
            dev->rp = dev->buffer;    /*wrapped */
        up(&dev->sem);
    
        /* finally, awake any writers and return */
        wake_up_interruptible(&dev->outq);
        PDEBUG(""%s" did read %li bytes
    ", current->comm, (long)count);
        return count;
    }
    例程
    无欲速,无见小利。欲速,则不达;见小利,则大事不成。
  • 相关阅读:
    面经-新浪
    面经-中兴
    面经-趋势科技
    面经-酷家乐
    面经-大华
    面经-明略科技
    面经-小米
    面经-虹软
    coroutine
    Coroutine 终止协程和异常处理
  • 原文地址:https://www.cnblogs.com/ch122633/p/9212141.html
Copyright © 2011-2022 走看看