• 嵌入式Linux 开发经验:编写用户态应用程序 ioctl 控制 misc 设备


    参考文章

    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 
    #include 
    #include 
    
    #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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 编写或者修改 内核态的 设备驱动 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__);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171

    Linux 用户态 应用,需要调用 ioctl 命令

    • 驱动注册后,用户不调用,设备就不会工作,用户不下发 ioctl 控制指令,设备就无法得到控制

    • 修改用户态 led_control.c 程序,位置在:apps/led_control/led_control.c,增加了 ioctl 命令的调用

    #include 
    #include 
    #include 
    #include 
    
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    编译与运行

    内核代码编译

    • 编译方法参考前面的文章,输出 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
    
    • 1
    • 2
    • 生成 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 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 运行 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
    
    • 1
    • 进入 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 # 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 通过运行日志,可以发现 misc 设备成功的 open ioctl 控制,读取状态, close 等

    小结

    • 本篇接着上一篇,讲了一下 ioctl 的命令控制,用户态的应用程序,通过 ioctl 下发各个 控制命令,如设置、获取状态等,Linux 内核 misc 设备接收 ioctl 命令并解析执行

    • 由于内核态与用户态内存的隔离,所以在参数的传递时,需要使用 copy_from_user 把内核态的传参复制到内核态,通过 copy_to_user,把内核态的数据 复制到 用户态

  • 相关阅读:
    java项目-第84期基于ssm的博客新闻管理系统
    并发编程详解 一文读懂并发编程 线程 线程三大特性 volatile synchronized final CAS AQS Semaphore信号量 线程池
    OpenLayers.js 入门教程:打造互动地图的入门指南
    Django——模板应用
    golang中的闭包的再说明
    【解决方案】浅谈基于边缘计算的智慧工地解决方案
    React组件之间的通信方式总结(上)
    [附源码]Python计算机毕业设计Django网咖管理系统
    HTML+CSS美食静态网页设计——简单牛排美食餐饮(9个页面)公司网站模板企业网站实现
    不小心清空了回收站怎么恢复,回收站删除的东西可以恢复吗
  • 原文地址:https://blog.csdn.net/tcjy1000/article/details/128175511