参考文章
VSCode SSH 连接远程ubuntu Linux 主机
ubuntu 20.04 qemu linux6.0.1 开发环境搭建
ubuntu 20.04 qemu linux6.0.1 制作ext4根文件系统
嵌入式Linux 开发经验:platform_driver_register 的使用方法
嵌入式Linux 开发经验:注册一个 misc 设备
嵌入式Linux 开发经验:编写用户态应用程序打开 misc 设备
-
通过以上的文章,应该可以搭建一个 基于 qemu 的 Linux 设备驱动开发验证平台,开发方法是 VS Code 远程连接 ubuntu 20.04,Linux 内核 在 ubuntu 主机上。
-
上一篇已经编写了用户态的应用程序,实现了 open close 操作 Linux 内核注册的misc 设备,本篇增加 ioctl 控制操作,熟练掌握 ioctl 的工作原理,可以实现 misc 设备的各种控制操作
测试环境搭建
-
ubuntu 20.04
-
VMware Workstation Pro 16
-
基于qemu(模拟器),vexpress-a9 平台
-
Linux 6.0.10 (当前最新版本)
-
编写一个简单的用户态应用程序,ioctl 方式 控制 Linux 内核注册的misc 驱动设备,掌握misc 设备使用方法:ioctl 命令控制
ioctl 命令控制
-
Linux 的 misc 设备,用户态的应用程序,是通过文件 file 的方式进行控制的, file 有个 ioctl 的函数接口,可以传入用户自定义的命令,与内核的 misc 设备进行交互操作
-
Linux 的 ioctl 命令 控制实现其实不复杂,有一个命令列表,收到什么命令,进行什么操作,就像是【空调】控制一样,除了点击【电源按钮】进行开关外,调节温度、更改模式,都是下发各种指令,空调接收
-
ioctl 就是类似【空调】遥控器的各种命令,调节温度、设置各个模式等
测试例程
Linux 内核态 misc 设备首先需要支持 ioctl 命令
-
编写或者修改 内核态的 设备驱动
led_misc.h
,位置放在linux-6.0.10/drivers/led_control/led_misc.h
目下 -
通过使用 宏
_IO
,组合出多个 ioctl 命令,注意这几个命令只用于 当前的 misc 设备,也就是说,不同的 misc 设备,ioctl 命令可以相同,也可以不同 -
同一个 misc 设备的命令,不能相同,这里的命令逐个加一,保证不相同
#ifndef __LED_MISC_H__
#define __LED_MISC_H__
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#define LED_CONTROL_IOCTL_MAGIC 'L'
#define LED_CONTROL_IOCTL_ON _IO(LED_CONTROL_IOCTL_MAGIC, 1)
#define LED_CONTROL_IOCTL_OFF _IO(LED_CONTROL_IOCTL_MAGIC, 2)
#define LED_CONTROL_IOCTL_GET_STATUS _IO(LED_CONTROL_IOCTL_MAGIC, 3)
int led_miscdev_init(void);
void led_miscdev_exit(void);
#endif
-
编写或者修改 内核态的 设备驱动
led_misc.c
,位置放在linux-6.0.10/drivers/led_control/led_misc.c
目下 -
这里主要是丰富了 ioctl 函数,增加了几个命令执行操作
#include "led_misc.h"
#define LED_MISC_DEVICE_NAME "led_misc"
struct led_misc_dev
{
struct miscdevice misc;
};
struct led_misc_dev *led_miscdev;
static int led_status = 0x00;
static int led_misc_open(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "%s : enter\n", __func__);
return 0;
}
static int led_misc_close(struct inode *inode, struct file *filp)
{
printk(KERN_INFO "%s : enter\n", __func__);
return 0;
}
static int led_misc_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret = 0;
if (filp == NULL)
{
printk(KERN_ERR "invalid file!");
return -EFAULT;
}
if (vma == NULL)
{
printk(KERN_ERR "invalid vma area");
return -EFAULT;
}
ret = remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
vma->vm_end - vma->vm_start, vma->vm_page_prot);
printk(KERN_INFO "%s : ret = %d\n", __func__, ret);
return ret;
}
static int led_control_ioctl_on(struct file *filp, int __user *led_num)
{
int ret;
int led_nums = 0;
ret = copy_from_user(&led_nums, led_num, sizeof(led_nums));
printk(KERN_INFO "%s : led_nums = 0x%08x\n", __func__, led_nums);
if (ret < 0) {
printk(KERN_ERR "%s : copy_from_user failed!\n", __func__);
return ret;
}
led_status |= led_nums;
return ret;
}
static int led_control_ioctl_off(struct file *filp, int __user *led_num)
{
int ret;
int led_nums = 0;
ret = copy_from_user(&led_nums, led_num, sizeof(led_nums));
printk(KERN_INFO "%s : led_nums = 0x%08x\n", __func__, led_nums);
if (ret < 0) {
printk(KERN_ERR "%s : copy_from_user failed!\n", __func__);
return ret;
}
led_status &= ~led_nums;
return ret;
}
static int led_control_ioctl_get_status(struct file *filp, int __user *status)
{
int ret;
printk(KERN_INFO "%s : led_status = 0x%08x\n", __func__, led_status);
ret = copy_to_user(status, &led_status, sizeof(led_status));
if (ret < 0)
{
printk(KERN_ERR "%s : copy_to_user failed!\n", __func__);
}
return ret;
}
static long led_misc_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{
int ret = 0;
printk(KERN_INFO "%s : enter, cmd = 0x%08x\n", __func__, cmd);
if (filp == NULL)
{
printk(KERN_ERR "invalid file!");
return -EFAULT;
}
switch(cmd)
{
case LED_CONTROL_IOCTL_ON:
ret = led_control_ioctl_on(filp, (void __user *)args);
break;
case LED_CONTROL_IOCTL_OFF:
ret = led_control_ioctl_off(filp, (void __user *)args);
break;
case LED_CONTROL_IOCTL_GET_STATUS:
ret = led_control_ioctl_get_status(filp, (void __user *)args);
break;
default:
ret = -EINVAL;
break;
}
return 0;
}
static const struct file_operations led_misc_fops =
{
.owner = THIS_MODULE,
.llseek = no_llseek,
.unlocked_ioctl = led_misc_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = led_misc_ioctl,
#endif
.mmap = led_misc_mmap,
.open = led_misc_open,
.release = led_misc_close,
};
int led_miscdev_init(void)
{
int ret;
led_miscdev = kzalloc(sizeof(*led_miscdev), GFP_KERNEL);
if (!led_miscdev)
return -ENOMEM;
led_miscdev->misc.minor = MISC_DYNAMIC_MINOR;
led_miscdev->misc.fops = &led_misc_fops;
led_miscdev->misc.name = LED_MISC_DEVICE_NAME;
led_miscdev->misc.nodename = LED_MISC_DEVICE_NAME;
ret = misc_register(&led_miscdev->misc);
if (ret < 0)
{
printk(KERN_INFO "%s : error\n", __func__);
}
else
{
printk(KERN_INFO "%s : ok\n", __func__);
}
return ret;
}
void led_miscdev_exit(void)
{
misc_deregister(&led_miscdev->misc);
printk(KERN_INFO "%s : ok\n", __func__);
}
Linux 用户态 应用,需要调用 ioctl 命令
-
驱动注册后,用户不调用,设备就不会工作,用户不下发 ioctl 控制指令,设备就无法得到控制
-
修改用户态 led_control.c 程序,位置在:
apps/led_control/led_control.c
,增加了 ioctl 命令的调用
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define LED_CONTROL_DEVICE_NAME "/dev/led_misc"
#define LED_CONTROL_IOCTL_MAGIC 'L'
#define LED_CONTROL_IOCTL_ON _IO(LED_CONTROL_IOCTL_MAGIC, 1)
#define LED_CONTROL_IOCTL_OFF _IO(LED_CONTROL_IOCTL_MAGIC, 2)
#define LED_CONTROL_IOCTL_GET_STATUS _IO(LED_CONTROL_IOCTL_MAGIC, 3)
int led_dev_fd = -1;
int led_dev_init(void)
{
int fd;
fd = open(LED_CONTROL_DEVICE_NAME, O_RDWR);
if (fd < 0)
{
printf("%s : open device error\n", __func__);
return -1;
}
led_dev_fd = fd;
printf("%s : ok\n", __func__);
return 0;
}
int led_dev_deinit(void)
{
if (close(led_dev_fd) != 0)
{
printf("%s : error\n", __func__);
return -1;
}
printf("%s : ok\n", __func__);
return 0;
}
int led_dev_on(int led_num)
{
if (ioctl(led_dev_fd, LED_CONTROL_IOCTL_ON, &led_num) != 0)
{
printf("%s : error, led_num = 0x%08x\n", __func__, led_num);
return -1;
}
printf("%s : ok\n", __func__);
return 0;
}
int led_dev_off(int led_num)
{
if (ioctl(led_dev_fd, LED_CONTROL_IOCTL_OFF, &led_num) != 0)
{
printf("%s : error, led_num = 0x%08x\n", __func__, led_num);
return -1;
}
printf("%s : ok\n", __func__);
return 0;
}
int led_dev_get_status(int *status)
{
if (ioctl(led_dev_fd, LED_CONTROL_IOCTL_GET_STATUS, status) != 0)
{
printf("%s : error\n", __func__);
return -1;
}
printf("%s : ok, led_status = 0x%08x\n", __func__, *status);
return 0;
}
int main(int argc, char **argv)
{
int led_status = 0;
printf("%s : enter\n", __func__);
led_dev_init();
led_dev_on(0x0f);
led_dev_get_status(&led_status);
printf("step 1 : led_status = 0x%08x\n", led_status);
led_dev_off(0x0f);
led_dev_get_status(&led_status);
printf("step 2 : led_status = 0x%08x\n", led_status);
led_dev_deinit();
printf("%s : exit\n", __func__);
return 0;
}
编译与运行
内核代码编译
- 编译方法参考前面的文章,输出 zImage
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4
用户态程序编译
- 执行 make,Makefile 编写可以参考前面的文章
zhangsz@zhangsz:~/linux/apps/led_control$ make
arm-linux-gnueabihf-gcc led_control.c -o led_control
- 生成 led_control,放在 qemu 根文件系统的
/home/root
目录下
/* apps led_control 路径 */
zhangsz@zhangsz:~/linux/apps/led_control$ ls
led_control led_control.c Makefile
zhangsz@zhangsz:~/linux/apps/led_control$ cd ../../rootfs/
zhangsz@zhangsz:~/linux/rootfs$ ls
1130 boot_qemu.sh ext4_rootfs make_rootfs.sh rootfs.ext4.img rootfs_mnt vexpress-v2p-ca9.dtb zImage
/* ext4 根文件系统镜像文件,使用 mount 挂载到一个目录 */
zhangsz@zhangsz:~/linux/rootfs$ sudo mount rootfs.ext4.img rootfs_mnt/
[sudo] password for zhangsz:
/* led_control 复制到 根文件系统镜像文件挂载的目录内 */
zhangsz@zhangsz:~/linux/rootfs$ sudo cp ../apps/led_control/led_control rootfs_mnt/home/root/
/* umount 后,文件就复制进根文件系统镜像文件中了 */
zhangsz@zhangsz:~/linux/rootfs$ sudo umount rootfs_mnt
- 运行 qemu,运行
/home/root
目录下 的led_control
用户程序,查看运行效果
qemu-system-arm -M vexpress-a9 -m 512M -dtb vexpress-v2p-ca9.dtb -kernel zImage -nographic -append "root=/dev/mmcblk0 rw console=ttyAMA0" -sd rootfs.ext4.img
- 进入 shell ,进入
/home/root
目录
/home/root # ./led_control
main : enter
led_misc_open : enter
led_dev_init : ok
led_misc_ioctl : enter, cmd = 0x00004c01
led_control_ioctl_on : led_nums = 0x0000000f
led_dev_on : ok
led_misc_ioctl : enter, cmd = 0x00004c03
led_control_ioctl_get_status : led_status = 0x0000000f
led_dev_get_status : ok, led_status = 0x0000000f
step 1 : led_status = 0x0000000f
led_misc_ioctl : enter, cmd = 0x00004c02
led_control_ioctl_off : led_nums = 0x0000000f
led_dev_off : ok
led_misc_ioctl : enter, cmd = 0x00004c03
led_control_ioctl_get_status : led_status = 0x00000000
led_dev_get_status : ok, led_status = 0x00000000
step 2 : led_status = 0x00000000
led_misc_close : enter
led_dev_deinit : ok
main : exit
/home/root #
- 通过运行日志,可以发现 misc 设备成功的 open ioctl 控制,读取状态, close 等
小结
-
本篇接着上一篇,讲了一下 ioctl 的命令控制,用户态的应用程序,通过 ioctl 下发各个 控制命令,如设置、获取状态等,Linux 内核 misc 设备接收 ioctl 命令并解析执行
-
由于内核态与用户态内存的隔离,所以在参数的传递时,需要使用
copy_from_user
把内核态的传参复制到内核态,通过copy_to_user
,把内核态的数据 复制到 用户态