1、options-->document operations-->C source file : *.c;*.h;*.S
x86 Asm source file : ;*.S
2、project-->new project
3、将project工程添加源文件
4、文件同步:project--->sync files
========================================================
二、什么是内核模块
在linux内核中,设备驱动程序是以模块的方式存在的,设计一个设备驱动,首先需要设计一个module。设备驱动是包含在module中的。
好处:
1、一个驱动程序是一个模块,各个模块之间是独立的。
2、模块可以安装、卸载,比较灵活
3、方便调试
=============================================================
三、设计一个简单的module
例:linux/arch/arm/mach-s5pv210/adc.c
#include <linux/module.h>
#include <linux/kernel.h>
static int __init gec210_led_init(void)
{
//根据字符设备驱动模型:定义cdev、申请设备号、定义初始化file_operations
//将cdev注册到内核
printk("hello linux device driver\n");
return 0;
}
static void __exit gec210_led_exit(void)
{
//相当于入口函数的反函数
printk("good bye\n");
}
module_init(gec210_led_init);
module_exit(gec210_led_exit);
//module的描述,可有可无。#modinfo led_drv.ko
MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("S5PV210 LED driver");
MODULE_VERSION("V1.0");
MODULE_LICENSE("GPL"); //源码符合GPL协议
//__init--用来修饰一个初始化函数,一般只执行一次,当一个驱动程序编译到内核的时候,当zImage启动过程中,这个驱动会自动安装,当内核启动结束,__init修饰的函数所占用的内存区会被释放掉。编译的时候,__init修饰的函数会放在初始化段。
内核的启动:
[ 0.000000] Memory: 256MB 256MB = 512MB total
[ 0.000000] Memory: 344408k/344408k available, 179880k 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 - 0xc07c7000 (7396 kB)
[ 0.000000] .data : 0xc07c8000 - 0xc0829460 ( 390 kB)
[ 11.175934] Freeing init memory: 536K
=============================================================
四、驱动程序设计和应用设计的区别
1、应用程序有入口(main函数),没有出口。
驱动程序有入口,有出口
入口:安装驱动(#insmod *.ko)的时候会调用入口函数--->module_init()--->gec210_led_init()
出口:卸载驱动(#rmmod *)的时候,会调用出口函数--->module_exit()--->gec210_led_exit()
2、应用程序在设备的时候,可以使用C的库函数,#include <stdio.h>
驱动程序只能使用linux内核提供函数,不能使用标准C库。stdio.h --->printf()
3、应用程序的编译使用arm-linux-gcc,再指定库的位置
驱动程序的编译要使用内核源码包中的编译工具,使用内核源码目录下的Makefile。
(嵌入式平台:编译驱动,需要内核源码)
=============================================================
五、编译驱动程序的Makefile
1、基于嵌入式平台的Makefile:
obj-m += led_drv.o
KERN_DIR=/home/gec/android-kernel-samsung-dev
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERN_DIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERN_DIR) M=$(PWD) modules clean
1)obj-m += led_drv.o
将led_drv.c编译成目标文件,然后将目标文件再编译链接成一个独立的module(led_drv.ko)
2)KERN_DIR=/home/gec/android-kernel-samsung-dev
定义了一个内核源码的路径,编译过程中,要去内核源码拿编译工具,使用头文件。
3)PWD := $(shell pwd)
指定当前目录
4)modules:
$(MAKE) -C $(KERN_DIR) M=$(PWD) modules
编译的时候,去内核原码的目录下,找编译工具(Makefile和kbuild)和头文件(include);然后回到当前路径下,将驱动源文件编译成一个module(led_drv.ko)
编译输出:
$ make
make -C /home/gec/linux-2.6.35.7-gec-v3.0-gt110 M=/mnt/hgfs/12-driver/01module/demo/demo1 modules
make[1]: Entering directory `/home/gec/linux-2.6.35.7-gec-v3.0-gt110'
CC [M] /mnt/hgfs/12-driver/01module/demo/demo1/led_drv.o
Building modules, stage 2.
MODPOST 1 modules
CC /mnt/hgfs/12-driver/01module/demo/demo1/led_drv.mod.o
LD [M] /mnt/hgfs/12-driver/01module/demo/demo1/led_drv.ko
make[1]: Leaving directory `/home/gec/linux-2.6.35.7-gec-v3.0-gt110'
---------------------------------------------------------------------------------------
2、ubuntu+x86平台
obj-m += hello.o
KERN_VER = $(shell uname -r)
KERN_DIR = /lib/modules/$(KERN_VER)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERN_DIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERN_DIR) M=$(PWD) modules clean
=============================================================
六、编译驱动的内核源码包的要求
1、linux源码包的版本要和目标平台运行的linux的版本要一致
2、内核源码包要针对目标平台进程配置
1)修改Makefile:ARCH ?=
CROSS_COMPILE=
2)将arch/arm/configs/smdkv210_android_defconfig拷贝给.config
3)make menuconfig
3、内核源码包必须要编译完成过。
=============================================================
七、驱动使用
1、查看ko的信息
$ modinfo led_drv.ko
filename: led_drv.ko
license: GPL
version: V1.0
description: S5PV210 LED driver
author: [email protected]
srcversion: B4AD7101902D9417761A14B
depends:
vermagic: 2.6.35.7-GEC210 preempt mod_unload ARMv7
关键:vermagic(版本魔数)--->ko安装的时候,目标环境的软件版本和硬件版本。
提示1:如何给内核源码包设置本地版本-->2.6.35.7-GEC210
General setup --->
(-GEC210) Local version - append to kernel release
提示2:如何查看硬件的版本
[root@GEC210 /]# uname -r
2.6.35.7-GEC210
[root@GEC210 /]# uname -a
Linux GEC210 2.6.35.7-GEC210 #1 PREEMPT Mon Sep 16 17:05:23 CST 2013 armv7l GNU/Linux
2、安装驱动
# insmod led_drv.ko
[ 885.327603] hello linux device driver
3、查看内核中的module
# lsmod
led_drv 560 0 - Live 0xbf011000
snd_soc_gec210_wm8960 3134 0 - Live 0xbf00b000
snd_soc_wm8960 19792 1 snd_soc_gec210_wm8960, Live 0xbf000000
4、卸载驱动
# rmmod led_drv
[ 1109.048002] good bye
=============================================================
八、printk函数
#include <linux/kernel.h>
与printf()的区别,printk()带有优先级
1、例:
static char banner[] __initdata = KERN_INFO \
"S5PV210 ADC driver, (c) 2010 Samsung Electronics\n";
printk(banner);
分析:
1)__initdata --->修饰的是一个初始化数据,与“__init”的作用相同
2)KERN_INFO--->printk的优先级
----------------------------------------------------------------------------------
2、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(KERN_WARNING "hello world");
printk("<4>" "hello world");
printk("<4> hello world");//错的
------------------------------------------------------------------------------------
3、查看系统设置的优先级
# cat /proc/sys/kernel/printk
7 4 1 7
7--->控制台(串口0,console)输出的优先级,只有高与这个优先级的printk,才可以通过控制台输出
printk("<4>" "hello world"); --->输出
printk("<7>" "hello world"); --->没有输出
4--->通过控制台输出的默认优先级
printk("hello world"); --->使用默认的优先级4
1--->写到日志中的最高优先级
7--->写道日志中的最低优先级
-----------------------------------------------------------------------------------
4、修改系统设置的优先级
# echo 7 5 1 7 > /proc/sys/kernel/printk
注意:
/proc下的内容挂载的是proc文件系统,proc文件系统是一个虚的文件系统,是基于内存的文件系统。可以实时的反应linux内核的工作信息。
=============================================================
九、内核符号表
1、应用场景:
一个模块A中定义一个函数,模块B使用模块A定义的函数。如何解决?
方法:
在模块A中,将该函数声明到内核符号表中,内核符号表对linux内核来讲是一个全局的表,在任何一个模块中,都可以使用内核符号表中声明的函数。
2、查看内核符号表
/proc/kallsyms
3、如何将一个函数(或一个全局变量)声明到内核符号表
EXPORT_SYMBOL()
EXPORT_SYMBOL_GPL() --->只是符合GPL协议的module才可以使用这个函数
提示:如何让一个module符合GPL协议????
MODULE_LICENSE("GPL"); //源码符合GPL协议
提示:
如何去/proc/kallsyms查找符号add_xy
#grep -r add_xy /proc/kallsyms