5.1 概述
在Linux驱动开发中,ioctl(输入输出控制)是一个非常重要的系统调用。它允许用户空间的应用程序与内核空间的驱动程序进行交互,执行一些标准读写操作之外的特殊操作。这种系统调用在大多数驱动程序类别中都可用,其主要用途是在处理某些设备的特定操作时,内核默认没有相应的系统调用。比如:
• 弹出“cd”驱动器中的介质
• 更改串口的波特率
• 调整音量
• 读取或写入设备寄存器
5.2 ioctl与read/write
在Linux驱动程序中,虽然已经有了read和write函数,但在某些情况下,ioctl系统调用仍然是必要的。
ioctl具备如下特性:
1). 特殊操作:read和write主要用于数据的传输,而ioctl可以执行设备的特殊操作。例如,调整设备的配置、控制设备的行 为等。
2). 灵活性:ioctl提供了一个灵活的接口,可以传递各种类型和大小的数据,而不仅仅是字节流。
3). 控制命令:ioctl可以定义和处理复杂的控制命令,这些命令可能涉及多个参数和复杂的逻辑。
4). 多功能性:ioctl可以处理各种类型的操作,不仅限于数据传输。例如,调整设备参数、获取设备状态等。
5). 统一接口:通过ioctl,可以为设备提供一个统一的控制接口,简化用户空间程序的开发。
5.3 ioctl的定义
int ioctl(int fd, unsigned long request, ...);
• fd:文件描述符,通常是通过open系统调用获得的设备文件描述符。
• request:控制命令,用于指定要执行的操作。
• ...:可选参数,根据request的不同,可能需要传递额外的参数。比如, 可以是传递int类型数据, 也可以从应用层传递一块内存buffer到驱动里。
5.3 如何实现ioctl
在驱动程序中, 通常使用如下步骤来实现ioctl功能。
5.3.1 定义ioctl命令
首先需要定义ioctl命令, 通常使用宏定义来创建命令。
#define MY_IOCTL_CMD _IOW(MAGIC_NUMBER, COMMAND_NUMBER, TYPE)
• MAGIC_NUMBER:一个唯一的数字,用于区分不同的设备。
• COMMAND_NUMBER:命令编号, 或者是命令索引值。
• TYPE:数据类型,用于指定传递给驱动程序的数据类型。
其中_IOW/_IOR
是Linux kernel里定义的宏, 如下:
#define _IOW(type, nr, size) _IOC(_IOC_WRITE, (type), (nr), (_IOC_TYPECHECK(size)))
参数解释
• type:一个8位的标识符,通常是一个字符,用于标识设备或子系统。
• nr:一个8位的命令编号,用于标识特定的ioctl命令。
• size:数据类型的名称,表示传递的数据的大小。
驱动代码定义ioctl命令如下:
#define MAGIC_NUMBER 'M' // a magic number
#define WR_VAL_CMD _IOW(MAGIC_NUMBER, 1, int32_t) // write commond, index is 1.
#define RD_VAL_CMD _IOR(MAGIC_NUMBER, 2, int32_t) // read command, index is 2.
#define IOCTL_SET_BUFFER _IOW(MAGIC_NUMBER, 3, char[128])
5.3.2 实现ioctl函数
在驱动程序中,需要实现一个ioctl函数来处理用户空间的请求。该函数通常定义在file_operations结构体中, 如下:
static long device_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
static int value = 0;
switch (cmd)
{
case WR_VAL_CMD:
if (copy_from_user(&value, (int32_t *)arg, sizeof(value)))
{
printk("Data from user error!\n");
}
printk("gotton data from user = %d\n", value);
break;
case RD_VAL_CMD:
value += 1; // test only
if (copy_to_user((int32_t *)arg, &value, sizeof(value)))
{
printk("Data Read : error!\n");
}
printk("sending value to user: %d\n", value);
break;
case IOCTL_SET_BUFFER:
if (copy_from_user(buffer, (char __user *)arg, sizeof(buffer))) {
printk("get buffer failed, request ioctl = %lu\n", IOCTL_SET_BUFFER);
}
printk("got userspace data success! data = %s\n", buffer);
break;
default:
printk("Default request cmd: %d\n", cmd);
break;
}
return 0;
}
需要注意: 对于小数据量和不频繁的调用,ioctl传递数据的性能通常是可以接受的。然而,当需要传递大量数据或频繁调用时,ioctl的性能可能会成为瓶颈, 需要使用内存映射或者DMA。
5.3.3 注册ioctl函数
将ioctl函数注册到file_operations结构体中, 如下:
static struct file_operations fops = {
.open = device_open,
.read = device_read,
.write = device_write,
.unlocked_ioctl = device_ioctl,
.release = device_release,
};
5.4 驱动代码
下面是完整的实现Ioctl功能的驱动代码, 首先创建一个ioctldrv.c文件, 内容如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#define DEVICE_NAME "demo_ioctl"
#define CLASS_NAME "demo_ioctl_class"
#define BUFFER_SIZE 1024
#define MAGIC_NUMBER 'M' // a magic number
#define WR_VAL_CMD _IOW(MAGIC_NUMBER, 1, int32_t) // write commond, index is 1.
#define RD_VAL_CMD _IOR(MAGIC_NUMBER, 2, int32_t) // read command, index is 2.
#define IOCTL_SET_BUFFER _IOW(MAGIC_NUMBER, 3, char[128])
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linx zhang");
MODULE_DESCRIPTION("A simple driver with ioctl feature");
static int major;
static char message[BUFFER_SIZE] = {0};
static short message_len;
static struct class *demo_ioctl_class = NULL;
static struct device *demo_ioctl_device = NULL;
static int baudrate = 0;
static char buffer[128] = { 0 }; // memory buffer to save data from user by ioctl.
static int device_open(struct inode *, struct file *filp);
static int device_release(struct inode *, struct file *filp);
static ssize_t device_read(struct file *filp, char __user *buffer, size_t len, loff_t *offset);
static ssize_t device_write(struct file *filp, const char __user *buffer, size_t len, loff_t *offset);
static long device_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
static struct file_operations fops = {
.open = device_open,
.read = device_read,
.write = device_write,
.unlocked_ioctl = device_ioctl,
.release = device_release,
};
static ssize_t show_bdrate(struct device *dev, struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", baudrate);
}
static ssize_t store_bdrate(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
sscanf(buf, "%d", &baudrate);
return count;
}
static DEVICE_ATTR(bdrate, 0664, show_bdrate, store_bdrate);
static int __init ioctl_init(void)
{
major = register_chrdev(0, DEVICE_NAME, &fops);
if (major < 0)
{
printk(KERN_ALERT "ioctldrv: Registering char device failed with %d\n", major);
return major;
}
demo_ioctl_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(demo_ioctl_class))
{
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_ALERT "Failed to register device class\n");
return PTR_ERR(demo_ioctl_class);
}
demo_ioctl_device = device_create(demo_ioctl_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
if (IS_ERR(demo_ioctl_device))
{
class_destroy(demo_ioctl_class);
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_ALERT "Failed to create the device\n");
return PTR_ERR(demo_ioctl_device);
}
if (device_create_file(demo_ioctl_device, &dev_attr_bdrate))
{
device_destroy(demo_ioctl_class, MKDEV(major, 0));
class_destroy(demo_ioctl_class);
unregister_chrdev(major, DEVICE_NAME);
return -ENOMEM;
;
}
printk(KERN_INFO "ioctldrv(demo ioctl): Device registered with major number %d\n", major);
return 0;
}
static void __exit ioctl_exit(void)
{
device_remove_file(demo_ioctl_device, &dev_attr_bdrate);
device_destroy(demo_ioctl_class, MKDEV(major, 0));
class_destroy(demo_ioctl_class);
unregister_chrdev(major, DEVICE_NAME);
printk(KERN_INFO "Goodbye!\n");
}
/**
* Applications open this device driver.
*/
static int device_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "ioctldrv: Device opened\n");
return 0;
}
/**
* Applications close this device driver.
*/
static int device_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "ioctldrv: Device closed\n");
return 0;
}
/**
* Send data to applications.
*/
static ssize_t device_read(struct file *filp, char __user *buffer, size_t len, loff_t *offset)
{
int bytes_read = 0;
size_t message_len = strlen(message); // Assuming message is a null-terminated string
// Ensure we do not read more than the message length
if (len > message_len)
{
len = message_len;
}
// Copy data from kernel space to user space
if (copy_to_user(buffer, message, len))
{
return -EFAULT; // Return error if copy fails
}
bytes_read = len;
message_len = 0; // Reset message length after reading
return bytes_read;
}
/**
* Get user data from applications.
*/
static ssize_t device_write(struct file *filp, const char __user *buffer, size_t len, loff_t *offset)
{
size_t bytes_to_write = len < BUFFER_SIZE ? len : BUFFER_SIZE;
// Copy data from user space to kernel space
if (copy_from_user(message, buffer, bytes_to_write))
{
return -EFAULT; // Return error if copy fails
}
message_len = bytes_to_write;
return bytes_to_write;
}
static long device_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
static int value = 0;
switch (cmd)
{
case WR_VAL_CMD:
if (copy_from_user(&value, (int32_t *)arg, sizeof(value)))
{
printk("Data from user error!\n");
}
printk("gotton data from user = %d\n", value);
break;
case RD_VAL_CMD:
value += 1; // test only
if (copy_to_user((int32_t *)arg, &value, sizeof(value)))
{
printk("Data Read : error!\n");
}
printk("sending value to user: %d\n", value);
break;
case IOCTL_SET_BUFFER:
if (copy_from_user(buffer, (char __user *)arg, sizeof(buffer))) {
printk("get buffer failed, request ioctl = %lu\n", IOCTL_SET_BUFFER);
}
printk("got userspace data success! data = %s\n", buffer);
break;
default:
printk("Default request cmd: %d\n", cmd);
break;
}
return 0;
}
module_init(ioctl_init);
module_exit(ioctl_exit);
5.5 Makefile
obj-m += ioctldrv.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
5.6 编译及加载
5.6.1 编译
neardi@LPA3588:~/drivers/ioctl$ make
make -C /lib/modules/5.10.110/build M=/home/neardi/drivers/ioctl modules
make[1]: Entering directory '/usr/src/linux-headers-5.10.110'
CC [M] /home/neardi/drivers/ioctl/ioctldrv.o
MODPOST /home/neardi/drivers/ioctl/Module.symvers
CC [M] /home/neardi/drivers/ioctl/ioctldrv.mod.o
LD [M] /home/neardi/drivers/ioctl/ioctldrv.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.10.110'
5.6.2 加载
neardi@LPA3588:~/drivers/ioctl$ sudo insmod ioctldrv.ko
neardi@LPA3588:~/drivers/ioctl$ dmesg
[30242.451894] ioctldrv(demo ioctl): Device registered with major number 234
5.7 测试APP
接下来设计一个APP来测试ioctl功能, 创建一个test.cpp文件, 内容如下:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#define DEVICE_PATH "/dev/demo_ioctl"
#define BUFFER_SIZE 1024
#define MAGIC_NUMBER 'M' // a magic number
#define WR_VAL_CMD _IOW(MAGIC_NUMBER, 1, int32_t) // write commond, index is 1.
#define RD_VAL_CMD _IOR(MAGIC_NUMBER, 2, int32_t) // read command, index is 2.
#define IOCTL_SET_BUFFER _IOW(MAGIC_NUMBER, 3, char[128])
int main()
{
int fd;
char write_buf[BUFFER_SIZE] = "Hello, Kernel!";
char read_buf[BUFFER_SIZE];
char user_buffer[128] = "Hello, ioctl driver! This is a 128-byte buffer.";
// open this device
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0)
{
std::cout << "Failed to open the device" << std::endl;
return EXIT_FAILURE;
}
// write data to device
if (write(fd, write_buf, strlen(write_buf)) < 0)
{
std::cout << "Failed to write to the device" << std::endl;
close(fd);
return EXIT_FAILURE;
}
// read data from device
if (read(fd, read_buf, BUFFER_SIZE) < 0)
{
std::cout << "Failed to read from the device" << std::endl;
close(fd);
return EXIT_FAILURE;
}
std::cout << "Read from device: " << read_buf << std::endl;
int value = 100;
int ret = ioctl(fd, WR_VAL_CMD, &value);
if (ret < 0)
{
std::cout << "ioctl WR_VAL_CMD failed, error code = " << ret << std::endl;
}
ret = ioctl(fd, RD_VAL_CMD, &value);
std::cout << "Reading Value from Driver: " << value << std::endl;
if (ioctl(fd, IOCTL_SET_BUFFER, user_buffer) < 0)
{
perror("Failed to send buffer...");
}
// close the device
close(fd);
return EXIT_SUCCESS;
}
通过如下命令编译APP:
neardi@LPA3588:~/drivers/ioctl$ aarch64-linux-gnu-g++ -o test test.cpp
5.8 运行APP
在终端分别执行如下命令:
neardi@LPA3588:~/drivers/ioctl$ sudo ./test
Read from device: Hello, Kernel!
Reading Value from Driver: 101
neardi@LPA3588:~/drivers/ioctl$ dmesg
[30242.451894] ioctldrv(demo ioctl): Device registered with major number 234
[30881.520105] ioctldrv: Device opened
[30881.520216] gotton data from user = 100
[30881.520222] sending value to user: 101
[30881.520240] got userspace data success! data = Hello, ioctl driver! This is a 128-byte buffer.
[30881.520246] ioctldrv: Device closed
从上面的Log看, ioctl驱动及测试APP运行成功。
5.9 小节
ioctl系统调用在Linux驱动开发中起着至关重要的作用。通过ioctl,用户空间的应用程序可以与内核空间的驱动程序进行灵活的交互,执行各种特殊操作。本节介绍了ioctl的基本概念、实现步骤及其在实际应用中的使用,希望能为读者提供有价值的参考。