Linux字符设备驱动开发一

linux字符设备驱动

    • 0 驱动介绍
    • 1 字符设备驱动
      • 1.1 字符设备相关概念和结构体
      • 1.2 实现简单的字符设备模块
      • 1.3 创建字符设备
      • 1.4 总结

应用程序调用文件系统的API(open、close、read、write) -> 文件系统根据访问的设备类型,调用对应设备的驱动API -> 驱动对硬件进行操作。
请添加图片描述

0 驱动介绍

驱动类型:

  1. 字符设备驱动:以一个字节一个字节读写的硬件对应的驱动。比如串口、watchdog、rtc、鼠标、触摸屏等。字符设备必须以串行顺序依次进行访问。
  2. 块设备驱动:以一个扇区一个扇区(512kb、4096kb)读写的硬件对应的驱动。比如硬盘。块设备可以按任意顺序进行访问。
  3. 网络设备驱动:走内核里的协议栈内存。比如网卡驱动、can驱动。网络设备面向数据包的接收和发送而设计。

除网络设备外,字符设备与块设备都被映射到Linux文件系统的文件和目录,通过文件系统的系统调用接口open()、write()、read()、close()等即可访问字符设备和块设备。

驱动的动态加载和静态加载区别:

  1. 编译方式不同:静态加载–obj-y,动态加载–obj-m
  2. 存在位置不同:静态加载,则直接编译到linux内核镜像中uimag;动态加载,则是独立的ko,驱动代码不在内核镜像中,而是独立存在于文件系统中
  3. 加载时机不同:静态加载,内核启动时就加载;动态加载,在系统启动的时候首先uboot的启动,然后linux内核启动,最后文件系统启动。静态加载,随linux内核一起加载启动,动态加载则需要等文件系统启动后手动insmod加载。

动态加载优势:

  1. 实现热插拔机制:硬件要实现热插拔,则驱动必须使用动态加载。设备插入时,加载驱动;设备移除时,卸载驱动。
  2. 驱动调试:调试更方便。单独编译驱动,通过insmod、rmmod加载和卸载进行单独调试。
  3. 开机优化:有的驱动加载非常耗时,影响设备启动速度。动态加载,可以等用户的图形界面起来后,再加载驱动,让用户感觉系统启动速度加快了。

1 字符设备驱动

字符设备必须以串行顺序依次进行访问,常见的字符设备:串口、watchdog、rtc、鼠标、触摸屏等。字符设备的读写以字节为单位。

1.1 字符设备相关概念和结构体

了解字符设备相关概念和结构体,为之后阅读字符设备驱动代码打下坚实基础。

1 设备号dev_t:类似一个人的身份证号。内核中通过dev_t来描述设备号,其实质是unsigned int 32位整数,其中高12位为主设备号,低20位为次设备号。

  • 为每一个外设设置一个独一无二的ID,让内核能够区分每一个设备。
  • 高12区分不同类别的设备
  • 低20位,区分同一类别的不同设备

linux如何为每一个设备生成设备号呢?
可以是驱动自行构造一个设备号,然后调用函数register_chrdev_region向内核注册。如果注册成功,该函数返回0;注册失败,返回一个负数,就不能用这个设备号。

int register_chrdev_region(dev_t from, unsigned count, const char *name);

2 设备描述cdev、file_operations:
一个linux设备,需要通过cdev结构体来描述设备信息,通过file_operations来描述如何操作设备。最后要通过cdev_add函数来注册设备,让linux内核知道这个设备。

/*设备信息的描述*/
struct cdev {
    struct kobject kobj; /**/
    struct module *owner;
    const struct file_operations *ops; /*一组函数集*/
    struct list_head list; /*链表*/
    dev_t dev; /*设备号*/
    unsigned int count; /*已支持的设备*/
}

/*设备行为的描述:驱动设备开发,就要实现这些函数*/
struct 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 *);
    int (*open) (struct inode *, struct file *);
    int (*release) (struct inode *, struct file *);
    ...
};

/*相关注册函数*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count);

1.2 实现简单的字符设备模块

#include <linux/module.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/fs.h>
#define OK (0)
#define ERROR (-1)
#define DEV_NUM (10)

/*
 创建设备ID:MKDEV
 注册设备ID:
 给设备行为结构体的成员函数赋值
 初始化设备:建立设备行为结构体file_operations和设备信息结构体cdev的关系;
 添加设备:向内核注册设备,使内核知道设备,建立设备ID和设备信息cdev的关系;
 加载设备:insmod
 创建设备文件:建立设备文件与设备ID的关系:mknod /dev/my_chr_dev c 232 0
 测试设备行为结构体的成员函数功能
*/

dev_t g_dev_num; /*设备号*/
struct cdev *g_cdev; /*设备信息*/
struct file_operations *g_fops; /*设备行为信息*/
const int g_major = 232; /*主设备号*/
const int g_minor = 0; /*次设备号*/
unsigned g_dev_count = 3;
const char g_dev_name[] = "my_char_dev"; /*设备名称,内核管理用的,随便写*/

ssize_t my_dev_read(struct file *my_file, char __user *my_user, size_t my_size, loff_t *my_loff)
{
    printk(KERN_EMERG "read my dev success\n");
    return OK;
}

ssize_t my_dev_write(struct file *my_file, const char __user *my_user, size_t my_size, loff_t *my_loff)
{
    printk(KERN_EMERG "write my dev success\n");
    return OK;
}

int my_dev_open(struct inode *my_inode, struct file *my_file)
{
    printk(KERN_EMERG "open my dev success\n");
    return OK;
}

int my_dev_release(struct inode *my_inode, struct file *my_file)
{
    printk(KERN_EMERG "release my dev success\n");
    return OK;
}

static int __init chr_init(void)
{
    g_dev_num = MKDEV(g_major, g_minor); /*生成设备号*/
    printk("dev id:%d\n", g_dev_num);

    /*注册设备号*/
    if (register_chrdev_region(g_dev_num, g_dev_count, g_dev_name) != 0) {
        printk(KERN_EMERG "register dev id failed\n");
        return ERROR;
    }

    /*内核函数:自动生成设备函数, 避免自定义设备号冲突,导致注册失败
    if (alloc_chrdev_region(&g_dev_num, g_major, g_dev_count, g_dev_name) != OK) {
        printk(KERN_EMERG "register dev id failed\n");
        return ERROR;
    }
    */

   /*为设备信息结构体cdev和设备行为结构体file_operations申请内存;kzalloc相比kmalloc,会将申请的内存地址置为0,*/
    g_cdev = kzalloc(sizeof(struct cdev), GFP_KERNEL);
    g_fops = kzalloc(sizeof(struct file_operations), GFP_KERNEL);

    /*为设备行为结构体成员函数赋值*/
    g_fops->read = my_dev_read;
    g_fops->write = my_dev_write;
    g_fops->open = my_dev_open;
    g_fops->release = my_dev_release;
    g_fops->owner = THIS_MODULE; /*一般就赋值为THIS_MODULE宏*/

    cdev_init(g_cdev, g_fops); /*初始化设备:建立cdev和file_operations的联系*/
    cdev_add(g_cdev, g_dev_num, g_dev_count); /*向内核注册设备:建立cdev和设备号的联系*/
    printk(KERN_EMERG "add my_chr_dev success\n");
    return OK;
}

static void __exit chr_exit(void)
{
    cdev_del(g_cdev); /*删除设备*/
    unregister_chrdev_region(g_dev_num, g_dev_count); /*注销设备号*/
    printk(KERN_EMERG "unregister my_chr_dev success\n");
}

module_init(chr_init);
module_exit(chr_exit);
MODULE_LICENSE("GPL");


make编译模块后,insmod my_chr_dev加载字符设备驱动。
makefile文件解读:

  • ifneq,首先判断是否定义了内核宏,第一次执行由于没有定义内核宏,先执行else分支,然后执行all。
  • make -C,切换到内核目录内,-M指定编译的模块。此时进入-M指定的模块内,再次执行makefile文件。
  • 此时,内核宏定义了,执行if分支,编译内核模块。
ifneq ($(KERNELRELEASE),)
  obj-m := my_chr_dev.o
else
  KERDIR ?="/lib/modules/$(shell uname -r)"/build
  PWD := $(shell pwd)

all:
	$(MAKE) -C $(KERDIR) M=$(PWD)
clean:
	rm -rf *.o *.ko *mod.c 9.module.o *.order
endif

1.3 创建字符设备

mknod /dev/my_chr_dev c 232 0232 0为主、次设备号。

  • mknod,实际上就是建立设备文件my_chr_dev与设备号232 0之间的联系。

驱动测试程序:

#include <stdio.h>
#include <fcntl.h> /*O_RDWR*/
#include <unistd.h> /*read open write close usleep*/
#define DATA_NUM (10)

int main()
{
    int fd;
    int wd, rd;
    char my_buffer[DATA_NUM] = "12345";
    fd = open("/dev/my_chr_dev", O_RDWR);
    printf("fd=%d\n", fd);
    if (fd == -1){
        printf("open failed\n");
        return -1;
    }
    wd = write(fd, my_buffer, DATA_NUM);
    rd = read(fd, my_buffer, DATA_NUM);
    printf("wd=%d, rd=%d\n", wd, rd);
    return 0;
}

该程序用于测试驱动程序的open、write、read、release。编译执行该驱动程序后,执行dmesg,可以查看内核调用驱动程序对应的open、write、read、release函数时日志信息。

1.4 总结

开发字符设备驱动主要有7步:

  1. 创建设备号MKDEV、注册设备号register_chrdev_region;
  2. 编写设备行为结构体的成员函数:成员函数实现硬件的open/close/read/write等操作。
  3. 建立设备信息结构体cdev与设备行为结构体file_operations之间的联系:cdev_init
  4. 向内核注册设备,建立设备信息结构体与设备号之间的联系:cdev_add
  5. make编译、insmod加载设备驱动。
  6. 创建设备文件,建立设备文件与设备号之间的联系:mknod /dev/chrDev c 主设备号 次设备号
  7. 编写测试脚本,测试驱动程序的open/close/read/write功能。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

『scrapy爬虫』03. 爬取多个页面(详细注释步骤)

目录 1. 分析网页试着拿到多个页面的url2. 抓取250个电影3. start_requests的使用4. 代码规范导库的优化关于重写最终修改后的代码 总结 欢迎关注 『scrapy爬虫』 专栏&#xff0c;持续更新中 欢迎关注 『scrapy爬虫』 专栏&#xff0c;持续更新中 1. 分析网页试着拿到多个页面…

yum安装mysql 数据库tab自动补全

centos7上面没有mysql&#xff0c;它的数据库名字叫做mariadb [rootlocalhost ~]#yum install mariadb-server -y [rootlocalhost ~]#systemctl start mariadb.service [rootlocalhost ~]#systemctl stop firewalld [rootlocalhost ~]#setenforce 0 [rootlocalhost ~]#ss -na…

数字人基础 | 3D手部参数化模型2017-2023

楔子: 2017年年底的泰国曼谷, SIGGRAPH Asia会议上, 来自马普所的 Javier Romero, Dimitrios Tzionas(两人都是 Michael J. Black的学生)发布了事实性的手部参数化模型标准: MANO [1]。 MANO的诞生意味着 Michael J. Black团队在继人体参数化模型 SMPL后, 事实性的将能够表达人…

信息系统项目管理师--沟通管理

IT 项⽬成功有关的最重要的四个因素是&#xff1a;主管层的⽀持、⽤户参与、有经验的项⽬经理和清晰的业务⽬标 项⽬沟通管理是确保及时、正确地产⽣、收集、分发、存储和最终处理项⽬信息所需的过程 项⽬沟通管理由两部分组成&#xff1a;⼀是制定策略&#xff0c;确保沟通对…

GUI编程--PyQt5--QTabWidget

文章目录 组件使用信号样式设置 组件使用 QTabWidget 页签 信号 self._ui Ui_Sub() self._ui.setupUi(right) # 切换tab页 self._ui.tabWidget.currentChanged.connect(self.tab_slot)def tab_slot(self):cur_index self._ui.tabWidget.currentIndex()tab_name self._ui…

微信小程序小案例实战

.wxml: <view class "title">狂飙经典语录 </view> <view class"out"><block wx:if"{{listArr.length}}"> <!-- bloock不会影响排版--><view class"list"><view class"row" wx:…

unicloud delete 删除

delete 删除 unicloud 删除大概分为两种 一种是 通过指定文档ID删除 语法如下 collection.doc(_id).remove()还有一种是条件查找文档然后直接批量删除 语法如下 collection.where().remove()反正总归是先查找到指定数据,然后使用remove()函数删除 示例如下 collection.doc(…

软考高级:需求变更管理过程概念和例题

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

【目标检测经典算法】R-CNN、Fast R-CNN和Faster R-CNN详解系列一:R-CNN图文详解

学习视频&#xff1a;Faster-RCNN理论合集 概念辨析 在目标检测中&#xff0c;proposals和anchors都是用于生成候选区域的概念&#xff0c;但它们在实现上有些许不同。 Anchors&#xff08;锚框&#xff09;&#xff1a; 锚框是在图像中预定义的一组框&#xff0c;它们通常以…

区间异或和异或区间最大值异或区间最小值 --- 题解 --- (字典树好题)

区间异或和异或区间最大值异或区间最小值 &#xff1a; 题目大意&#xff1a; 思路解析&#xff1a; 题目查询的是区间异或和 ^ 最小值 ^ 最大值&#xff0c;如果我们确定了最小值和最大值&#xff0c;[l,r]&#xff0c;假设a[l]是最小值&#xff0c;a[r]是最大值&#xff0c…

【漏洞复现】金和OA viewConTemplate.action RCE漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

【linux中cd指令使用】cd进入与退出路径

【linux中cd指令使用】cd如何进入与退出路径 1、cd进入指定路径&#xff0c;比如我要进入下面这个路径中去运行setup.py文件&#xff0c;如果我不跳转到该路径下直接运行&#xff0c;会报错找不到该文件 cd空格路径&#xff0c;即可跳转到该路径 cd /public2/xxx/tiny-cuda…

【零基础学习05】嵌入式linux驱动中platform与设备树基本实现

大家好,为了进一步提升大家对实验的认识程度,每个控制实验将加入详细控制思路与流程,欢迎交流学习。 今天主要学习一下,基于总线、设备和驱动进行匹配的平台驱动模型,这次将采用设备树的platform设备与驱动的编写方法,目前绝大多数的Linux内核已经支持设备树,这次主要来…

MyBatis-Plus学习记录

目录 MyBatis-Plus快速入门 简介 快速入门 MyBatis-Plus核心功能 基于Mapper接口 CRUD 对比mybatis和mybatis-plus&#xff1a; CRUD方法介绍&#xff1a; 基于Service接口 CRUD 对比Mapper接口CRUD区别&#xff1a; 为什么要加强service层&#xff1a; 使用方式 CR…

LEETCODE3

法一:记忆化递归 int climbStairsRecursive(int n, int* memo) {if (n < 2) {return n;}if (memo[n] > 0) {return memo[n];}memo[n] climbStairsRecursive(n - 1, memo) climbStairsRecursive(n - 2, memo);return memo[n]; }int climbStairs(int n) {int* memo (in…

2061:【例1.2】梯形面积

时间限制: 1000 ms 内存限制: 65536 KB 提交数:201243 通过数: 79671 【题目描述】 在梯形中阴影部分面积是150平方厘米&#xff0c;求梯形面积。 【输入】 (无&#xff09; 【输出】 输出梯形面积&#xff08;保留两位小数&#xff09;。 【输入样例】 &#xff…

redis在微服务领域的贡献,字节跳动只面试两轮

dubbo.registry.addressredis://127.0.0.1:6379 注册上来的数据是这样&#xff0c;类型是hash /dubbo/ s e r v i c e / {service}/ service/{category} 如 /dubbo/com.newboo.sample.api.DemoService/consumers /dubbo/com.newboo.sample.api.DemoService/providers has…

JOSEF约瑟 TQ-100同期继电器 额定直流电压220V 交流电压100V±10V

TQ-100型同期继电器 TQ-100同期继电器 ​ l 应用 本继电器用于双端供电线路的自动重合闸和备用电源自投装置中&#xff0c;以检查线路电压与母线电压的 相位差和幅值差。 2 主要性能 2 1采用进口集成电路和元器件构成&#xff0c;具有原理先进、性能稳定、可靠性高、动作值精…

mysql生成连续的日期

1.代码 例如&#xff1a;生成"2023-03-01"至"2023-03-10"之间的日期 WITH RECURSIVE date_range AS (SELECT "2023-03-01" AS date FROM dualUNION ALLSELECT DATE_ADD(date, INTERVAL 1 DAY) dateFROM date_rangeWHERE DATE_ADD(date, INTER…

windows系统提示msvcp120.dll丢失如何解决,如何找回dll文件?

如果你的电脑出现了关于msvcp120.dll丢失的情况那么大家一定要及时去解决msvcp140.dll丢失的问题&#xff0c;msvcp120.dll丢失可能会导致电脑出现各类的问题&#xff0c;今天就教大家四种关于msvcp120.dll丢失的解决办法&#xff0c;有效的解决msvcp120.dll丢失。 一、msvcp1…