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

news2024/7/4 4:35:50

一、字符设备驱动概念

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.coloradmin.cn/o/1884014.html

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

相关文章

Linux内核——Linux内核体系模式(二)

1 Linux系统的中断机制 Linux内核将中断分为两类&#xff1a;硬件中断和软件中断&#xff08;异常&#xff09;。每个中断是由0-255之间的一个数字进行标识。 中断int0-int31&#xff08;0x00-0x1f&#xff09;作为异常int32-int255由用户自己设定 int32-int47对应与8259A中断…

怎么永久禁止win10系统自动更新?一键屏蔽系统自动更新

现在 Windows 10 系统是很多办公用户的主力操作系统&#xff0c;可是 Windows 系统会自动更新&#xff0c;这会严重影响系统稳定性。因为微软虽然以提供更新为服务&#xff0c;但并不是每次更新它都是安全的。 接下来和我一起看看如何使用联想开发的小工具一键屏蔽系统自动更新…

数据库定义语言(DDL)

数据库定义语言&#xff08;DDL&#xff09; 一、数据库操作 1、 查询所有的数据库 SHOW DATABASES;效果截图&#xff1a; 2、使用指定的数据库 use 2403 2403javaee;效果截图&#xff1a; 3、创建数据库 CREATE DATABASE 2404javaee;效果截图&#xff1a; 4、删除数据…

Datax快速使用之牛刀小试

前言 一次我发现业务他们在用 datax数据同步工具&#xff0c;我尤记得曾经 19 年使用过&#xff0c;并且基于当时的版本还修复了个 BUG并且做了数据同步管道的集成开发。没想到时间过的飞快&#xff0c;业务方基于海豚调度 2.0.6 的版本中有在使用&#xff0c;由于业务方还没有…

光伏设计的原则和必备要素

光伏设计是一项复杂的工程任务&#xff0c;它涉及到将太阳能转换为电能的过程&#xff0c;并在各种环境条件下确保系统的稳定、高效运行。以下是光伏设计应遵循的原则和必备的要素。 一、光伏设计的原则 1、最大化能量产出&#xff1a;光伏设计的首要原则是通过合理的布局和选…

RedHat9 | 内部YUM本地源服务器搭建

服务器参数 标识公司内部YUM服务器主机名yum-server网络信息192.168.37.1/24网络属性静态地址主要操作用户root 一、基础环境信息配置 修改主机名 [rootyum-server ~]# hostnamectl hostname yum-server添加网络信息 [rootyum-server ~]# nmcli connection modify ens160 …

Python和tkinter单词游戏

Python和tkinter单词游戏 数据字典文本文件&#xff0c;文件名为Dictionary.txt&#xff0c;保存编码格式为&#xff1a;utf-8。文本内容&#xff1a;每行一个 单词 &#xff0c;单词和解释用空格分隔&#xff0c;如 a art.一(个)&#xff1b;每一(个) ability n.能力&#…

EKF+UKF+CKF+PF的效果对比|三维非线性滤波|MATLAB例程

前言 标题里的EKF、UKF、CKF、PF分别为&#xff1a;扩展卡尔曼滤波、无迹卡尔曼滤波、容积卡尔曼滤波、粒子滤波。 EKF是扩展卡尔曼滤波&#xff0c;计算快&#xff0c;最常用于非线性状态方程或观测方程下的卡尔曼滤波。 但是EKF应对强非线性的系统时&#xff0c;估计效果不如…

MySQL5.7安装初始化错误解决方案

问题背景 今天在给公司配数据库环境时,第一次报initializing database 数据库初始化错误? 起初没管以为是安装软件原因,然后就出现以下错误:如下图 点开log,我们观察日志会发现 无法识别的参数 ‘mysqlx_port=0.0’,???,官方的安装程序还能出这问题?

排序(堆排序、快速排序、归并排序)-->深度剖析(二)

前言 前面介绍了冒泡排序、选择排序、插入排序、希尔排序&#xff0c;作为排序中经常用到了算法&#xff0c;还有堆排序、快速排序、归并排序 堆排序&#xff08;HeaSort&#xff09; 堆排序的概念 堆排序是一种有效的排序算法&#xff0c;它利用了完全二叉树的特性。在C语言…

【Linux】:环境变量

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux环境变量的相关知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门…

万字总结随机森林原理、核心参数以及调优思路

万字总结随机森林原理、核心参数以及调优思路 在机器学习的世界里&#xff0c;随机森林&#xff08;Random Forest, RF&#xff09;以其强大的预测能力和对数据集的鲁棒性而备受青睐。作为一种集成学习方法&#xff0c;随机森林通过构建多个决策树并将它们的预测结果进行汇总&…

SpringCloud_Eureka注册中心

概述 Eureka是SpringCloud的注册中心。 是一款基于REST的服务治理框架&#xff0c;用于实现微服务架构中的服务发现和负载均衡。 在Eureka体系中&#xff0c;有两种角色: 服务提供者和服务消费者。 服务提供者将自己注册到Eureka服务器&#xff0c;服务消费者从Eureka服务器中…

禹神electron学习~

最近时间比较富裕 咱们浅浅来学习下electron 视频在这禹神&#xff1a;一小时快速上手Electron&#xff0c;前端Electron开发教程_哔哩哔哩_bilibili 先看下流程模型 先决条件 首先第一步 查看你的node和npm版本 创建你的应用 创建一个文件夹 我创建的名称为my-electron-…

在Zotero中使用Deepl翻译

文章目录 Zotero简介Zotero下载插件下载在Zotero中安装插件获取Deepl密钥在Zotero中使用deepl 参考链接 Zotero简介 Zotero是一款非常实用的文献管理软件&#xff0c;可以快速帮助我们下载、分类和标注文献。由于专业需要&#xff0c;很多使用者需要阅读外文文献&#xff0c;Z…

无锁编程——从CPU缓存一致性讲到内存模型(1)

一.前言 1.什么是有锁编程&#xff0c;什么是无锁编程&#xff1f; 在编程中&#xff0c;特别是在并发编程的上下文中&#xff0c;“无锁”和“有锁”是描述线程同步和资源访问控制的两种不同策略。有锁&#xff08;Locked&#xff09;: 有锁编程是指使用锁&#xff08;例如互…

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

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

Mysql 的账户管理,索引,存储引擎

目录 一.MySQL的账户管理 1.存放用户信息的表 2.查看当前使用的用户 3.新建用户 4.修改用户名称 5.删除用户 6.修改用户密码 7.破解密码 8. 远程登录 9.用户权限管理 9.1 权限类别 9.2 查看权限 9.3 授予权限 9.4 撤销权限 二.索引 1. 索引管理 1.1 查看索…

Generating Diverse Structure for Image Inpainting With Hierarchical VQ-VAE

Jialun Peng1 Dong Liu1* Songcen Xu2 Houqiang Li1 1 University of Science and Technology of China 2 Noahs Ark Lab, Huawei Technologies Co., Ltd.pjlmail.ustc.edu.cn, {dongeliu, lihq}ustc.edu.cn, xusongcenhuawei.com 原文提供代码链接&#xff1a; GitHub - UST…

MySQL:数据类型

数据类型 1. 字符串类型2. 整数类型3. 定点数类型和浮点数类型4. 布尔类型5. 枚举和集合类型6. 日期和时间类型7. Blob类型8. JSON类型 字符串类型、数字类型、日期和时间类型、存放二进制的数据类型、存放地理数据的类型。 1. 字符串类型 字符串类型也可以用来存储邮编&…