Linux驱动开发入门
本文来源:https://www.jianshu.com/p/81cdf20f75f8
设备驱动分类
字符设备:可一个一个字节读取的设备,一般要实现open close read write ioctl等操作,
内核为字符设备对应一个文件如"/dev/consloe",对字符设备的操作通过操作设备文件实现,不可随机读写
块设备 : 类似字符设备,可以容纳文件系统,存储大量信息,每次传输一个或多个块。也可像字符设备一样
每次读取一个字节,可随机读写
网络设备 : 负责主机之间数据交换,实现套接字接口
insmod 将模块加入正在运行的内核中
rmmod 将未使用的模块从内核中删除, 不可删除正在使用的模块。
静态加载:内核启动时加载
动态加载:内核运行时加载
模块基本框架
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
int __init xxx_init(void)
{ /* 模块加载时初始化 */
retrun 0;
}
void __exit xxx_exit(void)
{ /* 模块卸载时销毁工作 */
}
module_init(xxx_init);
module_exit(xxx_exit);
"linux/string.h"
"linux/slab_def.h" 内存分配函数实现
内核没有C库,没有内存保护机制,内核栈小(需固定常驻内存空间32位机8kb, 64位机16kb),重视可移植性
ARM (Advanced RISC Manchines)
###使用busybox构建根文件系统,make menuconfig配置(需要的时候细看)###
升级内核:
1,下载内核源码解压,make menuconfig
2, make / make modules / make modules_install / make install / reboot
3, 使用uname - r 查看内核版本
驱动模块的组成
1,头文件
#include <linux/module.h> // 加载模块时需要的符号和函数定义
#include <linux/init.h> // 包含模块加载函数和模块释放函数的宏定义
2,模块参数(optional) 驱动加载时,需要传递给驱动模块的参数
3,4,模块加载和卸载函数(必须)
5,模块许可声明(必须) MODULE_LICENSE("xxx");
"GPL", "GPL v2", "GPL and additional rights", "Dual BSD/GPL", "Dual MPL/GPL", "Proprietary"
编译模块
ifeq($(KERNELRELEASE), )
KERNELDIR ? = / linuxdir /
PWD : = $(shell pwd)
modules : $(MAKE) - C $(KERNELDIR) M = $(PWD) modules
modules_install : $(MAKE) - C $(KERNELDIR) M = $(PWD) modules_install
clean : rm - rf *.o *~core.depend.*.cmd *.ko *.mod.c.tmp_versions
else
obj - m : = hello.o
endif
modutils 工具操作模块
1,insmod 加载模块, insmod hello.ko,
或可在 / var / log / messages 文件中demsg | tail 查看加载的打印信息,
传参 insmod modules.ko param1 = value1 param2 = value2
2,rmmod 卸载模块
3,modprobe
4,lsmod 列出模块
5,modinfo 查询模块的相关信息
模块加载后查看信息
/ proc / modules
cat modules| grep xxx
包含模块名,使用的内存,引用计数,分隔符,活跃状态,加载到内核中的地址
/ proc / devices 如果是设备模块会有变化
/ sys / module / 增加模块的基本信息
/ sys / module / 增加一个模块的目录
定义模块参数
static long a = 1;
static int b = 1;
module_param(a, long, S_IRUGO); //参数名,参数类型,参数读写权限
module_param(b, int, S_IRUGO);
类型:byte, short, ushort, int, uint, long, ulong, bool, charp(字符指针类型)
file module.ko 查看模块文件格式
ELF header; 描述文件基本属性,如文件版本,目标机器型号,程序入口地址
.text
.data;存放初始化的数据
.Section Table;描述文件包含的段的信息,如段名,长度,偏移,读写权限即其他
.symtab ;符号表,映射函数到真实内存地址的数据结构,模块加载阶段系统赋予真实的内存地址
将模块添加到内核中
1,将去驱动程序文件放到linux源码目录中
2,在目录的Kconfig文件中添加新驱动程序对应的编译选项
3,在目录的Makefile文件中添加新驱动程序的编译语句
一个字符设备或块设备都有一个主设备号和此设备号,统称为设备号。
主设备号:表示一个特定的驱动程序
次设备号:表示使用该驱动的各设备
typedef u_long dev_t; //32位计算机,高12位为主设备号,低20位位次设备号
使用宏获取主次设备号
linux / include / kdev_t.h
register_chrdev_region() 静态分配设备号,容易造成冲突;
使用alloc_chrdev_region() 动态分配设备号;
fs / char_dev.c
unregister_chrdev_region()
读取 / proc / devices 获取设备的设备号
cdev 结构,描述字符设备,是所有字符设备的抽象 每个字符设备在/dev下都有一个设备文件,
打开设备文件,系统产生一个inode节点。通过inode的i_cdev字段找到cdev 结构体,
通过cdev的ops指针,就可以找到设备的操作函数。 file_operations结构体存储操作指针
内核使用inode结构表示文件 用户空间和内核空间的数据交换采用专用函数
copy_to_user(),
copy_from_user(),
put_user()