linux驱动制作

news/2025/9/22 9:02:26/文章来源:https://www.cnblogs.com/tlnshuju/p/19104526

linux驱动制作

2025-09-22 08:59  tlnshuju  阅读(0)  评论(0)    收藏  举报

开发环境搭建

1、gcc的交叉编译工具链,详细看我的另一篇博客。

2、安装常用包

sudo apt install bison flex libssl-dev lzop libncurses-dev

开发板需要烧录一些文件来运行Linux

1、需要一个后缀为bin的文件,这个文件是通过编译u-boot得来的。它的功能是

  1. 加载内核可执行文件到内存运行

  2. 给待运行的内核准备好启动参数

  3. 加载二进制设备树文件到内存

  4. 安装系统

2、Linux内核的裸机可执行文件,由Linux源码编译生成。

3、设备树文件,ARM-Linux内核启动、运行过程中需要一些来自各芯片手册的编程依据,该文件专门用于记录这些依据。设备树文件有两种格式:

  1. .dts、.dtsi:文本形式,便于书写、修改

  2. .dtb:二进制形式,由.dts文件经专门工具处理后生成

4、根分区文件,Linux内核运行成功后,需要运行第一个应用程序(即祖先进程)以及后续其它应用程序。而任何应用程序的运行需要各种文件的支持,如:可执行文件、库文件、配置文件、资源文件。这些文件的持久保存和按路径访问需要外存分区特定文件系统的支持。rootfs就是Linux系统根目录所在的分区,其内包含根分区下众多常用app所需的文件。

Linux源码目录介绍

arch和init目录是和启动有关的目录,drivers和设备驱动相关,fs和文件系统相关,mm是和内存管理相关,net是和网络协议栈相关,kernal和ipc和任务相关,lib目录主要存放公用的一些函数接口,Linux内核没有标准c库,lib可以理解为Linux内核中的标准c库,crypto和security它两个存放了加解密算法它们和安全相关,sound是声音设备驱动的框架,block是外存驱动框架,include存放了内核的头文件。其他的目录大多不是内核源码,比如Documentation存放的是一些文档,scripts存放的是和内核编译相关的东西。

linux内核的编译

一般开发板厂商会给一个sdk工具,这个工具会包括u-boot源码和Linux内核源码和rootfs等等开发需要用到的各种工具以及源码,通常厂商会有说明如何编译linux内核,按照厂商的说明做即可。

Linux驱动开发常用的命令行命令

sudo insmod ./文件名.ko ;将内核模块插入正在执行的内核中运行 ----- 相当于安装插件
lsmod;查看已被插入的内核模块有哪些,显示的是插入内核后的模块名
sudo rmmod 文件名.ko ;此处为插入内核后的模块名,此时将已被插入的内核模块从内核中移除 ----- 相当于卸载插件

sudo dmesg -C ;清除内核已打印的信息
sudo dmesg ;查看内核的打印信息

cat /proc/devices;用于查看设备对应的设备号。

sudo mknod 设备文件名 设备种类(c为字符设备,b为块设备)  主设备号  次设备号 ;用来创建设备文件并将设备文件与设备号进行关联,注意设备文件通常存放在/dev目录下,比如sudo mknod /dev/mytest c 289 0;

向Linux内核添加新功能

静态方法

在此举例说明,第一步在Linux内核的驱动目录下char目录下编写hello.c,代码如下。

#include
#include
static int __init myhello_init(void)
{
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("myhello is running\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
return 0;
}
static void __exit myhello_exit(void)
{
printk("myhello will exit\n");
}
MODULE_LICENSE("GPL");
module_init(myhello_init);
module_exit(myhello_exit);

第二步,在驱动目录下修改Makfile和Kconfig文件,这里就不介绍怎么修改了,就是向其中加点内容就行,按照里面格式添加就行,很简单。

第三步,在内核顶层目录下make menuconfig一下,选择上那个新功能,最后按照厂商方法编译内核。

动态方法

在此只介绍新功能源码与Linux内核源码不在同一目录结构下的情况,这也是比较推荐的。

第一步:在任意目录中新建一个目录,比如新建的目录为mydriver,在mydriver目录中新建新功能源码的文件,比如hello.c,在里面编写代码,代码同静态方法的代码。然后在mydriver目录中新建Makefile文件,文件内容如下,不过需要适当修改KERNELDIR对应的路径和obj-m对应的文件名,路径必须为Linux内核源码顶层目录的绝对路径。编译时在命令行输入make表示编译出来的ko文件适用于当前Ubuntu系统,make ARCH=arm表示编译出来的ko文件适用于开发板

ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/fs4412/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install
clean:
rm -rf  *.o  *.ko  .*.cmd  *.mod.*  modules.order  Module.symvers   .tmp_versions
else
obj-m += hello.o
endif

编译多个动态模块方法

ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/fs4412/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install
clean:
rm -rf  *.o  *.ko  .*.cmd  *.mod.*  modules.order  Module.symvers   .tmp_versions
else
obj-m += hello.o
obj-m += xyz.o
xyz-objs =test.o func.o
endif

动态方法常用命令行命令

sudo insmod ./文件名.ko ;将内核模块插入正在执行的内核中运行 ----- 相当于安装插件
lsmod;查看已被插入的内核模块有哪些,显示的是插入内核后的模块名
sudo rmmod 文件名.ko ;此处为插入内核后的模块名,此时将已被插入的内核模块从内核中移除 ----- 相当于卸载插件

sudo dmesg -C ;清除内核已打印的信息
sudo dmesg ;查看内核的打印信息

cat /proc/devices;用于查看设备对应的设备号。

内核模块的信息宏

MODULE_AUTHOR(字符串常量); //字符串常量内容为模块作者说明

MODULE_DESCRIPTION(字符串常量); //字符串常量内容为模块功能说明

MODULE_ALIAS(字符串常量); //字符串常量内容为模块别名

这些模块信息,用法:

modinfo  模块文件名

给模块传参(用的比较少)

module_param(name,type,perm);//将指定的全局变量设置成模块参数
/*
name:全局变量名
type:
    使用符号      实际类型                传参方式
    bool         bool           insmod xxx.ko  变量名=0 或 1
    invbool      bool           insmod xxx.ko  变量名=0 或 1
    charp        char *         insmod xxx.ko  变量名="字符串内容"
    short        short          insmod xxx.ko  变量名=数值
    int          int            insmod xxx.ko  变量名=数值
    long         long           insmod xxx.ko  变量名=数值
    ushort       unsigned short insmod xxx.ko  变量名=数值
    uint         unsigned int   insmod xxx.ko  变量名=数值
    ulong        unsigned long  insmod xxx.ko  变量名=数值
perm:给对应文件 /sys/module/name/parameters/变量名 指定操作权限,一般都是给传0664。
    #define S_IRWXU 00700
    #define S_IRUSR 00400
    #define S_IWUSR 00200
    #define S_IXUSR 00100
    #define S_IRWXG 00070
    #define S_IRGRP 00040
    #define S_IWGRP 00020
    #define S_IXGRP 00010
    #define S_IRWXO 00007
    #define S_IROTH 00004
    #define S_IWOTH 00002  //不要用 编译出错
    #define S_IXOTH 00001
*/

给数组传参

module_param_array(name,type,&num,perm);
/*
name、type、perm同module_param,type指数组中元素的类型
&num:存放数组大小变量的地址,可以填NULL(确保传参个数不越界)
    传参方式 insmod xxx.ko  数组名=元素值0,元素值1,...元素值num-1  
*/

例如

#include
#include
int a=10;
char *astr="hello";
int garr[5]={1,2,3,4,5};
module_param(a,int,0664);
module_param(astr,0664);
module_param_array(garr,int,NULL,0664);
static int __init myhello_init(void)
{
printk("%d\n",a);
printk("%s\n",astr);
for(int i=0;i<5;i++)
{
printk("%d\n",garr[i]);
}
printk("\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("myhello is running\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
return 0;
}
static void __exit myhello_exit(void)
{
printk("myhello will exit\n");
}
MODULE_LICENSE("GPL");
module_init(myhello_init);
module_exit(myhello_exit);

使用方式

sudo insmod ./hello.ko a=100 astr="hi" garr=5,6,7,8,9

模块依赖(详细请看书18-19页)

模块依赖需要用到的宏有EXPORT_SYMBOL()和EXPORT_SYMBOL_GPL(),前者用于导出变量,后者用于导出函数。导出后在使用它们时只需要extern一下即可,比如extern int expval;extern void expfun(void);

注意:如果模块a用到了模块b,即模块a依赖于模块b,那么要在Makefile先编译b再编译a,先加载b模块再加载a模块,先卸载a模块再卸载b模块。如果模块a和模块不在同一目录那么先编译b,然后会有一个后缀为.sysvers的文件,将这个文件拷贝到模块a的目录中再编译模块a。

EXPORT_SYMBOL()和EXPORT_SYMBOL_GPL()会将其变量或函数名添加到内核符号表中以实现内核模块间符号的共享。

设备号

可以参考书32-33页代码看看如何使用下面的各种函数和宏。

设备号是32位的,前12位是主设备号,后20位是次设备号。主设备号用来表示同类设备,它们共用一套驱动,次设备号用来表示具体设备,不同设备会有不同的次设备号。

与设备号相关的宏

MKDEV;用来将主设备号和次设备号合成设备号,返回类型为dev_t。

MAJOR;输入设备号返回主设备号。

MINOR;输入设备号返回次设备号

与设备号相关的命令行

sudo mknod 设备文件名 设备种类(c为字符设备,b为块设备)  主设备号  次设备号 ;用来创建设备文件,注意设备文件通常存放在/dev目录下,比如sudo mknod /dev/mytest c 289 0;

与设备号相关的函数

int mknod(const char *pathname,mode_t mode,dev_t dev);和上面一样用于创建设备文件,参数和上面的一样,返回值为0和-1,0表示成功,-1表示失败。

int register_chrdev_region(dev_t from, unsigned count, const char *name);静态申请设备号,from:自己指定的设备号,count:申请的设备数量,name:/proc/devices文件中与该设备对应的名字,方便用户层查询主设备号,返回值:成功为0,失败负数,绝对值为错误码。

int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count, const char *name);动态申请设备号,dev:分配设备号成功后用来存放分配到的设备号,baseminior:起始的次设备号,一般为0,count:申请的设备数量,name:/proc/devices文件中与该设备对应的名字,方便用户层查询主次设备号,返回值:成功为0,失败负数,绝对值为错误码。

void unregister_chrdev_region(dev_t from, unsigned count);释放设备号,from:已成功分配的设备号将被释放,count:申请成功的设备数量。

关联设备和设备操作函数,将设备和设备号关联起来以及不关联起来。

void cdev_init(struct cdev *cdev,const struct file_operations *fops);初始化cdev设备,给cdev设备赋予操作函数,对于cdev和file_operations结构体在下面列举出来了。

int cdev_add(struct cdev *p,dev_t dev,unsigned int count);将指定字符设备关联设备号并添加到内核,p:指向被添加的设备,dev:设备号,count:设备数量,一般填1。

void cdev_del(struct cdev *p);从内核中移除一个字符设备并将设备和设备号取消关联,p:指向被移除的字符设备。

cdev_init可能会参考下面的结构体

struct cdev
{
    struct kobject kobj;//表示该类型实体是一种内核对象
    struct module *owner;//填THIS_MODULE,表示该字符设备从属于哪个内核模块
    const struct file_operations *ops;//指向空间存放着针对该设备的各种操作函数地址
    struct list_head list;//链表指针域
    dev_t dev;//设备号
    unsigned int count;//设备数量
};

struct file_operations 
{
   struct module *owner;           //填THIS_MODULE,表示该结构体对象从属于哪个内核模块
   int (*ope/n) (struct inode *, struct file *);    //打开设备
   int (*release) (struct inode *, struct file *);    //关闭设备
   ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);    //读设备
   ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);    //写设备
   loff_t (*llseek) (struct file *, loff_t, int);        //定位
   long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//读写设备参数,读设备状态、控制设备
   unsigned int (*poll) (struct file *, struct poll_table_struct *);    //POLL机制,实现多路复用的支持
   int (*mmap) (struct file *, struct vm_area_struct *); //映射内核空间到用户层
   int (*fasync) (int, struct file *, int); //信号驱动
   //......
};

使用驱动函数

应用层调用open函数-》内核中的syscall_open函数-》内核驱动中的drv_open函数

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

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

相关文章

实用指南:RESTful API:@RequestParam与@PathVariable实战对比

实用指南:RESTful API:@RequestParam与@PathVariable实战对比pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Co…

Android普通应用切到后台后,多长时间会被系统回收 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

变分法和欧拉-拉格朗日方程 - Emi

基本概念函数(function):函数是一种映射,输入一个数,输出一个数。 泛函(functional):泛函也是一种映射,输入一个函数,输出一个数。故我们可以说泛函是函数的函数。 微分(derivation):微分是函数的”输入和…

【Android】View 的滑动 - 实践

【Android】View 的滑动 - 实践2025-09-22 08:49 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !importa…

AT_arc194_d [ARC194D] Reverse Brackets

考虑将包含关系建出一棵树,那么答案相当于你可以重排儿子,相同大小儿子不区分,问能得到多少种树的形态。 简单用树哈希做就好了。

2025.9.22——1橙

普及- P10376 [GESP202403 六级] 游戏 很简单的DP,但要注意数组偏移,把负数也存进去。

huggingface.co 无法访问

Ping查询结果: huggingface.co 查询时间:2025-09-22 08:46:27

202403_QQ_brutezip

流量分析,文件分离,ZIP文件,掩码爆破Tags:流量分析,文件分离,ZIP,掩码爆破 0x00. 题目 题目表述 附件路径:https://pan.baidu.com/s/1GyH7kitkMYywGC9YJeQLJA?pwd=Zmxh#list/path=/CTF附件 附件名称:202403_QQ_brut…

实用指南:Vue开发准备

实用指南:Vue开发准备pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", &quo…

完整教程:WPF 程序用户权限模块利用MarkupExtension实现控制控件显示

完整教程:WPF 程序用户权限模块利用MarkupExtension实现控制控件显示2025-09-22 08:10 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto…

私有外设总线PPB(Private Peripheral Bus) - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

enrichmcp 构建数据驱动mcp的框架

enrichmcp 构建数据驱动mcp的框架enrichmcp 构建数据驱动mcp的框架 包含的特性通过数据模型生成类型工具 处理实体关系 处理schema 发现 通过pydantic 模型进行input 以及output 校验 支持database,api,自定义逻辑 支…

完整教程:visual studio快捷键

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

AppSpider 7.5.020 for Windows - Web 应用程序安全测试

AppSpider 7.5.020 for Windows - Web 应用程序安全测试AppSpider 7.5.020 for Windows - Web 应用程序安全测试 Rapid7 Dynamic Application Security Testing (DAST) released Sep 2025 请访问原文链接:https://sys…

上周热点回顾(9.15

热点随笔:为什么不建议在 Docker 中跑 MySQL? (苏三说技术) C# 2025年6-9月TIOBE排名增长及未来展望 (张善友) 重要:Java25正式发布(长期支持版)! (磊哥|www.javacn.site) 刚刚 Java 25 炸裂发布!让 Java 再次…

“学术造神”何时休?

微信视频号:sph0RgSyDYV47z6快手号:4874645212抖音号:dy0so323fq2w小红书号:95619019828B站1:UID:3546863642871878B站2:UID: 3546955410049087这个时代的学术圈,正在上演一场盛大的滑稽剧:实验室里产出的不再…

vLLM 核心机密:大模型推理引擎内部长啥样?

微信视频号:sph0RgSyDYV47z6快手号:4874645212抖音号:dy0so323fq2w小红书号:95619019828B站1:UID:3546863642871878B站2:UID: 3546955410049087vLLM Offcie Hours #32 又披露了一批新技术。其中有两个比较特殊,…

华为销量下滑OV米荣迎来窗口期

微信视频号:sph0RgSyDYV47z6快手号:4874645212抖音号:dy0so323fq2w小红书号:95619019828B站1:UID:3546863642871878B站2:UID: 3546955410049087一、华为手机销量阶段性下滑以下是全国全渠道销量份额(不包含智选…

【GitHub每日速递 250922】开源 AI 搜索引擎 Perplexica:本地大模型 + 多模式搜索,免费又强大!

原文: https://mp.weixin.qq.com/s/F7KwZlUd5OQg5CbAEbZGug MarkItDown:多格式文件转Markdown神器,助力LLM文本分析! markitdown 是一个将文件和办公文档转换为 Markdown 的工具。简单讲,它能帮你把 Word、Excel 等…

coze工作流实战——三分钟读一本名著

导航前言 作品展示 工作流展示 操作步骤 结语 参考前言2025年被行业认为是智能体(Agent)元年。过去几年,我们见证了AI 大模型的飞速发展,从只会简单回答问题,简单生成图文,到可以写代码,生成复杂视频,甚至可以…