您的位置:首页 > 健康 > 美食 > 背景模板_长沙近期大型招聘会_我想开个网站平台怎么开呢_专业seo外包

背景模板_长沙近期大型招聘会_我想开个网站平台怎么开呢_专业seo外包

2025/4/16 4:44:54 来源:https://blog.csdn.net/ItJavawfc/article/details/147124656  浏览:    关键词:背景模板_长沙近期大型招聘会_我想开个网站平台怎么开呢_专业seo外包
背景模板_长沙近期大型招聘会_我想开个网站平台怎么开呢_专业seo外包

简要了解 字符设备驱动框架 整个流程

文章目录

  • 基本知识:
    • 实际应用效果说明
  • 参考资料
  • 字符设备驱动框架
    • 基本结构
    • 关键数据结构 - 文件操作结构体(file_operations)
      • struct module *owner
      • ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
      • ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
      • long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
      • int (*open) (struct inode *, struct file *);
      • int (*release) (struct inode *, struct file *);
    • 驱动实现步骤
  • 驱动程序编写
    • 源文件 chrdev_fops.c
    • 编译程序 Makefile
    • 测试程序 app.c
      • 源码解读 strcmp
      • 源码解读 argv[1]
  • 测试
    • 加载驱动 insmod chrdev_fops.ko dmesg | tail
      • ls /sys/class/class_test/ - ls /dev/device_test
    • ./app /dev/device_test
    • 读写测试 write-read
  • 总结


基本知识:

字符设备驱动是Linux设备驱动中最基本的一种类型,它提供了一种面向字节流的设备访问方式。前面我们了解了驱动中字符设备相关知识:

  • 设备号申请:动态申请 alloc_chrdev_region
  • 字符设备的注册:初始化字符设备 cdev_init;添加字符设备到内核:cdev_add
  • 创建设备节点: 创建类:class_create ; 创建类下面的节点:device_create

具体如下:首先驱动向 Linux 内核进行设备号申请, 之后的字符设备
注册时, 会对申请的设备号进行使用。而 Linux 内核会将字符设备抽象成一个具体的 struct cdev
结构体, 该结构体记录了字符设备的字符设备号、 内核对象等信息, cdev_init(…)函数对结构体
进行初始化之后, cdev_add(…)函数将设备号和 cdev 结构体进行链接, 这时设备号才真正指向
了内核中注册的设备。 设备注册成功之后, 此时还不能对字符设备进行文件操作, 所以需要设
备节节点来充当内核和用户层通信的桥梁

实际应用效果说明

上面基础知识做了了解,最终字符设备注册到驱动里面了。那怎么进行驱动调用? Linux 体系下一切皆文件,这里最重要的核心就是结构体
struct file_operations ,struct file_operations 结构体就是把系统调用和驱
动程序关联起来的关键数据结构。 该结构体的每一个成员都对应着一个系统调用, 读取
file_operation 中相应的函数指针, 接着把控制权转交给函数, 从而完成了 Linux 设备驱动程序
的工作。

我们这里重点讨论的其实就是这个文件操作。 在这个基础上把 字符驱动框架流程在此梳理一遍。

参考资料

基础相关内容
驱动-设备号申请
驱动-注册字符设备
驱动-创建设备节点

字符设备驱动框架

基本结构

字符设备驱动的核心是struct cdev结构体,它代表一个字符设备:


#include <linux/cdev.h>struct cdev {struct kobject kobj;struct module *owner;const struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;
};

这个知识在字符设备注册的时候理解过,参考资料:驱动-注册字符设备

关键数据结构 - 文件操作结构体(file_operations)

这个知识在字符设备注册的时候理解过,参考资料:驱动-注册字符设备

file_operations 文件操作结构体,对文件的打开、关闭、读、写 指向都是在这个结构体里面定义的


struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);int (*open) (struct inode *, struct file *);int (*release) (struct inode *, struct file *);// 其他操作...
};

struct module *owner

owner 是第一个 file_operations 成员, 它并不是一个操作, 而一个指向拥有该结构的模块
的指针, 避免正在操作时被卸载, 一般为初始化为 THIS_MODULES (在 <linux/module.h> 中定
义的宏)

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

read 函数指针用来从设备中同步读取数据, 读取成功返回读取的字节数。 与应用程序中的
read 函数对应

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

write 函数指针用来发送数据给设备. 写入成功返回写入的字节数。 与应用程序中的 write
函数对应。

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

unlocked_ioctl 函数指针提供对于设备的控制功能, 与应用程序中的 ioctl 函数对应。

int (*open) (struct inode *, struct file *);

open 函数指针用于打开设备,与应用程序中的 open 函数对应。

int (*release) (struct inode *, struct file *);

release 函数指针在 file 结构体释放时被调用

file_operations 文件操作集的部分常用函数就介绍完了, 填充了部分常用函数的
file_operations 结构体如下 所示


static struct file_operations cdev_fops_test = {
.owner = THIS_MODULE,//将 owner 字段指向本模块, 可以避免在模块的操作正在被使用时卸载该模块
.open = chrdev_open,//将 open 字段指向 chrdev_open(...)函数
.read = chrdev_read,//将 open 字段指向 chrdev_read(...)函数
.write = chrdev_write,//将 open 字段指向 chrdev_write(...)函数
.release = chrdev_release,//将 open 字段指向 chrdev_release(...)函数
};//定义 file_operations 结构体类型的变量 cdev_test_ops

驱动实现步骤

其实已经总结了步骤,唯一缺的就是如何系统如何调用驱动进而调用到内核的一个理解。
在字符设备号申请-字符设备注册-创建类和类下面的设备后,借助于文件结构体来实现即可,就是对 file_operations 结构体里面的 文件操作使用起来。

驱动程序编写

源文件 chrdev_fops.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>static dev_t dev_num;//定义dev_t类型(32位大小)的变量dev_num
static struct cdev cdev_test;  //定义struct cdev 类型结构体变量cdev_test,表示要注册的字符设备static int chrdev_open(struct inode *inode, struct file *file)
{printk("This is chrdev_open \n");return 0;
}static ssize_t chrdev_read(struct file *file,char __user *buf, size_t size, loff_t *off)
{printk("This is chrdev_read \n");return 0;
}static ssize_t chrdev_write(struct file *file,const char __user *buf,size_t size,loff_t *off)
{printk("This is chrdev_write \n");return 0;
}
static int chrdev_release(struct inode *inode, struct file *file)
{return 0;
}static struct file_operations cdev_test_ops = {.owner=THIS_MODULE,//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块.open = chrdev_open,//将open字段指向chrdev_open(...)函数.read = chrdev_read,//将open字段指向chrdev_read(...)函数.write = chrdev_write,//将open字段指向chrdev_write(...)函数.release = chrdev_release,//将open字段指向chrdev_release(...)函数
};//定义file_operations结构体类型的变量cdev_test_opsstruct class *class_test;//定于struct class *类型结构体变量class_test,表示要创建的类static int __init chrdev_fops_init(void)//驱动入口函数
{int ret;//定义int类型的变量ret,用来判断函数返回值int major,minor;//定义int类型的主设备号major和次设备号minorret=alloc_chrdev_region(&dev_num,0,1,"chardev_num"); //通过动态方式进行设备号注册if(ret < 0){printk("alloc_chrdev_region is error\n");}   printk("alloc_chrdev_region is ok\n");major=MAJOR(dev_num);//通过MAJOR()函数进行主设备号获取minor=MINOR(dev_num);//通过MINOR()函数进行次设备号获取printk("major is %d\n",major);printk("minor is %d\n",minor);使用cdev_init()函数初始化cdev_test结构体,并链接到cdev_test_ops结构体cdev_init(&cdev_test,&cdev_test_ops);cdev_test.owner = THIS_MODULE;//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块 ret= cdev_add(&cdev_test,dev_num,1);if(ret < 0 ){printk("cdev_add is error\n");}printk("cdev_add is ok\n");class_test  = class_create(THIS_MODULE,"class_test");//使用class_create进行类的创建,类名称为class_testdevice_create(class_test,NULL,dev_num,NULL,"device_test");//使用device_create进行设备的创建,设备名称为device_testreturn 0;
}
static void __exit chrdev_fops_exit(void)//驱动出口函数
{cdev_del(&cdev_test);//使用cdev_del()函数进行字符设备的删除unregister_chrdev_region(dev_num,1);//释放字符驱动设备号 device_destroy(class_test,dev_num);//删除创建的设备class_destroy(class_test);//删除创建的类printk("module exit \n");}
module_init(chrdev_fops_init);//注册入口函数
module_exit(chrdev_fops_exit);//注册出口函数
MODULE_LICENSE("GPL v2");//同意GPL开源协议
MODULE_AUTHOR("wang fang chen "); //作者信息

源码分析:这个源码测试程序和之前的 创建字符设备节点 源码程序 基本一致,唯一区别 在 文件结构体 file_operations 中添加了文件方法的实现:

  .open = chrdev_open,//将open字段指向chrdev_open(...)函数.read = chrdev_read,//将open字段指向chrdev_read(...)函数.write = chrdev_write,//将open字段指向chrdev_write(...)函数.release = chrdev_release,//将open字段指向chrdev_release(...)函数

这也是我们要做的测试,系统程序执行 read、write、open、release 对应的就是驱动程序的具体实现,这里其实就是一个映射关系。

编译程序 Makefile

#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += chrdev_fops.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:make -C $(KDIR) M=$(PWD) modulesclean:make -C $(KDIR) M=$(PWD) clean

测试程序 app.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main(int argc, char *argv[])
{int fd;                           // 定义 int 类型的文件描述符char buf[32];                     // 定义读取缓冲区 buffd = open(argv[1], O_RDWR, 0666); // 调用 open 函数, 打开输入的第一个参数文件, 权限为可读可写if (fd < 0){printf("open is error\n");return -1;}printf("open is ok\n");/*如果第二个参数为 read, 条件成立, 调用 read 函数, 对文件进行读取*/if (!strcmp(argv[2], "read")){read(fd, buf, 32);}/*如果第二个参数为 write, 条件成立, 调用 write 函数, 对文件进行写入*/else if (!strcmp(argv[2], "write")){write(fd, "hello\n", 6);}close(fd); // 调用 close 函数, 对取消文件描述符到文件的映射return 0;
}

源码解读 strcmp

比较两个字符串 str1 和 str2,返回一个整数值表示比较结果:

  • 返回值 < 0:str1 小于 str2(按字典顺序)。
  • 返回值 = 0:str1 等于 str2。
  • 返回值 > 0:str1 大于 str2

源码解读 argv[1]

fd = open(argv[1], O_RDWR, 0666) 这行代码中 argv[1] 传递的第一个参数,那应该就是设备节点。我们先看看实际测试怎样的

[root@topeet:/mnt/sdcard]# ./app /dev/device_test
open is ok
Segmentation fault
[root@topeet:/mnt/sdcard]# ./app /dev/device_test write
open is ok
[root@topeet:/mnt/sdcard]# ./app /dev/device_test read
open is ok

argv[] 参数是包括命令+参数的。 argv[0] 是程序指令,argv[1] 就是代表第一个参数的

相关知识参考:驱动传参实验

测试

加载驱动 insmod chrdev_fops.ko dmesg | tail

在这里插入图片描述
我们看看 创建的类、类下面的设备吧,看看: 通过查看

ls /sys/class/class_test/ - ls /dev/device_test


[root@topeet:/mnt/sdcard]# ls /sys/class/class_test/
device_test[root@topeet:/mnt/sdcard]# ls /dev/device_test
/dev/device_test

说明设备已经被创建出来了

./app /dev/device_test

在这里插入图片描述
哈哈哈,这个 内核打印 This is chrdev_open 不就是文件结构体 file_operations 中的 chrdev_open 调用日志打印吗?

static int chrdev_open(struct inode *inode, struct file *file)
{printk("This is chrdev_open \n");return 0;
}

读写测试 write-read

在这里插入图片描述
这里如open 函数调用一样,内核里面打印了相关 write 、read 的日志打印

总结

  • 这里是对字符设备的一个总结,前面知识的进一步理解
  • 字符设备最终通过 文件结构体 file_operations 来实现,系统和驱动之间文件操作方法的映射。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com