cuixingwudi 2020-07-26
一、linux内核模块
1.数据类型:char(8bits)、short int (16bits)、int(32bits)、long int(与CPU的字长一致)
2、内核模块的作用
linux kernel Module--->设备驱动是以独立的module的形式存在的,设计的驱动需要包含在module内部。module编译完成后,会生成ko文件,可安装可卸载。
#insmod *.ko 安装
#rmmod * 卸载
#lsmod 查看安装的module
#modinfo *.ko 查看module的信息:linux的版本;硬件的架构
======================================================================================================
二、设计一个简单module
//linux/arch/arm/mach-s5pv210/adc.c
#include <linux/kernel.h>
#include <linux/module.h>
//驱动的注册函数
static int __init gec210_led_init(void)
{
printk("register ko to kernel\n"); //同printf()-->C的库函数
return 0;
}
//驱动的注销函数
static void __exit gec210_led_exit(void)
{
printk("unregister ko from kernel\n");
}
module_init(gec210_led_init); //驱动的入口
module_exit(gec210_led_exit); //驱动的出口
//模块的描述:使用#modinfo 可以查看这些信息
MODULE_AUTHOR("");
MODULE_DESCRIPTION("gec210 led driver");
MODULE_LICENSE("GPL");
MODULE_VERSION("V1.0");
=================================================================================================================
三、linux驱动设计与应用程序设计的区别
1、应用程序有入口(main函数),但是没有出口。
驱动程序有入口也有出口。
入口:安装驱动的时候调用的函数: #insmod led_dev.ko --->module_init( )-->gec210_led_init()-->向内核注册驱动;
出口:卸载驱动的时候调用的函数: #rmmod led_dev --->module_exit()--->gec210_led_exit() --->从内核卸载驱动
2、应用程序编程的时候可以使用标准的C库,即可以使用libc提供的函数:输入/输出、标准IO、内存分配、数学计算、字符串、......
<stdio.h> <stdlib.h>
驱动程序不能使用标准的C库,只能使用内核提供的函数。
printf() ---> printk()
3、驱动程序稳定的、精简、可移植性
简单的驱动--->设计
复杂的驱动--->移植
malloc() ---> kmalloc()
sleep() ---> ssleep()
# man 2 open
# man 3 sleep
# man 2 printk 错误 ---->查看内核源码
4、应用程序和驱动程序的编译过程不同。
驱动程序编译的时候,需要内核源码。
四、驱动程序的编译
编译驱动程序的时候,对内核源码的要求
1、(操作系统版本)内核源码的版本要和驱动安装的目标内核版本一致。
2、(处理器的版本)内核源码要针对具体的硬件平台配置过:arm--->cortex-A8(ARMv7)
3、(工具链)内核源码要编译过。
=====================================================================================================================
七、驱动下载调试
led_dev.ko--->GEC210开发板上
1、查看ko的信息
$ modinfo led_dev.ko
filename: led_dev.ko
version: V1.0
license: GPL
description: gec210 led driver
author:
srcversion: 4807F988764B99B1EB2E5FB
depends:
vermagic: 2.6.35.7-GEC210 preempt mod_unload ARMv7
2、ko的属性
$ file led_dev.ko
led_dev.ko: ELF 32-bit LSB relocatable, ARM, version 1 (SYSV), BuildID[sha1]=0x2bee1d42add8c130c3f950871b7484a802e0a631, not stripped
3、安装ko
# insmod led_dev.ko
[ 516.775396] register ko to kernel
4、查看安装后的ko
[ /test]# lsmod
Module Size Used by Not tainted
led_dev 572 0
5、卸载ko
# rmmod led_dev
[ 624.641936] unregister ko from kernel
=====================================================================================================================
七、printk
查看内核驱动的调试输出。
和printf()的作用是相同的。区别:printk带有优先级
例子:
static char banner[] __initdata = KERN_INFO "S5PV210 ADC driver, (c) 2010 Samsung Electronics\n";
int __init s3c_adc_init(void) //驱动的入口函数
{
printk(banner);
return platform_driver_register(&s3c_adc_driver);
}
分析:
1、优先级
KERN_INFO ---> printk的优先级。高于系统默认的优先级,控制台(串口)开可以输出字符串。
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#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 condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
printk("hello world") --->使用默认优先级--KERN_WARNING。
printk("<4>" "hello world");
# cat /proc/sys/kernel/printk
7 4 1 7
7--->高于7的优先级才可以在控制台看到。
printk(KERN_DEBUG "hello\n") //看不到
printk(KERN_INFO "hello\n") //看到
4--->printk默认的优先级
如何修改printk设置的优先级
#echo 6 3 1 7 > /proc/sys/kernel/prink
2、__initdata与__init、__exit
__init -->用来修饰一些初始化的函数(__initdata用来修饰初始化的数据),这些函数在系统初始化过程中使用,初始化后不会再使用了。但是它还是占用内存的,浪费内存,可以将这部分内存释放。
[ 0.000000] Memory: 256MB 256MB = 512MB total
[ 0.000000] Memory: 344364k/344364k available, 179924k reserved, 0K highmem
[ 0.000000] Virtual kernel memory layout:
[ 0.000000] vector : 0xffff0000 - 0xffff1000 ( 4 kB)
[ 0.000000] fixmap : 0xfff00000 - 0xfffe0000 ( 896 kB)
[ 0.000000] DMA : 0xff000000 - 0xffe00000 ( 14 MB)
[ 0.000000] vmalloc : 0xe0800000 - 0xfc000000 ( 440 MB)
[ 0.000000] lowmem : 0xc0000000 - 0xe0000000 ( 512 MB)
[ 0.000000] modules : 0xbf000000 - 0xc0000000 ( 16 MB)
[ 0.000000] .init : 0xc0008000 - 0xc008e000 ( 536 kB) -->初始化的函数或数据占用的内存
[ 0.000000] .text : 0xc008e000 - 0xc07d5000 (7452 kB)
[ 0.000000] .data : 0xc07d6000 - 0xc08345a0 ( 378 kB)
[ 12.132256] VFS: Mounted root (yaffs2 filesystem) on device 31:4.
[ 12.132407] Freeing init memory: 536K
=====================================================================================================================
八、内核符号表
1、问题:
在驱动A中定义了一个函数,而驱动B调用这个函数,怎么来处理?
驱动A和驱动B是独立编译的,生成两个ko,这两个ko是独立的。
2、方法:
将A驱动中定义的函数输出到内核符号表中,内核符号表是一个全局的表,在内核程序中,都可以直接使用该表中的函数。
3、如何将一个函数输出到内核符号表:
EXPORT_SYMBOL() --->将一个符号输出到内核符号表中,在驱动设计的时候,module可以使用该符号。
EXPORT_SYMBOL_GPL() --->符合GPL协议的module才可以使用该符号
MODULE_LICENSE("GPL");
内核符号表输出举例:
int s3c_adc_get(struct s3c_adc_request *req)
{
unsigned adc_channel = req->channel;
int adc_value_ret = 0;
adc_value_ret = s3c_adc_convert();
req->callback(adc_channel, req->param, adc_value_ret);
return 0;
}
EXPORT_SYMBOL(s3c_adc_get); --->将s3c_adc_get放入内核符号表。
4、内核符号表在哪里?
# cat /proc/kallsyms
5、内核符号表使用举例
见demo2
6、在一个Makefile中,如何编译多个ko?
obj-m += demo1.o
obj-m += demo2.o
思考1:
1)+= ---> ?=
2) += ---> :=
思考2:
add_xy生命到/proc/kallsyms文件中,如何查找?
grep命令
如何在一个目录下找一个命名文件,使用什么命令?
find命令
=====================================================================================================================
九、如何将多个源文件编译到同一个ko中
引用一个中间变量
obj-m += demo.o
demo-objs = add.o demo3.o
见demo3
Makefile文件:
obj-m += demo.o
demo-objs = add.o demo3.o
KERN_DIR=/home/gec/linux-2.6.35.7-gec-v3.0-gt110
modules:
$(MAKE) -C $(KERN_DIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERN_DIR) M=$(PWD) modules clean
思考:函数的声明放在头文件中,如何处理?