linux設備驅動(一)

基本概念

什麼是設備驅動

計算機的運行,需要軟件和硬件的合作,沒有軟件的硬件,是一堆廢鐵,沒有硬件的軟件,是空中樓閣,軟件運行在硬件基礎之上。

軟件又分爲系統軟件和應用軟件,系統軟件屏蔽了底層硬件的差異,嚮應用層提供統一的接口,因而應用層的程序員,不需要知道不同網卡、硬盤等硬件之間有什麼差異,只需要調用相同的讀寫函數,即可完成對設備的讀寫操作。這個屏蔽具體硬件差異過程的重任,就落到了設備驅動的頭上。設備驅動程序,僅僅是系統軟件的一部分。

設備驅動是底層硬件和上層應用之間的紐帶,設備驅動最通俗的解釋是:驅使硬件設備行動。設備驅動與底層硬件直接打交道,完成寄存器的讀寫等、中斷處理等等操作。

無操作系統下的驅動

計算機的運轉不一定需要操作系統,目前stm8,stm32等主流單片機都可以直接跑裸機程序,一般認爲只需要完成main函數和中斷服務函數即可(當然IDE也幫我們完成了很多其它的底層工作)。這樣簡單的應用場景,並不需要操作系統提供的多任務調度,內存管理,文件系統等功能。

雖然沒有操作系統,但是並不意味着沒有驅動程序。一般情況下,每一個硬件設備,都對應了一個.c和.h文件,例如EEPROM芯片AT24C04,在程序中肯定會對該芯片多次的進行讀寫操作,爲了方便,肯定會將這些重複使用的代碼實現爲函數,加入到整個工程之中,這些代碼,也可以稱之爲驅動。只不過這些驅動程序的接口直接提供給應用層,應用層沒有跨越任何層次,就直接訪問到了底層硬件。

有操作系統下的驅動

驅動程序屬於內核的一部分,內核是操作系統最核心的部分,它負責管理系統的進程、內存、設備驅動程序、文件和網絡系統,決定着系統的性能和穩定性,因此驅動程序要按照內核的規定的格式和提供的接口來寫。

在有操作系統的情況下,驅動程序其實是連接硬件和內核的一個橋樑,操作系統的存在,讓驅動程序增加了很多代碼,操作系統就是通過給驅動程序編寫者製造麻煩,達到方便應用開發者的目的。

linux設備分類

驅動設備針對的存儲器和外設,而不是針對cpu內核,linux系統將存儲器和外設分爲三大類:

  • 字符設備
  • 塊設備
  • 網絡設備

字符設備

字符設備指那些必須以串行順序依次進行訪問(字符流)的設備,如鍵盤、鼠標、串口等,系統虛擬出一個文件,對該文件調用讀寫函數(read、write)即可完成對字符設備的操作

塊設備

系統可以隨機訪問快設備,以塊爲單位進行操作,如硬盤、FLASH、CD-ROM等,系統虛擬出一個文件,或者在塊設備上建立文件系統,對這些文件調用讀寫函數(read、write)即可完成對塊設備的操作

網絡設備

網絡設備通常指有線網卡、無線網卡等,網絡設備面向數據包的接收和發送而設計,與字符設備和塊設備不同,網絡通信主要依靠套接字接口

內核的組成

源碼目錄結構

Linux 內核源代碼包含如下目錄。

  • arch :包含和硬件體系結構相關的代碼,每種平臺佔一個相應的目錄,如 i386、arm、
    arm64、powerpc、mips 等。Linux 內核目前已經支持 30 種左右的體系結構。在 arch
    目錄下,存放的是各個平臺以及各個平臺的芯片對 Linux 內核進程調度、內存管理、
    中斷等的支持,以及每個具體的 SoC 和電路板的板級支持代碼。
  • block:塊設備驅動程序 I/O 調度。
  • crypto:常用加密和散列算法(如 AES、SHA 等),還有一些壓縮和 CRC 校驗算法。
  • documentation:內核各部分的通用解釋和註釋。
  • drivers :設備驅動程序,每個不同的驅動佔用一個子目錄,如 char、block、net、
    mtd、i2c 等。
  • fs:所支持的各種文件系統,如 EXT、FAT、NTFS、JFFS2 等。
  • include:頭文件,與系統相關的頭文件放置在 include/linux 子目錄下。
  • init:內核初始化代碼。著名的 start_kernel() 就位於 init/main.c 文件中。
  • ipc:進程間通信的代碼。
  • kernel :內核最核心的部分,包括進程調度、定時器等,而和平臺相關的一部分代碼
    放在 arch/*/kernel 目錄下。
  • ib:庫文件代碼。
  • mm:內存管理代碼,和平臺相關的一部分代碼放在 arch/*/mm 目錄下。
  • net:網絡相關代碼,實現各種常見的網絡協議。
  • scripts:用於配置內核的腳本文件。
  • security:主要是一個 SELinux 的模塊。
  • sound:ALSA、OSS 音頻設備的驅動核心代碼和常用設備驅動。
  • usr:實現用於打包和壓縮的 cpio 等。
  • include:內核 API 級別頭文件。

內核主要組成部分

內核

進程調度SCHED

進程調度控制系統使得多個進程在CPU的中微觀上分時執行,宏觀上併發執行
進程整體上可以分爲五種狀態:

  • 就緒態:資源準備就緒,等待系統調用該進程
  • 執行態:當前CPU正在執行該進程
  • 睡眠態:等待其它進程釋放資源
  • 暫停態:停止執行,等待某種信號
  • 殭屍態:進程已死,資源未釋放

內存管理MM

內存管理的主要作用是控制多個進程安全地共享主內存區域。當CPU提供內存管理單元(MMU)時,Linux內存管理對每個進程提供虛擬地址到物理地址的轉換

每個進程擁有虛擬的4G內存空間,其中3G是用戶空間,是屬於進程私有的,1G是內核空間,是所有進程共享的

虛擬文件系統VFS

用戶通過虛擬文件系統,對文件進行操作,即可達到訪問硬件的目的,虛擬文件系統隱藏了硬件差異,向上爲用戶提供統一的接口,向下調用驅動程序實現的file_operations結構體中的成員函數
文件系統

網絡接口NET

網絡接口提供了對各種網絡標準的存取和各種網絡硬件的支持。在 Linux中網絡接口可分爲網絡協議和網絡驅動程序,網絡協議部分負責實現每一種可能的網絡傳輸協議,網絡設備驅動程序負責與硬件設備通信,每一種可能的硬件設備都有相應的設備驅動程序。
網絡接口

進程間通信IPC

Linux支持進程間通信的多種機制,包括信號量、共享內存、消息隊列、管道、套接字等。利用這些機制,可以實現進程間的同步、互斥和消息傳遞。

內核代碼命名風格

與windows下流行的編程命名風格不同,linux社區形成了自己的一套風格

#define PI 3.1415926
int min_value, max_value;
void send_data(void);
  • 宏:大寫
  • 變量:全部小寫,單詞之間以下劃線分隔
  • 函數:同變量
  • 縮進:tab鍵
  • 括號{}:
    1.if、for、switch、while、else、else if { 不會另起一行
    2.函數的 { 需要另起一行
    3.只有一行代碼,不要用括號{}
  • switch:switch、case、default關鍵字左對齊

GNU C對ANSI C的拓展

Linux使用的C編譯器是GNU C編譯器,對標準C進行了一系列拓展,增強C的功能

變長數組

GNU C可以使用0長度數組,也可以使用變量定義數組長度

struct var_data {
int len;
char data[0];
};

int n=10;
int array[n];

其實也就是C99中新增加的伸縮數組和變長數組的概念

伸縮的意思是,比如定義一個結構體,可以讓其成員擁有不固定的長度,因此用sizeof去求該結構體類型,會忽略掉不固定的部分,而實際定義的時候,一般需要用malloc進行內存分配

變長數組不是指數組的長度在程序運行期間可變,而是指定義時可以利用變量,對長度進行說明,一旦定義了一個數組,其長度就不能變了

case

GNU C 支持 case x…y 這樣的語法,區間 [x,y] 中的數都會滿足這個 case 的條件

switch (ch) {
case '0'... '9': c -= '0';
break;
case 'a'... 'f': c -= 'a' - 10;
break;
case 'A'... 'F': c -= 'A' - 10;
break;
}

語句表達式

GNU C 把包含在括號中的複合語句看成是一個表達式,稱爲語句表達式,它可以出現在任何允許表達式的地方。我們可以在語句表達式中使用原本只能在複合語句中使用的循環、局部變量等,

typeof關鍵字

typeof(x) 語句可以獲得 x 的類型,可以方便一些宏的實現,例如求兩數中較大的值
# define max(a,b) ...
具體實現中,需要先判斷傳入數據的類型,此處可以通過typeof進行判斷

標號元素

C99標準已經增加了標號元素的使用,也就是數組、結構體等初始化過程不需要按順序來,可以隨意指定,例如:

int a[10] = {
    [1] = 2,
    [3] = 5,
    0,
}

在Linux內核代碼中,推薦以下格式進行初始化:

struct file_operations ext2_file_operations = {
    .llseek       = generic_file_llseek,
    .read        = generic_file_read,
    ...
};

特殊屬性說明

GNU C 允許聲明函數、變量和類型的特殊屬性,以便手動優化代碼和定製代碼檢查的方法。要指定一個聲明的屬性,只需要在聲明後添加_ _attribute_ _ (( ATTRIBUTE ))

其中 ATTRIBUTE 爲屬性說明,如果存在多個屬性,則以逗號分隔。GNU C 支持 noreturn、format、section、aligned、packed 等十多個屬性

  • noreturn 屬性作用於函數,表示該函數從不返回。這會讓編譯器優化代碼,並消除不必要的警告信息
  • format 屬性也用於函數,表示該函數使用 printf、scanf 或 strftime 風格的參數,指定format 屬性可以讓編譯器根據格式串檢查參數類型
  • unused 屬性作用於函數和變量,表示該函數或變量可能不會用到,這個屬性可以避免編譯器產生警告信息
  • aligned 屬性用於變量、結構體或聯合體,指定變量、結構體或聯合體的對齊方式,以字節爲單位
  • packed 屬性作用於變量和類型,用於變量或結構體成員時表示使用最小可能的對齊,用於枚舉、結構體或聯合體類型時表示該類型使用最小的內存

內建函數

內建函數是編譯器內部實現的函數,這些函數可以直接使用,無須聲明或者添加頭文件,不屬於標準庫函數的內建函數通常以_ _builtin開頭,還有一小部分內建函數與標準庫函數同名,不過編譯器做了優化或者重構。

工具鏈

最方便的就是直接下載已經編譯好的工具鏈,目前最常用的是Linaro提供的,可以在下面的網址進行下載

http://www.linaro.org/downloads/

Linaro 是 ARM Linux 領域中最著名最具技術成就的開源組織,其會員包括 ARM、Broadcom、Samsung、TI、Qualcomm 等,國內的海思、中興、全志和中國臺灣的 MediaTek 也是它的會員

工具鏈包含了很多程序,例如arm-linux-gnueabihf-gcc
下面介紹一下常用的幾種

  • gcc:編譯C源碼
  • g++:編譯C++源碼
  • gdb:調試
  • strip:刪除符號表和調試信息,縮減可執行文件的體積
  • objdump:反彙編
  • ld:鏈接器
  • gprof:檢查函數被調用的次數和時間
  • nm:顯示符號信息
  • readelf:查看ELF格式文件信息
  • addr2line:將指令的地址和可執行映像轉換爲文件名、函數名和源代碼行數的工具

環境搭建

練習驅動程序開發,最好的方式是虛擬機+qemu,不需要額外購買開發板,具體搭建的過程可以參考我之前的博客

https://blog.csdn.net/whitefish520/article/details/103850361

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章