本期主题: linux字符驱动之ioctl部分
往期链接:
1.为什么要用ioctl
前面已经讲了怎么对驱动设备进行读写,之所以还需要ioctl的原因是因为 工程师需要对设备进行控制,因此开出了这样的一些接口 因此,可以说ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。
2.一个简单的例子
ioctl应用层的使用方式: int ioctl(int fd, unsigned long request, …) fd是文件描述符,request是对应的cmd命令
例子中添加了一个0x01的命令作为ioctl的命令,在应用程序中也调用ioctl,并且往下传了1这个参数
驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
MODULE_LICENSE("GPL");
#define TEST_CMD 0x01
static int hello_open (struct inode *inode, struct file *filep)
{
printk("hello_open()\n");
return 0;
}
long hello_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch (cmd)
{
case TEST_CMD:
printk("for test ioctl\r\n");
printk("arg is %d\r\n", arg);
break;
default:
return -EINVAL;
}
return 0;
}
static struct file_operations hello_ops =
{
.open = hello_open,
.unlocked_ioctl = hello_ioctl,
};
static int major = 230;
static int minor = 0;
static dev_t dev_num = 0;
static struct cdev cdev;
static int hello_init(void)
{
int result;
int error;
dev_num = MKDEV(major, minor);
result = register_chrdev_region(dev_num, 1, "test");
if (result < 0)
{
printk("register_chrdev_region fail \n");
return result;
}
printk("hello_init, register_chrdev_region OK \n");
cdev_init(&cdev,&hello_ops);
error = cdev_add(&cdev,dev_num,1);
if(error < 0)
{
printk("cdev_add fail \n");
unregister_chrdev_region(dev_num,1);
return error;
}
return 0;
}
static void hello_exit(void)
{
printk("hello_exit \n");
cdev_del(&cdev);
unregister_chrdev_region(dev_num, 1);
return;
}
module_init(hello_init);
module_exit(hello_exit);
应用程序代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#define TEST_CMD 0x01
int main()
{
int fd;
int ret;
fd = open("/dev/test",O_RDWR);
if(fd<0)
{
perror("open fail \n");
return;
}
printf("open ok \n ");
ret = ioctl(fd, TEST_CMD, 1);
if (ret < 0)
{
perror("ioctl fail \n");
};
return ret;
}
实验结果:
3.思考
上面例子的问题在哪?
在上面的例子中,我们将TEST_CMD命令设置为0x01,但实际上这并不是一种好方法,因为如果每个模块都对命令设置成0x00,0x01…等等,内核将会十分混乱。
因此Linux内核采用了一套统一的ioctl()命令生成方式,详情看 linux内核中的 ioctl-number.txt文档 ioctl()的命令方式为: 内核中用来辅助ioctl的宏定义,文件位置:linux-linux-4.4.y\include\uapi\asm-generic\ioctl.h,其中:
type表示设备类型 nr表示序列号 size表示数据尺寸
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOR_BAD(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
#define IOC_IN (_IOC_WRITE << _IOC_DIRSHIFT)
#define IOC_OUT (_IOC_READ << _IOC_DIRSHIFT)
#define IOC_INOUT ((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)
#define IOCSIZE_MASK (_IOC_SIZEMASK << _IOC_SIZESHIFT)
#define IOCSIZE_SHIFT (_IOC_SIZESHIFT)
其实这个ioctl的cmd命令也不是很重要,只是如果想看起来更规范一点,可以参考ioctl-number.txt文档,我们参考他的来设计一下: 1.type类型 定义为"g" 2.序列号 我们自己的程序从1开始,定义为1 3.传输方向 这个是从应用程序的角度来看的,我们现在想传输参数给内核,所以对应用程序来说是写 4.数据尺寸 直接填需要传输的参数的类型
因此,上面例子的变化如下: 经过实验,与原来现象一致。
|