[Linux驱动开发五]实现ioctl函数

目录

一、前言

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:读写测试

五、小结

六、往期内容


代码下载链接

一、前言

1.1 ioctl用来做什么? 

        前文已经实现了对字符设备的具体读写功能,但如果我们想要驱动外设,不仅需要具备读写

设备的能力,还需要具备对它的控制能力。例如,要求设备报告错误信息,改变波特率,LED灯的

点亮或熄灭等,这些都需要通过ioctl()来实现。

1.2 ioctl和unlock_ioctl区别?

(1)ioctl()主要用于应用层系统调用

#include <sys/ioctl.h>

int ioctl(int d, int cmd, ...);

 输入参数:

        fd: 打开设备文件的时候获得的文件描述符;

        cmd: 用户程序对设备的控制命令(驱动层的命令要和应用层的命令一样);

        ... : 可变参数,可以配置cmd一起使用;

返回值:

        成功返回0, 失败返回 -1。

(2)unlock_ioctl主要用于驱动层系统调用      

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:命令编号,代表设备的第几个命令。

1.3 如何使用ioctl()操作内核模块的?

        用户程序通过命令码告诉驱动程序它想要实现的功能,如何解释和实现这些功能就是驱动程
序所要做的事情了,其大致流程图如下所示。    

 其中,“user space”为用户层,"vfs"为虚拟文件系统,“driver”为驱动层。

二、ioctl相关宏

2.1 置位_IO宏

(1)_IO(type, nr):创建不带参数cmd,只传输命令;

(2)_IOR(type, nr, size):创建从设备读取数据cmd;

(3)_IOW(type, nr, size):创建向设备写入数据cmd;

(4) _IOWR(type, nr, size):创建双向传输数据cmd;

2.2 取位_IO宏

(1)_IOC_DIR(cmd):检查cmd读写属性;

(2)_IOC_TYPE(cmd):检查cmd设备类型(幻数);

(3)_IOC_NR(cmd):检查cmd命令编号;

(4)_IOC_SIZE(cmd):检查cmd传输数据大小。

三、ioctl相关基本函数

3.1 access_ok(type, addr, size)

        检查用户空间地址是否是可用,通常在进行数据传输之前使用。

输入参数:

        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;

3.2 put_user(x, ptr) / __put_user(x, ptr)

        将内核空间的数据传到用户空间。从驱动程序的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);
}

3.3 get_user() / __get_user()

        将用户空间的数据传到内核空间。从用户空间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);
}

3.4 capable()

        检查进程是否有相应的权限,因为ioctl()会对硬件做出更改和控制,需要进程被授权相应权限

才能进行相关操作。

四、代码实现

4.1 核心代码

(1)头文件(hello_chr_locked.h)

#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 

(2) ioctl函数

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;
}

(3)应用空间的操作函数

#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<sys/ioctl.h>
#include<errno.h>

#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;
}

4.2 编译运行

step1: 编译

step2: 插入内核并查看节点

step3:读写测试

五、小结

        本章节完成了字符设备的ioctl()函数的实现,至此我们的字符设备的开发基本完成。

六、往期内容

[Linux驱动开发一] 最简单的内核模块

[Linux驱动开发二] 最简单的字符设备

[Linux驱动开发三] 实现自动生辰设备节点

[Linux驱动开发四] 设备具体读写功能的实现

  • 9
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值