文章目錄
基本概念
什麼是設備驅動
計算機的運行,需要軟件和硬件的合作,沒有軟件的硬件,是一堆廢鐵,沒有硬件的軟件,是空中樓閣,軟件運行在硬件基礎之上。
軟件又分爲系統軟件和應用軟件,系統軟件屏蔽了底層硬件的差異,嚮應用層提供統一的接口,因而應用層的程序員,不需要知道不同網卡、硬盤等硬件之間有什麼差異,只需要調用相同的讀寫函數,即可完成對設備的讀寫操作。這個屏蔽具體硬件差異過程的重任,就落到了設備驅動的頭上。設備驅動程序,僅僅是系統軟件的一部分。
設備驅動是底層硬件和上層應用之間的紐帶,設備驅動最通俗的解釋是:驅使硬件設備行動。設備驅動與底層硬件直接打交道,完成寄存器的讀寫等、中斷處理等等操作。
無操作系統下的驅動
計算機的運轉不一定需要操作系統,目前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