字符设备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# dmesg
36021.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# dmesg
36021.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#