素材之家思億歐seo靠譜嗎
Linux驅(qū)動(dòng)開發(fā)初識(shí)
文章目錄
- Linux驅(qū)動(dòng)開發(fā)初識(shí)
- 一、驅(qū)動(dòng)的概念
- 1.1 什么是驅(qū)動(dòng):
- 1.2 驅(qū)動(dòng)的分類:
- 二、設(shè)備的概念
- 2.1 主設(shè)備號(hào)&次設(shè)備號(hào):
- 2.2 設(shè)備號(hào)的作用:
- 三、設(shè)備驅(qū)動(dòng)整體調(diào)用過程
- 3.1 上層用戶操控設(shè)備的流程:
- 3.2 Linux驅(qū)動(dòng)的運(yùn)行方式:
- 四、基于框架編寫驅(qū)動(dòng)代碼
- 4.1 基本字符設(shè)備驅(qū)動(dòng)框架:
- 4.2 驅(qū)動(dòng)代碼的編譯:
- 4.3 驅(qū)動(dòng)的加載&卸載:
- 4.4 驅(qū)動(dòng)的測試:
- 五、樹莓派IO口驅(qū)動(dòng)的編寫
- 5.1 BCM2835芯片手冊(cè)導(dǎo)讀:
- 5.2 Pin4引腳定位:
- 5.3 根據(jù)驅(qū)動(dòng)框架編寫樹莓派Pin4引腳驅(qū)動(dòng):
- 5.4 編譯測試Pin4引腳驅(qū)動(dòng):
一、驅(qū)動(dòng)的概念
1.1 什么是驅(qū)動(dòng):
Linux內(nèi)核驅(qū)動(dòng):是指一段代碼,這段代碼可以驅(qū)動(dòng)底層硬件,即驅(qū)動(dòng)就是對(duì)底層硬件設(shè)備的操作進(jìn)行封裝,并向上層提供函數(shù)接口。
1.2 驅(qū)動(dòng)的分類:
Linux驅(qū)動(dòng)分為三個(gè)基礎(chǔ)大類:字符設(shè)備驅(qū)動(dòng),塊設(shè)備驅(qū)動(dòng),網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)。
- 字符設(shè)備(Char Device):
- 字符設(shè)備是個(gè)能夠像字節(jié)流(類似文件)一樣被訪問的設(shè)備
- 對(duì)字符設(shè)備發(fā)出讀/寫請(qǐng)求時(shí),實(shí)際的硬件I/O操作一般緊接著發(fā)生
- 字符設(shè)備驅(qū)動(dòng)程序通常至少要實(shí)現(xiàn)
open
、close
、read
和write
系統(tǒng)調(diào)用 - 比如我們常見的lcd、觸摸屏、鍵盤、led、串口等等,他們一般對(duì)應(yīng)具體的硬件都是進(jìn)行出具的采集、處理、傳輸
- 塊設(shè)備(Block Device):
- 一個(gè)塊設(shè)備驅(qū)動(dòng)程序主要通過傳輸固定大小的數(shù)據(jù)(一般為512或1k)來訪問設(shè)備
- 塊設(shè)備通過buffer cache(內(nèi)存緩沖區(qū))訪問,可以隨機(jī)存取,即:任何塊都可以讀寫,不必考慮它在設(shè)備的什么地方
- 塊設(shè)備可以通過它們的設(shè)備特殊文件訪問,但是更常見的是通過文件系統(tǒng)進(jìn)行訪問
- 只有一個(gè)塊設(shè)備可以支持一個(gè)安裝的文件系統(tǒng)
- 比如我們常見的電腦硬盤、SD卡、U盤、光盤等
- 網(wǎng)絡(luò)設(shè)備(Net Device):
- 任何網(wǎng)絡(luò)事務(wù)都經(jīng)過一個(gè)網(wǎng)絡(luò)接口形成,即一個(gè)能夠和其他主機(jī)交換數(shù)據(jù)的設(shè)備
- 訪問網(wǎng)絡(luò)接口的方法仍然是給它們分配一個(gè)唯一的名字(比如eth0),但這個(gè)名字在文件系統(tǒng)中不存在對(duì)應(yīng)的節(jié)點(diǎn)
- 內(nèi)核和網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序間的通信,完全不同于內(nèi)核和字符以及塊驅(qū)動(dòng)程序之間的通信,內(nèi)核調(diào)用一套和數(shù)據(jù)包傳輸相關(guān)的函(
socket
函數(shù))而不是read
、write
等 - 比如我們常見的網(wǎng)卡設(shè)備、藍(lán)牙設(shè)備
二、設(shè)備的概念
- 在學(xué)習(xí)驅(qū)動(dòng)和其開發(fā)之前,首先要知道所謂驅(qū)動(dòng),其對(duì)象就是設(shè)備。
2.1 主設(shè)備號(hào)&次設(shè)備號(hào):
在Linux中,各種設(shè)備都以文件的形式存在**/dev目錄下**,稱為設(shè)備文件。最上層的應(yīng)用程序可以打開,關(guān)閉,讀寫這些設(shè)備文件,從而完成對(duì)設(shè)備的操作。
為了管理這些設(shè)備,系統(tǒng)為設(shè)備編了號(hào),每個(gè)設(shè)備都擁有主設(shè)備號(hào)和次設(shè)備號(hào)。主設(shè)備號(hào)用于區(qū)分不同種類的設(shè)備,而次設(shè)備號(hào)用于區(qū)分同一類型的多個(gè)設(shè)備。(對(duì)于常用的設(shè)備如硬盤,Linux賦予的主設(shè)備號(hào)一般是3)
- 在**
/dev
目錄下輸入ls -l
**,就可以看到設(shè)備文件對(duì)應(yīng)的主次設(shè)備號(hào):
2.2 設(shè)備號(hào)的作用:
在了解了什么是主次設(shè)備號(hào)之后,就要了解設(shè)備號(hào)的用處:
- 在用戶態(tài)中:當(dāng)用戶調(diào)用了如
open
,read
,write
等函數(shù)想要操作設(shè)備文件時(shí),需要兩個(gè)參數(shù),第一個(gè)是文件名,第二個(gè)就是設(shè)備號(hào) - 在內(nèi)核態(tài)中:存在著一個(gè)驅(qū)動(dòng)鏈表,用于管理所有設(shè)備的驅(qū)動(dòng),而驅(qū)動(dòng)在鏈表中的位置就由設(shè)備號(hào)來檢索
三、設(shè)備驅(qū)動(dòng)整體調(diào)用過程
3.1 上層用戶操控設(shè)備的流程:
-
C語言上層調(diào)用
open
函數(shù)。open(“/dev/pin4”,O_RDWR);
調(diào)用/dev下的pin4以可讀可寫的方式打開。對(duì)于上層open
調(diào)用到內(nèi)核時(shí)會(huì)發(fā)生一次軟中斷中斷號(hào)是0X80,從用戶空間進(jìn)入到內(nèi)核空間。 -
open
會(huì)調(diào)用到system_call(內(nèi)核函數(shù))
,system_call
會(huì)根據(jù)/dev/pin4
設(shè)備名,去找出需要的設(shè)備號(hào)。 -
再調(diào)到虛擬文件VFS ,調(diào)用VFS里的
sys_open
,sys_open
會(huì)找到在驅(qū)動(dòng)鏈表里面,根據(jù)主設(shè)備號(hào)和次設(shè)備號(hào)找到引腳4里的open函數(shù),引腳4里的open是對(duì)寄存器操作及對(duì)硬件的操作。
3.2 Linux驅(qū)動(dòng)的運(yùn)行方式:
- 將驅(qū)動(dòng)編譯進(jìn) Linux 內(nèi)核中,當(dāng) Linux 內(nèi)核啟動(dòng)的時(shí)就會(huì)自動(dòng)運(yùn)行驅(qū)動(dòng)程序
- 將驅(qū)動(dòng)編譯成模塊(Linux 下模塊擴(kuò)展名為.ko),并在Linux 內(nèi)核啟動(dòng)以后使用相應(yīng)命令加載驅(qū)動(dòng)模塊
四、基于框架編寫驅(qū)動(dòng)代碼
4.1 基本字符設(shè)備驅(qū)動(dòng)框架:
#include <linux/fs.h> //file_operations聲明
#include <linux/module.h> //module_init module_exit聲明
#include <linux/init.h> //__init __exit 宏定義聲明
#include <linux/device.h> //class devise聲明
#include <linux/uaccess.h> //copy_from_user 的頭文件
#include <linux/types.h> //設(shè)備號(hào) dev_t 類型聲明
#include <asm/io.h> //ioremap iounmap的頭文件static struct class *pin4_class; //類對(duì)象
static struct device *pin4_class_dev; //設(shè)備對(duì)象static dev_t devno; //設(shè)備號(hào)
static int major =231; //主設(shè)備號(hào)
static int minor =0; //次設(shè)備號(hào)
static char *module_name="pin4"; //模塊名//_open函數(shù)
static int pin4_open(struct inode *inode,struct file *file)
{printk("pin4_open\n"); //內(nèi)核的打印函數(shù)和printf類似return 0;
}//_write函數(shù)
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{printk("pin4_write\n"); //內(nèi)核的打印函數(shù)和printf類似return 0;
}static struct file_operations pin4_fops = { //結(jié)構(gòu)體的類型是“file_operations”,名字可以自定義
//該結(jié)構(gòu)體的成員就包含實(shí)現(xiàn)open和write的驅(qū)動(dòng)函數(shù)
//當(dāng)上層用戶想要open或者write這個(gè)設(shè)備時(shí),就會(huì)最終跳轉(zhuǎn)到這個(gè)驅(qū)動(dòng)代碼中實(shí)現(xiàn)的open和write操作函數(shù)
//此處只賦值了該結(jié)構(gòu)體中的三個(gè)成員變量(在keil中是不能這樣寫的,linux中可以),這個(gè)結(jié)構(gòu)體其實(shí)有很多成員,如果想要實(shí)現(xiàn)更多的驅(qū)動(dòng)函數(shù),可以把更多的該結(jié)構(gòu)體成員賦值并在這段代碼中重寫.owner = THIS_MODULE,.open = pin4_open,.write = pin4_write,
};int __init pin4_drv_init(void) //真實(shí)驅(qū)動(dòng)入口
{int ret;devno = MKDEV(major,minor); //創(chuàng)建設(shè)備號(hào)ret = register_chrdev(major, module_name, &pin4_fops); //注冊(cè)驅(qū)動(dòng),告訴內(nèi)核:把這個(gè)驅(qū)動(dòng)加入到內(nèi)核驅(qū)動(dòng)的鏈表中//以下兩句代碼目的是“生成設(shè)備文件”,也可以通過“mknod”命令手動(dòng)生成,但是一般不會(huì)這樣做pin4_class=class_create(THIS_MODULE,"myfirstdemo"); //先創(chuàng)建‘類’pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //再創(chuàng)建‘設(shè)備’return 0;
}void __exit pin4_drv_exit(void)
{device_destroy(pin4_class,devno); //先銷毀‘設(shè)備’class_destroy(pin4_class); //在銷毀‘類’unregister_chrdev(major, module_name); //卸載驅(qū)動(dòng)
}module_init(pin4_drv_init); //入口,內(nèi)核加載驅(qū)動(dòng)的時(shí)候,這個(gè)宏會(huì)被調(diào)用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2"); //linux內(nèi)核遵循GPL協(xié)議
4.2 驅(qū)動(dòng)代碼的編譯:
- 進(jìn)入Linux源碼樹目錄下的驅(qū)動(dòng)目錄,因?yàn)轵?qū)動(dòng)的是字符設(shè)備,所以進(jìn)入的是驅(qū)動(dòng)目錄下的char目錄。
/home/shiyahao/SYSTEM/linux-rpi-4.19.y/drivers/char
- 在這個(gè)路徑下創(chuàng)建一個(gè)新的C文件:pin4driver.c,內(nèi)容為我們剛剛的字符設(shè)備驅(qū)動(dòng):
- 修改當(dāng)前路徑(字符設(shè)備驅(qū)動(dòng))下的Makefile,確保這個(gè)新的驅(qū)動(dòng)會(huì)被編譯到:
- 回到linux內(nèi)核源碼的路徑,運(yùn)行以下指令嘗試編譯:
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
- 將編譯好的驅(qū)動(dòng)模塊傳到樹莓派中:
scp ./drivers/char/pin4driver.ko pi@192.168.31.123:/home/pi
4.3 驅(qū)動(dòng)的加載&卸載:
由于現(xiàn)在剛剛把驅(qū)動(dòng)編譯成了.ko的模塊,所以需要運(yùn)行以下指令來加載驅(qū)動(dòng)模塊:
sudo insmod pin4driver.ko //加載驅(qū)動(dòng)模塊
sudo rmmod pin4driver.ko //卸載驅(qū)動(dòng)模塊,此時(shí)驅(qū)動(dòng)名字后不用加".ko"
運(yùn)行成功后,就可以在**/dev**下看到生成的設(shè)備文件“pin4”了:
使用ls -l指令查看這個(gè)設(shè)備的主設(shè)備號(hào)&次設(shè)備號(hào),和框架代碼中的設(shè)置一樣:
給pin驅(qū)動(dòng)加權(quán)限:
sudo chmod 666 /dev/pin4
4.4 驅(qū)動(dòng)的測試:
在樹莓派下寫一個(gè)測試驅(qū)動(dòng)的C代碼:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd;fd = open("/dev/pin4", O_RDWR); //打開GPIO4口設(shè)備文件if(fd < 0){printf("open pin4 failed\n");}else{printf("open pin4 success\n");}write(fd, "1", 1); //輸出高電平return 0;
}
執(zhí)行測試程序后用dmesg 查看內(nèi)核打印信息發(fā)現(xiàn)打印了驅(qū)動(dòng)函數(shù)的信息:
可見內(nèi)核也按照框架代碼中的printk成功打印了信息!驅(qū)動(dòng)測試成功!
同時(shí),結(jié)果也再次印證了:當(dāng)用戶在最上層對(duì) 驅(qū)動(dòng)文件 調(diào)用C庫的open函數(shù)后,最后的結(jié)果還是調(diào)用最底層 驅(qū)動(dòng)文件里實(shí)現(xiàn)的open驅(qū)動(dòng)函數(shù)
五、樹莓派IO口驅(qū)動(dòng)的編寫
? 前面我們通過一個(gè)基本的字符設(shè)備驅(qū)動(dòng)框架來測試了驅(qū)動(dòng)的運(yùn)行,但是在“pin4_open”和“pin4_write”這兩個(gè)驅(qū)動(dòng)函數(shù)的函數(shù)體里只寫了一句內(nèi)核打印的代碼,作為一個(gè)真正的驅(qū)動(dòng)文件這顯然是不夠的。
? 同時(shí),在之前就提到過,驅(qū)動(dòng)位于內(nèi)核態(tài)的最底層,其下方就直接是硬件,所以驅(qū)動(dòng)函數(shù)的目標(biāo)就是直接操控硬件,也就是直接操控寄存器。在我的pin4驅(qū)動(dòng)函數(shù)中應(yīng)該添加的也就是根據(jù)函數(shù)功能,操作寄存器從而實(shí)現(xiàn)I/O口操控的代碼。
5.1 BCM2835芯片手冊(cè)導(dǎo)讀:
明確了目標(biāo)后,就產(chǎn)生了這個(gè)問題:我怎么知道應(yīng)該使用哪些寄存器,又應(yīng)該怎么使用呢?
答案是:根據(jù)開發(fā)平臺(tái)的芯片手冊(cè)/電路圖來找到具體的描述,由于我是在樹莓派3B上玩驅(qū)動(dòng)的開發(fā),所以我應(yīng)該查閱這款樹莓派的芯片,也就是BCM2835的芯片手冊(cè)。
此處我只使用了芯片手冊(cè)就定位了寄存器,而沒有用電路圖,原因是樹莓派的這個(gè)芯片手冊(cè)已經(jīng)把用什么寄存器寫的很清楚了
在BCM2835芯片手冊(cè)的第六章描述了General Purpose I/O (GPIO)外設(shè)相關(guān)寄存器。這里驅(qū)動(dòng)pin4引腳需要用到的寄存器有:
-
GPIO Function Select Registers (GPFSELn) 功能選擇寄存器:
該寄存器共有五組,每個(gè)寄存器都有32位,以GPIO Alternate function select register 0為例,其中:
29-0位 :每三位對(duì)于一個(gè)引腳,比如29-27對(duì)應(yīng)的是GPIO Pin 9,26-24對(duì)應(yīng)的是GPIO Pin 8,且這三位取不同的值代表該三位對(duì)應(yīng)的引腳選擇不同的功能。比如,當(dāng)29-27位為000時(shí)表示GPIO Pin 9是輸入功能,29-27位為001時(shí)表示GPIO Pin 9是輸出的功能。 -
GPIO Pin Output Set Registers (GPSETn) 置位寄存器:
該寄存器共兩組,每個(gè)寄存器都有32位,將寄存器某一位置1即將對(duì)應(yīng)的引腳置1。
-
GPIO Pin Output Clear Registers (GPCLRn) 清0寄存器:
與置位寄存器用法一至,將對(duì)應(yīng)位數(shù)引腳置0。
-
所需寄存器的地址說明:
? 在編寫驅(qū)動(dòng)程序時(shí),IO空間的起始地址位0X3F000000,加上GPIO的偏移量0X200000,因此GPIO的物理地址是從0X3F200000開始的,而編程所需的地址是虛擬地址,需要通過MMU內(nèi)存虛擬化管理將地址映射到虛擬地址上。
5.2 Pin4引腳定位:
Pin4引腳指的是BCM4號(hào),對(duì)應(yīng)WiringPi庫第7號(hào),物理引腳的7腳:
5.3 根據(jù)驅(qū)動(dòng)框架編寫樹莓派Pin4引腳驅(qū)動(dòng):
#include <linux/fs.h> //file_operations聲明
#include <linux/module.h> //module_init module_exit聲明
#include <linux/init.h> //__init __exit 宏定義聲明
#include <linux/device.h> //class devise聲明
#include <linux/uaccess.h> //copy_from_user 的頭文件
#include <linux/types.h> //設(shè)備號(hào) dev_t 類型聲明
#include <asm/io.h> //ioremap iounmap的頭文件static struct class *pin4_class;
static struct device *pin4_class_dev;static dev_t devno; //設(shè)備號(hào)
static int major =231; //主設(shè)備號(hào)
static int minor =0; //次設(shè)備號(hào)
static char *module_name="pin4"; //模塊名//首先定義所要用的寄存器,為了防止地址被編譯器優(yōu)化需要用到volatile關(guān)鍵字
volatile unsigned int *GPFSEL0 = NULL;
volatile unsigned int *GPSET0 = NULL;
volatile unsigned int *GPCLR0 = NULL;static int pin4_open(struct inode *inode,struct file *file)
{printk("pin4_open\n"); //內(nèi)核的打印函數(shù)和printf類似//配置引腳4的寄存器,將其配置為輸出模式,即將GPFSEL0寄存器的第14-12位配置成001*GPFSEL0 &= 0XFFFF9FFF; //將第14,13位置0*GPFSEL0 |= 0X00001000; //將第12位置1return 0;
}static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{int usercmd;printk("pin4_write\n");copy_from_user(&usercmd,buf,count);//獲取應(yīng)用層write函數(shù)寫入的內(nèi)容if(usercmd == 1){printk("set 1\n");*GPSET0 |=(0x1 << 4); //將Pin4引腳置1}else if (usercmd == 0){printk("set 0\n");*GPCLR0 |=(0X1 << 4);//將Pin4引腳置0}else{printk("undo\n");}return 0;
}static struct file_operations pin4_fops = {.owner = THIS_MODULE,.open = pin4_open,//當(dāng)應(yīng)用層調(diào)用open函數(shù)時(shí),內(nèi)核會(huì)調(diào)用pin4_open..write = pin4_write,//當(dāng)應(yīng)用層調(diào)用write函數(shù)時(shí),內(nèi)核會(huì)調(diào)用pin4_write.
};int __init pin4_drv_init(void) //真實(shí)的驅(qū)動(dòng)入口
{int ret;devno = MKDEV(major,minor); //創(chuàng)建設(shè)備號(hào)ret = register_chrdev(major, module_name,&pin4_fops); //注冊(cè)驅(qū)動(dòng) 告訴內(nèi)核,把這個(gè)驅(qū)動(dòng)加入到內(nèi)核驅(qū)動(dòng)的鏈表中pin4_class=class_create(THIS_MODULE,"myfirstdemo");//由代碼在dev下自動(dòng)生成設(shè)備pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //創(chuàng)建設(shè)備文件GPFSEL0 = (volatile unsigned int *)ioremap(0X3f200000,4);//需要將物理地址映射位虛擬地址 ipremap第一個(gè)參數(shù)需要被映射的物理地址。第二個(gè)參數(shù)位映射的字節(jié)數(shù)GPSET0 = (volatile unsigned int *)ioremap(0X3f20001C,4);//通過芯片手冊(cè)可以看到該寄存器在基礎(chǔ)地址上偏移了1CGPCLR0 = (volatile unsigned int *)ioremap(0X3f200028,4);//通過芯片手冊(cè)可以看到該寄存器在基礎(chǔ)地址上偏移了28return 0;
}void __exit pin4_drv_exit(void)
{iounmap(GPFSEL0);iounmap(GPSET0);iounmap(GPCLR0);device_destroy(pin4_class,devno);class_destroy(pin4_class);unregister_chrdev(major, module_name); //卸載驅(qū)動(dòng)
}module_init(pin4_drv_init); //入口
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
然后在樹莓派上編寫測試代碼:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{int fd,cmd;fd = open("/dev/pin4",O_RDWR);printf("input 0 ro 1 , 0 :Pin4 Set 0,1:Pin4 Set 1\n");scanf("%d",&cmd);printf("cmd = %d \n",cmd);write(fd,&cmd,1);return 0;
}
5.4 編譯測試Pin4引腳驅(qū)動(dòng):
- 將驅(qū)動(dòng)代碼編譯后生成驅(qū)動(dòng)模塊放置在樹莓派上進(jìn)行測試:
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
- 將生成的驅(qū)動(dòng)模塊拷貝至樹莓派:
scp ./drivers/char/pin4driver.ko pi@192.168.31.123:/home/pi
- 在樹莓派上安裝驅(qū)動(dòng)并給驅(qū)動(dòng)權(quán)限:
sudo insmod pin4driver.ko
sudo chmod 666 /dev/pin4
運(yùn)行測試程序: