1.驱动的种类
字符设备驱动:按照字节流来访问,只能顺序访问,不能无序访问的设备
块设备驱动:按照block(512字节)访问,可以随机访问的设备。
网络设备驱动:网络设备没有设备节点,控制网卡硬件,负责网络数据收发的代码就是网络设备驱动
2.linux内核模
2.1内核模块的三要
-
入口:资源申请,在安装驱动的时候执行insmod
-
出口:资源释放,在卸载驱动的时候执行rmmod
-
许可证:内核模块必须遵从GPL开源协议
2.2内核模块代码实例
#include <linux/init.h> #include <linux/module.h> // 入口 // static:限定作用域 // int:返回值类型 // __init:给编译器使用,将demo_init放在.init.text段中 // #define __init __section(".init.text") vmlinux.lds 链接脚本 // uImage-zImage-Image-vmlinux // demo_init:入口函数的名字,一般的写法:led_init adc_init uart_init // (void):没有参数 static int __init demo_init(void) { return 0; } // 出口 // static:限定作用域 // void:返回值类型 // __exit:给编译器使用,将demo_exit放在.exit.text段中 // #define __exit __section(".exit.text") vmlinux.lds 链接脚本 // uImage-zImage-Image-vmlinux // demo_exit:出口函数的名字,一般的写法:led_exit adc_exit uart_exit // (void):没有参数 static void __exit demo_exit(void) { } module_init(demo_init); //告诉内核入口 module_exit(demo_exit); //告诉内核出口 MODULE_LICENSE("GPL"); // 许可证
2.5内核模块的编
方法1:内部编译
-
将demo.c拷贝到/drivers/char/
-
修改当前目录下的Kconfig文件
473 config DEMO
474 tristate "demo demo demo"
-
通过make menuconfig进行选配
<M> demo demo demo (NEW)
Y :编译到uImage
M:模块化编译
-
.config生成编译选项
CONFIG_DEMO=m
CONFIG_DEMO=y
#CONFIG_DEMO is not set
-
修改Makefile文件
obj-$(CONFIG_demo) += demo.o
obj-m +=demo.o
obj-y +=demo.o
-
编译
make uImage LOADADDR=0xc2000000
make modules ----->demo.ko
方法2:外部编译
KERNELDIR := /home/linux/linux-5.10.61 PWD := $(shell pwd) all: make -C $(KERNELDIR) M=$(PWD) modules @#make -C $(KERNELDIR) 进入内核顶层目录下 @#读取内核顶层目录下的Makefile文件对着它执行 @#make M=$(PWD) modules @#对着当前PWD路径执行make modules模块化编译 clean: make -C $(KERNELDIR) M=$(PWD) clean obj-m:=demo.o
通用的Makefile
make arch=arm modname=demo 编译开发板可以安装的模块
make arch=x86 modname=demo 编译ubuntu可以安装的模块
arch ?= x86 modname ?= demo ifeq ($(arch),arm) KERNELDIR := /home/linux/linux-5.10.61 else KERNELDIR := /lib/modules/$(shell uname -r)/build/ endif PWD := $(shell pwd) all: make -C $(KERNELDIR) M=$(PWD) modules clean: make -C $(KERNELDIR) M=$(PWD) clean obj-m:=$(modname).o
2.6内核模块的安装
sudo insmod demo.ko 安装内核模块
lsmod 查看内核模块
sudo rmmod demo 卸载内核模块
3.内核模块打印语句printk使用
3.1printk的语法格式
printk(消息级别 "控制格式"); //指定消息级别
或
printk("控制格式"); //使用默认消息级别
3.2printk的打印级别
内核中一共有8种打印级别,数值越小级别越高。
#define KERN_EMERG "0" /* system is unusable */ #define KERN_ALERT "1" /* action must be taken immediate*/ #define KERN_CRIT "2" /* critical conditions */ #define KERN_ERR "3" /* error conditions */ #define KERN_WARNING "4" /* warning conditions */ #define KERN_NOTICE "5" /* normal but significant conditi*/ #define KERN_INFO "6" /* informational */ #define KERN_DEBUG "7" /* debug-level messages */
3.3printk通过打印级别过来信息
只有消息的级别高于终端的级别的时候消息才会在终端上回显
cat /proc/sys/kernel/printk
4 4 1 7
4:终端的级别
4:消息默认级别
1:终端最大级别
7:终端最小级别
修改默认消息级别的方法:
su root
echo 4 3 1 7 > /proc/sys/kernel/printk
3.4进入/退出虚拟终端
进入虚拟终端:ctrl + fn +alt +[F2~F6]
退出虚拟终端:ctrl + fn + alt + F1
3.5printk函数的实例
#include <linux/init.h> #include <linux/module.h> // 入口 static int __init demo_init(void) { printk(KERN_ERR "this is test printk kern_err\n"); printk(KERN_INFO "this is test printk kern_info\n"); printk("%s:%s:%d\n",__FILE__,__func__,__LINE__); return 0; } // 出口 static void __exit demo_exit(void) { printk("%s:%s:%d\n",__FILE__,__func__,__LINE__); } module_init(demo_init); //告诉内核入口 module_exit(demo_exit); //告诉内核出口 MODULE_LICENSE("GPL"); // 许可证
3.6内核打印消息的查看方法
dmesg (从内核启动到当前时候内核打印的所有信息)
sudo dmesg -C //直接清除打印信息
sudo dmesg -c //先将信息显示到终端上然后在清除
4.内核模块传参
4.1内核模块传参的API
module_param(name, type, perm) 功能:接收命令行传递的参数 参数: @name:变量名 @type:变量的类型 /* Standard types are: * byte, hexint, short, ushort, int, uint, long, ulong * charp: a character pointer * bool: a bool, values 0/1, y/n, Y/N. * invbool: the above, only sense-reversed (N = true). */ @perm:权限,如果填写的不是0,就是在sys目录下产生一个文件,文件的权限就是perm 最大权限0664 MODULE_PARM_DESC(_parm, desc) 功能:对传递的变量进行字符串描述 参数: @_parm:变量名 @desc:描述的字符串
4.2内核模块传参的实例
#include <linux/init.h> #include <linux/module.h> int backlight=127; module_param(backlight,int,0664); MODULE_PARM_DESC(backlight,"this is backlight var,range[0-255],default:127"); // 入口 static int __init demo_init(void) { printk("%s:%d\n",__func__,backlight); printk("%s:%s:%d\n",__FILE__,__func__,__LINE__); return 0; } // 出口 static void __exit demo_exit(void) { printk("%s:%d\n",__func__,backlight); printk("%s:%s:%d\n",__FILE__,__func__,__LINE__); } module_init(demo_init); //告诉内核入口 module_exit(demo_exit); //告诉内核出口 MODULE_LICENSE("GPL"); // 许可证
4.3内核模块传参的测试流程
-
编译模块 make arch=x86 modname=demo
-
查看内核模块可传参的变量
-
安装命令行传参
sudo insmod demo.ko a=255
-
通过属性文件传参
/sys/module/驱动命名的目录/parameters
-rw-rw-r-- 1 root root 4096 3月 27 16:24 a
cat a //查看a文件中的内容
su root
echo 128 > a //修改a文件中的内容
-
通过dmesg查看现象dmesg
4.4内核模块传参练习
-
使用内核模块传参1个字节的变量
-
使用内核模块传参传递字符串
#include <linux/init.h> #include <linux/module.h> unsigned char a = 'A'; //不能够传递字符 module_param(a, byte, 0664); MODULE_PARM_DESC(a, "this is uchar var"); char* p = "hello driver"; //字符串中不能有空格 module_param(p, charp, 0664); MODULE_PARM_DESC(p, "this is char * var"); // 入口 static int __init demo_init(void) { printk("%s:a = %c\n", __func__, a); printk("%s:p = %s\n", __func__, p); printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); return 0; } // 出口 static void __exit demo_exit(void) { printk("%s:a = %c\n", __func__, a); printk("%s:p = %s\n", __func__, p); printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); } module_init(demo_init); // 告诉内核入口 module_exit(demo_exit); // 告诉内核出口 MODULE_LICENSE("GPL"); // 许可证 MODULE_AUTHOR("dzs daizs_bj@hqyj.com");
5.导出符号表
5.1什么是导出符号表
在内核中有两个模块demoA和demoB.如果demoA模块中实现了add函数,
此时demoB模块是可以调用demoA中的add函数的,因为两个模块都运行
同一个3-4G的内核空间,但是demoB如何拿到demoA中的add函数那?通
过导出符号表完成。
5.2导出符号表的API
EXPORT_SYMBOL_GPL(sym) 功能:导出符号表 参数: @sym:函数名
5.3导出符号表的实例
demoA.c
#include <linux/init.h> #include <linux/module.h> int add(int a,int b) { return (a+b); } EXPORT_SYMBOL_GPL(add); static int __init demoA_init(void) { printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); return 0; } static void __exit demoA_exit(void) { printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); } module_init(demoA_init); module_exit(demoA_exit); MODULE_LICENSE("GPL");
demoB.c
#include <linux/init.h> #include <linux/module.h> extern int add(int ,int); static int __init demoB_init(void) { printk("sum = %d\n",add(100,200)); printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); return 0; } static void __exit demoB_exit(void) { printk("%s:%s:%d\n", __FILE__, __func__, __LINE__); } module_init(demoB_init); module_exit(demoB_exit); MODULE_LICENSE("GPL");
5.4测试流程
编译:
首先先编译demoA模块,生成符号表文件Module.symvers。
在编译demoB之前需要将这个文件拷贝到demoB的目录下,否则
会出现add 函数undefined。如果是5.10以后的内核使用如下方式
符号表路径
安装:
先安装demoA模块,在安装demoB模块,因为demoB依赖demoA
模块,可以通过modinfo查看依赖
卸载:文章来源:https://www.toymoban.com/news/detail-821410.html
先卸载demoB模块,在卸载demoA模块。文章来源地址https://www.toymoban.com/news/detail-821410.html
到了这里,关于驱动开发——入门到入职1的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!