Linux驅動程式開發 – Kbuild系統

來源:互聯網
上載者:User

一個簡單的驅動

下面我們來編寫第一個驅動程式,它很簡單,在運行時會輸出‘Hello World’訊息。

// hello.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

static int __init hello_init(void)
{
printk(KERN_ALERT "Hello World!/n");
return 0;
}

static void __exit hello_exit(void)
{
printk(KERN_ALERT "Goodbye World!/n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

這就是一個簡單的驅動程式,它什麼也沒做,僅僅是輸出一些資訊,不過對於我們來說這已經足夠了。儲存這個程式,命名為hello.c。在寫一個Makefile檔案用來編譯它,Makefile和hello.c檔案儲存在同一個目錄下。

##Makefile
ifneq ($(KERNELRELEASE),)
MODULE_NAME = helloworld
$(MODULE_NAME)-objs := hello.o
obj-m := $(MODULE_NAME).o
else
KERNEL_DIR = /lib/modules/`uname -r`/build
MODULEDIR := $(shell pwd)

.PHONY: modules
default: modules

modules:
make -C $(KERNEL_DIR) M=$(MODULEDIR) modules

clean distclean:
rm -f *.o *.mod.c .*.*.cmd *.ko
rm -rf .tmp_versions
endif

編譯並運行這個模組:

//需要root許可權來運行
make

insmod helloworld.ko

rmmod
helloworld.ko

儘管我們對它的一些細節還不夠瞭解,它確實神奇的工作了,這個Hello World資訊輸出到了螢幕終端上(不是VT),或者系統的Kenrel log裡(/var/log/messages),你可以通過運行dmesg來看到這些資訊。

驅動基礎

我們通過分析上面的代碼來瞭解一個驅動程式的基本概念。

  • 標頭檔
就像你寫C程式需要包含C庫的標頭檔那樣,Linux核心編程也需要包含Kernel標頭檔,大多的Linux驅動程式需要包含下面三個標頭檔:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
    • init.h
      定義了驅動的初始化和退出相關的函數,
    • kernel.h
      定義了經常用到的函數原型及宏定義
    • module.h 定義了核心模組相關的函數、變數及宏。
  • 初始化
任何一個驅動都去需要提供一個初始化函數,當驅動載入到核心中時,這個初始化函數就會被自動執行,初始化的函數原型定義如下:

typedef int (*initcall_t)(void);
驅動程式是通過module_init宏來聲明初始化函數的:

static int __init hello_init(void)
{
printk(KERN_ALERT "Hello World!/n");
return 0;
}
module_init(hello_init);
__init
宏告訴編譯器如果這個模組被編譯到核心則把這個函數放到(.init.text)段,這樣當函數初始化完成後這個地區可以被清除掉以節約系統記憶體。
Kenrel啟動時看到的訊息“Freeing unused kernel memory: xxxk freed”同它有關。
初始化函數是有傳回值的,只有在初始化成功是才返回0,否則返回錯誤碼(errno)。
  • 卸載
如果驅動程式編譯成模組(動態載入)模式,那麼它需要一個清理函數。當移除一個核心模組時這個函數被調用執行清理工作。清理函數的函數原型定義為:

typedef void (*exitcall_t)(void);
驅動程式是通過module_exit宏來聲明清理函數的:

static void __exit hello_exit(void)
{
printk(KERN_ALERT "Goodbye World!/n");
}
module_exit(hello_exit);
同__init類似,如果驅動被編譯進核心,則__exit宏會忽略清理函數,因為編譯進核心的模組不需要做清理工作。顯然,__init和__exit對動態載入的模組是無效的。
  • 著作權資訊
Linux核心是按照GPL發布的,同樣Linux的驅動程式也要提供著作權資訊,否則當載入到核心中是系統會給出警告資訊。Hello World例子中的著作權資訊是GPL。

MODULE_LICENSE("GPL");

二、Kbuild與Makefile

從Linux核心2.6開始,Linux核心的編譯採用Kbuild系統,這同過去的編譯系統有很大的不同,尤其對於Linux核心模組的編譯。在
新的系統下,Linux編譯系統會兩次掃描Linux的Makefile:首先編譯系統會讀取Linux核心頂層的Makefile,然後根據讀到的內容
第二次讀取Kbuild的Makefile來編譯Linux核心。

Linux核心Makefile分類

  • Kernel Makefile
Kernel Makefile位於Linux核心原始碼的頂層目錄,也叫 Top
Makefile。它主要用於指定編譯Linux
Kernel目標檔案(vmlinux)和模組(module)。這編譯核心或模組是,這個檔案會被首先讀取,並根據讀到的內容配置編譯環境變數。對於內
核或驅動開發人員來說,這個檔案幾乎不用任何修改。
  • Kbuild Makefile
Kbuild系統使用Kbuild
Makefile來編譯核心或模組。當Kernel Makefile被解析完成後,Kbuild會讀取相關的Kbuild
Makefile進行核心或模組的編譯。Kbuild
Makefile有特定的文法指定哪些編譯進核心中、哪些編譯為模組、及對應的源檔案是什麼等。核心及驅動開發人員需要編寫這個Kbuild
Makefile檔案。
  • ARCH Makefile
ARCH Makefile位於ARCH/$(ARCH)/Makefile,是系統對應平台的Makefile。Kernel Top Makefile會包含這個檔案來指定平台相關資訊。只有平台開發人員會關心這個檔案。


Kbuild Makefile

Kbuild Makefile的檔案名稱不一定是Makefile
,儘管推薦使用Makefile這個名字。大多的Kbuild檔案的名字都是Makefile。為了與其他Makefile檔案相區別,你也可以指定Kbuild Makefile的名字為Kbuild
。而且如果“Makefile”和“Kbuild”檔案同時存在,則Kbuild系統會使用“Kbuild”檔案。

  • 目標定義
Kbuild Makefile的一個最主要功能就是指定編譯什麼,這個功能是通過下面兩個對象指定的obj-?和xxx-objs:
  • obj-?
obj-?指定編譯什麼,怎麼編譯?其中的“?”可能是“y”或“m”,“y”指定把對象編譯進核心中,“m”指定把對象編譯為模組。文法如下;
obj-? = $(target).o
target為編譯對象的名字。如果沒有指定xxx-objs,這編譯這個對象需要的源檔案就是$(target).c或$(target).s。如果指
定了$(target)-objs,則編譯這個對象需要的源檔案由$(target)-objs指定,並且不能有$(target).c
或$(target).s檔案。
  • xxx-objs
xxx-objs指定了編譯對象需要的檔案,一般只有在源檔案是多個時才需要它。只要包含了這兩行,Kbuild Makefile就應該可以工作了。
  • 嵌套編譯
有時一個對象可能嵌入到另一個對象的目錄下,那個如何編譯子目錄下的對象呢?其實很簡單,只要指定obj_?的對象為子目錄的名字就可以了:obj-? = $(sub_target)/其中“?”可以是“y”或“m”,$(sub_target)是子目錄名字。
  • 編譯器選項
儘管在大多數情況下不需要指定編譯器選項,有時我們還是需要指定一些編譯選項的。
  • ccflags-y, asflags-y and ldflags-y
這些編譯選項用於指定cc、as和ld的編譯選項

編譯外部模組

有時候我們需要在核心原始碼數的外面編譯核心模組,編譯的基本命令是:make -C $(KERNEL_DIR) M=`pwd` modules
我們可以把這個命令整合到Makefile裡,這樣我們就可以只輸入“make”命令就可以了。回想上一章的那個Makefile,它把Normal Makefile 和Kbuild Makefile整合到一個檔案中了。為了區別Kbuild Makefile 和Normal Makefile,這樣我們改寫Makefile為如下形式,並且添加Kbuild Makefile - “Kbuild”。

##Makefile
ifneq ($(KERNELRELEASE),)
include "Kbuild"
else
KERNEL_DIR = /lib/modules/`uname -r`/build
MODULEDIR := $(shell pwd)

.PHONY: modules
default: modules

modules:
make -C $(KERNEL_DIR) M=$(MODULEDIR) modules

clean distclean:
rm -f *.o *.mod.c .*.*.cmd *.ko
rm -rf .tmp_versions
endif

 

## Kbuild
MODULE_NAME = helloworld
$(MODULE_NAME)-objs := hello.o
obj-m := $(MODULE_NAME).o


一般不需要在Makefile裡包含如下代碼,這樣寫完全是為了相容老版本的Kbuild系統。KERNELRELEASE變數在Kernel Makefile裡定義的,因此只有在第二次由Kbuild讀取這個Makefile檔案時才會解析到Kbuild的內容。

ifneq ($(KERNELRELEASE),)
include "Kbuild"
else
...
endif


外部標頭檔

有時需要串連核心原始碼外部的系統標頭檔,但Kbuild系統預設的系統標頭檔都在核心原始碼內部,如何使用外部的標頭檔呢?這個可以藉助於Kbuild系統的特殊規則:

  • EXTRA_CFLAGS
EXTRA_CFLAGS可以給Kbuild系統添加外部系統標頭檔,
EXTRA_CFLAGS += $(ext_include_path)
一般外部標頭檔可能位於外部模組源檔案的目錄內,如何指定呢?這可以藉助$(src)或$(obj)
  • $(src)/$(obj)
$(src)是一個相對路徑,它就是Makefile/Kbuild檔案所在的路徑。同樣$(obj)就是編譯目標儲存的路徑,預設就是原始碼所在路徑。


因此,我們修改Kbuild檔案添加 EXTRA_CFLAGS 來包含外部標頭檔儘管在這個驅動裡沒有引用外部系統標頭檔:

## Kbuild
MODULE_NAME = helloworld
$(MODULE_NAME)-objs := hello.o
EXTRA_CFLAGS := -I$(src)/include
obj-m := $(MODULE_NAME).o

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.