目录
一、前言
1.1 ioctl用来做什么?
1.2 ioctl和unlock_ioctl区别?
(1)ioctl()主要用于应用层系统调用
(2)unlock_ioctl主要用于驱动层系统调用
1.3 如何使用ioctl()操作内核模块的?
二、ioctl相关宏
2.1 置位_IO宏
2.2 取位_IO宏
三、ioctl相关基本函数
3.1 access_ok(type, addr, size)
3.2 put_user(x, ptr) / __put_user(x, ptr)
3.3 get_user() / __get_user()
3.4 capable()
四、代码实现
4.1 核心代码
(1)头文件(hello_chr_locked.h)
(2) ioctl函数
(3)应用空间的操作函数
4.2 编译运行
step1: 编译
step2: 插入内核并查看节点
step3:读写测试
五、小结
六、往期内容
代码下载链接
前文已经实现了对字符设备的具体读写功能,但如果我们想要驱动外设,不仅需要具备读写
设备的能力,还需要具备对它的控制能力。例如,要求设备报告错误信息,改变波特率,LED灯的
点亮或熄灭等,这些都需要通过ioctl()来实现。
#include
int ioctl(int d, int cmd, ...);
输入参数:
fd: 打开设备文件的时候获得的文件描述符;
cmd: 用户程序对设备的控制命令(驱动层的命令要和应用层的命令一样);
... : 可变参数,可以配置cmd一起使用;
返回值:
成功返回0, 失败返回 -1。
long xxx_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
输入参数:
filp: 指向open产生的struct file类型的对象;
cmd: 用来表示做的是哪个操作;
arg: 和cmd配合使用的参数;
返回值:
成功返回0,失败返回-1。
其中,cmd命令码是唯一联系用户程序命令和驱动程序的媒介。在linux核心中,命令码的定义格式如下:
参数详解:
dir:ioctl命令访问模式,制定数据传输方向;
size:如果命令带参数,则制定参数所占用的内存空间大小;
type:设备类型,也叫幻数,代表一种设备,一般用一个字母或者8bit数字表示;
nr:命令编号,代表设备的第几个命令。
用户程序通过命令码告诉驱动程序它想要实现的功能,如何解释和实现这些功能就是驱动程
序所要做的事情了,其大致流程图如下所示。
其中,“user space”为用户层,"vfs"为虚拟文件系统,“driver”为驱动层。
(1)_IO(type, nr):创建不带参数cmd,只传输命令;
(2)_IOR(type, nr, size):创建从设备读取数据cmd;
(3)_IOW(type, nr, size):创建向设备写入数据cmd;
(4) _IOWR(type, nr, size):创建双向传输数据cmd;
(1)_IOC_DIR(cmd):检查cmd读写属性;
(2)_IOC_TYPE(cmd):检查cmd设备类型(幻数);
(3)_IOC_NR(cmd):检查cmd命令编号;
(4)_IOC_SIZE(cmd):检查cmd传输数据大小。
检查用户空间地址是否是可用,通常在进行数据传输之前使用。
输入参数:
type:访问类型,其值可以是VERIFY_READ或者VERIFY_WRITE;
addr:用户空间的指针变量,指向一个要检查的内存块的开始处;
size:要检查的内存块大小。
返回值:
如果内存块可用,返回真(非0),否则返回假(0)。
代码示例:
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (err)
return -EFAULT;
将内核空间的数据传到用户空间。从驱动程序的x变量地址处,拷贝sizeof(*ptr)个字节数据到ptr用户空间地址处。put_uer()是安全的,会自动执行access_ok()操作,
__put_user()需要手动执行access_ok()操作,运行速度更快。
输入参数:
x:驱动程序中的变量;
ptr:用户空间的地址。
代码示例:
static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
int x;
put_user(x, p);
}
将用户空间的数据传到内核空间。从用户空间ptr处读取sizeof(*ptr)个字节数据到驱动程序x变
量中。get_uer()是安全的,会自动执行access_ok()操作,__get_user()需要手动执行access_ok()
操作,运行速度更快。
代码示例:
static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
int x=3;
get_user(x, p);
}
检查进程是否有相应的权限,因为ioctl()会对硬件做出更改和控制,需要进程被授权相应权限
才能进行相关操作。
#ifndef _HELLO_CHR_LOCKED_H_
#define _HELLO_CHR_LOCKED_H_
/* 定义幻数 */
#define HC_IOC_MAGIC 0x81
/* 命令编号0:清空分配的空间 */
#define HC_IOC_RESET _IO(HC_IOC_MAGIC, 0)
/* 命令编号1: 通过指针获取字符串长度 */
#define HC_IOCP_GET_LENS _IOR(HC_IOC_MAGIC, 1, int)
/* 命令编号2: 通过返回值的方式返回字符串长度 */
#define HC_IOCV_GET_LENS _IO(HC_IOC_MAGIC, 2)
/* 命令编号3: 通过指针方式设置字符串长度 */
#define HC_IOCP_SET_LENS _IOW(HC_IOC_MAGIC, 3, int)
/* 命令编号4: 通过值的方式设置字符串长度 */
#define HC_IOCV_SET_LENS _IO(HC_IOC_MAGIC, 4)
/* 定义最大的命令编号 */
#define HC_IOC_MAXNR 4
#endif
long hc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct hello_char_dev *hc_dev = filp->private_data;
long retval = 0;
int tmp, err = 0;
/* 检查幻数 */
if (_IOC_TYPE(cmd) != HC_IOC_MAGIC)
return -ENOTTY;
/* 检查命令编号 */
if (_IOC_NR(cmd) > HC_IOC_MAXNR)
return -ENOTTY;
/* 检查用户空间地址是否可用 */
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (err)
return -EFAULT;
/* 若前面判断都正确,则说明命令合法,开始执行对应操作 */
switch (cmd) {
case HC_IOC_RESET:
printk(KERN_INFO "ioctl reset\n");
kfree(hc_dev->c);
hc_dev->n = 0;
break;
case HC_IOCP_GET_LENS:
printk(KERN_INFO "ioctl get lens through pointer\n");
retval = __put_user(hc_dev->n, (int __user *)arg);
break;
case HC_IOCV_GET_LENS:
printk(KERN_INFO "ioctl get lens through value\n");
return hc_dev->n;
break;
case HC_IOCP_SET_LENS:
printk(KERN_INFO "ioctl set lens through pointer");
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
retval = get_user(tmp, (int __user *)arg);
if (hc_dev->n>tmp)
hc_dev->n = tmp;
printk(KERN_INFO " %d\n", hc_dev->n);
break;
case HC_IOCV_SET_LENS:
printk(KERN_INFO "ioctl set lens through value");
if (!capable (CAP_SYS_ADMIN))
return -EPERM;
hc_dev->n = min(hc_dev->n, (int)arg);
printk(KERN_INFO " %d\n", hc_dev->n);
break;
default:
break;
}
return retval;
}
#include
#include
#include
#include
#include
#include
#include "hello_chr_locked.h"
int main(int argc ,char* argv[])
{
int n, retval =0 ;
int fd;
fd = open("/dev/hc_dev0", O_RDWR);
switch (argv[1][0])
{
case '0':
ioctl(fd, HC_IOC_RESET);
printf("reset hc\n");
break;
case '1':
ioctl(fd, HC_IOCP_GET_LENS, &n);
printf("get lens pointer, %d\n",n);
break;
case '2':
n = ioctl(fd, HC_IOCV_GET_LENS);
printf("get lens value, %d\n", n);
break;
case '3':
n=argv[2][0]-'0';
retval = ioctl(fd, HC_IOCP_SET_LENS, &n);
printf("set lens value, %d %s\n", n, strerror(errno));
break;
case '4':
n=argv[2][0] - '0';
retval = ioctl(fd, HC_IOCV_SET_LENS, n);
printf("set lens value, %d %s\n", n, strerror(errno));
break;
}
close(fd);
return 0;
}
本章节完成了字符设备的ioctl()函数的实现,至此我们的字符设备的开发基本完成。
[Linux驱动开发一] 最简单的内核模块
[Linux驱动开发二] 最简单的字符设备
[Linux驱动开发三] 实现自动生辰设备节点
[Linux驱动开发四] 设备具体读写功能的实现