linux驅動(驅動編譯、字符設備驅動框架、交叉編譯樹莓派驅動、樹莓派驅動本地編譯)

什么是驅動:

驅動就是對底層硬件設備的操作進行封裝,并向上層提供函數接口。

設備分類:
linux系統將設備分為3類:字符設備、塊設備、網絡設備。

  • 字符設備:指只能一個字節一個字節讀寫的設備,不能隨機讀取設備內存中的某一數據,讀取數據需要按照先后順序。字符設備是面向流的設備,常見的字符設備有鼠標、鍵盤、串口、控制臺和LED設備等。
  • 塊設備: 指可以從設備的任意位置讀取一定長度數據的設備。塊設備包括硬盤、磁盤、U盤和SD卡等。
  • 網絡設備: 網絡設備可以是一個硬件設備,如網卡; 但也可以是一個純粹的軟件設備, 比如回環接口(lo).一個網絡接口負責發送和接收數據報文。

用戶態:

  • 是指用戶編寫程序、運行程序的層面,用戶態在開發時需要C的基礎和C庫,C庫講到文件,進程,進程間通信,線程,網絡,界面(GTk)。C庫(是linux標準庫一定有):就是Clibary,提供了程序支配內核干活的接口,調用的open,read,write,fork,pthread,socket由此處封裝實現,由寫的應用程序調用,C庫中的各種API調用的是內核態,支配內核干活。

內核態:

  • 用戶要使用某個硬件設備時,需要內核態的設備驅動程序,進而驅動硬件干活,就比如之前文章里面所提到的wiringPi庫,就是提供了用戶操控硬件設備的接口,在沒有wiringPi庫時就需要自己實現wiringPi庫的功能,就是自己寫設備驅動程序。這樣當我們拿到另一種類型的板子時,同樣也可以完成開發。
  • 在linux中一切皆文件,各種的文件和設備(比如:鼠標、鍵盤、屏幕、flash、內存、網卡、如下圖所示:)都是文件,那既然是文件了,就可以使用文件操作函數來操作這些設備。有一個問題,open、read等這些文件操作函數是如何知道打開的文件是哪一種硬件設備呢?①在open函數里面輸入對應的文件名,進而操控對應的設備。②通過設備號(主設備號和次設備號)。除此之外我們還要了解這些驅動程序的位置,和如何實現這些驅動程序,每一種硬件設備對應不同的驅動(這些驅動有我們自己來實現)。
    在這里插入圖片描述
  • Linux的設備管理是和文件系統緊密結合的,各種設備都以文件的形式存放在/dev目錄下,稱為設備文件。應用程序可以打開、關閉和讀寫這些設備文件,完成對設備的操作,就像操作普通的數據文件一樣。為了管理這些設備,系統為設備編了號,每個設備號又分為主設備號和次設備號(如下圖所示:)。主設備號用來區分不同種類的設備,而次設備號用來區分同一類型的多個設備。對于常用設備,Linux有約定俗成的編號,如硬盤的主設備號是3。 一個字符設備或者塊設備都有一個主設備號和次設備號。主設備號和次設備號統稱為設備號。主設備號用來表示一個特定的驅動程序。次設備號用來表示使用該驅動程序的各設備。例如一個嵌入式系統,有兩個LED指示燈,LED燈需要獨立的打開或者關閉。那么,可以寫一個LED燈的字符設備驅動程序,可以將其主設備號注冊成5號設備,次設備號分別為1和2。這里,次設備號就分別表示兩個LED燈。
    在這里插入圖片描述
  • 驅動鏈表:管理所有設備的驅動,添加或查找, 添加是發生在我們編寫完驅動程序,加載到內核。查找是在調用驅動程序,由應用層用戶空間去查找使用open函數。驅動插入鏈表的順序由設備號檢索,就是說主設備號和次設備號除了能區分不同種類的設備和不同類型的設備,還能起到將驅動程序加載到鏈表的某個位置,在下面介紹的驅動代碼的開發無非就是添加驅動(添加設備號、設備名和設備驅動函數)調用驅動
  • 綜上所述:如果想要打開dev下面的pin4引腳,過程是:用戶態調用open(“/de/pin4”,O_RDWR),對于內核來說,上層調用open函數會觸發一個軟中斷(系統調用專用,中斷號是0x80,0x80代表發生了一個系統調用),系統進入內核態,并走到system_call,可以認為這個就是此軟中斷的中斷服務程序入口,然后通過傳遞過來的系統調用號來決定調用相應的系統調用服務程序(在這里是調用VFS中的sys_open)。sys_open會在內核的驅動鏈表里面根據設備名和設備號查找到相關的驅動函數(每一個驅動函數是一個節點),驅動函數里面有通過寄存器操控IO口的代碼,進而可以控制IO口實現相關功能。
  • system_call函數是怎么找到詳細的系統調用服務例程的呢? 通過系統調用號查找系統調用表sys_call_table!軟中斷指令INT 0x80運行時,系統調用號會被放入 eax 寄存器中,system_call函數能夠讀取eax寄存器獲取,然后將其乘以4,生成偏移地址,然后以sys_call_table為基址。基址加上偏移地址,就能夠得到詳細的系統調用服務例程的地址了!然后就到了系統調用服務例程了。

補充:

  • 每個系統調用都對應一個系統調用號,而系統調用號就對應內核中的相應處理函數。
  • 所有系統調用都是通過中斷0x80來觸發的。
  • 使用系統調用時,通過eax 寄存器將系統調用號傳遞到內核,系統調用的入參通過ebx、ecx……依次傳遞到內核
  • 和函數一樣,系統調用的返回值保存在eax中,所有要從eax中取出

基于框架編寫驅動代碼:

  • 最簡單的字符設備驅動框架:
#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>     //設備號  dev_t 類型聲明
#include <asm/io.h>          //ioremap iounmap的頭文件static struct class *pin4_class;  
static struct device *pin4_class_dev;static dev_t devno;                //設備號,devno是用來接收創建設備號函數的返回值,銷毀的時候需要傳這個參數
static int major =231;  		   //主設備號
static int minor =0;			   //次設備號
static char *module_name="pin4";   //模塊名//led_open函數
static int pin4_open(struct inode *inode,struct file *file)
{printk("pin4_open\n");  //內核的打印函數和printf類似   return 0;
}//led_write函數
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{printk("pin4_write\n");  //內核的打印函數和printf類似return 0;
}
//將上面的函數賦值給一個結構體中,方便下面加載到到驅動鏈表中去
static struct file_operations pin4_fops = {.owner = THIS_MODULE,.open  = pin4_open,.write = pin4_write,
};
/*
上面的代碼等同于以下代碼(但是在單片機的編譯環境里面不允許以上寫法):
static struct file_operations pin4_fops;pin4_fops.owner = THIS_MODULE;pin4_fops.open  = pin4_open;pin4_fops.write = pin4_write;
*/
//static限定這個結構體的作用,僅僅只在這個文件。int __init pin4_drv_init(void)   //真實的驅動入口
{int ret;devno = MKDEV(major,minor);  //創建設備號ret   = register_chrdev(major, module_name,&pin4_fops);  //注冊驅動  告訴內核,把這個驅動加入到內核驅動的鏈表中pin4_class=class_create(THIS_MODULE,"myfirstdemo");//由代碼在dev下自動生成設備pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //創建設備文件,先有上面那一行代碼,創建一個類然后這行代碼,類下面再創建一個設備。return 0;
}void __exit pin4_drv_exit(void)
{device_destroy(pin4_class,devno);//先銷毀設備class_destroy(pin4_class);//再銷毀類unregister_chrdev(major, module_name);  //卸載驅動}module_init(pin4_drv_init);  //入口,內核加載驅動的時候,這個宏(不是函數)會被調用,去調用pin4_drv_init這個函數
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
  • 上面這個字符設備驅動代碼里面有讓代碼自動的在dev下面生成設備,除此之外我們還可以手動創建設備名。使用指令:sudo mknod +設備名字 +設備類型(c表示字符設備驅動) +主設備號+次設備號 b : create a block (buffered) pecial file。 c, u: create a character (unbuffered) special file。 p: create a FIFO,刪除手動創建的設備名直接rm就好。如下圖所示:
    在這里插入圖片描述

驅動模塊代碼編譯(模塊的編譯需要配置過的內核源碼,編譯、連接后生成的內核模塊后綴為.ko,編譯過程首先會到內核源碼目錄下,讀取頂層的Makefile文件,然后再返回模塊源碼所在目錄。):

  • 使用下面的的代碼:
#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>     //設備號  dev_t 類型聲明
#include <asm/io.h>          //ioremap iounmap的頭文件static struct class *pin4_class;
static struct device *pin4_class_dev;static dev_t devno;                //設備號
static int major =231;                     //主設備號
static int minor =0;                       //次設備號
static char *module_name="pin4";   //模塊名//led_open函數
static int pin4_open(struct inode *inode,struct file *file)
{printk("pin4_open\n");  //內核的打印函數和printf類似return 0;
}
//read函數
static int pin4_read(struct file *file,char __user *buf,size_t count,loff_t *ppos)
{printk("pin4_read\n");  //內核的打印函數和printf類似return 0;
}//led_write函數
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{printk("pin4_write\n");  //內核的打印函數和printf類似return 0;
}static struct file_operations pin4_fops = {.owner = THIS_MODULE,.open  = pin4_open,.write = pin4_write,.read  = pin4_read,
};
//static限定這個結構體的作用,僅僅只在這個文件。
int __init pin4_drv_init(void)   //真實的驅動入口
{int ret;devno = MKDEV(major,minor);  //創建設備號ret   = register_chrdev(major, module_name,&pin4_fops);  //注冊驅動  告訴內核,把這個驅動加入到內核驅動的鏈表中pin4_class=class_create(THIS_MODULE,"myfirstdemo");//讓代碼在dev下自動>生成設備pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //創建設備文件return 0;
}void __exit pin4_drv_exit(void)
{device_destroy(pin4_class,devno);class_destroy(pin4_class);unregister_chrdev(major, module_name);  //卸載驅動
}
module_init(pin4_drv_init);  //入口,內核加載驅動的時候,這個宏會被調用,去調用pin4_drv_init這個函數
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
  • 在導入虛擬機的內核代碼中找到字符設備驅動的那一個文件夾:/SYSTEM/linux-rpi-4.19.y/drivers/char將以上代碼復制到一個文件中,然后下一步要做的是就是:將上面的代碼編譯生成模塊,就是修改Makefile這個文件。文件內容如下圖所示:(-y表示編譯進內核,-m表示生成驅動模塊,CONFIG_表示是根據config生成的),所以只需要將obj-m += pin4drive.o添加到Makefile中即可。
    在這里插入圖片描述
  • 然后使用指令:ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules進行編譯生成驅動模塊。然后將生成的.ko文件發送給樹莓派:scp pin4drive.ko pi@192.168.43.136:/home/pi
  • 編譯生成驅動模塊會生成以下幾個文件:
    在這里插入圖片描述
  • .o的文件是object文件,.ko是kernel object,與.o的區別在于其多了一些sections,比如.modinfo。.modinfo section是由kernel source里的modpost工具生成的,包括MODULE_AUTHOR, MODULE_DESCRIPTION, MODULE_LICENSE, device ID table以及模塊依賴關系等等。 depmod 工具根據.modinfo section生成modules.dep, modules.*map等文件,以便modprobe更方便的加載模塊。
  • 編譯過程中,經歷了這樣的步驟:先進入Linux內核所在的目錄,并編譯出pin4drive.o文件,運行MODPOST會生成臨時的pin4drive.mod.c文件,而后根據此文件編譯出pin4drive.mod.o,之后連接pin4drive.o和pin4drive.mod.o文件得到模塊目標文件pin4drive.ko,最后離開Linux內核所在的目錄。

操作驅動的上層代碼(pin4test):

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>void main()
{int fd,data;fd = open("/dev/pin4",O_RDWR);if(fd<0){printf("open fail\n");perror("reson:");}else{printf("open successful\n");}fd=write(fd,'1',1);
}
  • 將以上代碼進行交叉編譯后發送給樹莓派,就可以看到pi目錄下存在發送過來的.ko文件和pin4test這兩個文件,如下圖所示:
    在這里插入圖片描述

  • 然后使用指令:sudo insmod pin4drive.ko加載內核驅動(相當于通過insmod調用了module_init這個宏,然后將整個結構體加載到驅動鏈表中),加載完成后就可以在dev下面看到名字為pin4的設備驅動(這個和驅動代碼里面static char *module_name="pin4"; //模塊名這行代碼有關),設備號也和代碼里面相關。lsmod可以查看驅動已經裝進去了。
    在這里插入圖片描述
    在這里插入圖片描述

  • 執行上層代碼出現以下錯誤:表示沒有權限,使用指令:sudo chmod 666 /dev/pin4為pin4賦予權限,讓所有人都可以打開成功。
    在這里插入圖片描述

  • 然后再次執行pin4test表面上看沒有任何信息輸出,其實內核里面有打印信息只是上層看不到,如果想要查看內核打印的信息可以使用指令:dmesg |grep pin4。如下圖所示:表示驅動調用成功
    在這里插入圖片描述

  • 在裝完驅動后可以使用指令:sudo rmmod +驅動名(不需要寫ko)將驅動卸載。

為什么生成驅動模塊需要在虛擬機上生成?樹莓派不行嗎?

生成驅動模塊需要編譯環境(linux源碼并且編譯,需要下載和系統版本相同的Linux內核源代碼),也可以在樹莓派上面編譯,但在樹莓派里編譯,效率會很低,要非常久。這篇文章有講樹莓派驅動的本地編譯。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/272152.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/272152.shtml
英文地址,請注明出處:http://en.pswp.cn/news/272152.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

docker啟動報錯 ?(iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 9876 -j DNAT --

docker啟動報錯 : (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 9876 -j DNAT --to-destination 172.17.0.2:9876 ! -i docker0: iptables: No chain/target/match by that name. 解決方案&#xff1a; systemctl restart docker

第一個Spark程序

1、Java下Spark開發環境搭建&#xff08;from http://www.cnblogs.com/eczhou/p/5216918.html&#xff09;1.1、jdk安裝安裝oracle下的jdk&#xff0c;我安裝的是jdk 1.7&#xff0c;安裝完新建系統環境變量JAVA_HOME&#xff0c;變量值為“C:\Program Files\Java\jdk1.7.0_79”…

arduino判斷是否連接串口_Arduino-串口通信

Serial”系列函數&#xff0c;所以我們要對其有所了解&#xff0c;下面介紹幾個常“Serial”函數。1、Serial.begin()—設置串行每秒傳輸數據的速率(波特率)。在同計算機通訊時&#xff0c;使用下面這些值&#xff1a;300, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400…

最詳細的docker安裝rocketMQ教程來了

RocketMQ是一款分布式、隊列模型的消息中間件&#xff0c;是由阿里巴巴設計的&#xff0c;具有以下特點&#xff1a; 支持嚴格的消息順序 支持Topic與Queue兩種模式 億級消息堆積能力 比較友好的分布式特性 同時支持Push與Pull方式消費消息 歷經多次天貓雙十一海量消息考驗…

樹莓派IO口驅動代碼的編寫、微機總線地址、物理地址、虛擬地址、BCM2835芯片手冊

地址總線&#xff1a; 百度百科解釋&#xff1a; 地址總線 (Address Bus&#xff1b;又稱&#xff1a;位址總線) 屬于一種電腦總線 &#xff08;一部份&#xff09;&#xff0c;是由CPU 或有DMA 能力的單元&#xff0c;用來溝通這些單元想要存取&#xff08;讀取/寫入&#xff…

奪命雷公狗---DEDECMS----26dedecms面包屑導航的實現

我們在很多項目里面都會用到面包屑導航&#xff0c;而dedecms里面也是給我們封裝好面包屑導航的了,如下圖所示&#xff1a; 在dede里面實現面包屑導航主要用到{dede:field.position/}標簽&#xff0c;我們首先來修改下article_movie.htm內容頁的模版文件&#xff1a; 我們修改成…

rust油桶用什么打_草莓用什么膨大素好?草莓膨大劑什么時間打?草莓用什么肥料膨大...

農資365公眾號&#xff0c;了解更多生根、根腐、重茬、土傳、枯黃萎、根爛病、防治根結線蟲、微生物菌肥、膨大坐果、抗病增產的防治方法&#xff01;草莓含有豐富的營養&#xff0c;并且種植效益較高&#xff0c;其種植范圍也比較廣。草莓種植期間有很多因素影響草莓果實膨大&…

docker安裝kafka,超級簡單的

簡介 kafka是一個分布式消息隊列。具有高性能、持久化、多副本備份、橫向擴展能力。生產者往隊列里寫消息&#xff0c;消費者從隊列里取消息進行業務邏輯。一般在架構設計中起到解耦、削峰、異步處理的作用。 kafka對外使用topic的概念&#xff0c;生產者往topic里寫消息&…

Linux中常見的環境變量筆記

1、變量&#xff1a;BASHBash Shell的全路徑比如&#xff1a;echo $BASH2、變量&#xff1a;BASH_VERSIONBash Shell的版本號3、變量&#xff1a;EUID記錄當前用戶的UID。root用戶值為0。4、FUNCNAME在用戶函數體內部&#xff0c;記錄當前函數體的函數名。5、變量&#xff1a;H…

消防信號二總線有沒電壓_春曉161#地塊人防工程消防電源監控系統的設計與應用...

涂志燕安科瑞電氣股份有限公司&#xff0c;上海 嘉定 201801&#xff1b;摘要&#xff1a;本文簡述了消防設備電源的組成原理&#xff0c;分析了消防設備電源監控系統在應用中的設計依據和相關規范。通過安科瑞消防設備電源監控系統在春曉161#地塊項目的實例介紹&#xff0c;闡…

大學慕課數據結構單元測試——華中科技大學

第一章緒論單元測試 一、單選(2分) 1、?___C__ 是數據的最小單位。 A.信息項 B.數據元素 C.數據項 D.表元素 2、?以下說法不正確的是 ___B___。 A.數據元素是數據的基本單位 B.數據項可由若干個數據元素構成 C.數據可由若干個數據元素構成 D.數據項是不可分割的最小…

gitlab應用

1.git config --global user.email "mybimt.com"  //注冊本地環境 2.ssh-keygen -t rsa -C "mybimt.com" //生成本機的key 3.在gitlab加入.ssh中生成的key //gitlab中注冊本機 4.git clone gitmy.git …

RocketMQ同步刷盤和異步刷盤

刷盤機制 同步刷盤和異步刷盤 在broker配置文件里修改參數配置是同步還是異步

vim模式下報錯E37: No write since last change No write since last change for buffer “ “

報錯如下圖所示&#xff1a; 網上的解決方法&#xff1a; 文件為只讀文件&#xff0c;無法修改。使用命令:w!強制存盤即可在vim模式下&#xff0c;鍵入以下命令&#xff1a;:w&#xff01;存盤后在使用vim命令檢查是否保存&#xff0c;如未保存&#xff0c;編輯后重復以上操作…

Linux中Shell中取消變量和特殊變量的筆記

1、取消變量取消變量也就是將變量從內存中釋放出去&#xff0c;可以使用unset 后面加變量名即可&#xff0c;當然函數的釋放同樣可以采用該方式處理。比如&#xff1a;name"123"echo ${name}輸出&#xff1a;123unset nameecho ${name}輸出&#xff1a;#取消函數示例…

光華科技光刻膠_【收藏】6天5板!21只光刻膠概念(名單)“出爐”!

連板數量21家中迪投資5板&#xff0c;寧波聯合 神馳機電 神馬電力4板&#xff0c;漢纜股份 華盛昌 浙江鼎力3板&#xff0c;海航投資世聯行 飛龍股份 安潔科技 京威股份 三豐智能 容大感光 晶瑞股份 奧飛數據 光大嘉寶 電子城博天環境 兆易創新 聚辰股份2板二、科技股&#xff…

20159302 《網絡攻擊與防范》第四周學習總結

本節學習內容為網絡攻擊環境的配置。在此過程中&#xff0c;我們至少需要一臺靶機&#xff0c;一臺攻擊機。在此選用windows server 2000為靶機&#xff0c;kali系統為攻擊機。 一、系統的安裝 根據之前發布的kali系統的安裝過程&#xff0c;依據此流程進行windows server的安裝…

智能家居項目開發準備工作

智能家居功能細節拆分&#xff1a; 控制端支持語音設備的輸入&#xff08;用到之前所學習的LD3320語音識別模塊&#xff09;或者是socket客戶端&#xff08;這個客戶端可以是ftp項目的客戶端也可以是Android的app&#xff09;&#xff0c;主控芯片是樹莓派&#xff0c;既接收語…

catia曲面掃掠命令詳解_Mastercam快捷鍵命令,附中英文功能講解!值得收藏!

組合鍵式快捷鍵功能快 捷 鍵功能Alt 0設置Z向控制深度Alt 1設置繪制圖形的顏色Alt 2設置當前層Alt 3與Alt 2功能相同Alt 4設置刀具面(Tplane)Alt 5設置繪圖面(Cplane)Alt 6設置視圖面(Gview)Alt A進入自動存文件對話框快 捷 鍵功能Alt B工具條的顯示/關閉Alt C選擇執…