Linux驱动之 字符设备 ioctl接口使用
字符设备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接口使用相关推荐
- 【linux驱动之字符设备驱动基础】
linux驱动之字符设备驱动基础 文章目录 linux驱动之字符设备驱动基础 前言 一.开启驱动学习之路 二.驱动预备知识 三.什么是驱动? 3.1 驱动概念 3.2 linux 体系架构 3.3 模 ...
- linux驱动之字符设备
linux驱动之字符设备 linux驱动设备分类 linux驱动分为了三种驱动: 字符设备: 字符设备和应用程序之间是以字节进行进行数据交换的.在进行数据交换的时候数据是以一定顺序进行传输的,传输是实 ...
- Linux驱动之字符设备驱动
系列文章目录 第一章 Linux入门之驱动框架 第二章 Linux驱动之字符设备驱动 文章目录 系列文章目录 前言 一.认识字符设备驱动 1.基本概念 2.基本概念 二.字符设备旧框架 1.注册和注销 ...
- linux用户空间flash驱动,全面掌握Linux驱动框架——字符设备驱动、I2C驱动、总线设备驱动、NAND FLASH驱动...
原标题:全面掌握Linux驱动框架--字符设备驱动.I2C驱动.总线设备驱动.NAND FLASH驱动 字符设备驱动 哈~ 这几天都在发图,通过这种方式,我们希望能帮大家梳理学过的知识,全局的掌握Li ...
- 嵌入式linux驱动之———字符设备驱动(一)
一.简介: 在Linux内核驱动中,字符设备是最基本的设备驱动.字符设备是能够像字节流(比如文件)一样被访问的设备,就是说对它的读写是以子为单位的.比如串口在进行收发数据时就是一个字节一个字节进行的. ...
- 【Linux驱动】字符设备驱动
一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 1.字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面 ...
- Linux驱动笔记-字符设备,块设备,网络设备
在Linux设备驱动开发中,粗略的将设备分为三种类型:字符设备,块设备和网络设备. 1.字符设备:指能够像字节流串行顺序依次进行访问的设备,对它的读写是以字节为单位.字符设备的上层没有磁盘文件系统 ...
- linux驱动学习——字符设备号
字符设备号本质就是一个32位的无符号整型值.高12位为主设备号:低20位为次设备号. 查看设备号 cat /proc/devices 4.1.构造设备号 源码路径: include/linux/kde ...
- linux 驱动开发 --- 字符设备与混杂设备区别
2019独角兽企业重金招聘Python工程师标准>>> 一.主设备号的生成方式不同 1.所有的混杂设备都被分配一个主设备号10,次设备号系统自动生成 2.字符设备,的主设备号,开发驱 ...
最新文章
- html5日期转long
- python中matplotlib关于直方图AttributeError: ‘Rectangle‘ object has no property ‘normed‘的解决方法
- 洛谷 - P4015 运输问题(费用流)
- js中避免函数名和变量名跟别人冲突
- 蜘蛛日志分析工具_如何分析蜘蛛日志?
- 【网络安全】黑客攻防与入侵检测(练习题)
- C# 后台处理 webp图片
- Word大括号多行公式左对齐
- 语句块是什么意思python_《语》字意思读音、组词解释及笔画数 - 新华字典 - 911查询...
- 西工大机考《 合同法》大作业网考
- 小米5USB 计算机连接,小米手机如何连接win7电脑传文件|小米手机连接win7传文件的方法...
- python 路由_静态路由配置
- azure创建centos_如何使用Blazor和Azure计算机视觉创建光学字符读取器
- WIN10 连接 BOSE QC35 蓝牙耳机时断时续问题
- 关于分布式事务 两阶段提交 一阶段提交 Best Efforts 1PC模式和事务补偿机制的研究
- 图的遍历(染色法判断奇环)
- geotools 可视化,具有无状态渲染器
- Kakaotalk PC端无法登录,错误代码50151
- day46第九章动态规划(二刷)
- Brother打印机的安装