成长共勉 2019-11-12
linux驱动程序开发
1、linux驱动工程师需要具备的知识
1)硬件的知识
看懂电路原理图 (二极管 三极管 电阻...)
底板和核心板中名称相同的导线是同一条导线
目的:找到要驱动的硬件和CPU的连接方式
熟悉常见的接口:gpio uart i2c 1-wire spi 485 can usb
能够熟练阅读芯片的datasheet(谁读谁头疼)
2)驱动程序属于内核的一部分(uImage)
内核态编程要守内核态编程的规矩
3)内核中已经实现好大量的硬件驱动框架
字符设备:读写顺序固定 读写以字节为单位
例如:鼠标 键盘 ...
块设备: 读写顺序不固定 读写以块(多字节)为单位
例如: 硬盘 flash emmc u盘 ...
网络设备: 读写顺序固定 读写以帧(多字节)为单位
2、驱动课程的学习方法
UC: man
驱动课:最好的老师是内核源码
内核源码编程的原则:
1)代码执行效率高
2)可移植性强
内核编码使用的C语言,是GNU C
GNU C是标准C的扩展版本
经典书籍:
LDD3 (linux device driver 3th)
精通Linux 设备驱动程序开发
Linux内核设计与实现
3、搭建驱动开发环境
3.1 在PC机上装ubuntu (有的企业是提供linux 服务器)
3.2 安装交叉编译工具 (裸板阶段)
3.3 创建driver
cd ~
mkdir driver
1)rootfs //开发板挂载该目录作为根文件系统
sudo cp .../_install rootfs -a
或者
cp /mnt/hgfs/driver/env/rootfs.tar.gz ./
sudo tar xf rootfs.tar.gz
sudo vi /etc/exports
/home/tarena/driver/rootfs *(rw,sync,no_root_squash)
sudo service nfs-kernel-server restart
setenv bootcmd mmc read 48000000 800 3000 \;bootm 48000000
setenv bootargs root=/dev/nfs nfsroot=192.168.1.8:/home/tarena/driver/rootfs ip=192.168.1.6:192.168.1.8:192.168.1.1:255.255.255.0 init=/linuxrc console=ttySAC0 maxcpus=1 lcd=wy070ml tp=gslx680-linux
saveenv
2)kernel //存放内核源码 该源码保证编译通过
cp /mnt/hgfs/driver/env/kernel.tar.bz2 ./
tar xf kernel.tar.bz2
cd kernel/
cp arch/arm/configs/x6818_defconfig .config
make uImage
4、第一个内核模块
//以下两个头文件是写内核模块时必须加上的
//存在于内核源码目录下
#include <linux/init.h>
#include <linux/module.h>
//如果在安装模块时,希望被调用到的函数必须写成如下格式
//__init,它是一个宏
//被其修饰的代码 链接时放入“.init.text”段
//放入该段的代码只要执行一次 其对应的内存空间就释放
int __init xxx_init(void){...}
//module_init,它是一个宏
//被其修饰的函数在insmod时会被调用
//一个.c文件 最多只能有一个函数被其修饰
module_init(xxx_init);
//希望在卸载模块时 被调用的函数必须写成如下形式
//__exit, 它也是一个宏
//被其修饰的函数放入“.exit.text”段
//该段的代码一旦执行 其对应的内存空间就释放
void __exit xxx_exit(void){...}
module_exit(xxx_exit);
//声明为开源程序
//如果不声明 内核会被污染 内核中很多函数不让使用
MODULE_LICENSE("GPL");
vi Makefile
#编译为内核模块
obj-m += hello.o
all:
make -C $(KERNEL_PATH) M=$(PWD) modules
-C: 进入指定目录进行编译
modinfo hello.ko
M=$(PWD) :要编译的模块所在路径
modules: 编译内核模块时的固定写法
5、导出符号
解决内核模块于模块之间的函数调用问题的
应用程序:
xxx.c
int add(int x, int y){....}
yyy.c
extern int add(int, int);
内核编程:
xxx.c
int add(int x, int y){...}
EXPORT_SYMBOL(add);或者
EXPORT_SYMBOL_GPL(add)
yyy.c
extern int add(int, int);
res = add(1,2);
实验步骤:
insmod export.ko
lsmod
insmod import.ko
lsmod
rmmod import
lsmod
rmmod export
EXPORT_SYMBOL和EXPORT_SYMBOL_GPL的区别?
vi export.c
EXPORT_SYMBOL_GPL(my_add);
vi import.c
//MODULE_LICENSE("GPL");
小结:
1)如果不做符号导出,其它模块中不能使用my_add函数
2)如果EXPORT_SYMBOL导出符号
其它模块不管遵不遵循GPL 都可以使用
3)如果EXPORT_SYMBOL_GPL导出符号
只有遵循"GPL"的模块才能调用该函数
编译器内置的宏
__FILE__: 被会展开为当前文件名称
__func__或者__FUNCTION__:当前函数名
__LINE__: 所在行的行号
__DATE__: 编译代码的日期
__TIME__: 编译代码的时间
6、printk
作用: 用于内核态的代码调试
语法格式:
printk(优先级 "要输出的内容%s %d %f %c %p")
printk("<1>" "helloworld!\n");
//使用了默认的打印优先级
printk("helloworld!\n");
关于printk打印优先级:
1)linux内核将打印优先级为分为0~7 共8级
2)值越小优先级越高
3)可以通过打印优先级阈值控制
哪些信息可以输出到控制台 哪些不可以输出
8级:
include/linux/printk.h
#define KERN_EMERG "<0>"
...
打印优先级阈值:
printk函数调用时给定的优先级高于阈值
该打印信息可以输出到控制台
否则不能输出到控制台
如何查看优先级阈值:
cat /proc/sys/kernel/printk
7 4 1 7
第一个值:优先级阈值
第二个值:默认优先级
printk("helloworld!\n");
如何修改优先级阈值:
1) setenv bootargs .... loglevel=数字
2) echo 1 >proc/sys/kernel/printk
该修改方式掉电就无效了
注意:/proc目录下的文件不能通过vi 打开访问
读操作,
cat /proc/xxx
写操作,
echo dfasdfa >/proc/xxx
想到内核自启动以来所有的prink输出的信息:
dmesg
为什么要搞打印优先级阈值?
控制哪些信息可以输出
哪些不要输出
可以通过配置内核 将printk输出的时间相关信息干掉
make menuconfig
Kernel hacking --->
[ ] Show timing information on printks
#调整默认输出优先级的
(4) Default message log level (1-7)
printk函数的实现代码printk.c
面试题:C语言中是支持可变参数的,例如printf
如何实现的?
本质上使用了栈
va_start
#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("GPL"); int __init hello_init(void) { printk("<1>" "helloworld!\n"); return 0; } void __exit hello_exit(void) { printk("<1>" "byebye!\n"); } module_init(hello_init); module_exit(hello_exit);
//符号导出#include <linux/init.h> #include <linux/module.h> int my_add(int x, int y) { printk("<1>" "enter %s\n", __func__); return x+y; } //导出符号 EXPORT_SYMBOL(my_add); MODULE_LICENSE("GPL");//符号导入 #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("GPL"); extern int my_add(int, int); static int __init import_init(void) { int res = 0; res = my_add(11,22); printk("<1>" "in %s: %s: %d res=%d\n", __FILE__, __func__, __LINE__, res); return 0; } static void __exit import_exit(void) { printk("<1>" "byebye!\n"); } module_init(import_init); module_exit(import_exit);