【嵌入式linux学习】05_设备驱动 IO 控制原理

【嵌入式linux学习】设备驱动 IO 控制原理

设备驱动 IO 控制原理

在内核3.0 以前, ioctI接口的名字叫ioctl;内核3.0以后, ioctl接口的名字叫unlocked_ioctl.unlocked_ioctl就是 ioctl接口,但是功能和对应的系统调用均没有发生变化。unlocked_ioctl和 read/write函数有什么异同呢﹖相同点:都可以往内核写数据。不同点: read函数只能完成读的功能,write 只能完成写的功能。读取大数据的时候效率高。ioctl 既可以读也可以写,读取大数据的时候效率不高。

我们现在有了ioctl函数,内核已经把工作的任务给我们区分了,定义命令就不再使用read和 write 函数了,而是使用ioctl函数,因为ioctl函数的任务就是对我们的工作参数进行设置和查询,write和 read函数专注于数据传输。


文章目录

ioctl 的 32 位

在 Linux 中,一个 ioctl 命令通常由 32 位(4字节)组成,被划分为四个字段。

c 复制代码
第一个分区:0-7,命令的编号,范围是 0-255.
第二个分区:8-15,命令的幻数。
第一个分区和第二个分区主要作用是用来区分命令的。

第三个分区:16-29 表示传递的数据大小。
第四个分区:30-31 代表读写的方向。
00:表示用户程序和驱动程序没有数据传递
10:表示用户程序从驱动里面读数据
01:表示用户程序向驱动里面写数据
11:先写数据到驱动里面然后在从驱动里面把数据读出来。

合成宏:

c 复制代码
_IO(type,nr):用来定义没有数据传递的命令
_IOR(type,nr,size):用来定义从驱动中读取数据的命令
_IOW(type,nr,size):用来定义向驱动写入数据的命令
_IOWR(type,nr,size) :用来定义数据交换类型的命令,先写入数据,再读取数据这类命令。
参数说明:
type:表示命令组成的魔数,也就是 8~15 位
nr:表示命令组成的编号,也就是 0~7 位
size:表示命令组成的参数传递大小,注意这里不是传递数字,而是数据类型,如要传递 4 字节,就可
以写成 int。

分解宏:

c 复制代码
_IOC_DIR(nr):分解命令的方向,也就是上面说 30~31 位的值
_IOC_TYPE(nr):分解命令的魔数,也就是上面说 8~15 位的值
_IOC_NR(nr):分解命令的编号,也就是上面说 0~7 位
_IOC_SIZE(nr):分解命令的复制数据大小,也就是上面说的 16~29 位

nr :要分解的命令

一般通用的位定义如下(不同架构略有不同,但通常遵循此逻辑):

位段 (Bit range) 含义 (Meaning) 宏 (Macro) 解释
30-31 方向 (Direction) _IOC_DIR 读、写、或者无数据传输。
16-29 大小 (Size) _IOC_SIZE 传输数据类型的字节大小。
8-15 类型 (Type) _IOC_TYPE 驱动程序的"幻数"或标识符 (Magic Number)。
0-7 序号 (Nr) _IOC_NR 命令的序列号。

代码举例介绍

c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define CMD_TEST0 _IO('L', 0)
#define CMD_TEST1 _IO('A', 1)
#define CMD_TEST2 _IOW('L', 2, int)
#define CMD_TEST3 _IOR('L', 3, int)

int main(int argc, char const *argv[])
{
    printf("30-31 is %d \n", _IOC_DIR(CMD_TEST0));
    printf("30-31 is %d \n", _IOC_DIR(CMD_TEST3));
    printf("7-15 is %c \n", _IOC_TYPE(CMD_TEST0));
    printf("7-15 is %c \n", _IOC_TYPE(CMD_TEST1));
    printf("0-7 is %d \n", _IOC_NR(CMD_TEST2));
    return 0;
}


/*
最后打印的部分:
30-31 is 0 
30-31 is 2 
7-15 is L 
7-15 is A 
0-7 is 2 

*/
宏定义部分

这部分定义了四个不同的测试命令:

c 复制代码
#define CMD_TEST0 _IO('L', 0)        // 定义一个无数据传输的命令,类型'L',序号0
#define CMD_TEST1 _IO('A', 1)        // 定义一个无数据传输的命令,类型'A',序号1
#define CMD_TEST2 _IOW('L', 2, int)  // 定义一个写(Write)命令,类型'L',序号2,数据是int
#define CMD_TEST3 _IOR('L', 3, int)  // 定义一个读(Read)命令,类型'L',序号3,数据是int
  • _IO(type, nr): 构造没有参数传输的命令。
  • _IOW(type, nr, size): 构造向设备写入数据(Write)的命令。
  • _IOR(type, nr, size): 构造从设备读取数据(Read)的命令。
主函数解析部分

这部分使用了 _IOC_* 系列宏将上述打包好的命令"解包",还原出原始信息。

  1. 解析方向 (Direction)

    c 复制代码
    printf("30-31 is %d \n", _IOC_DIR(CMD_TEST0));
    printf("30-31 is %d \n", _IOC_DIR(CMD_TEST3));
    • CMD_TEST0 使用了 _IO,表示没有数据传输(None)。
    • CMD_TEST3 使用了 _IOR,表示读方向(Read)。
    • 结果 :这里会打印出代表"无方向"和"读方向"的整数值(具体数值取决于系统头文件定义,通常 _IOC_NONE 是 0 或 1,_IOC_READ 是 2)。
  2. 解析类型 (Type/Magic Number)

    c 复制代码
    printf("7-15 is %c \n", _IOC_TYPE(CMD_TEST0));
    printf("7-15 is %c \n", _IOC_TYPE(CMD_TEST1));
    • 这里提取的是我们在定义时传入的字符(幻数)。
    • 结果 :第一行打印 L,第二行打印 A。这是因为 _IOC_TYPE 宏把那 8 个位取出来并作为字符输出了。
  3. 解析序号 (Number)

    c 复制代码
    printf("0-7 is %d \n", _IOC_NR(CMD_TEST2));
    • 这里提取的是命令的序列号。
    • 结果 :打印 2,因为 CMD_TEST2 定义时的序号参数就是 2。
关于幻数
c 复制代码
#define CMD_TEST0 _IO('L',0)
#define CMD_TEST1 _IO('A',1)
#define CMD_TEST2 _IOW('L',2,int)
#define CMD_TEST3 _IOW('L',3,int)
#define CMD_TEST4 _IOR('L', 4, int)

在这段代码中,'L''A' 被称为 "幻数" (Magic Number) 或者 "类型标识" (Type)

1. 它们的作用是什么?

在 Linux 系统中,有很多很多的驱动程序(比如串口、摄像头、显卡等)。每个驱动程序都有一套自己的控制命令(ioctl 命令)。

为了防止不同驱动程序的命令发生冲突 (例如:摄像头驱动有一个"复位"命令是 1,串口驱动也有一个"复位"命令是 1),我们需要给每个驱动分配一个独一无二的标识符。

  • 'L' :在这段代码中,可能代表这是属于 "Module L" 或者某个特定设备的命令(比如 Light、Log 等,具体取决于程序员的意图)。
  • 'A' :在这段代码中,代表这是属于另一个设备(Module A)的命令。
2. 为什么用字符(如 'L', 'A')?

ioctl 的命令结构中,分配给"类型(Type)"的空间是 8个比特(8-bit)。 8个比特刚好能存放一个 ASCII 字符(0-255)。

程序员习惯用字符来定义幻数,因为这样可读性更强

  • 'k' 可能代表 Kernel 相关的设备。
  • 's' 可能代表 Socket 相关的设备。
  • 'L' 可能代表 LED 灯的驱动。
3. 在这段代码中的体现
  • CMD_TEST0CMD_TEST2CMD_TEST3 都是 'L' 家族 的命令。
  • CMD_TEST1'A' 家族 的命令。

当你运行代码中的 printf("7-15 is %c \n", _IOC_TYPE(CMD_TEST1)); 时,程序会从 32 位的整数中提取出那 8 位,发现是 ASCII 码的 65,也就是字符 'A'

总结
  • A 和 L 是自定义的分类标签
  • 它们告诉内核和应用程序:"这条命令是发给 'L' 类型设备的,而不是发给 'A' 类型设备的。"
  • 在正式的 Linux 内核开发中,为了避免和官方驱动冲突,通常需要查阅内核文档 Documentation/userspace-api/ioctl/ioctl-number.rst 来选择一个没被占用的字符。但在自己写测试代码时,随便选个字母(比如 'L' 或 'A')即可。

编写测试------不传递数据

注:以下所有程序 更改部分都是这一部分

驱动程序
c 复制代码
#include<linux/init.h>
#include<linux/module.h>
#include<linux/miscdevice.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/io.h>
#include<linux/kernel.h>

#define CMD_TEST1 _IO('A',1)
#define CMD_TEST0 _IO('L',0)

ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
    printk("misc_read\n ");
    return 0;
}

ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
    char kbuf[64] = {0};
    // copy_from_user 从应用层传递数据给内核层
    if (copy_from_user(kbuf, ubuf, size) != 0)
    {
        // copy_from_user 传递失败打印
        printk("copy_from_user error \n ");
        return -1;
    }
    //打印传递进内核的数据
    printk("kbuf is %d\n ", kbuf[0]);
    return 0;
}

int misc_release(struct inode *inode, struct file *file)
{
    printk("hello misc_relaease bye bye \n ");
    return 0;
}

int misc_open(struct inode *inode, struct file *file)
{
    printk("hello misc_open\n ");
    return 0;
}

long misc_ioctl(struct file *file, unsigned int cmd, unsigned long value)
{
    printk("LED ON \n");
    switch (cmd) //根据命令进行对应的操作
    {
        case CMD_TEST0:
            printk("LED ON \n");
            break;
        case CMD_TEST1:
            printk("LED OFF \n");
            break;
    }
    return 0;
}

struct file_operations misc_fops = {
    .owner = THIS_MODULE,
    .open = misc_open,
    .release = misc_release,
    .read = misc_read,
    .write = misc_write,
    .unlocked_ioctl = misc_ioctl 
};
struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "hello_misc",
    .fops = &misc_fops,
};

static int misc_init(void)
{
    int ret;
    ret = misc_register(&misc_dev);
    if (ret < 0)
    {
        printk("misc registe is error \n");
    }
    printk("misc registe is succeed \n");
    return 0;
}

static void misc_exit(void)
{
    misc_deregister(&misc_dev);
    printk(" misc gooodbye! \n");
}

module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");
应用程序
c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define CMD_TEST0 _IO('L',0)
#define CMD_TEST1 _IO('A',1)
//#define CMD_TEST2 _IOW('L',2,int)
//#define CMD_TEST3 _IOR('L',3,int)
int main(int argc,char *argv[])
{
    int fd;
    char buf[64] ={0};
    fd = open("/dev/hello_misc",O_RDWR);
    if(fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    while(1)
    {
        ioctl(fd,CMD_TEST0);
        sleep(2);
        ioctl(fd,CMD_TEST1);
    }
    return 0;
}
最终结果

写数据

驱动程序
c 复制代码
#include<linux/init.h>
#include<linux/module.h>
#include<linux/miscdevice.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/io.h>
#include<linux/kernel.h>

#define CMD_TEST1 _IO('A',1)
#define CMD_TEST0 _IO('L',0)
#define CMD_TEST2 _IOW('L', 2, int)
#define CMD_TEST3 _IOW('L', 3, int)
ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
    printk("misc_read\n ");
    return 0;
}

ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
    char kbuf[64] = {0};
    // copy_from_user 从应用层传递数据给内核层
    if (copy_from_user(kbuf, ubuf, size) != 0)
    {
        // copy_from_user 传递失败打印
        printk("copy_from_user error \n ");
        return -1;
    }
    //打印传递进内核的数据
    printk("kbuf is %d\n ", kbuf[0]);
    return 0;
}

int misc_release(struct inode *inode, struct file *file)
{
    printk("hello misc_relaease bye bye \n ");
    return 0;
}

int misc_open(struct inode *inode, struct file *file)
{
    printk("hello misc_open\n ");
    return 0;
}

long misc_ioctl(struct file *file, unsigned int cmd, unsigned long value)
{
    printk("LED ON \n");
    switch (cmd) //根据命令进行对应的操作
    {
        case CMD_TEST2:
            printk("LED ON \n");
            printk("value is %ld\n", value);
            break;
        case CMD_TEST3:
            printk("LED OFF \n");
            printk("value is %ld\n", value);
            break;
    }
    return 0;
}

struct file_operations misc_fops = {
    .owner = THIS_MODULE,
    .open = misc_open,
    .release = misc_release,
    .read = misc_read,
    .write = misc_write,
    .unlocked_ioctl = misc_ioctl 
/* 64 bit system special */};
struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "hello_misc",
    .fops = &misc_fops,
};

static int misc_init(void)
{
    int ret;
    ret = misc_register(&misc_dev);
    if (ret < 0)
    {
        printk("misc registe is error \n");
    }
    printk("misc registe is succeed \n");
    return 0;
}

static void misc_exit(void)
{
    misc_deregister(&misc_dev);
    printk(" misc gooodbye! \n");
}

module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");
应用程序
c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define CMD_TEST0 _IO('L',0)
#define CMD_TEST1 _IO('A',1)
#define CMD_TEST2 _IOW('L',2,int)
#define CMD_TEST3 _IOW('L',3,int)
//#define CMD_TEST3 _IOR('L',3,int)
int main(int argc,char *argv[])
{
    int fd;
    char buf[64] ={0};
    fd = open("/dev/hello_misc",O_RDWR);
    if(fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    while(1)
    {
        ioctl(fd,CMD_TEST2,0);//传value
        sleep(2);
        ioctl(fd,CMD_TEST3,1);//传value
    }
    return 0;
}
结果

读数据

驱动程序
c 复制代码
#include<linux/init.h>
#include<linux/module.h>
#include<linux/miscdevice.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/io.h>
#include<linux/kernel.h>

#define CMD_TEST1 _IO('A',1)
#define CMD_TEST0 _IO('L',0)
#define CMD_TEST2 _IOW('L', 2, int)
#define CMD_TEST3 _IOW('L', 3, int)
#define CMD_TEST4 _IOR('L', 4, int)
ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
    printk("misc_read\n ");
    return 0;
}

ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){
    char kbuf[64] = {0};
    // copy_from_user 从应用层传递数据给内核层
    if (copy_from_user(kbuf, ubuf, size) != 0)
    {
        // copy_from_user 传递失败打印
        printk("copy_from_user error \n ");
        return -1;
    }
    //打印传递进内核的数据
    printk("kbuf is %d\n ", kbuf[0]);
    return 0;
}

int misc_release(struct inode *inode, struct file *file)
{
    printk("hello misc_relaease bye bye \n ");
    return 0;
}

int misc_open(struct inode *inode, struct file *file)
{
    printk("hello misc_open\n ");
    return 0;
}

long misc_ioctl(struct file *file, unsigned int cmd, unsigned long value)
{
    int val;
    printk("LED ON \n");
    switch (cmd) //根据命令进行对应的操作
    {
        case CMD_TEST2:
            printk("LED ON \n");
            printk("value is %ld\n", value);
            break;
        case CMD_TEST3:
            printk("LED OFF \n");
            printk("value is %ld\n", value);
            break;

        case CMD_TEST4:
            val = 12;
            if (copy_to_user((int *)value, &val, sizeof(val)) != 0)
            {
                printk("cpoy_to_usr error \n");
                return -1;
            }
            break;
    }
    return 0;
}

struct file_operations misc_fops = {
    .owner = THIS_MODULE,
    .open = misc_open,
    .release = misc_release,
    .read = misc_read,
    .write = misc_write,
    .unlocked_ioctl = misc_ioctl 
/* 64 bit system special */};
struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "hello_misc",
    .fops = &misc_fops,
};

static int misc_init(void)
{
    int ret;
    ret = misc_register(&misc_dev);
    if (ret < 0)
    {
        printk("misc registe is error \n");
    }
    printk("misc registe is succeed \n");
    return 0;
}

static void misc_exit(void)
{
    misc_deregister(&misc_dev);
    printk(" misc gooodbye! \n");
}

module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");
应用程序
c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define CMD_TEST0 _IO('L',0)
#define CMD_TEST1 _IO('A',1)
#define CMD_TEST2 _IOW('L',2,int)
#define CMD_TEST3 _IOW('L',3,int)
#define CMD_TEST4 _IOR('L', 4, int)
int main(int argc,char *argv[])
{
    int fd;
    int value;
    char buf[64] ={0};
    fd = open("/dev/hello_misc",O_RDWR);
    if(fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    while(1)
    {
       ioctl(fd, CMD_TEST4, &value);
       printf("value is %d \n", value);
    }
    return 0;
}
结果
相关推荐
~光~~1 天前
【嵌入式linux学习】04_Pinctrl 和 GPIO子系统
linux·rk3588·嵌入式linux
hugerat11 天前
在AI的帮助下,用C++构造微型http server
linux·c++·人工智能·http·嵌入式·嵌入式linux
淮北也生橘121 个月前
Linux驱动开发:移植一个MIPI摄像头驱动并将其点亮(基于Sstar 2355平台)
linux·运维·驱动开发·嵌入式linux
淮北也生橘122 个月前
Linux驱动知识点:容器嵌入机制(Container Embedding)
linux驱动·嵌入式linux
淮北也生橘124 个月前
Linux的V4L2视频框架学习笔记
linux·笔记·学习·音视频·嵌入式linux
林政硕(Cohen0415)5 个月前
使用docker搭建嵌入式Linux开发环境
linux·docker·sdk·嵌入式linux
宁静致远202110 个月前
openssl交叉编译
openssl·嵌入式linux
知立10 个月前
嵌入式Linux开发环境搭建,三种方式:虚拟机、物理机、WSL
linux·开发环境·嵌入式linux
zxfeng~10 个月前
泰山派开发之—Ubuntu24.04下Linux开发环境搭建
linux·嵌入式·嵌入式linux·泰山派