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.o

build: kernel_modules

kernel_modules:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
    $(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 1024

int 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.o

build: kernel_modules

kernel_modules:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
    gcc -o test_app test_app.c

clean:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
    rm -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/hello
    return 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.mfbz.cn/a/762710.html

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

相关文章

Vue2 - 项目上线后生产环境中去除console.log的输出以及断点的解决方案

前言 当你准备将Vue.js应用程序部署到生产环境时,一个关键的优化步骤是移除代码中的所有 console.log 语句以及断点。在开发阶段,console.log 是一个非常有用的调试工具,但在生产环境中保留它们可能会影响性能和安全性。在本文中,我将向你展示如何通过使用Vue CLI 2来自动…

【TB作品】atmega16 计算器,ATMEGA16单片机,Proteus仿真

实验报告&#xff1a;基于ATmega16单片机的简易计算器设计 1. 实验背景 计算器是日常生活和工作中不可或缺的工具&#xff0c;通过按键输入即可实现基本的四则运算。通过本实验&#xff0c;我们将利用ATmega16单片机、矩阵键盘和LCD1602显示屏&#xff0c;设计并实现一个简易…

docker 部署jitsi meet

1. 部署环境&#xff1a; 1.1 vm 虚拟机 安装的 centos 7 1.2 centos7安装docker 和 docker-compose 2.docker命令 官网部署文档地址&#xff1a;&#xff08;文档地址有可能失效&#xff09; Self-Hosting Guide - Docker | Jitsi Meet 2.1Download and extract the late…

机器人控制系列教程之任务空间运动控制器搭建(2)

Simulink中的实例 推文《机器人控制系列教程之任务空间运动控制器搭建(1)》中&#xff0c;我们详细的讲解了Simulink中的taskSpaceMotionModel模块&#xff0c;实现的方式可以按照如下的步骤。 可以控制器模型替换为taskSpaceMotionModel模块后&#xff0c;该模块的输入分别为…

(1)Jupyter Notebook 下载及安装

目录 1. Jupyter Notebook是什么&#xff1f;2. Jupyter Notebook特征3. 应用3. 利用Google Colab安装Jupyter Notebook3.1 什么是 Colab&#xff1f;3.2 访问 Google Colab 1. Jupyter Notebook是什么&#xff1f; 百度百科: Jupyter Notebook&#xff08;此前被称为 IPython …

快钱支付股东全部股权已被质押!

根据近期工商信息&#xff0c;第三方支付机构快钱支付清算信息有限公司&#xff08;简称“快钱支付”&#xff09;实际控股方快钱金融服务&#xff08;上海&#xff09;有限公司&#xff08;简称“快钱金融”&#xff09;&#xff0c;作为出质股权标的企业&#xff0c;被出质给…

如何实现Action菜单

文章目录 1. 概念介绍2. 思路与方法2.1 实现思路2.2 实现方法 3. 示例代码4. 内容总结 我们在上一章回中介绍了"自定义标题栏"相关的内容&#xff0c;本章回中将介绍自定义Action菜单.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在这里提到的…

2024年【浙江省安全员-C证】考试报名及浙江省安全员-C证考试总结

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 浙江省安全员-C证考试报名考前必练&#xff01;安全生产模拟考试一点通每个月更新浙江省安全员-C证考试总结题目及答案&#xff01;多做几遍&#xff0c;其实通过浙江省安全员-C证复审模拟考试很简单。 1、【多选题】…

基于CNN的股票预测方法【卷积神经网络】

基于机器学习方法的股票预测系列文章目录 一、基于强化学习DQN的股票预测【股票交易】 二、基于CNN的股票预测方法【卷积神经网络】 文章目录 基于机器学习方法的股票预测系列文章目录一、CNN建模原理二、模型搭建三、模型参数的选择&#xff08;1&#xff09;探究window_size…

【区块链+基础设施】珠三角征信链 | FISCO BCOS应用案例

“珠三角征信链”是中国人民银行广州分行、中国人民银行深圳市中心支行按照中国人民银行总行工作部署&#xff0c;积 极贯彻珠三角一体化发展、粤港澳大湾区建设等国家战略而建设的跨区域征信一体化数据中心枢纽&#xff0c;以 FISCO BCOS 为底链构建应用平台&#xff0c;并由微…

WPS图片无法居中、居中按钮无法点击(是灰色的)

在PPT中复制对象到WPS word中后&#xff0c;导致图片一直靠左&#xff0c;而无法居中 直接选中图片是错误的&#xff1a; 这时你会发现居中按钮无法点击&#xff08;是灰色的&#xff09; 正确的是选中图片的前面的部分&#xff0c;然后点击居中&#xff0c;或者Ctrl E

AI基本概念(人工智能、机器学习、深度学习)

人工智能 、 机器学习、 深度学习的概念和关系 人工智能 &#xff08;Artificial Intelligence&#xff09;AI- 机器展现出人类智慧机器学习 &#xff08;Machine Learning) ML, 达到人工智能的方法深度学习 &#xff08;Deep Learning&#xff09;DL,执行机器学习的技术 从范围…

论坛万能粘贴手(可将任意文件转为文本)

该软件可将任意文件转为文本。 还原为原文件的方法&#xff1a;将得到的文本粘贴到记事本&#xff0c;另存为UUE格式&#xff0c;再用压缩软件如winrar解压即可得到原文件。建议用于小软件。 下载地址&#xff1a;https://download.csdn.net/download/wgxds/89505015 使用演示…

各类排序方法 归并排序 扩展练习 逆序对数量

七月挑战一个月重刷完Y总算法基础题&#xff0c;并且每道题写详细题解 进度:(3/106) 归并排序的思想也是分而治之 归并优点&#xff1a;速度稳定,排序也稳定 排序也稳定&#xff08;数组中有两个一样的值&#xff0c;排序之后他们的前后顺序不发生变化&#xff0c;我们就说…

09 - matlab m_map地学绘图工具基础函数 - 绘制区域填充、伪彩色、加载图像和绘制浮雕效果的有关函数

09 - matlab m_map地学绘图工具基础函数 - 绘制区域填充、伪彩色、加载图像和绘制浮雕效果的有关函数 0. 引言1. 关于m_pcolor2. 关于m_image3. 关于m_shadedrelief4. 关于m_hatch5. 结语 0. 引言 本篇介绍下m_map中区域填充函数&#xff08;m_hatch&#xff09;、绘制伪彩色图…

安装和微调大模型(基于LLaMA-Factory)

打开终端&#xff08;在Unix或macOS上&#xff09;或命令提示符/Anaconda Prompt&#xff08;在Windows上&#xff09;。 创建一个名为lora的虚拟环境并指定Python版本为3.9。 https://github.com/echonoshy/cgft-llm/blob/master/llama-factory/README.mdGitHub - hiyouga/…

教你如何在群晖上部署m3u8视频下载工具,支持浏览器一键添加下载任务

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 快速开始📝 群晖部署📝 部署浏览器一键添加任务🎈 常见问题 🎈⚓️ 相关链接 ⚓️📖 介绍 📖 在当今数字化时代,视频内容的下载和管理变得越来越重要。尤其是对于那些使用群晖NAS设备的用户,一…

Redis慢查询

Redis慢查询 目录 Redis慢查询慢查询配置慢日志操作返回参数介绍 Redis的慢查询就是当命令执行时间超过预定的阈值后将这条命令记录下来&#xff0c;与MySQL的功能类似 慢查询配置 默认阈值是10毫秒&#xff0c;即10000微秒 临时修改阈值为20毫秒 127.0.0.1:6379> confi…

旋转变压器软件解码simulink仿真

1.介绍 旋转变压器是一种精密的位置、速度检测装置&#xff0c;尤其适用于高温、严寒、潮湿、高速、振动等环境恶劣、旋转编码器无法正常工作的场合。旋转变压器在使用时并不能直接提供角度或位置信息&#xff0c;需要特殊的激励信号和解调、计算措施&#xff0c;才能将旋转变压…