原文:IOCTL in Linux Device Drivers Tutorial with Source Code Examples
Intro
操作系统将虚拟内存分为内核空间和用户空间。内核空间严格保留用于运行内核、内核扩展和大多数设备驱动程序。相反,用户空间是所有用户模式应用程序工作的内存区域,必要时可以交换这些内存。
有很多方法可以实现用户空间和内核空间的通信,比如:
- IOCTL
- Procfs
- Sysfs
- Configfs
- Debugfs
- Sysctl
- UDP Sockets
- Netlink Sockets
IOCTL
IOCTL = input and output control
它的主要用途支持设备的一些特殊操作,而这些操作内核没有对应的默认system call
IOCTL的使用步骤
- 在驱动程序中创建IOCTL命令
- 在驱动程序中写入IOCTL功能
- 在用户空间应用程序中创建IOCTL命令
- 在用户空间中使用IOCTL系统调用
1. 在驱动程序中创建IOCTL命令
#define "ioctl name" __IOX("magic number","command number","argument type")
- IOX可以是:
“IO”:不带参数的ioctl
“IOW”:具有写入参数的ioctl(copy_from_user)
“IOR”:具有读取参数的ioctl(copy_to_user)
“IOWR”:同时具有写入和读取参数的ioctl
magic number是一个唯一的数字或字符,可以将我们的一组ioctl调用与其他ioctl呼叫区分开来。有时这里使用设备的主要编号。
command number是指分配给ioctl的编号。这用于将命令彼此区分开来。
最后是数据的类型。
然后添加头文件linux/ioctl.h以利用上述调用。
例如:
#include <linux/ioctl.h>
#define WR_VALUE _IOW('a','a',int32_t*)
#define RD_VALUE _IOR('a','b',int32_t*)
2. 在驱动中定义IOCTL函数
下一步是将我们定义的IOCTL调用实现到相应的驱动程序中。我们需要将ioctl函数添加到驱动程序中。函数原型如下:
int ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg)
<inode>:是正在处理的文件的inode编号。
<file>:是指向应用程序传递的文件的文件指针。
<cmd>:是从用户空间调用的ioctl命令。
<arg>:是从用户空间传递的参数
在函数“ioctl”中,我们需要实现上面定义的所有命令(WR_VALUE、RD_VALUE)。我们需要在上面定义的switch语句中使用相同的命令。
然后我们需要通知内核ioctl调用是在函数“etx_ioctl”中实现的。这是通过使fops指针“unlocked_octl”指向“etx_io ctl”来完成的,如下所示。
/*
** This function will be called when we write IOCTL on the Device file
*/
static long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch(cmd) {
case WR_VALUE:
if( copy_from_user(&value ,(int32_t*) arg, sizeof(value)) )
{
pr_err("Data Write : Err!\n");
}
pr_info("Value = %d\n", value);
break;
case RD_VALUE:
if( copy_to_user((int32_t*) arg, &value, sizeof(value)) )
{
pr_err("Data Read : Err!\n");
}
break;
default:
pr_info("Default\n");
break;
}
return 0;
}
/*
** File operation sturcture
*/
static struct file_operations fops =
{
.owner = THIS_MODULE,
.read = etx_read,
.write = etx_write,
.open = etx_open,
.unlocked_ioctl = etx_ioctl,
.release = etx_release,
};
之后需要在应用中调用ioctl指令。
3. 在应用中创建ioctl指令
在应用中使用和驱动同样的方式声明ioctl指令:
#define WR_VALUE _IOW('a','a',int32_t*)
#define RD_VALUE _IOR('a','b',int32_t*)
4. 在用户空间中使用IOCTL系统调用
包含头文件<sys/ioctl.h>。
现在,我们需要从用户应用程序中调用新的ioctl命令。
long ioctl( "file descriptor","ioctl command","Arguments");
<file descriptor>:这是需要执行ioctl命令的打开文件,通常是设备文件。
<ioctl command>:实现ioctl命令以实现所需的功能
<arguments>:需要将参数传递给ioctl命令
比如:
ioctl(fd, WR_VALUE, (int32_t*) &number);
ioctl(fd, RD_VALUE, (int32_t*) &value);
完整例程
在这个例子中,我们只实现了IOCTL。在这个驱动程序中,我定义了一个int32_t value,使用ioctl命令,我们可以读取或更改变量。因此,其他函数,如打开、关闭、读取和写入,都是空的。
驱动代码 driver.c
/***************************************************************************//**
* \file driver.c
*
* \details Simple Linux device driver (IOCTL)
*
* \author EmbeTronicX
*
* \Tested with Linux raspberrypi 5.10.27-v7l-embetronicx-custom+
*
*******************************************************************************/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include<linux/slab.h> //kmalloc()
#include<linux/uaccess.h> //copy_to/from_user()
#include <linux/ioctl.h>
#include <linux/err.h>
#define WR_VALUE _IOW('a','a',int32_t*)
#define RD_VALUE _IOR('a','b',int32_t*)
int32_t value = 0;
dev_t dev = 0;
static struct class *dev_class;
static struct cdev etx_cdev;
/*
** Function Prototypes
*/
static int __init etx_driver_init(void);
static void __exit etx_driver_exit(void);
static int etx_open(struct inode *inode, struct file *file);
static int etx_release(struct inode *inode, struct file *file);
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len,loff_t * off);
static ssize_t etx_write(struct file *filp, const char *buf, size_t len, loff_t * off);
static long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
/*
** File operation sturcture
*/
static struct file_operations fops =
{
.owner = THIS_MODULE,
.read = etx_read,
.write = etx_write,
.open = etx_open,
.unlocked_ioctl = etx_ioctl,
.release = etx_release,
};
/*
** This function will be called when we open the Device file
*/
static int etx_open(struct inode *inode, struct file *file)
{
pr_info("Device File Opened...!!!\n");
return 0;
}
/*
** This function will be called when we close the Device file
*/
static int etx_release(struct inode *inode, struct file *file)
{
pr_info("Device File Closed...!!!\n");
return 0;
}
/*
** This function will be called when we read the Device file
*/
static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
pr_info("Read Function\n");
return 0;
}
/*
** This function will be called when we write the Device file
*/
static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
pr_info("Write function\n");
return len;
}
/*
** This function will be called when we write IOCTL on the Device file
*/
static long etx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch(cmd) {
case WR_VALUE:
if( copy_from_user(&value ,(int32_t*) arg, sizeof(value)) )
{
pr_err("Data Write : Err!\n");
}
pr_info("Value = %d\n", value);
break;
case RD_VALUE:
if( copy_to_user((int32_t*) arg, &value, sizeof(value)) )
{
pr_err("Data Read : Err!\n");
}
break;
default:
pr_info("Default\n");
break;
}
return 0;
}
/*
** Module Init function
*/
static int __init etx_driver_init(void)
{
/*Allocating Major number*/
if((alloc_chrdev_region(&dev, 0, 1, "etx_Dev")) <0){
pr_err("Cannot allocate major number\n");
return -1;
}
pr_info("Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
/*Creating cdev structure*/
cdev_init(&etx_cdev,&fops);
/*Adding character device to the system*/
if((cdev_add(&etx_cdev,dev,1)) < 0){
pr_err("Cannot add the device to the system\n");
goto r_class;
}
/*Creating struct class*/
if(IS_ERR(dev_class = class_create(THIS_MODULE,"etx_class"))){
pr_err("Cannot create the struct class\n");
goto r_class;
}
/*Creating device*/
if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"etx_device"))){
pr_err("Cannot create the Device 1\n");
goto r_device;
}
pr_info("Device Driver Insert...Done!!!\n");
return 0;
r_device:
class_destroy(dev_class);
r_class:
unregister_chrdev_region(dev,1);
return -1;
}
/*
** Module exit function
*/
static void __exit etx_driver_exit(void)
{
device_destroy(dev_class,dev);
class_destroy(dev_class);
cdev_del(&etx_cdev);
unregister_chrdev_region(dev, 1);
pr_info("Device Driver Remove...Done!!!\n");
}
module_init(etx_driver_init);
module_exit(etx_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeTronicX <embetronicx@gmail.com>");
MODULE_DESCRIPTION("Simple Linux device driver (IOCTL)");
MODULE_VERSION("1.5");
Makefile
obj-m += driver.o
KDIR = /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(shell pwd) modules
clean:
make -C $(KDIR) M=$(shell pwd) clean
应用程序 app.c
/***************************************************************************//**
* \file test_app.c
*
* \details Userspace application to test the Device driver
*
* \author EmbeTronicX
*
* \Tested with Linux raspberrypi 5.10.27-v7l-embetronicx-custom+
*
*******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<sys/ioctl.h>
#define WR_VALUE _IOW('a','a',int32_t*)
#define RD_VALUE _IOR('a','b',int32_t*)
int main()
{
int fd;
int32_t value, number;
printf("*********************************\n");
printf("*******WWW.EmbeTronicX.com*******\n");
printf("\nOpening Driver\n");
fd = open("/dev/etx_device", O_RDWR);
if(fd < 0) {
printf("Cannot open device file...\n");
return 0;
}
printf("Enter the Value to send\n");
scanf("%d",&number);
printf("Writing Value to Driver\n");
ioctl(fd, WR_VALUE, (int32_t*) &number);
printf("Reading Value from Driver\n");
ioctl(fd, RD_VALUE, (int32_t*) &value);
printf("Value is %d\n", value);
printf("Closing Driver\n");
close(fd);
}
使用sodo make编译驱动,gcc指令编译应用程序,具体执行结果自己看原文吧。