1. 程式人生 > >linux的核心模組介紹

linux的核心模組介紹

一、建立一個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:如何檢視硬體的版本
[[email protected] /]# uname -r
2.6.35.7-GEC210
[[email protected] /]# 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