Linux驱动——字符设备驱动

article/2025/11/10 1:14:20

目录

一、代码编译环境

二、Linux驱动开发分类

三、字符设备驱动开发流程

1.编译驱动模块代码(Makefile)

2.驱动模块的加载与卸载

四、字符设备驱动编写

1.驱动模块

2. 字符设备驱动编写步骤

2.1 设备号的注册与注销

2.2 设备节点的注册与注销

2.3 实现硬件初始化

2.4 在驱动中实现文件io的接口

2.5 应用程序需要传递数据给驱动

3.应用程序和驱动程序扮演什么角色

4.应用程序编写

5.规范

6.测试


一、代码编译环境

1.使用什么工具来写驱动代码:安装source insight工具

(1)找到软件提示把工具安装激活
(2)把Linux内核代码解压到windows目录中
(3)打开工具添加查看的项目
project🡒new project:
第一个对话框:
第一个文本框:输入工程名字

第二个文本框:路径(默认)

第二个对话框: 在项目源码位置,选择 Linux内核代码位置 点击OK 第三个对话框: 选择需要查看的Linux内核代码的包含的源码文件(要查看的内核目录),单击 add tree。
点击close 退出,
然后 重新打开刚才创建的工程 project→open project,如果提示 同步,点击确定,可能
需要很长时间

(4)编写代码
使用source insight工具编写代码,然后把这个程序拷贝到ubuntu系统中,使用交叉编译工
具进行编译,生成适配与开发板的程序。

二、Linux驱动开发分类

1、字符设备驱动,最多的。

2、块设备驱动,存储。

3、网络设备驱动。

一个设备不说是一定只属于某一个类型。比如USB WIFI,SDIO WIFI,属于网络设备驱动,因为他又有USB和SDIO,因此也属于字符设备驱动。

三、字符设备驱动开发流程

1.编译驱动模块代码(Makefile)

KERNEL_PATH=/home/yky/Code/linux-3.14-fs4412 #内核中的Makefile需要配置交叉编译工具链
obj-m += 模块文件名.o #要编译为模块的文件
all:make modules -C $(KERNEL_PATH) M=$(shell pwd) #借助已经编译好的内核,编译模块# -C 指定内核路径
# M:当前模块的位置

2.驱动模块的加载与卸载

Linux驱动程序可以编译到kernel里面,也就是zImage,也可以编译为模块,.ko。测试的时候只需要加载.ko模块就可以。

编写驱动的时候注意事项!

1、编译驱动的时候需要用到linux内核源码!因此要解压缩linux内核源码,编译linux内核源码。得到zImage和.dtb。需要使用编译后的到的zImage和dtb启动系统。

2、从SD卡启动,SD卡烧写了uboot。uboot通过tftp从ubuntu里面获取zimage和dtb,rootfs也是通过nfs挂载。

3、设置bootcmd和bootargs(根据自身情况进行调整)

set bootargs root=/dev/nfs nfsroot=192.168.3.201:/home/xwq/nfshome/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=192.168.3.6

setenv bootcmd tftp 41000000 uImage\;tftp 42000000 exynos4412-fs4412.dtb\;bootm 41000000 - 42000000


4、将编译出来的.ko文件放到根文件系统里面。加载驱动会用到加载命令:insmod,modprobe。移除驱动使用命令rmmod。对于一个新的模块使用modprobe加载的时候需要先调用一下depmod命令。驱动模块加载成功以后可以使用lsmod查看一下。卸载模块使用rmmod命令

(1)insmod:加载模块

insmod 模块路径

(2)rmmod:卸载模块

mkdir /lib/modules
mkdir /lib/modules/3.14.0
rmmod 模块名

(3)lsmod:查看已经加载的模块

四、字符设备驱动编写

1.驱动模块

 (1)符号导出

如果模块中内容需要在其他模块中使用,可以把内容进行导出:添加导出声明
EXPORT_SYMBOL(内容名字);
注:如果模块只用于进行导出,可以不写入口声明以及定义

(2)参数传递 

在编写驱动时,有些变量是不确定的,是根据驱动具体加载到哪个设备才确定,在进行
insmod装载驱动模块时再传递这些参数值。

如:加载模块:insmod perm.ko a=10 b=5 p="okokok"


在驱动代码中如何处理参数传递: module_param(name,type,perm);

参数1:参数的名字,变量名
参数2:参数的类型,int,char
参数3:/sys/modules 文件的权限,0666

2. 字符设备驱动编写步骤

1.实现模块加载和卸载入口函数

/*
1、头文件
2、驱动模块装载入口和卸载入口声明
3、模块装载函数和卸载函数
4、GPL声明
装载入口:当内核加载这个驱动模块时,从那个函数执行(声明,实现)
*///头文件
#include <linux/init.h>
#include <linux/module.h>//模块加载入口函数实现
static int __init 函数名1(void)
{//资源的创建,申请,创建驱动return 0;
}//模块卸载入口函数实现
static void __exit 函数名2(void)
{//资源的释放,删除驱动
}//模块入口声明
//装载声明(内核加载的入口指定)
module_init(函数名1);//只要加载就执行其中声明的函数//卸载声明(内核卸载的入口指定)
module_exit(函数名2);//GPL开源声明
MODULE_LICENSE("GPL");

2.在模块加载入口
3. 申请设备号(内核中用于区分和管理不同的字符设备驱动)
4. 创建设备节点(为用户提供一个可操作的文件接口---用户操作文件)
5. 实现硬件初始化
(1) 地址的映射
(2) 实现硬件的寄存器的初始化
(3) 中断的申请
6.实现文件io接口(struct file_operations fops结构体)

字符设备驱动要素

a. 必须有一个设备号,用于在内核中,众多的设备驱动进行区分
b. 用户(应用)必须知道设备驱动对应到哪个设备文件(设备节点)——Linux一切皆文件(把所有的设备都看作是文件)
c. 对设备进行操作(驱动),其实就是对文件进行操作,应用空间操作open、read、write等文
件IO时,实际上驱动代码中对应执行的open、read、write函数

2.1 设备号的注册与注销

//申请设备号
int register_chrdev(unsigned int major, const char *name,const structfile_operations *fops)

参数1:
unsigned int major:主设备号
设备号:32bit == 主设备号(12bit) + 次设备号(20bit)
主设备号:表示同一类型的设备
次设备号:表示同一类型中的不同设备
参数有两种设置:
静态:指定一个整数:250----主设备号为250
动态:让内核随机指定,参数为0
参数2:
const char *name:一个字符串,描述设备信息,自定义
参数3:
const struct file_operations *fops:结构体指针,结构体变量的地址(结构体中就是应
用程序和驱动程序函数关联,open、read、write)---文件操作对象,提供open、read、write等驱动
中的函数
返回值:
如果是静态指定主设备号,返回0表示申请成功,返回负数表示申请失败
如果是动态申请,返回值就是申请成功的主设备号
/proc/devices:文件中包含了所有注册到内核的设备

//销毁注销设备号
void unregister_chrdev(unsigned int major,const char * name)

参数1:
unsigned int major:主设备号
参数2:
const char * name:设备信息,自定义

2.2 设备节点的注册与注销

 1.手动创建

创建设备节点---手动
mknod 设备节点名 设备类型 主设备号 次设备号
#如:
mknod /dev/xxx c 250 0

2. 自动创建(通过udev/mdev机制)

//创建一个类(信息)
struct class * class_create(owner,name)

参数1:
owner:一般填写 THIS_MODULE
参数2:
name:字符串首地址,名字,自定义
返回值:
返回值就返回信息结构体的地址

//创建设备节点(设备文件)
struct device * device_create(struct class *class, struct device *parent,dev_t
devt, void *drvdata, const char *fmt, ...)

参数1:
struct class *class:class信息对象地址,通过 class_create()函数创建参数2:
struct device *parent:表示父亲设备,一般填 NULL参数3:
dev_t devt:设备号(主设备+次设备)
Linux内核将设备号分为两部分:主设备号和次设备号。主设备号占用前12位,次设备号占用低20位。
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))参数4:
void *drvdata:私有数据,一般填NULL参数5、参数6:
const char *fmt, ...:可变参数,表示字符设备文件名(设备节点名)

//销毁设备节点
void device_destroy(struct class * class,dev_t devt)

 2.3 实现硬件初始化

1.控制外设,其实就是控制地址,内核驱动中通过虚拟地址操作,需要把外设控制的物理地址,映射到内核空间

//虚拟地址映射
void *ioremap(phys_addr_t offset, unsigned long size)

参数1:
phys_addr_t offset:物理地址参数2:
unsigned long size:映射大小返回值:
映射之后的虚拟地址

//解除映射
void iounmap(void *addr)

 2.操作寄存器地址的方式

(1)通过指针取 *

例如:
volatile unsigned int * gpx1con;
*gpx1con---进行操作

(2)IO内存访问函数

        使用ioremap函数将寄存器的物理地址映射到虚拟地址后,我们就可以直接通过指针访问这些地址,但是Linux内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。

 例如:

我们32bit就采用readl()、writel()

//从地址中读取地址空间的值
u32 readl(const volatile void *addr)//将value值,存储到对应地址中
void writel(u32 value, volatile void *addr)

2.4 在驱动中实现文件io的接口

1.在驱动中实现文件io的接口操作
const struct file_operations fops;结构体中就是 驱动 与 应用程序文件io的接口关联

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 *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long,loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long,loff_t);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long,unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *,
size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *,size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);
};
//函数指针集合,其实就是接口,表示文件IO函数用函数指针来表示,存储驱动中的关联函数
// 如: read函数指针 = xxxx_ok;表示 read函数,在驱动中的关联函数为xxxx_ok函数

2.  应用调用去调用文件io去控制驱动

open();
read();
write();

2.5 应用程序需要传递数据给驱动

1.将驱动空间拷贝数据给应用空间

//这个功能一般用于驱动中的 read
long copy_to_user(void __user *to,const void *from, unsigned long n)

参数1:
void __user *to:目标地址,应用空间地址参数2:
const void *from:源地址,内核空间地址参数3:
unsigned long n:个数返回值:
成功返回0,失败返回大于0,表示还有多少个没有拷贝完

2.将应用空间数据拷贝到驱动空间

long copy_from_user(void * to,const void __user * from,unsigned long n)

参数1:
void *to:目标地址,内核空间地址参数2:
const void *from:源地址,应用空间地址参数3:
unsigned long n:个数返回值:
成功返回0,失败返回大于0,表示还有多少个没有拷贝完

3.应用程序和驱动程序扮演什么角色

4.应用程序编写

Linux下一切皆文件,首先要open

例如:


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>int main()
{int fd = open("/dev/led0",O_RDWR);int value = 0;while(1){scanf("%d",&value);if(value > 1)break;write(fd,&value,4);}close(fd);return 0;
}

5.规范

 (1)在加载入口实现资源申请,需要在卸载入口实现资源释放

//申请资源
register_chrdev();//申请设备号
class_create();
device_create();//创建设备节点
ioremap();//硬件资源映射
------------------------
//释放资源
iounmap();//解除硬件资源映射
device_destroy();//释放设备节点
class_destroy();//释放节点信息类
unregister_chrdev();//释放设备号

(2)出错处理
在某个位置出错,要将之前申请的资源进行释放

static int __init key_init(void)
{int ret;//申请设备号key.major = register_chrdev(0,"key",&fops);if(key.major < 0){printk("register_chrdev error\n");ret = -1;goto err_0;}//创建设备节点key.cls = class_create(THIS_MODULE,"key cls");if(IS_ERR(key.cls)){printk("class_create error\n");ret = -2;goto err_1;}key.dev = device_create(key.cls,NULL,MKDEV(key.major,0),NULL,"key3");if(IS_ERR(key.dev)){printk("device_create error\n");ret = -3;goto err_2;}//硬件设备初始化//中断初始化struct device_node * np = of_find_node_by_path("/key_int_node");if(np == NULL){printk("of_find_node_by_path error\n");ret = -4;goto err_3;}key.irqno = irq_of_parse_and_map(np,0);//申请中断if( request_irq(key.irqno,key_handler,IRQF_TRIGGER_FALLING,"this is key","hello world") != 0 ){printk("request_irq error\n");ret = -5;goto err_3;}return 0;err_3:device_destroy(key.cls,MKDEV(key.major,0));err_2:class_destroy(key.cls);err_1:unregister_chrdev(key.major,"key");err_0:return ret;
}

(3) 面向对象编程思想

用一个结构体来表示一个对象
设计一个类型,描述一个设备的信息

例如:
struct BEEP
{
unsigned int major;
struct class * cls;
struct device * dev;
unsigned int * pwmtcfg0;
};
struct BEEP beep;//表示一个设备对象

 小妙招:文件私有数据

1.在open函数里面设置file->private_data为设备变量(结构体变量);

2.在read、write里要访问设备的时候,直接读取私有数据;

如:  struct newcharled_dev *dev=file->private_data;

6.测试

(1)加载驱动。

modprobe 驱动.ko

(2)进入/dev查看设备文件。

mknod /dev/........

(3)测试

./应用程序名


http://chatgpt.dhexx.cn/article/coZuLN3b.shtml

相关文章

【Linux设备驱动】设备驱动分类

Linux设备驱动分类 驱动分为有操作系统设备驱动和误操作系统的设备驱动。 有无操作系统设备驱动 无操作系统设备驱动 不是所有的计算机系统都一定要有操作系统&#xff0c;在许多情况下&#xff0c;操作系统都是不必存在的。对于功能比较单一、控制不复杂的系统&#xff0c…

Linux-设备驱动概述

文章目录 Linux设备驱动概述1. 设备驱动的作用2. 无操作系统的设备驱动3. 有操作系统时的设备驱动4. Linux设备驱动4.1 设备的分类及特点4.2 Linux设备驱动与整个软硬件系统的关系4.3 Linux设备驱动的重难点 5. 源代码阅读6. 设备驱动&#xff1a;LED驱动6.1 无操作系统的LED驱…

Linux设备驱动和设备匹配过程

Linux设备驱动和设备匹配过程 1. 设备驱动匹配简述2. 重点结构体介绍2.1 struct device2.2 struct platform_device2.3 struct platform_driver2.4 struct device_driver 3. device端发起匹配&#xff1a;3.1 流程图3.2 start_kernel时候解析设备树3.2.1 start_kernel3.2.2 set…

Linux设备驱动之字符设备驱动

一、linux系统将设备分为3类&#xff1a;字符设备、块设备、网络设备。 应用程序调用的流程框图&#xff1a; 三种设备的定义分别如下&#xff0c; 字符设备&#xff1a;只能一个字节一个字节的读写的设备&#xff0c;不能随机读取设备内存中的某一数据&#xff0c;读取数据需…

Linux设备驱动基础01:Linux设备驱动概述

目录 1. 设备驱动的作用 2. 有无操作系统时的设备驱动 2.1 无操作系统 2.1.1 硬件、驱动和应用程序的关系 2.1.2 单任务软件典型架构 2.2 有操作系统 2.2.1 硬件、驱动、操作系统和应用软件的关系 3. Linux设备分类 3.1 常规分类法 3.1.1 字符设备 3.1.2 块设备 3.…

Linux设备驱动模型

目录 一、linux设备驱动模型简介1、什么是设备驱动模型2、为什么需要设备驱动模型3、驱动开发的2个点 二、设备驱动模型的底层架构1、kobject2、kobj_type3、kset 三、总线式设备驱动组织方式1、总线2、设备3、驱动4、类5、总结 四、platform平台总线工作原理1、何为平台总线2、…

【linux内核分析与应用-陈莉君】字符设备驱动

目录 1.什么是字符设备 2.如何来描述字符设备 3 struct cdev与const struct file_operations之间的关系 4.struct file_operations源码 5.字符设备驱动框架 6.编写字符设备驱动的步骤 7.字符设备结构 8.字符设备驱动程序的注册 9.从系统调用到驱动程序 10.用户空间与内…

微信小程序云数据库使用讲解

第一步&#xff1a;注册开通 单击云开发 第二步&#xff1a;创建数据库 选择数据库&#xff0c;并点击号创建一个集合 输入名字 创建完毕后点击添加记录即可添加数据 数据库获取&#xff1a; 查询&#xff1a; 查询指令&#xff1a;

微信小程序云开发入门-数据库插入数据(包含批量)

一、前言 文章将介绍如何在微信小程序云开发中向云开发数据库插入数据&#xff08;单条或批量&#xff09;。 写法有好几种&#xff0c;文章将会一一进行对比&#xff0c;看看每种写法之间有何优缺点&#xff0c;如何让代码看起来更优雅。 为了更加贴合实际的开发逻辑&#xf…

【微信小程序】如何获取微信小程序云开发数据库的数据并渲染到页面?

前言 上一篇博客我把微信小程序云开发数据库操作&#xff08;增删改查&#xff09;的实现方法都已经分享出来啦&#xff0c;可以戳链接进去阅读哦 【微信小程序】小程序云开发实现数据库增删改查(小白速度Get起来&#xff01;&#xff01;一步步教你如何实现) 基于微信小程序…

开发一个可以查询并显示数据库内容的微信小程序

使用微信开发者工具可以创建云数据库&#xff0c;并通过代码可以查询并在客户端显示数据库的内容。 附&#xff1a;小程序一个功能页面有wxml(客户端呈现)&#xff0c;js(功能函数)&#xff0c;json&#xff0c;wxss(个性化处理)&#xff0c;这些是局部的文件。还有全局的文件如…

微信小程序开发---连接云开发数据库,实现数据获取

之前几篇博客里面都详细交代了如何配置云函数&#xff0c;现在就讲一下关于云函数中数据库的使用&#xff0c;主要是讲如何从云开发平台的数据库中调取数据 我们直接来到需要调用数据库数据的页面的js文件&#xff0c;直接设置全局变量&#xff0c;来便于后续调用数据库 const …

微信小程序查询数据库

微信小程序云开发的官方例子&#xff1a; const db wx.cloud.database() //获取数据库的引用 const _ db.command //获取数据库查询及更新指令 db.collection("china") //获取集合china的引用.where({ //查询的条件指令wheregdp: _.gt(3000) …

微信小程序云数据库操作

微信小程序云数据库操作 1、云数据库简介1.1 数据类型Date地理位置Null 1.2 权限控制 2、云数据库操作2.1 查询数据2.1.1 通过collection.doc获取一条记录2.1.2 通过collection.get获取所有记录的数据2.1.3 通过document.get获取某一条记录的数据2.1.4 通过collection.count获取…

微信小程序取本地数据库数据(实测有图)

测试效果如下&#xff1a; 本实验主要分为如下几个步骤&#xff1a; 一、安装数据库 二、安装PHPApache 三、编辑微信小程序代码 前两项的简单介绍在如下连接&#xff1a; PHPApache 四、本文主要介绍第三项的内容 需要用到的文件如下&#xff1a; 1、新建微信小程序工程 2、…

微信小程序云开发(数据库)

开发者可以使用云开发开发微信小程序、小游戏&#xff0c;无需搭建服务器&#xff0c;即可使用云端能力。 云开发为开发者提供完整的云端支持&#xff0c;弱化后端和运维概念&#xff0c;无需搭建服务器&#xff0c;使用平台提供的 API 进行核心业务开发&#xff0c;即可实现快…

微信小程序云开发(云数据库篇)

微信小程序云开发[云数据库篇] 云数据库关系型数据库和 JSON 数据库对比数据类型数据库操作联表查询事务处理 云数据库 云开发提供了一个 JSON 数据库&#xff0c;顾名思义&#xff0c;数据库中的每条记录都是一个 JSON 格式的对象。 一个数据库可以有多个集合&#xff08;相当…

微信小程序云开发 1 - 数据库

微信小程序云开发最重要的有两点&#xff1a; 1、云数据库&#xff1b; 2、云函数&#xff1b; 学会这两点基本就能够进行微信小程序的云开发&#xff1b; 首先&#xff0c;我们先看微信小程序云数据库的基本操作&#xff1a; 1&#xff09;打开微 信开发者工具&#xff0…

微信小程序笔记 -- 数据库

6.15 学习微信小程序 -- 数据库 数据库1. 初始化2. 数据库操作2.1 数据类型2.2 增删查改2.2.1 增加/插入 数据&#xff08;add方法&#xff09;2.2.2 删除数据&#xff08;remove方法&#xff09;2.2.3 查看数据&#xff08;get&#xff0c;where&#xff09;2.2.4 更新数据&am…

nohup忽略SIGHUP信号

今天遇到一个问题&#xff1a;开启终端启动gunicorn进程后台运行&#xff0c;终端不关闭时&#xff0c;可以导入excel&#xff0c;关闭终端后&#xff0c;不能导入excel。原因是&#xff0c;xlrd模块需要向控制台输出内容&#xff0c;终端关闭后&#xff0c;控制台消失&#xf…