字符设备ioctl接口使用:

Linux驱动编写除了对设备进行读写数据之外,通常还希望可以对设备进行控制。
从应用层传递一些命令参数,并在驱动层实现相应设备操作,这时候就用到了 ioctl函数:
应用层:

#include <sys/ioctl.h>
/** @description       :  应用层 ioctl* @param - fd      : 打开设备文件的时候获得文件描述符 * @param - request : 给驱动层传递的命令,需要注意的时候,驱动层的命令和应用层的命令一定要统一* @param - "..."  : 可变参数* @return            : 0 成功; -1 失败,同时设置errno*/
int ioctl(int fd, unsigned long request, ...);

驱动层:

#include <linux/ioctl.h>
/** @description       : 驱动层 ioctl* @param - file *   : 为打开字符设备文件的进程创建的结构体,用于存放文件的动态信息 * @param - cmd     : 用户层传入的命令,根据不同的命令执行不一样的操作* @param - arg        : 用户层传入的可变参数* @return          : 0 成功; 失败,返回带错误码的负值*/
long (*unlocked_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg)
long (*compat_ioctl) (struct file *filp, unsigned int cmd, unsigned long arg)
//unlocked_ioctl,顾名思义,应该在无大内核锁(BKL)的情况下调用;
//compat_ioctl,compatible(兼容的),主要目的是为 64 位系统提供 32 位 ioctl 的兼容方法,同样也是在无大内核锁的情况下调用。

access_ok 函数:

/** @description        : 检查用户空间中的内存地址是否可用* @param - type  : 检查的访问类型,可为 VERIFY_READ(可读) 或者 VERIFY_WRITE(可写),可写必可读。* @param - addr   : 用户空间的指针变量,其指向一个要检查的内存块开始处。* @param - size : 检查的内存块大小* @return            : 该内存地址可用,则返回真(非0值),否则返回 0*/
access_ok (type, addr, size);

ERRORS:

EBADF  fd is not a valid descriptor. //该fd文件描述符无效
EFAULT argp references an inaccessible memory area. //参数argp引用了一个不可访问的内存区域
EINVAL request or argp is not valid. //命令request或参数argp无效。
ENOTTY fd is not associated with a character special device. //描述符fd与字符特殊设备没有关联,设备类型不匹配
ENOTTY The specified request does not apply to the kind of object that the descriptor fd references.//指定的请求不适用于描述符fd引用的对象的类型。

传递的命令参数(request ) :
在linux中,提供了一种 ioctl 命令的统一格式,将 32 位 int 型数据划分为四个位段:

设备类型,type(device type),占据 8 bit,也叫做 “幻数” 或者 “魔数”,可以为任意 char 型字符,
例如’a’、‘b’、‘c’、‘k’ 等等,其作用是使 ioctl 命令有唯一的设备标识;
序列号,nr(number),占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增,代表这个设备的第几个命令;
方向,ioctl 命令访问模式(数据传输方向),dir(direction),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;
数据尺寸,size,涉及到 ioctl 函数 第三个参数 arg ,占据 8~14bit,指定 arg 的数据类型及长度,表示了需要读写的参数大小,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数;

ioctl_cmd.h

#ifndef __IOCTL_CMD_H__
#define __IOCTL_CMD_H__/*  定义设备类型,任意 char 型字符 */
#define DEV_FIFO_TYPE 'k'
/*
*   访问模式:
*   _IOC_NONE   不带参数操作,值为 0
*   _IOC_READ   带读参数,读数据操作,值为 1
*   _IOC_WRITE  带写参数,写数据操作,值为 2
*   _IOC_READ|_IOC_WRITE 带读写参数,读写数据操作
*/
/*
*   宏
*   _IO:       定义 不带参数的 ioctl 命令
*   _IOR:      定义带 读参数的ioctl命令    (copy_to_user)
*   _IOW:      定义带 写参数的 ioctl 命令  (copy_from_user)
*   _IOWR:     定义带 读写参数的 ioctl 命令
*/
/* 不带参数操作,常用于初始化 */
#define DEV_FIFO_CLEAN _IO(DEV_FIFO_TYPE,0)
/* 读操作 */
#define DEV_FIFO_GETVALUE _IOR(DEV_FIFO_TYPE,1,int)
/* 写操作 */
#define DEV_FIFO_SETVALUE _IOW(DEV_FIFO_TYPE,2,int)
/* 命令数 */
#define DEV_FIFO_MAXNR  3#endif

app_cdev.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>#include <sys/ioctl.h>
#include "ioctl_cmd.h"int main()
{int fd;int ret;int num = 0;char *filename = "/dev/hello";/* 打开驱动文件 */fd = open(filename,O_RDWR);if(fd<0){printf("Can't open file %s\r\n", filename);return fd;}//int ioctl(int fd, unsigned long request, ...);ioctl(fd,DEV_FIFO_CLEAN); //不带参数ret = ioctl(fd,DEV_FIFO_GETVALUE,&num); //读数据if(ret < 0){perror("ioctl");return ret;}printf("read num = %d \n",num);num = 888;ret = ioctl(fd,DEV_FIFO_SETVALUE,&num); //写数据if(ret < 0){perror("ioctl");return ret;}printf("set num = %d \n",num);/* 关闭设备 */ret = close(fd);if(ret < 0){printf("Can't close file %s\r\n", filename);return ret;}return 0;
}

cdev_hello.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include "ioctl_cmd.h"/* Globals */
#define NEWCHRDEV_CNT   1           /* 设备号个数 */
#define NEWCHRDEV_NAME  "hello"       /* 名字 */
#define KMAX_LEN 32
char kbuf[KMAX_LEN+1] = "kernel_data";
static int knum = 99; /* newchrdev设备结构体 */
struct newchr_dev{dev_t devid;          /* 设备号   */struct cdev cdev;        /* cdev     */struct class *class;      /* 类        */struct device *device;     /* 设备   */int major;               /* 主设备号   */int minor;              /* 次设备号   */
};
struct newchr_dev chr_hello;    /* 设备hello */
/** @description       : 打开设备* @param - inode     : 传递给驱动的inode* @param - filp   : 设备文件,file结构体有个叫做private_data的成员变量*                       一般在open的时候将private_data指向设备结构体。* @return             : 0 成功;其他 失败*/
static int hello_open (struct inode *inode, struct file *filep)
{printk("hello_open()\n");return 0;
}
/** @description       : 关闭/释放设备* @param - filp   : 要关闭的设备文件(文件描述符)* @return             : 0 成功;其他 失败*/
static int hello_release (struct inode *inode, struct file *filep)
{printk("hello_release()\n");return 0;
}
/** @description       : 从设备读取数据 * @param - filp  : 要打开的设备文件(文件描述符)* @param - buf    : 返回给用户空间的数据缓冲区* @param - size     : 要读取的数据长度* @param - pos   : 相对于文件首地址的偏移* @return             : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t hello_read (struct file *filp, char __user *buf, size_t size, loff_t *pos)
{int error;if(size > strlen(kbuf)){size = strlen(kbuf);}if(copy_to_user(buf,kbuf, size)){error = -EFAULT;return error;}return size;
}
/** @description       : 向设备写数据 * @param - filp   : 设备文件,表示打开的文件描述符* @param - buf     : 要写给设备写入的数据* @param - size    : 要写入的数据长度* @param - pos   : 相对于文件首地址的偏移* @return             : 写入的字节数,如果为负值,表示写入失败*/
static ssize_t hello_write (struct file *filp, const char __user *buf, size_t size, loff_t *pos)
{int error;if(size > KMAX_LEN){size = KMAX_LEN;}memset(kbuf,0,sizeof(kbuf));if(copy_from_user(kbuf, buf, size)){error = -EFAULT;return error;}printk("kernel kbuf: %s\n",kbuf);return size;
}
/** @description       : 向设备写数据 * @param - filp   : 设备文件,表示打开的文件描述符* @param - cmd     : 用户空间传入 的命令* @param - arg     : 用户空间传入 可变参数* @return             : */
static long hello_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
{long err,ret;  void __user *argp = (void __user *)arg;int __user *p = argp;/* 反向解析 ioctl 命令 *//* 检查设备类型 */ if(_IOC_TYPE(cmd)!=DEV_FIFO_TYPE){pr_err("cmd   %u,bad magic 0x%x/0x%x.\n",cmd,_IOC_TYPE(cmd),DEV_FIFO_TYPE);return-ENOTTY; /* 设备类型不匹配 */}/* 验证访问模式,并检查用户空间中cmd 的内存地址是否可用 */if(_IOC_DIR(cmd) & _IOC_READ)ret = !access_ok(VERIFY_WRITE, (void __user*)arg, _IOC_SIZE(cmd));   else if(_IOC_DIR(cmd) & _IOC_WRITE) ret = !access_ok(VERIFY_READ, (void  __user*)arg, _IOC_SIZE(cmd));if(ret){return-EFAULT; //内存访问错误}switch(cmd) //执行命令对应操作{case DEV_FIFO_CLEAN:   /* 初始化 */printk("DEV_FIFO_CLEAN\n");break;case DEV_FIFO_GETVALUE: /* 读操作 */err = put_user(knum, p); /* 内核空间和用户空间交换简单类型变量数据 */printk("DEV_FIFO_GETVALUE %d\n",knum);break;case DEV_FIFO_SETVALUE:  /* 写操作 */err = get_user(knum, p); /* 内核空间和用户空间交换简单类型变量数据 */printk("DEV_FIFO_SETVALUE %d\n",knum);break;default:return -EINVAL;   /* 命令或参数无效 */   }return err;
}/* 设备操作函数 */
static struct file_operations chr_hello_ops = {.owner = THIS_MODULE,  .open = hello_open,.release = hello_release,.read = hello_read,.write = hello_write,.unlocked_ioctl = hello_ioctl,
};/* * @description    : 驱动入口函数* @param       : 无* @return       : 0 成功;其他 失败*/
static int hello_init(void)
{int result = 0;printk("chrdev_hello init!\r \n");/* 注册字符设备驱动 *//* 1、创建设备号 */if (chr_hello.major) {      /*  定义了设备号 */chr_hello.devid = MKDEV(chr_hello.major, 0);/* 据定义设备号申请注册 */result = register_chrdev_region(chr_hello.devid, NEWCHRDEV_CNT, NEWCHRDEV_NAME);if(result < 0){printk("register_chrdev fail \n");  goto out_err_1;}} else {         /* 没有定义设备号,自动分配*/result = alloc_chrdev_region(&chr_hello.devid, 0, NEWCHRDEV_CNT, NEWCHRDEV_NAME); /* 申请设备号 */if(result < 0){printk("alloc_chrdev_region fail \n"); //自动分配设备号错误goto out_err_1;}chr_hello.major = MAJOR(chr_hello.devid); /* MAJOR宏获取分配号的主设备号 */chr_hello.minor = MINOR(chr_hello.devid);    /* MINOR宏获取分配号的次设备号 */}printk("chr_hello major=%d,minor=%d\r\n",chr_hello.major, chr_hello.minor);  /* 2、初始化cdev */chr_hello.cdev.owner = THIS_MODULE;cdev_init(&chr_hello.cdev, &chr_hello_ops);/* 3、添加一个cdev */cdev_add(&chr_hello.cdev, chr_hello.devid, NEWCHRDEV_CNT);/* 4、创建类 */chr_hello.class = class_create(THIS_MODULE, NEWCHRDEV_NAME);if (IS_ERR(chr_hello.class)) {printk(KERN_ERR "class_create() failed\n");result = PTR_ERR(chr_hello.class);goto out_err_2;}/* 5、创建设备 */chr_hello.device = device_create(chr_hello.class, NULL, chr_hello.devid, NULL, NEWCHRDEV_NAME);if (IS_ERR(chr_hello.device)) {printk(KERN_ERR "device_create() failed\n");result = PTR_ERR(chr_hello.device);goto out_err_3;}return result;
//释放已申请的资源返回
out_err_3:device_destroy(chr_hello.class, chr_hello.devid); /*  删除device */
out_err_2:class_destroy(chr_hello.class);  /*  删除class */unregister_chrdev_region(chr_hello.devid, NEWCHRDEV_CNT); /* 注销设备号 */cdev_del(&chr_hello.cdev);/*  删除cdev */
out_err_1:return    result;
}
/** @description   : 驱动出口函数* @param       : 无* @return       : 无*/
static void hello_exit(void)
{printk("chrdev_hello exit!\r \n");/* 注销字符设备驱动 */device_destroy(chr_hello.class, chr_hello.devid); /*  删除device */class_destroy(chr_hello.class);  /*  删除class */unregister_chrdev_region(chr_hello.devid, NEWCHRDEV_CNT); /* 注销设备号 */cdev_del(&chr_hello.cdev);/*  删除cdev */return;
}
//modinfo  name.ko
/* Module parameters */
MODULE_AUTHOR("CJX");
MODULE_DESCRIPTION("Just for Demon");
MODULE_LICENSE("GPL"); //遵循GPL协议module_init(hello_init);
module_exit(hello_exit);
//cat proc/devices

make、 insmod、./a.out、dmesg 、rmmod

root@ubuntu16:/home/cxx/driver/5_ioctrl# ls
app_cdev.c    cdev_hello.ko     cdev_hello.mod.o  ioctl_cmd.h  modules.order
cdev_hello.c  cdev_hello.mod.c  cdev_hello.o      Makefile     Module.symvers
root@ubuntu16:/home/cxx/driver/5_ioctrl# insmod cdev_hello.ko
root@ubuntu16:/home/cxx/driver/5_ioctrl# gcc app_cdev.c
root@ubuntu16:/home/cxx/driver/5_ioctrl# ./a.out
read num = 99
set num = 888
root@ubuntu16:/home/cxx/driver/5_ioctrl# dmesg36021.923033] chrdev_hello init!
[36021.923035] chr_hello major=243,minor=0
[36032.900894] hello_open()
[36032.900898] DEV_FIFO_GETVALUE 99
[36032.900947] DEV_FIFO_SETVALUE 888
[36032.900951] hello_release()
root@ubuntu16:/home/cxx/driver/5_ioctrl# ./a.out
read num = 888
set num = 888
root@ubuntu16:/home/cxx/driver/5_ioctrl# rmmod cdev_hello
root@ubuntu16:/home/cxx/driver/5_ioctrl# dmesg36021.923033] chrdev_hello init!
[36021.923035] chr_hello major=243,minor=0
[36032.900894] hello_open()
[36032.900898] DEV_FIFO_GETVALUE 99
[36032.900947] DEV_FIFO_SETVALUE 888
[36032.900951] hello_release()
[36056.465195] hello_open()
[36056.465198] DEV_FIFO_GETVALUE 888
[36056.465316] DEV_FIFO_SETVALUE 888
[36056.465338] hello_release()36075.782627] chrdev_hello exit!
root@ubuntu16:/home/cxx/driver/5_ioctrl#

Linux驱动之 字符设备 ioctl接口使用相关推荐

  1. 【linux驱动之字符设备驱动基础】

    linux驱动之字符设备驱动基础 文章目录 linux驱动之字符设备驱动基础 前言 一.开启驱动学习之路 二.驱动预备知识 三.什么是驱动? 3.1 驱动概念 3.2 linux 体系架构 3.3 模 ...

  2. linux驱动之字符设备

    linux驱动之字符设备 linux驱动设备分类 linux驱动分为了三种驱动: 字符设备: 字符设备和应用程序之间是以字节进行进行数据交换的.在进行数据交换的时候数据是以一定顺序进行传输的,传输是实 ...

  3. Linux驱动之字符设备驱动

    系列文章目录 第一章 Linux入门之驱动框架 第二章 Linux驱动之字符设备驱动 文章目录 系列文章目录 前言 一.认识字符设备驱动 1.基本概念 2.基本概念 二.字符设备旧框架 1.注册和注销 ...

  4. linux用户空间flash驱动,全面掌握Linux驱动框架——字符设备驱动、I2C驱动、总线设备驱动、NAND FLASH驱动...

    原标题:全面掌握Linux驱动框架--字符设备驱动.I2C驱动.总线设备驱动.NAND FLASH驱动 字符设备驱动 哈~ 这几天都在发图,通过这种方式,我们希望能帮大家梳理学过的知识,全局的掌握Li ...

  5. 嵌入式linux驱动之———字符设备驱动(一)

    一.简介: 在Linux内核驱动中,字符设备是最基本的设备驱动.字符设备是能够像字节流(比如文件)一样被访问的设备,就是说对它的读写是以子为单位的.比如串口在进行收发数据时就是一个字节一个字节进行的. ...

  6. 【Linux驱动】字符设备驱动

    一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 1.字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面 ...

  7. Linux驱动笔记-字符设备,块设备,网络设备

      在Linux设备驱动开发中,粗略的将设备分为三种类型:字符设备,块设备和网络设备. 1.字符设备:指能够像字节流串行顺序依次进行访问的设备,对它的读写是以字节为单位.字符设备的上层没有磁盘文件系统 ...

  8. linux驱动学习——字符设备号

    字符设备号本质就是一个32位的无符号整型值.高12位为主设备号:低20位为次设备号. 查看设备号 cat /proc/devices 4.1.构造设备号 源码路径: include/linux/kde ...

  9. linux 驱动开发 --- 字符设备与混杂设备区别

    2019独角兽企业重金招聘Python工程师标准>>> 一.主设备号的生成方式不同 1.所有的混杂设备都被分配一个主设备号10,次设备号系统自动生成 2.字符设备,的主设备号,开发驱 ...

最新文章

  1. html5日期转long
  2. python中matplotlib关于直方图AttributeError: ‘Rectangle‘ object has no property ‘normed‘的解决方法
  3. 洛谷 - P4015 运输问题(费用流)
  4. js中避免函数名和变量名跟别人冲突
  5. 蜘蛛日志分析工具_如何分析蜘蛛日志?
  6. 【网络安全】黑客攻防与入侵检测(练习题)
  7. C# 后台处理 webp图片
  8. Word大括号多行公式左对齐
  9. 语句块是什么意思python_《语》字意思读音、组词解释及笔画数 - 新华字典 - 911查询...
  10. 西工大机考《 合同法》大作业网考
  11. 小米5USB 计算机连接,小米手机如何连接win7电脑传文件|小米手机连接win7传文件的方法...
  12. python 路由_静态路由配置
  13. azure创建centos_如何使用Blazor和Azure计算机视觉创建光学字符读取器
  14. WIN10 连接 BOSE QC35 蓝牙耳机时断时续问题
  15. 关于分布式事务 两阶段提交 一阶段提交 Best Efforts 1PC模式和事务补偿机制的研究
  16. 图的遍历(染色法判断奇环)
  17. geotools 可视化,具有无状态渲染器
  18. Kakaotalk PC端无法登录,错误代码50151
  19. day46第九章动态规划(二刷)
  20. Brother打印机的安装

热门文章

  1. NGSIM数据集提取换道前4s周围车辆的特征数据
  2. 霸王别姬——一个时代变迁的眼泪
  3. 产品经理经典面试题整理:问题拆解与回答思路
  4. 电脑必备高质量软件,各有千秋,总有一款能惊艳到你
  5. 树下阅读用户隐私协议
  6. 微信公众号商城怎么搭建和如何运营
  7. Google+推出相册管理器Album Organizer
  8. python期末考试题目及答案_Python语言基础答案试题题目及答案,期末考试题库,章节测验答案...
  9. 清除Windows安全中心保护历史记录
  10. 深入Vue2.x的虚拟DOM diff原理