Linux驅動基礎開發


來源:http://www.linuxidc.com/Linux/2011-10/44721.htm

Linux 設備驅動概述

目前,Linux軟件工程師大致可分為兩個層次:

 (1)Linux應用軟件工程師(Application Software Engineer):

主要利用C庫函數和Linux API進行應用軟件的編寫;

      從事這方面的開發工作,主要需要學習:符合linux posix標准的API函數及系統調用,linux的多任務編程技巧:多進程、多線程、進程間通信、多任務之間的同步互斥等,嵌入式數據庫的學習,UI編程:QT、miniGUI等。

(2)Linux固件工程師(Firmware Engineer):

主要進行Bootloader、Linux的移植及Linux設備驅動程序的設計工作。

一般而言,固件工程師的要求要高於應用軟件工程師的層次,而其中的Linux設備驅動編程又是Linux程序設計中比較復雜的部分,究其原因,主要包括如下幾個方面:

    1)設備驅動屬於Linux內核的部分,編寫Linux設備驅動需要有一定的Linux操作系統內核基礎;需要了解部分linux內核的工作機制與系統組成

    2)編寫Linux設備驅動需要對硬件的原理有相當的了解,大多數情況下我們是針對一個特定的嵌入式硬件平台編寫驅動的,例如:針對特定的主機平台:可能是三星的2410、2440,也可能是atmel的,或者飛思卡爾的等等

    3)Linux設備驅動中廣泛涉及到多進程並發的同步、互斥等控制,容易出現bug;因為linux本身是一個多任務的工作環境,不可避免的會出現在同一時刻對同一設備發生並發操作

    4)由於屬於內核的一部分,Linux設備驅動的調試也相當復雜。linux設備驅動沒有一個很好的IDE環境進行單步、變量查看等調試輔助工具;linux驅動跟linux內核工作在同一層次,一旦發生問題,很容易造成內核的整體崩潰。

 

本系列文章我們將一步步、深入淺出的介紹linux設備驅動編程中設計的一些問題及學習方法,希望對大家學習linux設備驅動有所幫助。

在任何一個計算機系統中,大至服務器、PC機、小至手機、mp3/mp4播放器,無論是復雜的大型服務器系統還是一個簡單的流水燈單片機系統,都離不開驅動程序的身影,沒有硬件的軟件是空中樓閣,沒有軟件的硬件只是一堆廢鐵,硬件是底層的基礎,是所有軟件得以運行的平台,代碼最終會落實到硬件上的邏輯組合。

    但是硬件與軟件之間存在一個駁論:為了快速、優質的完成軟件功能設計,應用程序工程師不想也不願關心硬件,而硬件工程師也很難有功夫去處理軟件開發中的一些應用。例如軟件工程師在調用printf的時候,不許也不用關心信息到底是通過什么樣的處理,走過哪些通路顯示在該顯示的地方,硬件工程師在寫完了一個4*4鍵盤驅動后,無需也不必管應用程序在獲得鍵值后做哪些處理及操作。

    也就是說軟件工程師需要看到一個沒有硬件的純軟件世界,硬件必須透明的提供給他,誰來實現這一任務?答案是驅動程序,驅動程序從字面解釋就是:“驅使硬件設備行動”。驅動程序直接與硬件打交道,按照硬件設備的具體形式,驅動設備的寄存器,完成設備的輪詢、中斷處理、DMA通信,最終讓通信設備可以收發數據,讓顯示設備能夠顯示文字和畫面,讓音頻設備可以完成聲音的存儲和播放。

    可見,設備驅動程序充當了硬件和軟件之間的樞紐,因此驅動程序的表現形式可能就是一些標准的、事先協定好的API函數,驅動工程師只需要去完成相應函數的填充,應用工程師只需要調用相應的接口完成相應的功能。無論有沒有操作系統,驅動程序都有其存在價值,只是在裸機情況下,工作環境比較簡單、完成的工作較單一,驅動程序完成的功能也就比較簡單,同時接口只要在小范圍內符合統一的標准即可。但是在有操作系統的情況下,此問題就會被放大:硬件來自不同的公司、千變萬化,全世界每天都會有大量的新芯片被生產,大量的電路板被設計出來,如果沒有一個很好的統一標准去規范這一程序,操作系統就會被設計的非常冗余,效率會非常低。

所以無論任何操作系統都會制定一套標准的架構去管理這些驅動程序:linux作為嵌入式操作系統的典范,其驅動架構具有很高的規范性與聚合性,不但把不同的硬件設備分門別類、綜合管理,並且針對不同硬件的共性進行了統一抽象,將其硬件相關性降到最低,大大簡化了驅動程序的編寫,形成了具有其特色的驅動組織架構。

 

下圖反映了應用程序、linux內核、驅動程序、硬件的關系。

linux內核分為5大部分:多任務管理、內存管理、文件系統管理、設備管理、網絡管理;

每一部分都有承上下的作用,對上提供API接口,提供給應用開發工程師使用;

對下通過驅動程序屏蔽不同的硬件構成,完成硬件的具體操作。

Linux 設備驅動基本概念

學習linux設備驅動首先我們必須明確以下幾個概念,為我們接下來學習linux驅動打下堅實的基礎:

  • 應用程序、庫、內核、驅動程序的關系
  • 設備類型
  • 設備文件、主設備號與從設備號
  • 驅動程序與應用程序的區別
  • 用戶態與內核態
  • Linux驅動程序功能

 

一、應用程序、庫、內核、驅動程序的關系

    1)應用程序調用一系列函數庫,通過對文件的操作完成一系列功能:

       應用程序以文件形式訪問各種硬件設備(linux特有的抽象方式,把所有的硬件訪問抽象為對文件的讀寫、設置)

函數庫:

         部分函數無需內核的支持,由庫函數內部通過代碼實現,直接完成功能

         部分函數涉及到硬件操作或內核的支持,由內核完成對應功能,我們稱其為系統調用

    2)內核處理系統調用,根據設備文件類型、主設備號、從設備號(后面會講解),調用設備驅動程序;

    3)設備驅動直接與硬件通信;


 

二、設備類型

硬件是千變萬化的,沒有八千也有一萬了,就像世界上有三種人:男人、女人、女博士一樣,linux做了一個很偉大也很艱難的分類:把所有的硬件設備分為三大類:字符設備、塊設備、網絡設備。

1)字符設備:字符(char)設備是個能夠像字節流(類似文件)一樣被訪問的設備。

    對字符設備發出讀/寫請求時,實際的硬件I/O操作一般緊接着發生;

字符設備驅動程序通常至少要實現open、close、read和write系統調用。

    比如我們常見的lcd、觸摸屏、鍵盤、led、串口等等,就像男人是用來干活的一樣,他們一般對應具體的硬件都是進行出具的采集、處理、傳輸。

2)塊設備:一個塊設備驅動程序主要通過傳輸固定大小的數據(一般為512或1k)來訪問設備。

   塊設備通過buffer cache(內存緩沖區)訪問,可以隨機存取,即:任何塊都可以讀寫,不必考慮它在設備的什么地方。

    塊設備可以通過它們的設備特殊文件訪問,但是更常見的是通過文件系統進行訪問。

    只有一個塊設備可以支持一個安裝的文件系統。

    比如我們常見的電腦硬盤、SD卡、U盤、光盤等,就像女人一樣是用來存儲信息的。

3)網絡接口:任何網絡事務都經過一個網絡接口形成,即一個能夠和其他主機交換數據的設備。

    訪問網絡接口的方法仍然是給它們分配一個唯一的名字(比如eth0),但這個名字在文件系統中不存在對應的節點。

    內核和網絡設備驅動程序間的通信,完全不同於內核和字符以及塊驅動程序之間的通信,內核調用一套和數據包傳輸相關的函數(socket函數)而不是read、write等。

    比如我們常見的網卡設備、藍牙設備,就像女博士一樣,數量稀少但又不可或缺。

    linux中所有的驅動程序最終都能歸到這三種設備中,當然他們之間也沒有非常嚴格的界限,這些都是程序中對他們的划分而已,比如一個sd卡,我們也可以把它封裝成字符設備去操作也是沒有問題的。就像。。。

三、設備文件、主設備號、從設備號

有了設備類型的划分,那么應用程序應該怎樣訪問具體的硬件設備呢?

或者說已經確定他是一個男人了,那么怎么從萬千世界中區分他與他的不同呢?

答案是:姓名,在linux驅動中也就是設備文件名。

那么重名怎么辦?

答案是:身份證號,在linux驅動中也就是設備號(主、從)。

設備文件:

在linux

系統

中有一個約定俗成的說法:“一切皆文件”,

應用程序使用設備文件節點訪問對應設備,

Linux下的各種硬件設備以文件的形式存放於/dev目錄下,可以使用ls /dev 查看

Linux把對硬件的操作全部抽象成對文件的操作

(open,read,write,close,…)

每個設備文件都有其文件屬性(c或者b),使用ls /dev -l 的命令查看,

表明其是字符設備或者塊設備,網絡設備沒有在這個文件夾下,用來明其性別(男人、女人)

主設備號、從設備號

在設備管理中,除了設備類型外,內核還需要一對被稱為主從設備號的參數,才能唯一標識一個設備,類似人的身份證號

主設備號:

用於標識驅動程序,相同的主設備號使用相同的驅動程序,例如:S3C2440 有串口、LCD、觸摸屏三種設備,他們的主設備號各不相同;

從設備號:

    用於標識同一驅動程序的不同硬件

    例:PC的IDE設備,主設備號用於標識該硬盤,從設備號用於標識每個分區,2440有三個串口,每個串口的主設備號相同,從設備號用於區分具體屬於那一個串口。

四、驅動程序與應用程序的區別

應用程序以main開始

驅動程序沒有main,它以一個模塊初始化函數作為入口

應用程序從頭到尾執行一個任務

驅動程序完成初始化之后不再運行,等待系統調用

應用程序可以使用glibc等標准C函數庫

驅動程序不能使用標准C庫

五、用戶態與內核態的區分

驅動程序是內核的一部分,工作在內核態

應用程序工作在用戶態

數據空間訪問問題

無法通過指針直接將二者的數據地址進行傳遞

系統提供一系列函數幫助完成數據空間轉換
get_user
put_user
copy_from_user
copy_to_user

 

六、Linux驅動程序功能

對設備初始化和釋放資源

把數據從內核傳送到硬件和從硬件讀取數據

讀取應用程序傳送給設備文件的數據和回送應用程序請求的數據

檢測和處理設備出現的錯誤(底層協議)

用於區分具體設備的實例

Linux 驅動開發前奏(模塊編程)

一、linux內核模塊簡介

linux內核整體結構非常龐大,其包含的組件也非常多。我們怎么把需要的部分都包含在內核中呢?

     一種辦法是把所有的需要的功能都編譯到內核中。這會導致兩個問題,一是生成的內核會很大,二是如果我們要在現有的內核中新增或刪除功能,不得不重新編譯內核,工作效率會非常的低,同時如果編譯的模塊不是很完善,很有可能會造成內核崩潰。

    linux提供了另一種機制來解決這個問題,這種集中被稱為模塊,可以實現編譯出的內核本身並不含有所有功能,而在這些功能需要被使用的時候,其對應的代碼可以被動態的加載到內核中。

二、模塊特點:

1)模塊本身並不被編譯入內核,從而控制了內核的大小。

    2)模塊一旦被加載,他就和內核中的其他部分完全一樣。

    注意:模塊並不是驅動的必要形式:即:驅動不一定必須是模塊,有些驅動是直接編譯進內核的;同時模塊也不全是驅動,例如我們寫的一些很小的算法可以作為模塊編譯進內核,但它並不是驅動。就像燒餅不一定是圓的,圓的也不都是燒餅一樣。

三、最簡單的模塊分析

1)以下是一個最簡單的模塊例子

  1. #include <linux/init.h>         /* printk() */
  2. #include <linux/module.h>       /* __init __exit */
  3. static int  __init  hello_init(void)      /*模塊加載函數,通過insmod命令加載模塊時,被自動執行*/
  4.   printk(KERN_INFO " Hello World enter\n"); 
  5. return 0; 
  6. static void  __exit  hello_exit(void)    /*模塊卸載函數,當通過rmmod命令卸載時,會被自動執行*/
  7.   printk(KERN_INFO " Hello World exit\n "); 
  8. module_init(hello_init); 
  9. module_exit(hello_exit); 
  10. MODULE_AUTHOR("dengwei");           /*模塊作者,可選*/
  11. MODULE_LICENSE("Dual BSD/GPL");     /*模塊許可證明,描述內核模塊的許可權限,必須*/
  12. MODULE_DESCRIPTION("A simple Hello World Module"); /*模塊說明,可選*/
  13. MODULE_ALIAS("a simplest module");                  /*模塊說明,可選*/<span style="font-family:SimSun;font-size:18px;color:#FF0000;"><strong> 
  14. </strong></span> 

2) 以下是編譯上述模塊所需的編寫的makefile

  1. obj-m :=hello.o                     //目標文件
  2. #module-objs := file1.o file.o      //當模塊有多個文件組成時,添加本句
  3. KDIR :=/usr/src/linux               //內核路徑,根據實際情況換成自己的內核路徑,嵌入式的換成嵌入式,PC機的指定PC機路徑
  4. PWD := $(shell pwd)                 //模塊源文件路徑
  5. all:     
  6.     $(MAKE)  -C  $(KDIR)  SUBDIRS=$(PWD)  modules 
  7.     @rm -rf *.mod.* 
  8.     @rm -rf .*.cmd 
  9.     @rm -rf *.o 
  10.     @rm -rf Module.* 
  11. clean: 
  12.     rm -rf *.ko 

最終會編譯得到:hello.ko文件

使用insmodhello.ko將模塊插入內核,然后使用dmesg即可看到輸出提示信息。

 

常用的幾種模塊操作:

insmod XXX.ko    加載指定模塊

lsmod                      列舉當前系統中的所有模塊

rmmod  XXX         卸載指定模塊(注意沒有.ko后綴)

dmesg                    當打印等級低於默認輸出等級時,采用此命令查看系統日志

3)linux內核模塊的程序結構

1.模塊加載函數:

Linux內核模塊一般以__init標示聲明,典型的模塊加載函數的形式如下:

  1. static int __init myModule_init(void
  2. /* Module init code */
  3.     PRINTK("myModule_init\n"); 
  4. return 0; 
  5. module_init(myModule_init); 

模塊加載函數的名字可以隨便取,但必須以“module_init(函數名)”的形式被指定;

執行insmod命令時被執行,用於初始化模塊所必需資源,比如內存空間、硬件設備等;

它返回整形值,若初始化成功,應返回0,初始化失敗返回負數。

2.模塊卸載函數

典型的模塊卸載函數形式如下:

  1. static void __exit myModule_exit(void
  2. /* Module exit code */
  3.     PRINTK("myModule_exit\n"); 
  4. return
  5. module_exit(myModule_exit); 

模塊卸載函數在模塊卸載的時候執行,不返回任何值,需用”module_exit(函數名)”的形式被指定。

卸載模塊完成與加載函數相反的功能:

   若加載函數注冊了XXX,則卸載函數應當注銷XXX

   若加載函數申請了內存空間,則卸載函數應當釋放相應的內存空間

   若加載函數申請了某些硬件資源(中斷、DMA、I/0端口、I/O內存等),則卸載函數應當釋放相應的硬件資源

   若加載函數開啟了硬件,則卸載函數應當關閉硬件。

 

其中__init 、__exit 為系統提供的兩種宏,表示其所修飾的函數在調用完成后會自動回收內存,即內核認為這種函數只會被執行1次,然后他所占用的資源就會被釋放。

 

3.模塊聲明與描述

在linux內核模塊中,我們可以用MODULE_AUTHOR、MODULE_DESCRIPTION、MODULE_VERSION、MODULE_TABLE、MODULE_ALIA,分別描述模塊的作者、描述、版本、設備表號、別名等。

  1. MODULE_AUTHOR("dengwei"); 
  2. MODULE_LICENSE("Dual BSD/GPL"); 
  3. MODULE_DESCRIPTION("A simple Hello World Module"); 
  4. MODULE_ALIAS("a simplest module"); 

四、有關模塊的其它特性

1)模塊參數:

我們可以利用module_param(參數名、參數類型、參數讀寫屬性) 為模塊定義一個參數,例如:

  1. static char *string_test = “this is a test”; 
  2. static num_test = 1000;   
  3. module_param (num_test,int,S_IRUGO); 
  4. module_param (steing_test,charp,S_ITUGO); 

在裝載模塊時,用戶可以給模塊傳遞參數,形式為:”insmod 模塊名 參數名=參數值”,如果不傳遞,則參數使用默認的參數值

    參數的類型可以是:byte,short,ushort,int,uint,long,ulong,charp,bool;

權限:定義在linux/stat.h中,控制存取權限,S_IRUGO表示所有用戶只讀;

模塊被加載后,在sys/module/下會出現以此模塊命名的目錄,當讀寫權限為零時:表示此參數不存在sysfs文件系統下的文件節點,當讀寫權限不為零時:此模塊的目錄下會存在parameters目錄,包含一系列以參數名命名的文件節點,這些文件節點的權限值就是傳入module_param()的“參數讀/寫權限“,而該文件的內容為參數的值。

    除此之外,模塊也可以擁有參數數組,形式為:”module_param_array(數組名、數組類型、數組長、參數讀寫權限等)”,當不需要保存實際的輸入的數組元素的個數時,可以設置“數組長“為0。

運行insmod時,使用逗號分隔輸入的數組元素。

下面是一個實際的例子,來說明模塊傳參的過程。

  1. #include <linux/module.h>    /*module_init()*/
  2. #include <linux/kernel.h> /* printk() */
  3. #include <linux/init.h>       /* __init __exit */
  4. #define DEBUG   //open debug message
  5. #ifdef DEBUG
  6. #define PRINTK(fmt, arg...)     printk(KERN_WARNING fmt, ##arg)
  7. #else
  8. #define PRINTK(fmt, arg...)     printk(KERN_DEBUG fmt, ##arg)
  9. #endif
  10. static char *string_test="default paramater"; 
  11. static int num_test=1000; 
  12. static int __init hello_init(void
  13.   PRINTK("\nthe  string_test is : %s\n",string_test); 
  14.   PRINTK("the  num_test is : %d\n",num_test); 
  15. return 0; 
  16. static void __exit hello_exit(void
  17.   PRINTK(" input paramater module exit\n "); 
  18. module_init(hello_init); 
  19. module_exit(hello_exit); 
  20. module_param(num_test,int,S_IRUGO); 
  21. module_param(string_test,charp,S_IRUGO); 
  22. MODULE_AUTHOR("dengwei"); 
  23. MODULE_LICENSE("GPL"); 

當執行 insmod hello_param.ko時,執行dmesg 查看內核輸出信息:

  1. Hello World enter 
  2. the test string is: this is a test 
  3. the test num is :1000 

當執行insmod  hello_param.ko num_test=2000 string_test=“edit by dengwei”,執行dmesg查看內核輸出信息:

  1. Hello World enter 
  2. the test string is: edit by dengwei 
  3. the test num is :2000 

2)導出模塊及符號的相互引用

Linux2.6內核的“/proc/kallsyms“文件對應內核符號表,它記錄了符號以及符號所在的內存地址,模塊可以使用下列宏導到內核符號表中。

EXPORT_SYMBOL(符號名);       任意模塊均可

EXPORT_SYMBOL_GPL(符號名);   只使用於包含GPL許可權的模塊

導出的符號可以被其它模塊使用,使用前聲明一下即可。

下面給出一個簡單的例子:將add sub符號導出到內核符號表中,這樣其它的模塊就可以利用其中的函數

  1. #include <linux/module.h>    /*module_init()*/
  2. #include <linux/kernel.h> /* printk() */
  3. #include <linux/init.h>       /* __init __exit */
  4. int add_test(int a ,int b)                                 
  5. {                                 
  6. return a + b;                              
  7. }  
  8. int sub_test(int a,int b)                                 
  9. {                                 
  10. return a - b;                              
  11. }                             
  12. EXPORT_SYMBOL(add_test); 
  13. EXPORT_SYMBOL(sub_test); 
  14. MODULE_AUTHOR("dengwei"); 
  15. MODULE_LICENSE("GPL"); 

執行 cat/proc/kallsyms | grep test 即可找到以下信息,表示模塊確實被加載到內核表中。

  1. f88c9008 r __ksymtab_sub_integar        [export_symb] 
  2. f88c9020 r __kstrtab_sub_integar         [export_symb] 
  3. f88c9018 r __kcrctab_sub_integar         [export_symb] 
  4. f88c9010 r __ksymtab_add_integar        [export_symb] 
  5. f88c902c r __kstrtab_add_integar          [export_symb] 
  6. f88c901c r __kcrctab_add_integar         [export_symb] 
  7. f88c9000 T add_tes                [export_symb] 
  8. f88c9004 T sub_tes                [export_symb] 
  9. 13db98c9 a __crc_sub_integar           [export_symb] 
  10. e1626dee a __crc_add_integar           [export_symb] 

在其它模塊中可以引用此符號

  1. #include <linux/module.h>    /*module_init()*/
  2. #include <linux/kernel.h> /* printk() */
  3. #include <linux/init.h>       /* __init __exit */
  4. #define DEBUG   //open debug message
  5. #ifdef DEBUG
  6. #define PRINTK(fmt, arg...)     printk(KERN_WARNING fmt, ##arg)
  7. #else
  8. #define PRINTK(fmt, arg...)     printk(KERN_DEBUG fmt, ##arg)
  9. #endif
  10. extern int add_test(int a ,int b);  
  11. extern int sub_test(int a,int b);  
  12. static int __init hello_init(void
  13. int a,b; 
  14.   a = add_test(10,20); 
  15.   b = sub_test(30,20); 
  16.   PRINTK("the add test result is %d",a); 
  17.   PRINTK("the sub test result is %d\n",b); 
  18. return 0; 
  19. static void __exit hello_exit(void
  20.   PRINTK(" Hello World exit\n "); 
  21. module_init(hello_init); 
  22. module_exit(hello_exit); 
  23. MODULE_AUTHOR("dengwei"); 
  24. MODULE_LICENSE("GPL"); 
  25. Linux 內核配置機制(make menuconfig、Kconfig、makefile)講解

    前面我們介紹模塊編程的時候介紹了驅動進入內核有兩種方式:模塊和直接編譯進內核,並介紹了模塊的一種編譯方式——在一個獨立的文件夾通過makefile配合內核源碼路徑完成

    那么如何將驅動直接編譯進內核呢?

        在我們實際內核的移植配置過程中經常聽說的內核裁剪又是怎么麽回事呢?

    我們在進行linux內核配置的時候經常會執行make menuconfig這個命令,然后屏幕上會出現以下界面:

    這個界面是怎么生成的呢?

    跟我們經常說的內核配置與與編譯又有什么關系呢?

    下面我們借此來講解一下linux內核的配置機制及其編譯過程。

    一、配置系統的基本結構

    Linux內核的配置系統由三個部分組成,分別是:

    1、Makefile:分布在 Linux 內核源代碼根目錄及各層目錄中,定義 Linux 內核的編譯規則;

        2、配置文件(config.in(2.4內核,2.6內核)):給用戶提供配置選擇的功能;

        3、配置工具:包括配置命令解釋器(對配置腳本中使用的配置命令進行解釋)和配置用戶界面(提供基於字符界面、基於 Ncurses 圖形界面以及基於 Xwindows 圖形界面的用戶配置界面,各自對應於 Make config、Make menuconfig 和 make xconfig)。

    這些配置工具都是使用腳本語言,如 Tcl/TK、Perl 編寫的(也包含一些用 C 編寫的代碼)。本文並不是對配置系統本身進行分析,而是介紹如何使用配置系統。所以,除非是配置系統的維護者,一般的內核開發者無須了解它們的原理,只需要知道如何編寫 Makefile 和配置文件就可以。

    二、makefile menuconfig過程講解

    當我們在執行make menuconfig這個命令時,系統到底幫我們做了哪些工作呢?

    這里面一共涉及到了一下幾個文件我們來一一講解

    Linux內核根目錄下的scripts文件夾

    arch/$ARCH/Kconfig文件、各層目錄下的Kconfig文件

    Linux內核根目錄下的makefile文件、各層目錄下的makefile文件

    Linux內核根目錄下的的.config文件、arm/$ARCH/下的config文件

    Linux內核根目錄下的 include/generated/autoconf.h文件

    1)scripts文件夾存放的是跟make menuconfig配置界面的圖形繪制相關的文件,我們作為使用者無需關心這個文件夾的內容

     

    2)當我們執行make menuconfig命令出現上述藍色配置界面以前,系統幫我們做了以下工作:

        首先系統會讀取arch/$ARCH/目錄下的Kconfig文件生成整個配置界面選項(Kconfig是整個linux配置機制的核心),那么ARCH環境變量的值等於多少呢?

    它是由linux內核根目錄下的makefile文件決定的,在makefile下有此環境變量的定義:

    或者通過 make ARCH=arm menuconfig命令來生成配置界面,默認生成的界面是所有參數都是沒有值的

    比如教務處進行考試,考試科數可能有外語、語文、數學等科,這里相當於我們選擇了arm科可進行考試,系統就會讀取arm/arm/kconfig文件生成配置選項(選擇了arm科的卷子),系統還提供了x86科、milps科等10幾門功課的考試題

    3)假設教務處比較“仁慈”,為了怕某些同學做不錯試題,還給我們准備了一份參考答案(默認配置選項),存放在arch/$ARCH/configs下,對於arm科來說就是arch/arm/configs文件夾:

    此文件夾中有許多選項,系統會讀取哪個呢?內核默認會讀取linux內核根目錄下.config文件作為內核的默認選項(試題的參考答案),我們一般會根據開發板的類型從中選取一個與我們開發板最接近的系列到Linux內核根目錄下(選擇一個最接近的參考答案)

    #cp arch/arm/configs/s3c2410_defconfig .config

    4).config

    假設教務處留了一個心眼,他提供的參考答案並不完全正確(.config文件與我們的板子並不是完全匹配),這時我們可以選擇直接修改.config文件然后執行make menuconfig命令讀取新的選項

        但是一般我們不采取這個方案,我們選擇在配置界面中通過空格、esc、回車選擇某些選項選中或者不選中,最后保存退出的時候,Linux內核會把新的選項(正確的參��答案)更新到.config中,此時我們可以把.config重命名為其它文件保存起來(當你執行make distclean時系統會把.config文件刪除),以后我們再配置內核時就不需要再去arch/arm/configs下考取相應的文件了,省去了重新配置的麻煩,直接將保存的.config文件復制為.config即可.

    5)經過以上兩步,我們可以正確的讀取、配置我們需要的界面了

    那么他們如何跟makefile文件建立編譯關系呢?

    當你保存make menuconfig選項時,系統會除了會自動更新.config外,還會將所有的選項以宏的形式保存在

    Linux內核根目錄下的 include/generated/autoconf.h文件下

    內核中的源代碼就都會包含以上.h文件,跟宏的定義情況進行條件編譯。

    當我們需要對一個文件整體選擇如是否編譯時,還需要修改對應的makefile文件,例如:

        我們選擇是否要編譯s3c2410_ts.c這個文件時,makefile會根據CONFIG_TOUCHSCREEN_S3C2410來決定是編譯此文件,此宏是在Kconfig文件中定義,當我們配置完成后,會出現在.config及autconf中,至此,我們就完成了整個linux內核的編譯過程。

        最后我們會發現,整個linux內核配置過程中,留給用戶的接口其實只有各層Kconfig、makefile文件以及對應的源文件。

        比如我們如果想要給內核增加一個功能,並且通過make menuconfig控制其聲稱過程

        首先需要做的工作是:修改對應目錄下的Kconfig文件,按照Kconfig語法增加對應的選項;

        其次執行make menuconfig選擇編譯進內核或者不編譯進內核,或者編譯為模塊,.config文件和autoconf.h文件會自動生成;

        最后修改對應目錄下的makefile文件完成編譯選項的添加;

        最后的最后執行make zImage命令進行編譯。

    三、具體實例

    下面我們以前面做過的模塊實驗為例,講解如何通過make menuconfig機制將前面單獨編譯的模塊編譯進內核或編譯為模塊

    假設我已經有了這么一個驅動:

    modules.c

    1. #include <linux/module.h>       /*module_init()*/
    2. #include <linux/kernel.h> /* printk() */
    3. #include <linux/init.h>       /* __init __exit */
    4. #define DEBUG   //open debug message
    5. #ifdef DEBUG
    6. #define PRINTK(fmt, arg...)     printk(KERN_WARNING fmt, ##arg)
    7. #else
    8. #define PRINTK(fmt, arg...)     printk(KERN_DEBUG fmt, ##arg)
    9. #endif
    10. /* Module Init & Exit function */
    11. static int __init myModule_init(void
    12. /* Module init code */
    13.     PRINTK("myModule_init\n"); 
    14. return 0; 
    15. static void __exit myModule_exit(void
    16. /* Module exit code */
    17.     PRINTK("myModule_exit\n"); 
    18. return
    19. module_init(myModule_init); 
    20. module_exit(myModule_exit); 
    21. MODULE_AUTHOR("dengwei");                          /*模塊作者,可選*/
    22. MODULE_LICENSE("GPL");                             /*模塊許可證明,描述內核模塊的許可權限,必須*/
    23. MODULE_DESCRIPTION("A simple Hello World Module"); /*模塊說明,可選*/

    Step1:將modules.c拷到drivers/char/目錄下(這個文件夾一般存放常見的字符驅動)

     

    Step2: vi driver/char/Kconfig,在

        config DEVKMEM后添加以下信息

     

    config MODULES
    tristate "modules device support"
    default y
    help
    Say Y here,the modules will be build in kernel.
    Say M here,the modules willbe build to modules.
    Say N here,there will be nothing to be do.

     

    Step3:make menuconfig

    Device driver-character devices

               [*]modules device suppor

     

    Step4:vi driver/char/Makefile,在js-rtc后添加

    obj-$(CONFIG_MODULES)+= modules.o

    CONFIG_MODULES 必須跟上面的Kconfig中保持一致,系統會自動添加CONFIG_前綴

    modules.o必須跟你加入的.c文件名一致

    最后執行:make zImage modules就會被編譯進內核中

    第三步:

    Step3:make menuconfig

    Device driver-character devices

               [M]modules device suppor

    把星號在配置界面通過空格改為M,最后執行make modules,在driver/char/目錄下會生成一個modules.ko文件

    跟我們前面講的單獨編譯模塊效果一樣,也會生成一個模塊,將它考入開發板執行insmod moudles.ko,即可將生成的模塊插入內核使用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM