关注微信公众号:Linux 内核拾遗
文章来源:https://mp.weixin.qq.com/s/lbhCd6gri4hQKDT6ezsW7A
Linux 内核将虚拟内存空间划分成相互隔离的两个部分:内核空间和用户空间。内核空间只能用于运行内核代码、内核扩展以及大部分的设备驱动程序。相反,用户空间的内存可以被所有运行的用户态程序使用,并且必要的时候用户空间的内存会被交换到磁盘中。
为了实现内核空间和用户空间之间的通信,Linux 提供了 ioctl、procfs、sysfs、configfs、debugfs、sysctl、UDP sockets 以及 Netlink sockets 等通信机制。本文将重点介绍基于 ioctl 系统调用的通信机制。
1 ioctl 介绍
IOCTL(Input and Output Contol)全称”输入和输出控制“,它通常用来与设备驱动程序通信。
系统调用作为通用的接口,不太可能满足所有的设备操作需求,这时候可以通过实现设备驱动的 ioctl 系统调用,处理一些系统调用没有实现的设备特定操作。
2 ioctl 实现步骤
下面介绍一下,如果要在 Linux 设备驱动程序中实现一个 ioctl 系统调用,通常要涉及的步骤。
2.1 设备驱动创建 ioctl 命令
"linux/ioctl.h"头文件中提供了创建 ioctl 命令的宏:
#define "ioctl name" __IOX("magic number","command number","argument type")
复制代码
其中 IOX 可以是"IO"、"IOW"、"IOR"和"IOWR",分别表示 ioctl 命令不带参数、携带写入参数、携带读取参数以及同时携带读写参数。
Magic number 是新创建 ioctl 命令集合的唯一标识,用于区分其他的 ioctl 命令,通常设置被设备的主设备号,而 command number 则是表示当前 ioctl 命令在命令集合中的唯一标识。
例如:
#include <linux/ioctl.h>
#define WR_VALUE _IOW(1, 1, int32_t *)
#define RD_VALUE _IOR(1, 2, int32_t *)
复制代码
2.2 设备驱动实现 ioctl 函数
接下来就是实现设备驱动的 ioctl 调用,其函数原型如下:
int ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
复制代码
其中 cmd 表示当前 ioctl 调用接收到的 ioctl 命令,即前面创建的 ioctl 命令。
ioctl 调用函数需要实现前面创建的所有 ioctl 命令,通常使用 switch 来处理,最后将实现好的 ioctl 函数设置到设备文件的 fops->unlocked_ioctl 上。
static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch(cmd) {
case WR_VALUE:
return copy_from_user(&value ,(int32_t*) arg, sizeof(value));
case RD_VALUE:
return copy_to_user((int32_t*) arg, &value, sizeof(value));
}
return 0;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
...
.unlocked_ioctl = my_ioctl,
};
复制代码
2.3 用户程序创建 ioctl 命令
用户程序创建 ioctl 命令和设备驱动中的类似:
#define WR_VALUE _IOW(1, 1, int32_t *)
#define RD_VALUE _IOR(1, 2, int32_t *)
复制代码
2.4 用户程序使用 ioctl 系统调用
"sys/ioctl.h"头文件中提供了 ioctl()系统调用函数,可以通过它向设备驱动发送 ioctl 命令:
long ioctl(fd, cmd, args);
复制代码
例如:
ioctl(fd, WR_VALUE, (int32_t *)&value);
ioctl(fd, RD_VALUE, (int32_t *)&value);
复制代码
3 设备驱动 ioctl 示例
说明一下:随着知识点的深入,后面的示例代码行数会越来越多,为了节省篇幅突出重点,从本文开始,后续的示例代码将简化异常路径的处理以及其他的一些次要代码,但这种做法在实际代码开发中是非常不推荐的。
3.1 设备驱动代码
kernle_driver.c
#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>
#include <linux/uaccess.h>
#include <linux/ioctl.h>
#include <linux/err.h>
#define WR_VALUE _IOW(1, 1, int32_t *)
#define RD_VALUE _IOR(1, 2, int32_t *)
int32_t value = 0;
dev_t dev = 0;
static struct class *dev_class;
static struct cdev my_cdev;
static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch (cmd)
{
case WR_VALUE:
return copy_from_user(&value, (int32_t *)arg, sizeof(value));
case RD_VALUE:
return copy_to_user((int32_t *)arg, &value, sizeof(value));
}
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = my_ioctl,
};
static int __init my_driver_init(void)
{
if ((alloc_chrdev_region(&dev, 0, 1, "my_dev")) < 0)
return -1;
cdev_init(&my_cdev, &fops);
if ((cdev_add(&my_cdev, dev, 1)) < 0)
goto r_class;
if (IS_ERR(dev_class = class_create(THIS_MODULE, "my_class")))
goto r_class;
if (IS_ERR(device_create(dev_class, NULL, dev, NULL, "my_device")))
goto r_device;
return 0;
r_device:
class_destroy(dev_class);
r_class:
unregister_chrdev_region(dev, 1);
return -1;
}
static void __exit my_driver_exit(void)
{
device_destroy(dev_class, dev);
class_destroy(dev_class);
cdev_del(&my_cdev);
unregister_chrdev_region(dev, 1);
}
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("feifei <feifei@gmail.com>");
MODULE_DESCRIPTION("Simple Linux device driver");
MODULE_VERSION("1.5");
复制代码
3.2 用户程序代码
user_prog.c
#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(1, 1, int32_t *)
#define RD_VALUE _IOR(1, 2, int32_t *)
int main()
{
int fd;
int32_t value, number;
fd = open("/dev/my_device", O_RDWR);
if (fd < 0)
{
printf("Failed to open /dev/my_device\n");
return -1;
}
printf("Enter the Value to send: \n");
scanf("%d", &number);
ioctl(fd, WR_VALUE, (int32_t *)&number);
ioctl(fd, RD_VALUE, (int32_t *)&value);
printf("Value read from Driver: %d\n", value);
close(fd);
return 0;
}
复制代码
3.3 代码演示
关注微信公众号:Linux 内核拾遗
文章来源:https://mp.weixin.qq.com/s/lbhCd6gri4hQKDT6ezsW7A
评论