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# 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# 
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程一时爽Cxx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值