2011年12月30日 星期五

基礎 Linux Device Driver 驅動程式#2 (hello world驅動)

今天,我們先以hello world 這個簡單的驅動程式作為我們的第一個範例程式。

kernel的module, 例如驅動程式,並沒有像user space的程式一樣,
能夠使用 stdio.h, string.h等這些標頭檔,
所以取而代之的是 linux/module.h 以及 linux/init.h 這兩個標頭檔,
最基本的驅動程式,都一定會有這兩個標頭檔。
廢話不多說,我們就先來示範一下hello world這支驅動程式。
以下是程式碼:

/*****************************************************************************/

     1 #include <linux/init.h>
     2 #include <linux/module.h>
     
     
     3 static int hello_init(void)
     4 {
     5 printk(KERN_ALERT "Hello world init.\n");
     6 return 0;
     7 }
     
     8 static void hello_exit(void)
     9 {
    10 printk(KERN_ALERT "Hello world exit.");
    11 }
     
    12 module_init(hello_init);
    13 module_exit(hello_exit);
     
    14 MODULE_LICENSE("GPL");
    15 MODULE_AUTHOR("Wang Chen Shu");
    16 MODULE_DESCRIPTION("This is hello world module test.");

/*****************************************************************************/

// 底下這兩行是模組所需的標頭檔 //
1  #include <linux/init.h>
2  #include <linux/module.h>

/*
 下面的hello_init函式則是模組在載入時的進入點,
 加上static 是要將命名空間限制在檔案內。
 */
3  static int hello_init(void)
4  {
5          printk(KERN_ALERT "Hello world init.\n");
6          return 0;
7  }

// 下面的hello_exit函式則是模組在卸載時的進入點。 //
8  static void hello_exit(void)
9  {
10          printk(KERN_ALERT "Hello world exit.");
11 }

// 透過module_init 巨集指定hello_init //
12  module_init(hello_init);

// 透過module_exit 巨集指定hello_exit //
13  module_exit(hello_exit);

// 下面則是對模組的一些聲明,像授權方式, 作者以及模組的描述 //
14  MODULE_LICENSE("GPL");
15  MODULE_AUTHOR("Wang Chen Shu");
16  MODULE_DESCRIPTION("This is hello world module test.");

至於, printk的部份,可參考include/linux/printk.h, 如下:
#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                 */

有了,程式碼,當然您也要準備linux 的kernel 原始碼,
因為模組的編譯需要核心原始碼。
我們這裡是用linux 2.6.38的核心做為示範。
當然您得下載您需要的版本,

# uname -r
2.6.38-13-generic-pae
像我的核心就是 2.6.38,所以視您的版本而定。
當然,您也不想為了每次編譯都打一長串的指令,
所以當初核心的開發人員也為各位想到了這點,所以才有makefile這類的script。

所以,我們還要撰寫一個makefile,檔名為Makefile,內容如下:

/*****************************************************************************/

     1 KDIR="/opt/linux-source-2.6.38"
     2 PWD=$(shell pwd)
     3 VV=
     
     4 obj-m := hello.o
     
     5 all:
     6 $(MAKE) -C ${KDIR} M=${PWD} V=${VV} modules
     7 clean:
     8 $(MAKE) -C ${KDIR} M=${PWD} clean

/*****************************************************************************/

-C 後面接的是核心原始碼的路徑
M= 後面接的是目前的目錄位置
6          $(MAKE) -C ${KDIR} M=${PWD} V=${VV} modules

這裡看到一個變數VV,如果指定為1的話, 就可以在編譯時看到更詳細的建構過程。
我這裡將我下載的核心原始碼放在/opt做為測試用。
有了驅動原始碼和Makefile, 就可以開始進行下一步了。示範如下:

# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/hello V= modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CC [M]  /opt/test_driver/my_driver/hello/hello.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /opt/test_driver/my_driver/hello/hello.mod.o
  LD [M]  /opt/test_driver/my_driver/hello/hello.ko
make[1]: Leaving directory `/opt/linux-source-2.6.38'

接著我做了一個動作:
# vim Makefile

3  VV=
改成
3  VV=1

再做一次。

# make
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/hello V=1 modules
make[1]: Entering directory `/opt/linux-source-2.6.38'
test -e include/generated/autoconf.h -a -e include/config/auto.conf || ( \
echo; \
echo "  ERROR: Kernel configuration is invalid."; \
echo "         include/generated/autoconf.h or include/config/auto.conf are missing.";\
echo "         Run 'make oldconfig && make prepare' on kernel src to fix it."; \
echo; \
/bin/false)
mkdir -p /opt/test_driver/my_driver/hello/.tmp_versions ; rm -f /opt/test_driver/my_driver/hello/.tmp_versions/*
make -f scripts/Makefile.build obj=/opt/test_driver/my_driver/hello
  gcc -Wp,-MD,/opt/test_driver/my_driver/hello/.hello.o.d  -nostdinc -isystem /usr/lib/i386-linux-gnu/gcc/i686-linux-gnu/4.5.2/include  -I/opt/linux-source-2.6.38/arch/x86/include -Iinclude  -include include/generated/autoconf.h -Iubuntu/include  -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -fno-delete-null-pointer-checks -O2 -m32 -msoft-float -mregparm=3 -freg-struct-return -mpreferred-stack-boundary=2 -march=i686 -mtune=generic -maccumulate-outgoing-args -Wa,-mtune=generic32 -ffreestanding -fstack-protector -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -Wframe-larger-than=1024 -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -pg -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -DCC_HAVE_ASM_GOTO  -DMODULE  -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(hello)"  -D"KBUILD_MODNAME=KBUILD_STR(hello)" -c -o /opt/test_driver/my_driver/hello/.tmp_hello.o /opt/test_driver/my_driver/hello/hello.c
  if [ "-pg" = "-pg" ]; then if [ /opt/test_driver/my_driver/hello/hello.o != "scripts/mod/empty.o" ]; then /opt/linux-source-2.6.38/scripts/recordmcount "/opt/test_driver/my_driver/hello/hello.o"; fi; fi;
(cat /dev/null;   echo kernel//opt/test_driver/my_driver/hello/hello.ko;) > /opt/test_driver/my_driver/hello/modules.order
make -f /opt/linux-source-2.6.38/scripts/Makefile.modpost
  scripts/mod/modpost -m -a -i /opt/linux-source-2.6.38/Module.symvers -I /opt/test_driver/my_driver/hello/Module.symvers  -o /opt/test_driver/my_driver/hello/Module.symvers -S -w  -s
  gcc -Wp,-MD,/opt/test_driver/my_driver/hello/.hello.mod.o.d  -nostdinc -isystem /usr/lib/i386-linux-gnu/gcc/i686-linux-gnu/4.5.2/include  -I/opt/linux-source-2.6.38/arch/x86/include -Iinclude  -include include/generated/autoconf.h -Iubuntu/include  -D__KERNEL__ -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs -fno-strict-aliasing -fno-common -Werror-implicit-function-declaration -Wno-format-security -fno-delete-null-pointer-checks -O2 -m32 -msoft-float -mregparm=3 -freg-struct-return -mpreferred-stack-boundary=2 -march=i686 -mtune=generic -maccumulate-outgoing-args -Wa,-mtune=generic32 -ffreestanding -fstack-protector -DCONFIG_AS_CFI=1 -DCONFIG_AS_CFI_SIGNAL_FRAME=1 -DCONFIG_AS_CFI_SECTIONS=1 -pipe -Wno-sign-compare -fno-asynchronous-unwind-tables -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -Wframe-larger-than=1024 -fno-omit-frame-pointer -fno-optimize-sibling-calls -g -pg -Wdeclaration-after-statement -Wno-pointer-sign -fno-strict-overflow -fconserve-stack -DCC_HAVE_ASM_GOTO  -D"KBUILD_STR(s)=#s" -D"KBUILD_BASENAME=KBUILD_STR(hello.mod)"  -D"KBUILD_MODNAME=KBUILD_STR(hello)" -DMODULE  -c -o /opt/test_driver/my_driver/hello/hello.mod.o /opt/test_driver/my_driver/hello/hello.mod.c
  ld -r -m elf_i386 -T /opt/linux-source-2.6.38/scripts/module-common.lds --build-id  -o /opt/test_driver/my_driver/hello/hello.ko /opt/test_driver/my_driver/hello/hello.o /opt/test_driver/my_driver/hello/hello.mod.o
make[1]: Leaving directory `/opt/linux-source-2.6.38'

看到了嗎,修改成V=1,就秀出更詳細的過程了。
此時,該資料夾下就會多一個hello.ko的模組,就可以進行載入了。

# ls
hello.c   hello.mod.c  hello.o   modules.order   README
hello.ko  hello.mod.o  Makefile  Module.symvers

# insmod hello.ko
Hello world init.
看到了,這就成功的載入了hello world的模組。

也可以從這邊看到。
# lsmod | grep hello
hello                  12448  0

卸載模組也很簡單,只需下模組的名稱即可。
# rmmod hello
Hello world exit.
看到了嗎,成功的卸載了。

如果要做清除動作的話,就執行clean即可。
# ls
hello.c   hello.mod.c  hello.o   modules.order   README
hello.ko  hello.mod.o  Makefile  Module.symvers

# make clean
make -C "/opt/linux-source-2.6.38" M=/opt/test_driver/my_driver/hello clean
make[1]: Entering directory `/opt/linux-source-2.6.38'
  CLEAN   /opt/test_driver/my_driver/hello/.tmp_versions
  CLEAN   /opt/test_driver/my_driver/hello/Module.symvers
make[1]: Leaving directory `/opt/linux-source-2.6.38'

# ls
hello.c  Makefile  README
如此,就成功的清除了。


註記及聲明:
本教學,是參考Linux Device Driver Programming驅動程式設計由平田豐著的這本書。

1 則留言:

  1. 感謝格主的整理!
    不過在3.6kernel上測試的結果,
    exit裡面的printfk如果沒有結尾\n字元的話,
    在kernel log的呈現會不即時、延後一段時間才出現。

    回覆刪除