第6章 高级字符驱动操作

摘要:
定义ioctl命令号的正确方法使用4个位段,新符号定义在type:魔数,只是一个选择数,并且使用它在整个驱动中,8位宽number:序号,它是8位宽direction:数据传送方向,如果这个特殊的命令设计数据传送。
一、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的使用例子:

第6章 高级字符驱动操作第1张第6章 高级字符驱动操作第2张
/*使用"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(intcapability);

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

ioctl命令实现

第6章 高级字符驱动操作第3张第6章 高级字符驱动操作第4张
switch(cmd)
{
caseSCULL_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) */
    returnscull_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;
    returntmp;

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

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

第6章 高级字符驱动操作第5张第6章 高级字符驱动操作第6张
staticDECLARE_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>自动包含。

structscull_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实现既管理阻塞也管理非阻塞输入:

第6章 高级字符驱动操作第7张第6章 高级字符驱动操作第8张
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);
    returncount;
}
例程

免责声明:文章转载自《第6章 高级字符驱动操作》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇python使用zipfile解压文件中文乱码问题1.2.1LVM逻辑卷镜像实现方法下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Linux逻辑卷管理LVM学习总结

LVM(Logical Volume Manager),它是Linux环境下对磁盘分区进行管理的一种机制,LVM是建立在硬盘和分区之上的一个逻辑层,可以弹性的增加或减小分区的大小,使得磁盘分区管理更具灵活性。LVM的实现示意图: 创建LVM实例: 1、首先创建3个准备组成卷组VG的物理分区,并且把分区格式改为8e。以/dev/sdb1、/dev/sdb2...

V4L2驱动的移植与应用(二)

二、V4L2的应用 下面简单介绍一下V4L2驱动的应用流程。 1、 视频采集的基本流程 一般的,视频采集都有如下流程: 2、 打开视频设备 在V4L2中,视频设备被看做一个文件。使用open函数打开这个设备: // 用非阻塞模式打开摄像头设备int cameraFd;cameraFd = open("/dev/video0", O_RDWR | O_NO...

ffmpeg安装的问题

php语音转换需要安装ffmpeg文件 参考地址: http://thierry-xing.iteye.com/blog/2017864 http://diogomelo.net/blog/11/encoder-codec-id-86017-not-found-output-stream-00-compile-ffmpeg-yourself ubuntu...

vscode利用dev配置c语言,VSCode搭建C++/C调试编译环境(使用DevC++)

关于VSCode使用Dev C++的MinGW64来调试C++/C网上的教程试了很多,大部分都已经过时了或者说是不适配了,最后就选择使用Dev原有的东西来实现,不建议自己下载MinGW64,里面安装的时候有些选项不知道选什么的话很容易出现问题。 配置Dev下MinGW64的路径 假设Dev已经安装好了,然后现在要做的就是将Dev目录下的MinGW添加到环境...

002输入子系统驱动

输入子系统概念介绍(第十三课/第一节) 回顾第三个驱动程序(中断方式的按键驱动程序)和测试程序,发现有一些缺点:这个驱动程序没办法用在别人写的现成的应用程序上(比如:QT),因为别人写的应用程序肯定不会来打开你这个"/dev/third_chrdev"。别人打开的是一些现成的设备(比如:/dev/tty),甚至别人都不打开设备,而是直接调用 scanf()...

ROS知识(3)----功能包package编译的两种方式

ROS的包编译有两种方法(我知道的),一种是用rosmake,这种方法简单;另一种是用catkin_make,这种方法更方便包的管理和开发。这两种方法都是先建立工作空间workspace(类似于vs下的解决方案,用来管理很多的项目),然后建立包package(类似于vs下的项目),最后利用rosmake或者catkin_make进行编译和运行。学会第一种方...