1. 内核模块传参
1.1 引入
1. 在使用insmod安装驱动时,可以在外部通过传递参数改变变量的值,而不用修改驱动文件
2. 部分厂商只会提供对应的驱动.ko文件,驱动工程师根据.ko修改对应变量的值
1.2 接收命令行传递参数
module_param(name, type, perm)
函数功能:接收命令行传递参数
参数:
name:变量名字
type:传递参数类型
* byte, hexint, short, ushort, int, uint, long, ulong
* charp: a character pointer
perm:修饰变量的权限
1.3 变量添加描述
MODULE_PARM_DESC(_parm, desc)
函数功能:对变量进行描述
参数:
parm:变量名
desc:对变量的描述
1.4 传递数组
module_param_array(name, type, nump, perm)
函数功能:传递数组
参数:
name:数组的名字
type:传递参数类型
nump:数组的长度 传递的需要是一个地址
perm:修饰数组名字的权限
2. 导出符号表
2.1 引入机制
在应用层,每个进程都有各自独立的用户空间,A进程不可以直接调用B进程的函数。但在内核层,内核共享3-4G内核空间,A进程只要知道B进程的函数地址就可以直接调用B中的函数。
2.2 导出符号表的好处
驱动工程师可以调用内核任意导出符号表的函数
解决代码冗余问题
2.3 如何导出符号表
EXPORT_SYMBOL_GPL(name)
函数功能:导出对应函数符号表
参数:
name:函数名
3. 通用Makefile
4. 应用层和内核层的区别
应用层 内核层
入口 main函数入口 入口函数
出口 return 0 出口函数
许可证 无 有
指定入口地址 无 需要指定
指定出口地址 无 需要指定
编译 gcc 使用Makefile
执行程序 ./可执行程序 安装/卸载
打印方法 printf printk
内核模块传参 argv/argc 借助宏函数
导出符号表 无 可以导出
5. 字符设备驱动
5.1 字符设备驱动API
注册字符设备驱动
#include <linux/fs.h>
int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
函数功能:注册字符设备驱动,编写在入口函数中
参数:
major:主设备号
major = 0:动态分配主设备号 系统自动分配主设备号的值
major > 0:静态分配主设备号 需要注意不能和/proc/devices主设备号重复
name:字符设备驱动名字
fops:操作方法结构体
返回值:
major = 0:成功返回主设备号的值,否则失败
major > 0:成功返回0,失败返回错误码注销字符设备驱动
void unregister_chrdev(unsigned int major, const char *name)
函数功能:注销字符设备驱动
参数:
major:注册成功的主设备号的值
name:字符设备驱动的名字
返回值:无
6. 用户空间和内核空间数据传输
用户空间到内核空间(写)
#include <linux/uaccess.h>
int copy_from_user(void *to, const void *from,unsigned long n)
函数功能:将用户空间的数据,拷贝到内核空间,需要写在write函数中
参数:
to:内核空间首地址
from:用户空间首地址
n: 拷贝字节大小
返回值:
成功返回0,失败返回未拷贝的字节数
内核空间向用户空间(读)
int copy_to_user(void *to, const void *from,unsigned long n)
函数功能:将内核空间的数据,拷贝到用户空间,需要写在read函数中
参数:
to:用户空间首地址
from:内核空间首地址
n: 拷贝字节大小
返回值:
成功返回0,失败返回未拷贝的字节数
7. 地址映射
7.1 映射概念
物理地址 ===> 硬件层 ===> 芯片手册可以查看到的地址
虚拟地址 ===> 内核层 ===> 将物理地址映射为虚拟地址
如何将物理地址映射为虚拟地址 ===> mmu 内存管理单元
在arm课程中,所有操作的地址都属于物理地址
在驱动课程中,所有操作的地址都属于虚拟地址
所以需要将物理地址映射为虚拟地址
7.2 映射API接口
#include <linux/io.h>
void *ioremap(unsigned long port, unsigned long size)
函数功能:将物理地址映射为虚拟地址
参数:
port:物理地址
size:映射的大小
返回值:
成功返回映射虚拟地址首地址
失败返回NULLvoid iounmap(volatile void *addr)
函数功能:取消地址映射
参数:
addr:映射成功之后虚拟地址首地址
返回值:无
8. 自动创建设备节点
8.1 引入目的
每次驱动工程师安装驱动时,都需要通过mknod命令创建设备节点 ==> 手动
需要根据创建字符设备驱动成功之后,返回的主设备号,根据主设备号,在手动创建设备节点(设备号 + 设备节点名字)
linux操作系统启动成功之后,执行的第一个脚本文件为rcS脚本文件
驱动工程师,直接将mknod自动创建设备节点,写入到rcS脚本中
8.2 机制
udev(mdev)机制:在2.6内核版本中引入,引入udev自动创建设备节点,在用户空间实现
在内核层,先使用class_create向上层提交目录信息,再使用device_create向上层提交设备信息,在用户空间udev自动创建设备节点
8.3 接口函数
#include <linux/device.h>
struct class * class_create(owner, name)
函数功能:向上层提交目录信息
参数:
owner:THIS_MODULE
name:目录信息的名字
返回值:
成功返回struct class *结构体指针首地址
失败判断是否在4K空间
例子:
struct class *cls;
cls = class_create(THIS_MODULE, "myled");
if(IS_ERR(cls))
{
return PTR_ERR(cls);
}
void class_destroy(struct class *cls) //取消向上层提交目录信息struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
函数功能:向上层提交设备节点信息
参数:
class:目录句柄指针
parent:NULL
devt : 设备号
MAJOR(dev) : 根据设备号获取主设备号的值
MINOR(dev) : 根据设备号获取次设备号的值
MKDEV(ma,mi) :根据主设备号和次设备合成设备号
drvdata:NULL
fmt:设备节点名字
返回值:
成功返回struct device *结构体指针首地址
失败判断是否在4K空间void device_destroy(struct class *class, dev_t devt) //取消向上层提交设备节点信息
面试题
如何对驱动程序进行编译
指定驱动文件名称,指定架构,使用obj-m 对.o文件进行编译
内核模块传参有什么好处
在使用insmod的时候可以直接使用外部传参修改驱动文件中的变量值,而不用改变驱动文件,非常方便
如何给内核模块int型变量传递参数
使用module_param(name,type,perm)函数
用户空间和内核空间如何完成数据传输
用户向内核传数据是write操作,驱动层使用copy_from_user函数
内核向用户传数据是read操作,驱动层使用copy_to_user函数
自动创建设备节点机制以及实现
hotplug监听来自目录的提交申请,当有申请出现时,通知udev
udev(mdev)机制:在2.6内核版本中引入,引入udev自动创建设备节点,在用户空间实现
在内核层,先使用class_create向上层提交目录信息,再使用device_create向上层提交设备信息,在用户空间udev自动创建设备节点