Linux驱动入门-最简单字符设备驱动

一、字符设备驱动概念

1. 什么是字符设备驱动?

字符设备是 Linux 驱动中最基本的一类设备驱动,按字节流进行读写操作,数据读写有先后顺序。常见的字符设备包括LED灯、按键、IIC、SPI、LCD等。字符设备驱动就是为这些设备编写的驱动程序。

2. Linux应用程序如何调用驱动程序

在 Linux 中,一切皆为文件,驱动加载成功后,会在 /dev 目录下生成一个相应的文件,应用程序通过操作该文件即可实现对硬件的操作。例如 /dev/led 是一个 LED 灯的驱动文件,应用程序可以使用 open 函数打开文件 /dev/led,使用 close 函数关闭文件 /dev/led。要点亮或关闭 LED,可以使用 write 函数向该驱动写入数据;要获取 LED 状态,可以使用 read 函数从驱动读取状态。

3. 系统调用与驱动函数

应用程序运行在用户空间,驱动运行在内核空间,用户空间不能直接操作内核空间。因此,通过系统调用的方式实现用户空间与内核空间的交互。每个系统调用在驱动中都有对应的驱动函数。驱动程序中的 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 *);// 其他函数省略
};

4. file_operations 结构体常用函数

  • owner: 指向拥有该结构体的模块的指针,一般设置为 THIS_MODULE
  • read: 用于读取设备文件。
  • write: 用于向设备文件写入数据。
  • open: 用于打开设备文件。
  • release: 用于释放(关闭)设备文件。

二、字符设备驱动开发步骤

1. 驱动模块的加载和卸载

Linux 驱动有两种运行方式:编译进内核或编译成模块。模块的加载和卸载注册函数如下:

module_init(xxx_init); // 注册模块加载函数
module_exit(xxx_exit); // 注册模块卸载函数

驱动模块加载和卸载模板:

static int __init xxx_init(void) {// 入口函数的具体内容return 0;
}static void __exit xxx_deinit(void) {// 出口函数的具体内容
}module_init(xxx_init);
module_exit(xxx_deinit);

2. 添加LICENSE和作者信息

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author Name");

3. 示例程序

3.1 编写 hello_driver.c
#include <linux/module.h>static int __init hello_driver_init(void) {printk("hello_driver_init\n");return 0;
}static void __exit hello_driver_cleanup(void) {printk("hello_driver_cleanup\n");
}module_init(hello_driver_init);
module_exit(hello_driver_cleanup);
MODULE_LICENSE("GPL");
3.2 编写 Makefile
KERNELDIR := /lib/modules/$(shell uname -r)/build
CURRENT_PATH := $(shell pwd)
obj-m := hello_driver.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modulesclean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
3.3 编译、加载、卸载模块
make
sudo insmod hello_driver.ko
lsmod | grep hello_driver
dmesg | grep hello_driver
sudo rmmod hello_driver

4. 字符设备注册与注销

字符设备的注册和注销函数原型:

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);
static inline void unregister_chrdev(unsigned int major, const char *name);

设备号的定义和操作:

#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma, mi) (((ma) << MINORBITS) | (mi))

5. 内核空间与用户空间数据交互

unsigned long copy_to_user(void *dst, const void *src, unsigned long len);
unsigned long copy_from_user(void *to, const void *from, unsigned long n);

6. 示例程序:注册字符设备

6.1 hello_driver.c 内容
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>#define CHRDEVBASE_MAJOR 200
static char kernel_buffer[1024];static int hello_open(struct inode *inode, struct file *file) {printk("hello_open\n");return 0;
}static int hello_release(struct inode *inode, struct file *file) {printk("hello_release\n");return 0;
}static ssize_t hello_read(struct file *file, char __user *buffer, size_t size, loff_t *ppos) {printk("hello_read: size=%zu\n", size);copy_to_user(buffer, kernel_buffer, size);return size;
}static ssize_t hello_write(struct file *file, const char __user *buffer, size_t size, loff_t *ppos) {printk("hello_write: size=%zu\n", size);copy_from_user(kernel_buffer, buffer, size);return size;
}static const struct file_operations hello_fops = {.owner = THIS_MODULE,.open = hello_open,.release = hello_release,.read = hello_read,.write = hello_write,
};static int __init hello_init(void) {int ret = register_chrdev(CHRDEVBASE_MAJOR, "hello", &hello_fops);if (ret < 0) {printk("register_chrdev failed\n");return ret;}printk("hello_init\n");return 0;
}static void __exit hello_cleanup(void) {unregister_chrdev(CHRDEVBASE_MAJOR, "hello");printk("hello_cleanup\n");
}module_init(hello_init);
module_exit(hello_cleanup);
MODULE_LICENSE("GPL");
6.2 test_app.c 内容
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>#define BUFFER_SIZE 1024int main(int argc, char *argv[]) {if (argc != 3) {fprintf(stderr, "Usage: %s <device> <read/write>\n", argv[0]);return 1;}int fd = open(argv[1], O_RDWR);if (fd < 0) {perror("open");return 1;}char buffer[BUFFER_SIZE];if (strcmp(argv[2], "read") == 0) {ssize_t ret = read(fd, buffer, BUFFER_SIZE);if (ret < 0) {perror("read");close(fd);return 1;}printf("Read from kernel: %s\n", buffer);} else if (strcmp(argv[2], "write") == 0) {printf("Enter data to write: ");fgets(buffer, BUFFER_SIZE, stdin);ssize_t ret = write(fd, buffer, strlen(buffer));if (ret < 0) {perror("write");close(fd);return 1;}} else {fprintf(stderr, "Invalid operation: %s\n", argv[2]);}close(fd);return 0;
}
6.3 Makefile 内容
KERNELDIR := /lib/modules/$(shell uname -r)/build
CURRENT_PATH := $(shell pwd)
obj-m := hello_driver.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modulesgcc -o test_app test_app.cclean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) cleanrm -f test_app

7. 自动创建设备节点

7.1 创建和删除类
struct class *class_create(struct module *owner, const char *name);
void class_destroy(struct class *cls);
7.2 创建设备
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
void device_destroy(struct class *class, dev_t devt);
8. 示例程序:自动创建设备节点
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>dev_t hello_devid;
struct cdev hello_cdev;
static struct class *hello_class;static char kernel_buffer[1024];static int hello_open(struct inode *inode, struct file *file) {printk("hello_open\n");return 0;
}static int hello_release(struct inode *inode, struct file *file) {printk("hello_release\n");return 0;
}static ssize_t hello_read(struct file *file, char __user *buffer, size_t size, loff_t *ppos) {printk("hello_read: size=%zu\n", size);copy_to_user(buffer, kernel_buffer, size);return size;
}static ssize_t hello_write(struct file *file, const char __user *buffer, size_t size, loff_t *ppos) {printk("hello_write: size=%zu\n", size);copy_from_user(kernel_buffer, buffer, size);return size;
}static const struct file_operations hello_fops = {.owner = THIS_MODULE,.open = hello_open,.release = hello_release,.read = hello_read,.write = hello_write,
};static int __init hello_init(void) {int ret;printk("hello_init\n");ret = alloc_chrdev_region(&hello_devid, 0, 1, "hello");if (ret < 0) {printk("alloc_chrdev_region failed\n");return ret;}cdev_init(&hello_cdev, &hello_fops);ret = cdev_add(&hello_cdev, hello_devid, 1);if (ret < 0) {unregister_chrdev_region(hello_devid, 1);printk("cdev_add failed\n");return ret;}hello_class = class_create(THIS_MODULE, "hello_class");device_create(hello_class, NULL, hello_devid, NULL, "hello"); // /dev/helloreturn 0;
}static void __exit hello_cleanup(void) {printk("hello_cleanup\n");device_destroy(hello_class, hello_devid);class_destroy(hello_class);cdev_del(&hello_cdev);unregister_chrdev_region(hello_devid, 1);
}module_init(hello_init);
module_exit(hello_cleanup);
MODULE_LICENSE("GPL");

总结

这篇博文详细介绍了 Linux 字符设备驱动开发的基本概念、步骤和示例程序。希望通过实践操作,大家能够更好地理解和掌握字符设备驱动开发的核心知识和技巧。

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

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

相关文章

ollama,springAi实现自然语言处理

ollama安装使用&#xff1a; https://ollama.com/ 下载速度比较慢的可以直接使用以下版本0.1.41 https://pan.baidu.com/s/1hCCkYvFjWqxvPyYA2-YElA?pwdotap 直接管理员身份双击安装&#xff0c;安装成功后会在任务栏里出现这个小图标&#xff1a; 打开cmd&#xff0c;输入…

java实现图像分割合并

Java实现图片操作&#xff1a;切割、缩放、重置、拼接、合并、水印、画单点、画线段等_java拼接图片并截掉一部分-CSDN博客

WhatsApp:连接世界的即时通讯巨头

在数字化浪潮席卷全球的今天&#xff0c;即时通讯工具已成为人们日常生活中不可或缺的一部分。其中&#xff0c;WhatsApp凭借其卓越的功能、出色的用户体验和广泛的用户基础&#xff0c;在全球通讯领域崭露头角&#xff0c;成为连接世界的即时通讯巨头。今天将带您深入了解What…

tkinter显示图片

tkinter显示图片 效果代码解析打开和显示图像 代码 效果 代码解析 打开和显示图像 def open_image():file_path filedialog.askopenfilename(title"选择图片", filetypes(("PNG文件", "*.png"), ("JPEG文件", "*.jpg;*.jpeg&q…

2024.7.1 刷题总结

2024.7.1 **每日一题** 2065.最大化一张图中的路径价值&#xff0c;本题可以从数据范围得到思路的参考&#xff0c;根据总最大时间和单个最小时间得到最多可以有十条边&#xff0c;即搜索树有11层&#xff0c;每个节点最多有4个儿子&#xff0c;可视为一棵层数至多为11的四叉树…

数据资产赋能企业决策:通过精准的数据分析和洞察,构建高效的数据资产解决方案,为企业提供决策支持,助力企业实现精准营销、风险管理、产品创新等目标,提升企业竞争力

一、引言 在信息化和数字化飞速发展的今天&#xff0c;数据已成为企业最宝贵的资产之一。数据资产不仅包含了企业的基本信息&#xff0c;还蕴含了丰富的市场趋势、消费者行为和潜在商机。如何通过精准的数据分析和洞察&#xff0c;构建高效的数据资产解决方案&#xff0c;为企…

【论文通读】GUI Action Narrator: Where and When Did That Action Take

GUI Action Narrator: Where and When Did That Action Take 前言AbstractMotivationSolutionAct2CapData CollectionMetrics MethodExperimentAblation StudyVisual Prompt SizeSpatial PromptTemporal Prompt Conclusion 前言 一篇GUI操作benchmark的工作&#xff0c;作者提…

tkinter实现进度条

tkinter实现进度条 效果代码解析导入需要的模块定义进度条 代码 效果 代码解析 导入需要的模块 import tkinter as tk from tkinter import ttk定义进度条 def start_progress():progress[value] 0max_value 100step 10for i in range(0, max_value, step):progress[valu…

Win11找不到组策略编辑器(gpedit.msc)解决

由于需要同时连接有线网络和无线网络&#xff0c;且重启后双网络都自动连接&#xff0c;因此需要配置组策略。 但是win11找不到组策略编辑器。 灵感来源&#xff1a;Win11找不到组策略编辑器&#xff08;gpedit.msc&#xff09;解决教程 - 知乎 (zhihu.com) 在Win11中&#…

国网协议电表采集方案

项目背景及需求项目地点&#xff1a;重庆港西光伏电站&#xff08;中广核重庆&#xff09;项目背景&#xff1a;光伏发电并网项目电能监控项目目的及难点&#xff1a;实现对EDMI协议电表&#xff08;Mk6E&#xff09;的数据采集&#xff0c;监控光伏发电有效性&#xff0c;做到…

项目管理九大口诀

有工作一定有目标 有目标一定有任务 有任务一定有计划 有计划一定有执行 有执行一定有监控 有监控一定有调整 有调整一定有结果 有结果一定有责任 有责任一定有奖惩 &#x1fa77;有工作一定有目标 目标制定&#xff1a;SMART Specific&#xff08;具体性&#xff09;&#x…

# 职场生活之道:善于团结

在职场这个大舞台上&#xff0c;每个人都是演员&#xff0c;也是观众。要想在这个舞台上站稳脚跟&#xff0c;除了专业技能&#xff0c;更要学会如何与人相处&#xff0c;如何团结他人。团结&#xff0c;是职场生存的重要法则之一。 1. 主动团结&#xff1a;多一个朋友&#x…

《昇思25天学习打卡营第1天|基本介绍》

文章目录 前言&#xff1a;今日所学&#xff1a; 前言&#xff1a; 今天非常荣幸的收到了昇思25天学习打卡营的邀请。昇思MindSpore作为华为昇腾AI全栈的重要一员&#xff0c;他支持端、边、云独立的和协同的统一训练和推理框架&#xff0c;有着易于开发、执行效率高、全场景框…

客户满意度调查方法有哪些

用户满意度调查作为改进用户体验工作中重要的一项活动&#xff0c;可以帮助企业深入了解客户对产品服务各方面评价。有许多企业想开展客户满意度调查&#xff0c;但是在调查方式上不清楚该用那种方式&#xff1f;另外还要考虑预算&#xff0c;民安智库&#xff08;公众满意度调…

Kotlin扩展函数(also apply run let)和with函数

also apply run let with的使用例子 private fun testOperator() {/*** also*/val person Person("ZhangSan", 18)person.also {// 通常仅仅打印使用, 也可以通过it修改it.name "ZhangSan1"println("also inner name: " it.name)}println(&qu…

Redis-分布式锁(基本原理和不同实现方式对比)

文章目录 1、基本原理2、不同实现方式 1、基本原理 分布式锁&#xff1a;满足分布式系统或集群模式下多进程可见并且互斥的锁。 分布式锁的核心思想就是让大家都使用同一把锁&#xff0c;只要大家使用的是同一把锁&#xff0c;那么我们就能锁住线程&#xff0c;不让线程进行&am…

Firewalld 概述

1.firewalld简介 firewalld的作用是为包过滤机制提供匹配规则(或称为策略),通过各种不同的规则&#xff0c;告诉 netfiter对来自指定源、前往指定目的或具有某些协议特征的数据包采取何种处理方式。 为了更加方便地组织和管理防火墙&#xff0c;firewalld 提供了支持网络区域…

题目汇总一

Question One 编写一个应用程序&#xff0c;输出100以内的全部偶数&#xff0c;并以10个一行的形式输出。 public class Main{public static void main(String[] args){int cnt 0;for(int i 2; i < 100; i 2){cnt ;System.out.print(i);if(cnt % 10 ! 0) System.out.prin…

基于内存认证的 Spring Security

在Spring Security中&#xff0c;基于内存的认证是一种简单直接的方式&#xff0c;适合于开发环境或者小型应用&#xff0c;它允许我们直接在安全配置中定义用户。在这篇文章中&#xff0c;我们将详细介绍如何使用Spring Security进行基于内存的认证配置。 开始之前 首先&…

以太网基础知识

文章目录 一、以太网&#xff08;Ethernet&#xff09;介绍二、协议介绍三、什么是PHY&#xff1f;1.标准接口协议&#xff1a;2.寄存器配置&#xff1a;3.自动协商&#xff1a; 四、时序4.1RGMII接口时序4.1.1 对其模式4.1.2 延时模式&#xff08;常用&#xff09; 4.2MDIO接口…