Linux 字符驱动架构

news/2025/2/28 21:37:45/文章来源:https://www.cnblogs.com/tggpx/p/18734234

如何往内核里添加一个字符驱动程序

  1. 分配设备号

    • 前置:
    • 设备号分为主设备号和次设备号.
    • 主设备号是分配给设备驱动程序的唯一标识符,用于标识设备所属的驱动程序。它告诉内核在访问设备时应该调用哪个驱动程序来处理请求.
    • 次设备号是与主设备号配合使用的较小标识符,用于区分同一主设备号下的不同设备实例
    • 内核中设备号是一个dev_t类型的变量, 通过MAJORMINOR宏可以获取主设备号和次设备号.MKDEV宏可以将主设备号和次设备号合并成一个dev_t类型的变量.

    申请设备号的方法有两种:

    1. 静态申请, 自己指定设备号
    int register_chrdev_region(dev_t first, unsigned int count, char *name);
    
    1. 动态申请, 调用该函数后内核会分配一个未被使用的设备号
    int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
    

    无论使用哪种方法都在不需要的时候释放设备号

    void unregister_chrdev_region(dev_t first, unsigned int count);
    
  2. 实现设备操作函数
    在linux中一切皆文件, linux使用统一的接口来访问设备, 实现这些接口就是实现设备的驱动程序, 不支持的接口可以设置为NULL.
    接口的定义是struct file_operations:

 struct module *owner // 指向拥有这个结构的模块的指针, 避免它还在被使用时模块被卸载. 通常使用被初始为 THIS_MODULEloff_t (*llseek) (struct file *, loff_t, int); //改变文件中的当前读/写位置ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //从设备中获取数据ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t); //异步读ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //发送数据给设备ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *); //异步写int (*open) (struct inode *, struct file *); //对设备文件的第一个操作int (*release) (struct inode *, struct file *); //在文件结构被释放时引用这个操作,文件关闭时long (*unlocked_ioctl) (struct file *, unsigned int cmd, unsigned long parm); //原生的ioctl,设备特定的控制命令long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //为了兼容32为的ioctl
...
  1. 内核中使用cdev管理驱动程序.

    1. 使用cdev_alloc函数分配一个cdev结构体
    2. 使用cdev_init(struct cdev *cdev, struct file_operations *fops)函数初始化cdev结构体, 把设备操作函数和cdev绑定.
    3. 使用cdev_add(struct cdev *cdev, dev_t dev, unsigned int count)函数将cdev添加到内核中, 和设备号绑定在一起, 一个cdev可以和多个设备号绑定在一起,这个函数会把dev - (dev+count-1)的设备号都绑定到这个cdev.
    • 在不需要的时候,使用cdev_del(struct cdev *cdev)函数将cdev从内核中删除, 使其失效.
  2. 使用模块技术加载进入内核.

创建设备节点(文件)

设备节点通常是在/dev目录下, 通过mknod命令创建, 也可以通过udev自动创建.
mknod 命令格式: c 表示字符设备, b 表示块设备

mknod /dev/设备名 c 主设备号 次设备号

创建设备节点的时候会根据设备号把对应的cdev放在文件的inode中, 这样就能通过这个设备节点文件访问到对应的设备驱动程序.

使用动态分配设备号的一个问题就是不能提前知道设备号, 不能提前创建设备节点. 只能在分配通过/proc/devices查看设备号, 然后创建设备节点.
下面是一个创建设备节点的脚本:

#!/bin/sh
module="scull"
device="scull"
mode="664"
# invoke insmod with all arguments we got
# and use a pathname, as newer modutils don't look in . by default
/sbin/insmod ./$module.ko $* || exit 1 # $*表示所以传入当前脚本的参数, || exit 1表示如果insmod执行失败,则exit 1 错误退出
# remove stale nodes
rm -f /dev/${device}[0-3]
major=$(awk "\\$2==\"$module\" {print \\$1}" /proc/devices) 
mknod -m ${mode} /dev/${device}0 c $major 0
mknod -m ${mode} /dev/${device}1 c $major 1
mknod -m ${mode} /dev/${device}2 c $major 2
mknod -m ${mode} /dev/${device}3 c $major 3
# give appropriate group/permissions, and change the group.
# Not all distributions have staff, some have "wheel" instead.
# group="staff"
# grep -q '^staff:' /etc/group || group="wheel"
# chgrp $group /dev/${device}[0-3]
# chmod $mode /dev/${device}[0-3]

快速参考

<linux/types.h>
dev_t 内核设备号类型
<linux/kdev_t.h>
MAJOR(dev_t dev);
MINOR(dev_t dev);
MKDEV(int major, int minor);
<linux/fs.h>
file_operations
alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name)
register_chrdev_region(dev_t first, unsigned int count, char *name)
unregister_chrdev_region(dev_t first, unsigned int count);
iminor(inode) 通过inode获取次设备号
imajor(inode)
<linux/cdev.h>
struct cdev *cdev_alloc(void);
void cdev_init(struct cdev *dev, struct file_operations *fops);
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
void cdev_del(struct cdev *dev);
<linux/kernel.h>
container_of(pointer, container_type, container_field); 通过filed找到container的地址
<linux/uaccess.h>
copy_to_user(void __user *to, const void *from, unsigned long n)
copy_from_user(void *to, const void __user *from, unsigned long n)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/891354.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

定时任务在若依中的应用

定时任务任务调度是指系统为了自动完成特定任务,在约定的特定时刻去执行任务的过程。有了任务调度即可解放更多的人力,而是由系统自动去执行任务。Cron表达式cron表达式是一个字符串, 用来设置定时规则, 由七部分组成, 每部分中间用空格隔开, 每部分的含义如下表所示:组成部分…

CMD批处理脚本+VBScript脚本+Potplayer 实现文件夹内所有视频的截图任务(指定时间点)

实现自动化视频截图,一般会直接借视频编解码如FFmpeg,动用相关函数来实现,直接从解码源头设计程序。然而我没有接触过FFmpeg,借助cmd批处理,以及vbs,还有现成的播放器potplayer,一样可以实现。【思路】 查找特定文件夹内所有的视频,每个视频使用potplayer打开,使用视频…

第四天

001数组(常见操作)【】定义数组的两种格式【】角标最高位是数组长度减一,异常编译器不会发现执行时才在内存开辟空间被查出【】arr变量值为空null,不指向数组,就会出异常 【】获取数组的元素,一般遍历获取 for循环遍历 引入数组长度来优化遍历 【】数组求和:变量+循环 …

符号匹配

符号匹配是一种常见的算法问题,主要用于检查给定的字符串中 各种符号(如括号()、方括号[]、花括号{}等)是否正确配对和嵌套。 在一个合法的符号序列中,每个左符号(如(、[、{)都必须有一个对应 的右符号(如)、]、}),并且符号的嵌套顺序必须正确。 例如,{[()]} 是一个合…

API方式开发AI应用的三点总结

1. 编程式prompt 让 AI 具备类似程序的运行逻辑。把大模型当CLR使用。与传统的角色扮演提示prompt相比,此方式所需的tokens数量更少,且输出结果的准确性更高 。示例如下:2. 语音对话(STT/TTS) 因为如今碎片化的时代,没多少人愿意看完一大篇文字内容,所以需要将AI生成的内…

五下数学第1单元练习情况反馈204班

五下数学第1单元练习情况反馈204班 本周进行了数学第1单元的综合练习,已经进行了讲评。试卷已经下发,请学生带回家改完错误,家长签字。 签字在试卷的左上角,签字示范:家长阅,2月28日,或者再写一些建议与意见都可以。 下面分析一下考试情况: 第1单元数学练习 下面是具体…

逆序对的解法——归并排序

题目来源:洛谷P8613小朋友排队(https://www.luogu.com.cn/problem/P8613) 题目大意为:给你一段序列,只允许相邻两个数交换,对于某个数,一次操作ans+1,第二次操作ans+2,以此类推。问让此序列排列成递增序列的ans最小值。 思路: 当一个数左边存在比它大的数时,一定需要交…

从红屏到断点,VSCode+Chrome打开调试vue.js项目的黄金配置公式,后端转前端必看!

夙夜小哥这几天有个项目前端人手不够,要我支援几天。我本人之前是写后端的,在支援的过程中发现前端对JavaScript或者typescript的方法提示以及代码导航功能都比较弱,可能是由于js本身属于弱类型语言,所以这波并不属于VSCode的锅。 但是我又发现好像VSCode不进行配置的话,没…

PCB绘制学习--嘉立创EDA专业版

PCB绘制学习--嘉立创EDA专业版 由于最近找工作四处碰壁,打算跟着大部分岗位的招聘要求学点东西起来,目前的计划是学习PCB绘制、STM32Cube开发工具以及HAL库的使用,FreeRTOS在STM32上的移植。同时还要做毕设,忙~ 做博客记录呢是觉得学了不记点笔记久了就跟没学过一样。 言归…

厂房AI火焰识别网络摄像机

厂房AI火焰识别网络摄像机通过深度学习算法,能够识别火焰的细微特征,即使在复杂环境背景下也能准确判断,在设计上借助传感器过滤掉图像上像火的物体,比如车尾灯,晚霞。算法帮助传感器过滤掉带有辐射的物体,比如人体,汽车尾气,太阳光等。使得误报率几乎不会存在,大大降低…

乱扔垃圾行为检测系统

乱扔垃圾行为检测系统基于YOLOX+RNN的深度学习算法,乱扔垃圾行为检测系统通过前端摄像头一旦检测到乱扔垃圾行为,系统会立即发出警报,通知相关人员及时处理,从而起到保障社会卫生的作用。本系统通过安装在垃圾桶周围的摄像头,实时监测垃圾桶内的垃圾量。当垃圾桶内的垃圾达…

现代CPU调优3: CPU 微架构

3 CPU CPU 微架构 本章简要概述了对软件性能有直接影响的关键 CPU 微体系结构特性。本章的目的并不是要涵盖 CPU 架构的所有细节和权衡,文献[Hennessy & Patterson, 2017 Computer Architecture, Sixth Edition]、[Shen & Lipasti, 2013 Modern Processor Design: Fun…