驅動程序編譯
A. 如何編譯wince6.0流驅動
最近開始嘗試寫WinCE6.0的驅動,當然從最簡單的流驅動開始,選擇了GPIO的驅動進行實驗。本文參考了網上有很多流驅動的開發資料,但在開發的過程中也發現了一些細節問題,網路上並沒有給出詳細的解答,所以在這里記錄下來,並對流驅動開發中的一些問題做了總結。 流驅動的開發有兩種方法:添加驅動到BSP和藉助驅動調試助手。 第一種,添加驅動到BSP。修改BSP,將驅動加入到BSP當中,再選擇該BSP當做OS下載到目標板上。下面以6410下實現GPIO為例,說明詳細的步驟: 首先,在\WINCE600\PLATFORM\SMDK6410\SRC\DRIVES目錄下面創建文件夾,命名為GPIO,並在GPIO文件夾下面創建源代碼文件,命名為gpio.c。同時在DRIVES目錄下修改dirs文件,在dirs文件最後添加新建的目錄名GPIO。在gpio.c文件中實現的流式介面函數如下(這些介面函數的參數介紹見博文最後):BOOL WINAPI DllEntry(HANDLE hInstDll, DWORD dwReason, LPVOID lpvReserved)DWORD GPI_Init(LPCTSTR PContext, LPCVOID lpvBuscontext)BOOL GPI_Deinit(DWORD hDeviceContext)DWORD GPI_Open(DWORD hDeviceContext, DWORD AccessCode, DWORD shareMode)BOOL GPI_Close(DWORD hOpenContext)DWORD GPI_Read(DWORD hOpenContext, LPVOID pBuff, DWORD Count)DWORD GPI_Write(DWORD hOpenContext, LPVOID pBuff, DWORD Count)DWORD GPI_Seek(DWORD hOpenContext, long Amount, WORD Type)void GPI_PowerUp(DWORD hDeviceContext)void GPI_PowerDown(DWORD hDeviceContext)BOOL GPI_IOControl( DWORD hOpenContext, DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwactualOut ) 其次,在GPIO文件夾下面創建gpio.def文件,定義需要輸出的函數,這些函數能夠被其它代碼用動態載入的方法調用。具體內容如下,有的介紹裡面導出的函數中還有EntryDll,其實沒有必要,它是一個入口函數而已,主要導出那些會被調用的就可以了。LIBRARY GPIOEXPORTS GPI_Init GPI_Deinit GPI_Open GPI_Close GPI_Read GPI_Write GPI_Seek GPI_PowerDown GPI_PowerUpGPI_IOControl再次,在GPIO文件夾下面創建makefile文件,具體內容如下,這一步對於不同的流驅動基本不變。!INCLUDE $(_MAKEENVROOT)\makefile.def 接下來,在GPIO文件夾下面創建sources文件,具體內容如下,各個宏的意義不做詳細介紹,也可以添加其他的一些宏,根據具體情況定。WINCEOEM=1TARGETNAME=GPIOTARGETTYPE=DYNLINKRELEASETYPE=PLATFORMDEFFILE=gpio.defDLLENTRY=DllEntryTARGETLIBS= \ $(_SYSGENSDKROOT)\lib\$(_CPUINDPATH)\coredll.libSOURCES=gpio.c第五步,修改注冊表信息了,打開WINCE600\PLATFORM\SMDK6410\FILES\Platform.reg 文件,添加以下內容:[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\GPIO]"Prefix"="GPI""Dll"="GPIO.Dll""Index"=dword:1"Order"=dword:1這里要注意添加的時候不要添加到帶有條件編譯的語句當中去,否則還需要設置編譯條件,比較麻煩。最後,修改Platform.bib 文件,將GPIO驅動載入到NK當中。打開WINCE600\PLATFORM\SMDK6410\FILES\Platform.bib 文件,添加以下內容,GPIO.dll $(_FLATRELEASEDIR)\GPIO.dll NK SH到這里基本上准備工作就已經完成了,接下來編譯,下載到目標板上。之後可以用調試工具查看驅動是否已經成功加入到注冊表中,還有驅動是否被OS成功載入了。上述正確後,就可以編寫上層測試代碼進行驗證了。 上述的步驟基本和網路的介紹一致,在調試過程中,遇到了以下兩個問題:1、驅動信息已經加入到注冊表中,但是NK總是沒有載入成功GPIO.dll驅動?解決:出現這種情況,有可能是一下原因造成的:(1)沒有實現上面提到的全部的流驅動介面函數,只是實現了其中的一部分,只要補齊所有的驅動介面函數就可以了;(2)在修改Platform.bib文件時,在文件末尾添加的內容。應該在宏FILES前面添加內容,否則就會無法載入驅動了。也有可能是其他原因,本人暫時沒有遇到。2、驅動載入後立馬又被卸載?解決:本人在調試過程中,通過串口輸出信息,發現驅動GPIO被載入後立馬又被卸載了,並且調用了GPI_Init函數,後來發現時由GPI_Init的返回值引起的。GPI_Init函數是驅動成功載入後調用的第一個函數,設備管理器通過調用GPI_Init初始化硬體,分配自己的內存空間,並將此內存塊的地址以一個DWORD值返回給上層。如果返回0,說明初始化失敗,之前分配的系統資源將全部釋放,也就是驅動會被卸載。在簡單的輸出信息的流驅動中,最好將所有的流介面都設置為返回成功。 快速編譯技巧:上面的方法要求每次都重新編譯內核,很耗費時間,這里收集了網路上提供的修改驅動後快速編譯的方法,下面的步驟建立在已經進行過一次上面的操作了,否則必須先修改注冊表和.bib文件:(1)在VS2005下Build菜單選擇「Open Release Directory in Build Window」,進入到命令行模式,在命令行中進入到你的驅動目錄,執行build命令就OK了。其實也可以直接在右側「Solution Explorer」中找到你的驅動目錄,右鍵選擇Build就可以了。(2)在VS2005下Build菜單選擇「Make Run-Time Image」就可以產生NK文件了。第二種,使用串口助手調試,這里給出原創的連接we-hjb的BLOG。該方法將會比第一種方法效率高,不需要每次都編譯NK,然後下載鏡像到目標板,但存在適用范圍的問題,即該方法並不是適用於任何的驅動。驅動調試助手,是用來動態管理流驅動。本地驅動和USB驅動不再它的控制范圍之內,各位在使用時注意這一點。 補充流介面函數介紹(轉自網路):DllEntry(HINSTANCE DllInstance, INT Reason, LPVOID Reserved )這個函數是動態鏈接庫的入口,每個動態鏈接庫都需要輸出這個函數,它只在動態庫被載入和卸載時被調用,也就是設備管理器調用LoadLibrary而引起它被裝入內存和調用UnloadLibrary將其從內存釋放時被調用,因而它是每個動態鏈接庫最早被調用的函數,一般用它做一些全局變數的初始化。
參數:
DllInstance:DLL的句柄,與一個EXE文件的句柄功能類似,一般可以通過它在得到DLL中的一些資源,例如對話框,除此之外一般沒什麼用處。
Reason:一般我們只關心兩個值:DLL_PROCESS_ATTACH與DLL_PROCESS_DETACH,Reason等於前者是動態庫被載入,等於後者是動態庫被釋放。
所以,我們可以在Reason等於前者是初始化一些資源,等於後者時將其釋放。
DWORD XXX_Init(LPCTSTR pContext,LPCVOID lpvBusContext);它是驅動程序的動態庫被成功裝載以後第一個被調用的函數。其調用時間僅次與DllEntry,而且,當一個庫用來生成多於一個的驅動程序實例時僅調用一次DllEntry,而xxx_Init會被調用多次。驅動程序應當在這個函數中初始化硬體,如果初始化成功,就分配一個自已的內存空間(通常用結構體表示),將自已的狀態保存起來,並且將此內存塊的地址做為一個DWORD值返回給上層。設備管理器就會用在調用XXX_Open時將此句柄傳回,我們就能訪問自已的狀態。如果初始化失敗,則返回0以通知這個驅動程序沒有成功載入,先前所分配的系統資源應該全部釋放,此程序的生命即告終至。當這個函數成功返回,設備管理器對這個程序就不做進一步處理,除非它設置了更多的特性。至此一個各為XXX的設備就已經載入成功,當用戶程序調用CreateFile來打開這個設備時,設備管理器就會調XXX_Open函數。參數:pContext:系統傳入的注冊表鍵,通過它可以講到我們在注冊表中設置的配置信息。lpvBusContext:一般不用。實際上,很多程序中將這個函數寫成了DWORD XXX_Init(DWORD pContext ),我們只需要將pContext轉化成LPCTSTR即可。DWORD XXX_Open(DWORD hDeviceContext,DWORD dwAccess, DWORD dwShareMode);當用戶程序調用CreateFile打開這個設備時,設備管理器就會調用此驅動程序的XXX_Open函數。參數:hDeviceContext XXX_Init 返回給上層的值,也就是我們在XXX_Init中分配的用來記錄驅動程序信息的那個結構體的指針,我們可以在這個函數中直接將其轉化成所定義的結構,從而獲取驅動程序的信息。dwAccess 上層所要求的訪問方式,可以是讀或者寫,或者是0,即不讀也不寫。dwShareMode 上層程序所請求的共享模式,可以是共享讀、共享寫這兩個值的邏輯或,或者是0,即獨占式訪問。系統層對設備文件的存取許可權及共享方法已經做了處理,所以在驅動程序中對這兩個參數一般可以不用理會。這個函數一般不用做太多處理,可以直接返回hDeviceContext表示成功,對於一個不支持多個用戶的硬體,在設備已經打開後,應該總是返回0以至失敗,則CreateFile調用不成功。DWORD XXX_Close( DWORD hDeviceContext); 當用戶程序調用CloseHandle關閉這個設備句柄時,這個函數就會被設備管理器調用。參數:hDeviceContext 為XXX_Open返回給上層的那個值。這個函數應該做與XXX_Open相反的事情,具體包括:釋放XXX_Open分配的內存,將驅動程序被打開的記數減少等。DWORD XXX_Deinit(DWORD hDeviceContext);這個函數在設備被卸載時被調用,它應該實現與XXX_Init相反的操作,主要為釋放前者佔用的所有系統資源。參數:hDeviceContext XXX_Init函數返回給上層的那個句柄u void XXX_PowerUp( DWORD hDeviceContext );void XXX_PowerDown(DWORD hDeviceContext );正如其名稱中體現的那樣,這兩個函數在系統PowerUp與PowerDown時被調用,這兩個函數中不能使用任何可能引起線程切換的函數,否則會引起系統死機。所以,在這兩個函數中,實際上幾乎是什麼做不了,一般在PowerDown時做一個標志,讓驅動程序知道自已曾經被Power Down過。在Power Down/On的過程中硬體可能會掉電,所以,盡管Power On以後,原來的IO操作仍然會從接著執行,但可能會失敗。這時,當我們發現一次IO操作失敗是因為程序曾經進入過Power Down狀態,就重新初始化一次硬體,再做同樣的IO操作。BOOL XXX_IOControl(DWORD hDeviceContext,DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut );幾乎可以說一個驅動程序的所有功能都可以在這個函數中實現。對於一類CE自身已經支持的設備,它們已經被定義了一套IO操作定,我們只需按照各類設備已經定義的內容去實現所有的IO操作。但當我們實現一個自定義的設備時,我們就可以隨心所欲定義我們自已的IO操作。參數:hDeviceContext XXX_Open返回給上層的那個句柄,即我們自已定義的,用來存放程序所有信息的一個結構。dwCode IO操作碼,如果是CE已經支持的設備類,就用它已經定義好碼值,否則就可以自已定義。pBufIn 傳入的Buffer,每個IO操作碼都會定義自已的Buffer結構dwLenIn pBufIn以位元組記的大小 pBufOut,dwLenOut分別為傳出的Buffer,及其以位元組記的大小pdwActualOut 驅動程序實際在pBufOut中填入的數據以位元組記的大小其中,前兩個參數是必須的,其它的任何一個都有可能是NULL或0。所以,當給pdwActualOut賦值時應該先判斷它是否為一個有效的地址。本文出自 「飛雪待劍」 博客
B. 如何編譯一個linux下的驅動模塊
linux下編譯運行驅動
嵌入式linux下設備驅動的運行和linux x86 pc下運行設備驅動是類似的,由於手頭沒有嵌入式linux設備,先在vmware上的linux上學習驅動開發。
按照如下方法就可以成功編譯出hello world模塊驅動。
1、首先確定本機linux版本
怎麼查看Linux的內核kernel版本?
'uname'是Linux/unix系統中用來查看系統信息的命令,適用於所有Linux發行版。配合使用'uname'參數可以查看當前伺服器內核運行的各個狀態。
#uname -a
Linux whh 3.5.0-19-generic #30-Ubuntu SMPTue Nov 13 17:49:53 UTC 2012 i686 i686 i686 GNU/Linux
只列印內核版本,以及主要和次要版本:
#uname -r
3.5.0-19-generic
要列印系統的體系架構類型,即的機器是32位還是64位,使用:
#uname -p
i686
/proc/version 文件也包含系統內核信息:
# cat /proc/version
Linux version 3.5.0-19-generic(buildd@aatxe) (gcc version 4.7.2 (Ubuntu/Linaro 4.7.2-2ubuntu1) ) #30-UbuntuSMP Tue Nov 13 17:49:53 UTC 2012
發現自己的機器linux版本是:3.5.0-19-generic
2、下載機器內核對應linux源碼
到下面網站可以下載各個版本linux源碼https://www.kernel.org/
如我的機器3.5.0版本源碼下載地址為:https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.5.tar.bz2
下載完後,找一個路徑解壓,如我解壓到/linux-3.5/
然後很重要的一步是:執行命令uname -r,可以看到Ubuntu的版本信息是3.5.0-19-generic
。進入linux源碼目錄,編輯Makefile,將EXTRAVERSION = 修改為EXTRAVERSION= -19-generic。
這些都是要配置源碼的版本號與系統版本號,如果源碼版本號和系統版本號不一致,在載入模塊的時候會出現如下錯誤:insmod: error inserting 'hello.ko': -1 Invalid mole format。
原因很明確:編譯時用的hello.ko的kenerl 不是我的pc的kenerl版本。
執行命令cp /boot/config-3.5.0-19-generic ./config,覆蓋原有配置文件。
進入linux源碼目錄,執行make menuconfig配置內核,執行make編譯內核。
3、寫一個最簡單的linux驅動代碼hello.c
/*======================================================================
Asimple kernel mole: "hello world"
======================================================================*/
#include <linux/init.h>
#include <linux/mole.h>
MODULE_LICENSE("zeroboundaryBSD/GPL");
static int hello_init(void)
{
printk(KERN_INFO"Hello World enter\n");
return0;
}
static void hello_exit(void)
{
printk(KERN_INFO"Hello World exit\n ");
}
mole_init(hello_init);
mole_exit(hello_exit);
MODULE_AUTHOR("zeroboundary");
MODULE_DESCRIPTION("A simple HelloWorld Mole");
MODULE_ALIAS("a simplestmole");
4、寫一個Makefile對源碼進行編譯
KERN_DIR = /linux-3.5
all:
make-C $(KERN_DIR) M=`pwd` moles
clean:
make-C $(KERN_DIR) M=`pwd` clean
obj-m += hello.o
5、模塊載入卸載測試
insmod hello.ko
rmmod hello.ko
然後dmesg|tail就可以看見結果了
最後,再次編譯驅動程序hello.c得到hello.ko。執行insmod ./hello.ko,即可正確insert模塊。
使用insmod hello.ko 將該Mole加入內核中。在這里需要注意的是要用 su 命令切換到root用戶,否則會顯示如下的錯誤:insmod: error inserting 'hello.ko': -1 Operation not permitted
內核模塊版本信息的命令為modinfo hello.ko
通過lsmod命令可以查看驅動是否成功載入到內核中
通過insmod命令載入剛編譯成功的time.ko模塊後,似乎系統沒有反應,也沒看到列印信息。而事實上,內核模塊的列印信息一般不會列印在終端上。驅動的列印都在內核日誌中,我們可以使用dmesg命令查看內核日誌信息。dmesg|tail
可能還會遇到這種問題insmod: error inserting 'hello.ko': -1 Invalid mole format
用dmesg|tail查看內核日誌詳細錯誤
disagrees about version of symbolmole_layout,詳細看這里。
http://www.ibm.com/developerworks/cn/linux/l-cn-kernelmoles/index.html
在X86上我的辦法是:
make -C/usr/src/linux-headers-3.5.0-19-generic SUBDIRS=$PWD moles
C. 驅動程序編譯為啥出錯
文件不存在或者目錄不對,還有種就是沒有寫入訪問讀取許可權,所以導致編譯出錯
D. 如何編譯驅動程序
驅動的編譯和上層應用程序的編譯完全不同,作為初學者應該先了解一下,即使你還不懂得怎麼寫驅動程序。
首先安裝DDK,然後隨便找一個例子來測試。在菜單中找到BUILD環境菜單執行,不同的系統要使用不同的BUILD環境。會打開一個DOS窗口,這時CD到那個例子程序,輸入 build –cZ回車就可以了。 驅動程序都是用一個由DDK提供的叫build.exe的工具編譯的。此程序以一個名為SOURCES的文件作為輸入,該文件中包含目標可執行文件的名稱、類型和要創建的可執行文件的路徑,注意這個文件沒有後綴名。
SOURCES的文件格式:
TARGETNAME=drivername ,
- 本參數用於指定生成的設備驅動程序名稱(不需後綴名),所產生的文件
- 為drivername.sys.
TARGETPATH=./lib
- 本參數用於指定生成的設備驅動程序所存放的路徑. 一般採用./lib.
TARGETTYPE=DRIVER
- build能夠生成許多不同的目標對象,設備驅動程序一般選用 DRIVER.
INCLUDES=path1;path2;...
- 本參數是可選的, 用於指定其他的#include文件的搜索路徑.
TARGETLIBS=lib1;lib2;...
- 本參數是可選的, 用於指定其他的lib庫文件的搜索路徑.
SOURCES=file1.c file2.c ...
- 本參數用於指定需被編譯的全部源文件名稱, 後綴名不能省略,文件名之間用空格分開.
SOURCES文件是必需的,如果沒有它則表示沒有任何源文件需要編譯。
如果要換行可以用 『/』 符號,表示對上一行的繼續。
也可以創建DIRS文件,DIRS文件用於指定在當前目錄下必須創建的子目錄。
DIRS文件格式:
DIRS文件的內容由一系列用空格分開的目錄名組成
DIRS = /
subdir1 /
subdir2 /
subdir3
DIRS文件是可選的。
有的時候,會提示找不到依賴的文件(.h,.lib 之類),其實設置好 source 文件的
INCLUDES和TARGETLIBS就可以,我第一次編譯時就碰到這個問題,和VC環境區別較大,但習慣就好。
E. 如何使用ubuntu來編譯驅動
工具/原料
Ubuntu12.04操作系統和測試驅動程序(beep_arv.c)
方法/步驟
在介紹2種方法前,必須知道的知識點:
1.關聯文件Makefile:
Makefile:分布在Linux內核源代碼中的Makefile用於定義Linux內核的編譯規則;
2.管理文件Kconfig:
給用戶提供配置選擇的功能;
配置工具:
1)包括配置命令解析器;
2)配置用戶界面;menuconfig || xconfig;
3)通過腳本語言編寫的;
3.
---tristate 代表三種狀態:1.[ ]不選擇,2.[*]選擇直接編譯進內核,載入驅動到內核里,3.[m]動態載入驅動;
---bool 代表兩種狀態,1.[ ]不選擇,2.[*]選擇;
---"Mini2440 mole sample"這個是在make menuconfig時刷出的提示字元;
---depends on MACH_MINI2440 這個配置選項出現在make menuconfig菜單欄下,在內核配置中必須選中、MACH_MINI2440;
---default m if MACH_MINI2440 這個如果選中了MACH_MINI2440,默認是手
動載入這個驅動;
help:提示幫助信息;
在了解了基本的知識點,便開始進行第一種添加驅動的方法,本次交流是以beep_arv.c蜂鳴驅動程序為基礎的
方法一:
1)進入內核的驅動目錄;
#cp beep_arv.c /XXX/.../linux-XXXl/drivers/char
2)進入Kconfig添加驅動信息;
#cd /XXX/linux-XXX/.../drivers/char
#vim Kconfig
添加基本信息:
config BEEP_MINI2440
tristate "---HAH--- BEEP"
default
help
this is test makefile!
3)進入Makefile添加驅動編譯信息;
#vim Makefile
添加基本信息:
obj-$(CONFIG-BEEP_MINI2440) +=beep_drv.o
方法一結果:
在--Character devices下就能看到配置信息了;
方法二:
1)進入驅動目錄,創建BEED目錄;
#cd /XXX/.../linux-XXX/drivers/char
#mkdir beep
2)將beep_arv.c驅動程序復制到新建目錄下;
#cp beep_arv.c /XXX/.../linux-XXXl/drivers/char/beep
3)創建Makefile和Kconfig文件
#cd char/beep
#mkdir Makefile Kconfig
#chmod 755 Makefile
#chmod 755 Kconfig
4)進入Kconfig添加驅動信息;
#vim Kconfig
添加基本信息:
config BEEP_MINI2440
tristate "---HAH--- BEEP"
default
help
this is test makefile!
5)進入Makefile添加驅動編譯信息;
#vim Makefile
添加基本信息:
obj-$(CONFIG_BEEP_MINI2440) +=beep_drv.o
6)並且要到上一級目錄的Makefile和Kconfig添加驅動信息;
#cd ../
#vim Makefile
#vim Kconfig
F. 如何編寫驅動程序
代碼:
#include<linux/mole.h>
#include<linux/kernel.h>
#include<asm/io.h>
#include<linux/miscdevice.h>
#include<linux/fs.h>
#include<asm/uaccess.h>
//流水燈代碼
#define GPM4CON 0x110002e0
#define GPM4DAT 0x110002e4
static unsigned long*ledcon=NULL;
static unsigned long*leddat=NULL;
//自定義write文件操作(不自定義的話,內核有默認的一套文件操作函數)
static ssize_t test_write(struct file*filp,const char __user*buff,size_t count,loff_t*offset)
{
int value=0;
int ret=0;
ret=_from_user(&value,buff,4);
//底層驅動只定義基本操作動作,不定義功能
if(value==1)
{
*leddat|=0x0f;
*leddat&=0xfe;
}
if(value==2)
{
*leddat|=0x0f;
*leddat&=0xfd;
}
if(value==3)
{
*leddat|=0x0f;
*leddat&=0xfb;
}
if(value==4)
{
*leddat|=0x0f;
*leddat&=0xf7;
}
return 0;
}
//文件操作結構體初始化
static struct file_operations g_tfops={
.owner=THIS_MODULE,
.write=test_write,
};
//雜設備信息結構體初始化
static struct miscdevice g_tmisc={
.minor=MISC_DYNAMIC_MINOR,
.name="test_led",
.fops=&g_tfops,
};
//驅動入口函數雜設備初始化
static int __init test_misc_init(void)
{
//IO地址空間映射到內核的虛擬地址空間
ledcon=ioremap(GPM4CON,4);
leddat=ioremap(GPM4DAT,4);
//初始化led
*ledcon&=0xffff0000;
*ledcon|=0x00001111;
*leddat|=0x0f;
//雜設備注冊函數
misc_register(&g_tmisc);
return 0;
}
//驅動出口函數
static void __exit test_misc_exit(void)
{
//釋放地址映射
iounmap(ledcon);
iounmap(leddat);
}
//指定模塊的出入口函數
mole_init(test_misc_init);
mole_exit(test_misc_exit);
MODULE_LICENSE("GPL");
(6)驅動程序編譯擴展閱讀:
include用法:
#include命令預處理命令的一種,預處理命令可以將別的源代碼內容插入到所指定的位置;可以標識出只有在特定條件下才會被編譯的某一段程序代碼;可以定義類似標識符功能的宏,在編譯時,預處理器會用別的文本取代該宏。
插入頭文件的內容
#include命令告訴預處理器將指定頭文件的內容插入到預處理器命令的相應位置。有兩種方式可以指定插入頭文件:
1、#include<文件名>
2、#include"文件名"
如果需要包含標准庫頭文件或者實現版本所提供的頭文件,應該使用第一種格式。如下例所示:
#include<math.h>//一些數學函數的原型,以及相關的類型和宏
如果需要包含針對程序所開發的源文件,則應該使用第二種格式。
採用#include命令所插入的文件,通常文件擴展名是.h,文件包括函數原型、宏定義和類型定義。只要使用#include命令,這些定義就可被任何源文件使用。如下例所示:
#include"myproject.h"//用在當前項目中的函數原型、類型定義和宏
你可以在#include命令中使用宏。如果使用宏,該宏的取代結果必須確保生成正確的#include命令。例1展示了這樣的#include命令。
【例1】在#include命令中的宏
#ifdef _DEBUG_
#define MY_HEADER"myProject_dbg.h"
#else
#define MY_HEADER"myProject.h"
#endif
#include MY_HEADER
當上述程序代碼進入預處理時,如果_DEBUG_宏已被定義,那麼預處理器會插入myProject_dbg.h的內容;如果還沒定義,則插入myProject.h的內容。
G. 字元型設備驅動如何編譯
字元設備驅動程序框架
1、寫出open、write函數
2、告訴內核
1)、定義一個struct file_operations結構並填充好
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動創建的__this_mole變數 */
.open = first_drv_open,
.write = first_drv_write,
};
2)、把struct file_operations結構體告訴內核
major = register_chrdev(0, "first_drv", &first_drv_fops); // 注冊, 告訴內核
相關參數:第一個,設備號,0自動分配主設備號,否則為主設備號0-255
第二個:設備名
第二個:struct file_operations結構體
4)、register_chrdev由誰調用(入口函數調用)
static int first_drv_init(void)
5)、入口函數須使用內核宏來修飾
mole_init(first_drv_init);
mole_init會定義一個結構體,這個結構體裡面有一個函數指針指向first_drv_init這個函數,當我們載入或安裝一個驅動時,內核會自動找到這個結構體,然後調用裡面的函數指針,這個函數指針指向first_drv_init這個函數,first_drv_init這個函數就是把struct file_operations結構體告訴內核
6)、有入口函數就有出口函數
mole_exit(first_drv_exit);
最後加上協議
MODULE_LICENSE("GPL");
3、mdev根據系統信息自動創建設備節點:
每次寫驅動都要手動創建設備文件過於麻煩,使用設備管理文件系統則方便很多。在2.6的內核以前一直使用的是devfs,但是它存在許多缺陷。它創建了大量的設備文件,其實這些設備更本不存在。而且設備與設備文件的映射具有不確定性,比如U盤即可能對應sda,又可能對應sdb。沒有足夠的主/輔設備號。2.6之後的內核引入了sysfs文件系統,它掛載在/sys上,配合udev使用,可以很好的完成devfs的功能,並彌補了那些缺點。(這里說一下,當今內核已經使用netlink了)。
udev是用戶空間的一個應用程序,在嵌入式中用的是mdev,mdev在busybox中。mdev是udev的精簡版。
首先在busybox中添加支持mdev的選項:
Linux System Utilities --->
[*] mdev
[*] Support /etc/mdev.conf
[*] Support subdirs/symlinks
[*] Support regular expressions substitutions when renaming device
[*] Support command execution at device addition/removal
然後修改/etc/init.d/rcS:
echo /sbin/mdev > /proc/sys/kernel/hotplug
/sbin/mdev -s
執行mdev -s :以『-s』為參數調用位於 /sbin目錄寫的mdev(其實是個鏈接,作用是傳遞參數給/bin目錄下的busybox程序並調用它),mdev掃描 /sys/class 和 /sys/block 中所有的類設備目錄,如果在目錄中含有名為「dev」的文件,且文件中包含的是設備號,則mdev就利用這些信息為這個設備在/dev 下創建設備節點文件。一般只在啟動時才執行一次 「mdev -s」。
熱插拔事件:由於啟動時運行了命 令:echo /sbin/mdev > /proc/sys/kernel/hotplug ,那麼當有熱插拔事件產生時,內核就會調用位於 /sbin目錄的mdev。這時mdev通過環境變數中的 ACTION 和 DEVPATH,來確定此次熱插拔事件的動作以及影響了/sys中的那個目錄。接著會看看這個目錄中是否「dev」的屬性文件,如果有就利用這些信息為 這個設備在/dev 下創建設備節點文件
重新打包文件系統,這樣/sys目錄,/dev目錄就有東西了
下面是create_class的原型:
#define class_create(owner, name) /
({ /
static struct lock_class_key __key; /
__class_create(owner, name, &__key); /
})
extern struct class * __must_check __class_create(struct mole *owner,
const char *name,
struct lock_class_key *key);
class_destroy的原型如下:
extern void class_destroy(struct class *cls);
device_create的原型如下:
extern struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...)
__attribute__((format(printf, 5, 6)));
device_destroy的原型如下:
extern void device_destroy(struct class *cls, dev_t devt);
具體使用如下,可參考後面的實例:
static struct class *firstdrv_class;
static struct class_device *firstdrv_class_dev;
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
下面再來看一下應用程序如何找到這個結構體的
在應用程序中我們使用open打開一個設備:如:open(/dev/xxx, O_RDWR);
xxx有一個屬性,如字元設備為c,後面為讀寫許可權,還有主設備名、次設備名,我們注冊時 通過register_chrdev(0, "first_drv", &first_drv_fops)(有主設備號,設備名,struct file_operations結構體)將first_drv_fops結構體注冊到內核數組chrdev中去的,結構體中有open,write函數,那麼應用程序如何找到它的,事實上是根據打開的這個文件的屬性中的設備類型及主設備號在內核數組chrdev裡面找到我們注冊的first_drv_fops,
實例代碼:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static struct class *firstdrv_class;
static struct class_device *firstdrv_class_dev;
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
static int first_drv_open(struct inode *inode, struct file *file)
{
//printk("first_drv_open\n");
/* 配置GPF4,5,6為輸出 */
*gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
*gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
return 0;
}
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
//printk("first_drv_write\n");
_from_user(&val, buf, count); // _to_user();
if (val == 1)
{
// 點燈
*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
}
else
{
// 滅燈
*gpfdat |= (1<<4) | (1<<5) | (1<<6);
}
return 0;
}
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動創建的__this_mole變數 */
.open = first_drv_open,
.write = first_drv_write,
};
int major;
static int first_drv_init(void)
{
major = register_chrdev(0, "first_drv", &first_drv_fops); // 注冊, 告訴內核
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv"); // 卸載
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
iounmap(gpfcon);
}
mole_init(first_drv_init);
mole_exit(first_drv_exit);
MODULE_LICENSE("GPL");
編譯用Makefile文件
KERN_DIR = /work/system/linux-2.6.22.6
all:
make -C $(KERN_DIR) M=`pwd` moles
clean:
make -C $(KERN_DIR) M=`pwd` moles clean
rm -rf moles.order
obj-m += first_drv.o
測試程序:
#include
#include
#include
#include
/* firstdrvtest on
* firstdrvtest off
*/
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("/dev/xyz", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
if (argc != 2)
{
printf("Usage :\n");
printf("%s \n", argv[0]);
return 0;
}
if (strcmp(argv[1], "on") == 0)
{
val = 1;
}
else
{
val = 0;
}
write(fd, &val, 4);
return 0;
}
H. linux顯卡驅動怎麼編譯進內核
一、 驅動程序編譯進內核的步驟
在 linux 內核中增加程序需要完成以下三項工作:
1. 將編寫的源代碼復制到 Linux 內核源代碼的相應目錄;
2. 在目錄的 Kconfig 文件中增加新源代碼對應項目的編譯配置選項;
3. 在目錄的 Makefile 文件中增加對新源代碼的編譯條目。
bq27501驅動編譯到內核中具體步驟如下:
1. 先將驅動代碼bq27501文件夾復制到 ti-davinci/drivers/ 目錄下。
確定bq27501驅動模塊應在內核源代碼樹中處於何處。
設備驅動程序存放在內核源碼樹根目錄 drivers/ 的子目錄下,在其內部,設備驅動文件進一步按照類別,類型等有序地組織起來。
a. 字元設備存在於 drivers/char/ 目錄下
b. 塊設備存放在 drivers/block/ 目錄下
c. USB 設備則存放在 drivers/usb/ 目錄下。
I. 我可以直接用gcc編譯一個驅動程序嗎
驅動程序肯定不能這么編譯啦,驅動程序要用內核來編譯的。Makefile也很復雜,我也看不太懂,但是Makefile裡面要制定內核所在的目錄以及生產的驅動文件名。這個Makefile可以直接去網上找吧,實在不行我傳給你一個。把Makefile和驅動的
c文件
放在同一個目錄下,make一下就可以產生.ko的驅動文件。移到
開發板
上insmod就可以測試了。但貌似要想在內核里配置上這個模塊。
J. 修改了WINCE自帶的驅動程序後如何編譯
IDE 方式的編譯很簡單,以PB5.0為例,打開定製內核的工程,在左邊的workspaceFileView中找到你已經修改了的目錄,然後單擊右鍵彈出菜單,在菜單中選擇Build and Sysgen Current Project,這樣PB就會編譯指定的目錄中的項目源碼文件,然後執行sysgen命令根據source文件中的內容生成目標文件並復制到當前內核工程目錄下。 命令行方式的編譯需要打開Build OS Open Release Directory,以cd命令進入你已經修改的驅動程序目錄中,然後鍵入build –cfs,然後鍵入 sysgen –p 項目名稱,一般項目名稱為source文件中的TARGETNAME。