1. 程式人生 > >Linux裝置驅動第一天學習筆記(如何將系統在開發板上執行起來、驅動開發基本步驟)

Linux裝置驅動第一天學習筆記(如何將系統在開發板上執行起來、驅動開發基本步驟)

如何將系統在開發板上執行起來?
4.0 交叉編譯器的獲取?廠家提供 網上下載(廠家確認)
4.1 uboot進行操作?
1,解壓廠家原始碼
2,進入原始碼
3,make distclean 徹底刪除原始碼的目標、臨時檔案
4,make xxx_config 針對某個CPU和開發板進行配置
5,make編譯
6,結果生成u-boot.bin
7,利用廠家提供的燒寫方法進行燒寫
8,uboot原始碼/include/configs/xxx.h,硬體相關的標頭檔案資訊,也是移植的重點關注的檔案,瞭解當有uboot當前的支援的硬體資訊。

4.2 kernel進行操作

1,解壓廠家原始碼
2,進入原始碼
3,make distclean 徹底刪除原始碼的目標、臨時檔案,獲取最乾淨的原始碼,不要讓上一次的編譯結果影響到這一次編譯
4,make xxx_defconfig 針對某個CPU和開發板進行配置, xxx_defconfig位於arch/arm/configs/目錄下面找同名的配置檔案.
5,make menuconfig 做三個檢查:
檢查當前核心是否支援當前CPU架構;
檢查當前核心是否支援當前處理器;
The System type(處理器) -> ….
檢查當前核心是否支援當前的開發板;
Board selection(開發板)
6,make zImage/make uImage
7,編譯結果;arch/arm/boot/zImage(uImage)
8,平臺程式碼檔案 arch/arm/mach-處理器名/mach-開發板名.c
多多看這個目錄下對應平臺下面的檔案,這裡的檔案很多與硬體相關;

4.3 掛接rootfs(根檔案系統)

方法一:可以使用廠家提供(廠家的提供的根檔案系統非常龐大,裡面可能有很多我們用不到的服務);
方法二:鼓勵自己製作rootfs,利用busybox;

系統啟動時間:uboot時間+核心啟動時間+掛接根檔案系統時間;

4.4 設定系統啟動的引數;
最關鍵的引數:
bootcmd : 用於載入和引導核心
bootargs:用於給核心傳遞引數,指示核心將來掛接根檔案系統。

給核心傳遞的方法?切記
方法一:利用uboot的bootargs ,
方法一:如果有的boardloader沒有bootargs,可以核心自身傳遞引數(在原始碼裡面);
在原始碼中執行:
make menuconfig
Boot options ->
(console = xxx)Default kernel command string //核心自己給自己傳遞引數,把游標移動到這個位置,按回車進行修改,例如利用NFS網路啟動:root = /dev/nfs nfsroot = 192.168.1.5 儲存即可
[] Alwasy use the default dernel commad string //如果選擇為*,uboot的bootargs無效,核心自己傳遞有效;如果不選擇,則相反;

4.5 NFS網路檔案系統啟動的注意事項
1,ubuntu系統必須搭建好NFS網路服務
vim /etc/exports
sudo /etc/init.d/nfs-kernel-server restart
2,配置核心支援NFS網路檔案系統
make menuconfig
File system —>
[*] Network File systems —>
[*] Root file system on NFS //必須選中,否則無法掛接NFS網路檔案系統

Linux裝置驅動開發相關內容:

ARM&Linux工作模式?
SVC(管理)模式、USR模式;
USR模式切換到SVC模式?通過軟體中斷切換;
當代碼在USR模式執行,軟體就對應在使用者空間執行;
當代碼在SVC模式執行,軟體就對應在核心空間執行;

使用者空間與核心空間:
使用者空間:
包含的軟體:應用程式(軟體)、C庫、自己封裝製作的動態庫
CPU的工作模式:USR
虛擬地址空間範圍:0x000,0000~ 0XBFFF,FFFF (3G記憶體)
採用虛擬記憶體技術前提是CPU必須整合MMU
使用者不能訪問核心地址空間,包括程式碼和資料;
使用者不能直接操作硬體資源;
要和核心空間通訊必須採用系統呼叫
要和核心空間進行通訊,其實就是CPU工作模式的切換
USR模式與SVC模式的切換採用軟中斷
核心空間:
包含的軟體:程序管理、記憶體管理、裝置驅動、檔案系統、TCP/IP網路協議棧
CPU的工作模式:SVC
虛擬地址空間範圍為:0xC000,0000 ~ 0xFFF,FFFF(核心1G的虛擬地址空間對於所有程序共享)
能夠直接訪問硬體資源

總結:使用者空間與核心空間劃分的目的:實現作業系統的安全保護
使用者空間與核心空間劃分的依據本質要依賴ARM工作模式
使用者空間與核心空間進行資料通訊必須利用系統呼叫

使用者 軟體程式設計格式

#include<stdio.h> //標準C的標頭檔案
int main(int argc,char *argv[])//程式入口
{
   printf("hello word! \n");
   return 0;//程式出口
}

核心空間 軟體程式設計格式

#include <linux/module.h>
#include <linux/init.h>//標頭檔案位於核心原始碼

static int fifth_drv_init(void){
    printk("hello word,%s \n!",__func__);//其中k代表kernel
    return 0;
}
static void fifth_drv_exit(void)
{
}
module_init(fifth_drv_init);//module_init 入
module_exit(fifth_drv_exit);//module_exit 出

入口:通過module_init修飾的函式
出口:通過module_exit修飾的函式
module_init的定義也位於核心原始碼;

實驗:

1,mkdir /opt/drivers/day01/1.0  -p     //  -p, --parents     需要時建立上層目錄,如目錄早已存在則不當作錯誤
2, cd   /opt/drivers/day01/1.0 
3, vim helloword
4,儲存退出

核心基本程式設計規範:
1,不允許使用標準C的庫和標頭檔案,核心使用的標頭檔案都是位於核心原始碼
2,核心列印函式不是printf,而是使用核心提供的列印函式printk();列印輸出的格式與C一樣
3,核心模組程式碼沒有main函式,取而代之的是module_init巨集指定的函式為核心模組程式碼的入口函式;整個軟體在執行時,首先執行的是入口函式。
入口函式的返回值:成功返回0 失敗返回負值
4,核心模組程式碼對應的出口是module_exit巨集指定的函式;
5,module_init、module_exit的原始碼同樣位於核心原始碼
6,核心驅動程式設計一定要注意記憶體的合法訪問,否則會造成系統崩潰
7,核心驅動程式設計不允許處理float或double
8,程序在使用者空間的棧和核心空間的棧是不一樣的,核心空間分配8K
9,在編譯核心驅動程式時,需要額外編寫Makefile,來關聯原始碼。
10,驅動對應的可執行檔案為ko結尾,相關操作命令
insmod rmmod lsmmod modinfo
11,核心模組資訊 許可宣告必須新增 : MODULE_LICENSE(“GPL”);
12,模組的命令列傳參
本質目的:在載入模組或模組執行期間(解除安裝模組之前)能夠給模組傳遞引數資訊

筆試題:闡述你對static的認識!

核心模組學原始碼編譯問題:
明確:核心模組原始碼編譯必須要結合核心原始碼 /opt/kernel
編譯只需在 /opt/drivers/day01/1.0 新增一個Makefile即可:

obj -m += helloword.o #將helloword.c編譯成對應的二進位制可執行模組檔案helloword.ko
KDIR = /opt/kernel  #指定核心原始碼路徑
all :  #偽目標,這裡沒有依賴
    #make前是tab鍵而非空格
    make -C $(KDIR) SUBDIRS = $(PWD) modules 
    # -C表示到某個指定的目錄下去編譯,類似: cd /opt/kernel; make
    # SUBDIRS:指定一個子目錄,這個子目錄為當前路徑。 其中PWD為系統全域性環境變數,當前目錄;告訴核心原始碼,在這個目錄下面的檔案需要編譯成模組
    #採用模組化編譯,將.c編譯成.ko檔案
    #展開為:make -C /opt/kernel SUBDIRS=/opt/drivers/day01/1.0 modules

clean:
    make -C $(KDIR) SUBDIRS = $(PWD) clean

編譯:
只需執行make命令即可
結果helloworld.c編譯成對應的helloworld.ko檔案
注意:在編譯helloworld.c之前,如果核心原始碼沒有配置、編譯,必須先對其進行配置、編譯

除錯巨集:
FILE,FUNCTION,LINE,DATE,TIME

vim技巧:
如果在同一個檔案裡面,有重複出現的函式或變數,可以ctrl+n自動補全
行復制:shift+v 選中行,然後上下選擇

如何執行使用核心模組可執行檔案helloword.ko?核心空間其實就是我們的zImage;

insmod helloword.ko #載入模組到核心中,一旦載入成功,核心首先執行對應的入口函式,如果入口函式返回0,模組載入成功,模組正式投入執行,如果返回一個負數,模組載入失敗
rmmod helloword  #用於從核心中解除安裝模組,當解除安裝模組時,對應的出口函式被核心執行,一旦解除安裝,核心就無此模組
lsmod  #檢視當前核心有哪些載入的模組
modinfo helloword #檢視模組.ko本身的屬性或特性

實驗步驟:

 1,file helloworld.ko檢視模組的屬性
 1cp helloworld.ko  /opt/rootfs /   //拷貝到開發板的根檔案系統
 2,insmod helloworld.ko
 4,lsmod
 5,rmmod 

可能遇到的問題
問題1:
helloworld: module license ‘unspecified’ taints kernel。
原因:helloworld模組沒有指定具體的許可宣告。
解決方法:新增 MODULE_LICENSE(“GPL”) ;新增許可資訊必須加

問題2:
解除安裝模組時提示以下資訊,無法解除安裝:rmmod chdir(/lib/modules) no such file….
原因:沒有目錄
解決方法:
在開發板中執行:mkdir /lib/modules
如果又提示:rmmod chdir(/lib/modules/2.6.3.5) no such file….
在開發板中執行:mkdir /lib/modules/2.6.3.5

核心模組引數

應用程式的命令列傳參:

int main(int argc,char *argv[]){//argc 表示引數個數,argv 引數資訊
      int a,b,c;
      if(argc<4){
         return  -1;
      }
       //將字元轉為整數
      a = strtoul(argv[1],NULL,0);
      b = strtoul(argv[2],NULL,0);
      c = strtoul(argv[3],NULL,0);
      printf("a = %d,b=%b,c=%c",a,b,c);
}

執行 ./a.out 100 200 300

問:核心模組原始碼對應的ko檔案,當在載入時,能夠像應用程式的命令列傳參一樣也傳遞引數?可以,需要利用核心的模組引數!
核心模組的使用:
目的:能夠在載入模組或模組執行期間(只要模組不解除安裝)能夠給模組傳遞額外的引數資訊

static int irq;//0
static char *pstr;//NULL
/**
* module_param(name,type,perm)
* 功能:指定模組引數,用於載入模組時或載入以後傳遞引數給模組
* name:模組引數的名稱
* type:模組引數的資料型別,不允許傳浮點資料,如果一定要
*       bool inbool  charp short ushort int uint long ulong
* perm:模組引數的訪問許可權。許可權一般用八進位制表示,如 0664 (0表示八進位制)
*       如果perm(許可權)非0,那麼載入模組以後,會在/sys/modules目錄下生成一個跟模組名同名的目錄,在這個目錄的paramter目錄下會有一個跟變數名同名的檔案,通過修改這個檔案可以修改變數的值 
*       如果perm(許可權)為0,則不會生成許可權檔案
**/

module_param(irq,int,0664);//模組引數的宣告,0664可讀可寫
module_param(pstr,charp,0);//模組引數的宣告

/* 
*  如irq的許可權為 0664,則會生成  /sys/modules/helloworld/parameters/變數名,檔案的內容為變數的內容
*  可以通過 "echo 要寫入的內容 > /sys/modules/helloworld/parameters/變數名" 修改變數(會修改檔案內容)
*/ 

static int fifth_drv_init(void){
    printk("init irq = %d,pstr = %s \n",irq,pstr);
    return 0;
}

static void fifth_drv_exit(void)
{
    printk("exit irq = %d,pstr = %s \n",irq,pstr);
}
module_init(fifth_drv_init);//module_init 入
module_exit(fifth_drv_exit);//module_exit 出

將上述儲存退出,然後將檔案拷貝到。。。,實驗步驟:

insmod helloworld.ko
rmmod helloworld
insmod helloworld.ko irq = 100  pstr = tarena  #傳參
cat /sys/modules/helloworld/parameters/irq #檢視irq檔案的內容
echo 255 > /sys/modules/helloworld/parameters/irq #向檔案寫入新值
rmmod helloworld #檢視輸出的值是否發生了變化