能够驱使硬件实现特定功能的软件代码,可以根据驱动程序是否依赖于系统内核将其分为裸机驱动和系统驱动
编写的驱动代码中没有进行任何内核相关的API调用,开发者查询资料配置寄存器完成硬件相关控制,不依赖于系统内核,由开发者独立完成,相对而言比较简单。
系统驱动指的是编写的驱动代码中需要调用系统内核中提供到的各自API,驱动最终也会加载到系统内核生效。系统驱动开发者无法独立完成,需要依赖于系统内核,基于系统驱动实现的硬件功能也更加复杂
进程上下文切换概念:当进程进行系统调用时,进程访问到的资源从用户空间到内核空间,就叫上下文切换。
文件管理、内存管理、进程管理、网络管理(socket)、设备管理(设备驱动的管理)
linux设备驱动的类型要根据设备的类型进行驱动,系统将其分为字符设备、块设备、网卡设备
字符设备:能够以字节流的形式进行顺序访问的设备叫做字符设备(鼠标、键盘、led灯)
块设备:能够以块(512字节)为单位进行随机访问的设备叫做块设备(磁盘)
网卡设备:进行网络通信时使用网卡设备实现,网卡设备的数据读写基于套接字实现
不同于应用程序,驱动是加载到内核空间中的,所以需要内核模块的编程框架编写驱动代码
内核模块由三部分组成:入口、出口、许可证
入口:内核模块安装时执行,主要负责资源的申请工作
出口:卸载内核模块时执行、主要负责资源的释放工作
许可证:声明内核模块遵循GPL协议
#include
#include
//入口函数 安装内核模块时执行
static int __init mycdev_init(void)
{
//static用于限制入口函数只能在当前文件中使用
//int指定函数的返回值为int类型,返回值一定要加,不然编译报错
//mycdev_init,入口函数的函数名,可以自己修改
//void表示这个函数参,当函数没有参数时一定在参数列表中加void,不然会报错
/*在ubuntu中的内核目录下索引指定的API时,可以在内核顶层目录下输入ctags -R
接着使用vi -t 接口名即可索引
*/
//#define __init __section(".init.text")
//__init用于向编译器声明当前mycdev_init函数加载到.init.text段
//.init.text段指定在内核的链接脚本文件vmlinux.lds中
return 0;
}
//出口函数,卸载内核模块时执行
static void __exit mycdev_exit(void)
{
//#define __exit __section(".exit.text")
}
//用于声明当前内核模块入口函数的地址
module_init(mycdev_init);
//用于声明当前内核模块出口函数的地址
module_exit(mycdev_exit);
//声明当前内核模块遵循GPL协议
MODULE_LICENSE("GPL");
make modules
需要依赖于内核源码树进行编译
外部编译(动态编译)
不需要依赖于内核源码树,在编译时只需要编译当前内核模块文件即可,外部编译需要自己手写当前内核模块编译的Makefile
4.3编写动态编译的内核模块的Makefile
#定义变量存放内核源码顶层路径 #KERNELDIR:= /home/ubuntu/linux-5.10.61 #用于编译生成ARM架构的模块 KERNELDIR := /lib/modules/$(shell uname -r)/build #用于生成x86架构的模块 #定义变量保存模块化编译的文件的路径 PWD:= $(shell pwd) all: #make modules是模块化编译的命令 #make -C $(KERNELDIR)表示读取KERNELDIR路径下的Makefile进行make编译 #M=$(PWD)指定模块化编译的路径 make -C $(KERNELDIR) M=$(PWD) modules clean: #编译清除 make -C $(KERNELDIR) M=$(PWD) clean obj-m:=demo.o #指定将demo.o独立链接生成内核模块文件
通用版本的Makefile
modname?=demo #给定一个默认的模块名
arch?=arm #给定一个架构的模块名
ifeq ($(arch),arm)#判断要编译的架构进而使用不同的Makefile规则进行编译
#定义变量存放内核源码顶层路径
KERNELDIR:= /home/ubuntu/linux-5.10.61 #用于编译生成ARM架构的模块
else
KERNELDIR := /lib/modules/$(shell uname -r)/build #用于生成x86架构的模块
endif
#定义变量保存模块化编译的文件的路径
PWD:= $(shell pwd)
all:
#make modules是模块化编译的命令
#make -C $(KERNELDIR)表示读取KERNELDIR路径下的Makefile进行make编译
#M=$(PWD)指定模块化编译的路径
make -C $(KERNELDIR) M=$(PWD) modules
clean: #编译清除
make -C $(KERNELDIR) M=$(PWD) clean
obj-m:=$(modname).o #指定将demo.o独立链接生成内核模块文件
安装内核模块:
sudo insmod ****.ko
查看被安装的内核模块
lsmod
卸载内核模块
sudo rmmod ***
查看内核模块相关信息
modinfo ****.ko
内核中的消息打印函数:
printk(消息输出级别 "格式控制符",输出列表);
查看消息默认级别
终端输入 cat /proc/sys/kernel/printk查看
终端默认级别 消息默认级别 终端支持的消息最高级别 终端支持消息的最低级别
dmesg命令的使用
功能:输出从内核启动到当前时刻开始所有的打印信息
dmesg -c/dmesg -C: 清除当前dmesg的buf中保存的所有打印信息
内核模块传参指的是安装模块时在命令行给内核模块中的变量传递数值
ex:insmod demo.ko a=100
通过内核模块传参的使用,我们可以对内核模块的一些属性进行修改,让其适配多种不同的硬件
内核模块传参实现的相关API
module_param(name,type,perm)
//声明可以通过命令行传参的变量信息
参数:name 要进行命令行传参的变量名
type:要进行命令行传参的变量类型
/ * Standard types are:
byte(单字节类型), hexint, short, ushort, int, uint, long, ulong
charp: a character pointer(char *)
bool: a bool, values 0/1, y/n, Y/N.
invbool: the above, only sense-reversed (N = true).
*/
perm:文件权限,当使用module_param函数声明要传参的变量时,会在/sys/module/当前内核模块名/parameters/目录下生成一个以当前变量名为名的文件,文件的权限就是perm和文件权限掩码运算得到,文件的数值时变量的值
2.MODULE_PARM_DESC(变量名, 对变量的描述)
功能:添加对要传参的变量的描述,这个描述可以通过modinfo ***.ko查看到