第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\n",
        current-pid, current-comm);
    wait_event_interruptible(wq, flag != 0);
    flag = 0;
    printk(KERN_DEBUG "awoken %i (%s)\n", 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...\n",
        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\n", 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\n", current->comm, (long)count);
    return count;
}
例程

 

posted @ 2018-06-22 10:15  习惯就好233  阅读(293)  评论(0编辑  收藏  举报