linux的内核模块介绍

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_26786109/article/details/78246289
一、创建一个linux内核的source insight
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


猜你喜欢

转载自blog.csdn.net/qq_26786109/article/details/78246289