最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

【i.MX6ULL】驅動開發(fā)1——字符設備開發(fā)模板

2021-09-01 01:00 作者:碼農(nóng)愛學習-B站  | 我要投稿

之前的幾篇文章(從i.MX6ULL嵌入式Linux開發(fā)1-uboot移植初探起),介紹了嵌入式了Linux的系統(tǒng)移植(uboot、內核與根文件系統(tǒng))以及使用MfgTool工具將系統(tǒng)燒寫到板子的EMMC中。

本篇開始介紹嵌入式Linux驅動開發(fā)。

內容較多,先看目錄:


Linux中的外設驅動可以分為三大類:字符設備驅動、塊設備驅動和網(wǎng)絡設備驅動。

  • 字符設備驅動:字符設備是能夠按照字節(jié)流(比如文件)進行讀寫操作的設備。字符設備最常見,從最簡單的點燈到I2C、SPI、音頻等都屬于字符設備驅動

  • 塊設備驅動:以存儲塊為基礎的設備驅動,如EMMC、NAND、SD卡等。對用戶而言,字符設備與塊設備的訪問方式?jīng)]有差別。

  • 網(wǎng)絡設備驅動:即網(wǎng)絡驅動,它同時具有字符設備和塊設備的特點,因為它是輸入輸出是有結構塊的(報文,包,幀),但它的塊的大小又不是固定的。

2 Linux驅動基本原理

Linux中一切皆文件,驅動加載成功以后會在“/dev”目錄下生成一個相應的文件,應用程序通過對這個名為“/dev/xxx”的文件進行相應的操作即可實現(xiàn)對硬件的操作。

比如最簡單的點燈功能,會有/dev/led這樣的驅動文件,應用程序使用open函數(shù)來打開文件/dev/led,如果要點亮或關閉led,那么就使用write函數(shù)寫入開關值,如果要獲取led的狀態(tài),就用read函數(shù)從驅動中讀取相應的狀態(tài),使用完成以后使用close函數(shù)關閉/dev/led這個文件。

2.1 Linux軟件分層結構

Linux軟件從上到下可以分層4層結構,以控制LED為例:

  • 應用層:應用程序使用庫提供的open函數(shù)打開LED設備

  • :庫根據(jù)open函數(shù)傳入的參數(shù)執(zhí)行“swi”指令,進而引起CPU異常,進入內核

  • 內核:內核的異常處理函數(shù)根據(jù)傳入的參數(shù)找到對應的驅動程序,返回文件句柄給庫,進而返回給應用層

  • 應用層得到文件句柄后,使用庫提供的write或ioctl發(fā)出控制指令

  • 庫根據(jù)write或ioctl函數(shù)傳入的參數(shù)執(zhí)行“swi”指令,進入內核

  • 內核的異常處理函數(shù)根據(jù)傳入的參數(shù)找到對應的驅動程序

  • 驅動:驅動程序控制硬件,點亮LED

應用程序運行在用戶空間,而Linux驅動屬于內核的一部分,因此驅動運行于內核空間。當應用層通過open函數(shù)打開/dev/led 這個驅動時,因用戶空間不能直接操作內核,因此會使用“系統(tǒng)調用”的方法來從用戶空間“陷入”到內核空間,實現(xiàn)對底層驅動的操作。

比如應用程序調用了open這個函數(shù),則在驅動程序中也應有一個對應的open的函數(shù)。

2.2 Linux內核驅動操作函數(shù)

每一個系統(tǒng)調用,在驅動中都有與之對應的一個驅動函數(shù),在Linux內核文件include/linux/fs.h中有個file_operations結構體,就是Linux內核驅動操作函數(shù)集合:

其中有關字符設備驅動開發(fā)中常用的函數(shù)有:

  • owner:擁有該結構體的模塊的指針,一般設置為THIS_MODULE。

  • llseek函數(shù):用于修改文件當前的讀寫位置。

  • read函數(shù):用于讀取設備文件。

  • write函數(shù):用于向設備文件寫入(發(fā)送)數(shù)據(jù)。

  • poll函數(shù):是個輪詢函數(shù),用于查詢設備是否可以進行非阻塞的讀寫。

  • unlocked_ioctl函數(shù):提供對于設備的控制功能, 與應用程序中的 ioctl 函數(shù)對應。

  • compat_ioctl函數(shù):與 unlocked_ioctl功能一樣,區(qū)別在于在 64 位系統(tǒng)上,32 位的應用程序調用將會使用此函數(shù)。在 32 位的系統(tǒng)上運行 32 位的應用程序調用的是unlocked_ioctl。

  • mmap函數(shù):用于將將設備的內存映射到進程空間中(也就是用戶空間),一般幀緩沖設備會使用此函數(shù), 比如 LCD 驅動的顯存,將幀緩沖(LCD 顯存)映射到用戶空間中以后應用程序就可以直接操作顯存了,這樣就不用在用戶空間和內核空間之間來回復制。

  • open函數(shù):用于打開設備文件。

  • release函數(shù):用于釋放(關閉)設備文件,與應用程序中的 close 函數(shù)對應。

  • fasync函數(shù):用于刷新待處理的數(shù)據(jù),用于將緩沖區(qū)中的數(shù)據(jù)刷新到磁盤中。

  • aio_fsync函數(shù):與fasync功能類似,只是 aio_fsync 是異步刷新待處理的


2.3 Linux驅動運行方式

Linux 驅動有兩種運行方式:

  • 將驅動編譯進Linux內核中, 這樣當Linux內核啟動的時候就會自動運行驅動程序。

  • 將驅動編譯成模塊(擴展名為 .ko), 在Linux內核啟動以后使用“insmod”命令加載驅動模塊。

在驅動開發(fā)階段一般都將其編譯為模塊,不需要編譯整個Linux代碼,方便調試驅動程序。當驅動開發(fā)完成后,根據(jù)實際需要,可以選擇是否將驅動編譯進Linux內核中。

2.4 Linux設備號

2.4.1 設備號的組成

Linux中每個設備都有一個設備號,設備號由主設備號和次設備號兩部分組成。

  • 主設備號:表示某一個具體的驅動

  • 次設備號:表示使用這個驅動的各個設備

Linux 提供了名為dev_t的數(shù)據(jù)類型表示設備號,其本質是32位的unsigned int數(shù)據(jù)類型,其中高12位為主設備號,低20位為次設備號,因此Linux中主設備號范圍為0~4095

在文件include/linux/kdev_t.h中提供了幾個關于設備號操作的宏定義:

  • MINORBITS:表示次設備號位數(shù),一共20位

  • MINORMASK:表示次設備號掩碼

  • MAJOR:用于從dev_t中獲取主設備號,將dev_t右移20位即可

  • MINOR:用于從dev_t中獲取次設備號,取dev_t的低20位的值即可

  • MKDEV:用于將給定的主設備號和次設備號的值組合成dev_t類型的設備號

2.4.2 主設備號的分配

主設備號的分配包括靜態(tài)分配和動態(tài)分配

  • 靜態(tài)分配需要手動指定設備號,并且要注意不能與已有的重復,一些常用的設備號已經(jīng)被Linux內核開發(fā)者給分配掉了,使用“cat /proc/devices”命令可查看當前系統(tǒng)中所有已經(jīng)使用了的設備號。

  • 動態(tài)分配是在注冊字符設備之前先申請一個設備號,系統(tǒng)會自動分配一個沒有被使用的設備號, 這樣就避免了沖突。在卸載驅動的時候釋放掉這個設備號即可。

設備號的申請函數(shù):

設備號的釋放函數(shù):

3 字符設備驅動開發(fā)模板

3.1 加載與卸載

在編寫驅動的時候需要注冊模塊加載和卸載這兩種函數(shù):

module_init(xxx_init); ? //注冊模塊加載函數(shù)
module_exit(xxx_exit); ? //注冊模塊卸載函數(shù)

  • 用來向Linux內核注冊一個模塊加載函數(shù),參數(shù)xxx_init就是需要注冊的具體函數(shù),當使用 “insmod” 命令加載驅動的時候,xxx_init這個函數(shù)就會被調用。

  • 用來向Linux內核注冊一個模塊卸載函數(shù),參數(shù)xxx_exit就是需要注冊的具體函數(shù),當使 用“rmmod”命令卸載具體驅動的時候 xxx_exit函數(shù)就會被調用。

字符設備驅動模塊加載和卸載模板如下所示:

驅動編譯完成以后擴展名為.ko, 有兩種命令可以加載驅動模塊:

  • :最簡單的模塊加載命令,用于加載指定的.ko模塊,此命令不能解決模塊的依賴關系

  • :該命令會分析模塊的依賴關系,將所有的依賴模塊都加載到內核中,因此更智能

    modprobe 命令默認會去目錄中查找模塊(自制的根文件系統(tǒng)沒有這個目錄,需要手動創(chuàng)建)

卸載驅動也有兩種命令:

  • :例如使用來卸載 drv.ko這一個模塊

  • :該命令除了卸載指定的驅動,還卸載其所依賴的其他模塊,若這些依賴模塊還在被其它模塊使用,就不能使用 modprobe來卸載驅動模塊?。。?/p>


3.2 注冊與注銷

對于字符設備驅動而言,當驅動模塊加載成功以后需要注冊字符設備,同樣,卸載驅動模塊的時候也需要注銷掉字符設備。

字符設備的注冊函數(shù)原型如下所示:


字符設備的注銷函數(shù)原型如下所示:


一般字符設備的注冊在驅動模塊的入口函數(shù) xxx_init 中進行,字符設備的注銷在驅動模塊的出口函數(shù) xxx_exit 中進行。

注:選擇沒有被使用的主設備號,可輸入命令“cat /proc/devices”來查看當前已經(jīng)被使用掉的設備號


3.3 實現(xiàn)設備的具體操作函數(shù)

file_operations 結構體就是設備的具體操作函數(shù)。

假設對chrtest這個設備有如下兩個要求:

  • 能夠實現(xiàn)打開和關閉操作:需要實現(xiàn) file_operations 中的openrelease這兩個函數(shù)

  • 能夠實現(xiàn)進行讀寫操作:需要實現(xiàn) file_operations 中的readwrite這兩個函數(shù)

首先是 打開(open)、讀取(read)、寫入(write)、釋放(release) 4個基本操作

然后是 驅動的入口(init)和出口(exit) 函數(shù):

3.4 添加LICENSE和作者信息

LICENSE是必須添加的,否則編譯時會報錯,作者信息可加可不加。

總結一下:

4 字符設備驅動開發(fā)實驗

下面以正點原子提供的教程中的chrdevbase這個虛擬設備為例,完整的編寫一個字符設備驅動模塊。chrdevbase不是實際存在的一個設備,只是為了學習字符設備的開發(fā)的流程。

4.1 程序編寫

需要分別編寫驅動程序應用程序。

注:為了區(qū)分兩個程序的打印信息,在驅動程序的打印前都添加“[BSP]”標識,在應用程序的打印前都添加”[APP]“標識。

4.1.1 編寫驅動程序

  • 一些定義

  • 打開、關閉、讀取、寫入


  • 驅動加載與注銷

  • 最后的LIENSE與作者

4.1.2 編寫應用程序

這里把程序截取為3段分析,首先看開頭

主要是一些頭文件和main函數(shù)入口,調用main函數(shù)時需要傳入2個參數(shù)(實際是3個參數(shù),函數(shù)名本身是默認的第0個參數(shù),不需要手動指定),具體作用為:

  • 參數(shù)0:argv[0],函數(shù)名本身,這里不作用途

  • 參數(shù)1:argv[1],filename,這里不作用途

  • 參數(shù)2:argv[2],自定義的操作參數(shù),下面函數(shù)會講到,1為從驅動文件中讀取,2為向驅動文件中寫入數(shù)據(jù)

再來看具體操作:

最后是關閉設備

4.2 程序編譯

4.2.1 編譯驅動程序

?編譯驅動,即編譯chrdevbase.c這個文件為.ko 模塊,使用Makefile來編譯,先創(chuàng)建Makefile:

各行含義:

  • KERNELDIR:開發(fā)板所使用的Linux內核源碼目錄

  • CURRENT_PATH:當前路徑,通過運行“pwd”命令獲取

  • obj-m:將 chrdevbase.c 這個文件編譯為chrdevbase.ko模塊

  • 具體的編譯命令:后面的modules表示編譯模塊,-C 表示切換工作目錄到KERNERLDIR目錄,M表示模塊源碼目錄

輸入“make”命令即可編譯,編譯后會出現(xiàn)許多編譯文件

注:若直接make編譯報如下錯誤,是因為kernel中沒有指定編譯器和架構,使用了默認的x86平臺編譯報錯。

修改Kernel工程的頂層Makefile,直接定義ARCH和CROSS_COMPILE 這兩個的變量值為 arm 和 arm-linux-gnueabihf-

(內核篇的介紹見:i.MX6ULL嵌入式Linux開發(fā)3-Kernel移植)


4.2.2 編譯應用程序

編譯應用程序不需要內核文件參與,只有一個文件就能編譯,因此直接輸入指令進行編譯:

arm-linux-gnueabihf-gcc chrdevbaseAPP.c -o chrdevbaseAPP

編譯會生chrdevbaseAPP,它是32位LSB格式的ARM版本可執(zhí)行文件

4.3 測試

上一篇文章(i.MX6ULL嵌入式Linux開發(fā)6-系統(tǒng)燒寫到eMMC與遇到的坑!)已經(jīng)實現(xiàn)了系統(tǒng)移植的打包燒錄工作,系統(tǒng)已經(jīng)燒錄的EMMC中了。這次我們就直接在這個基礎上進行實驗。

4.3.1 創(chuàng)建驅動模塊目錄

加載驅動模塊,使用的modprobe命令,會從特定的目錄下尋找文件。比如開發(fā)板使用的是4.1.15版的Linux內核 ,則是“/lib/modules/4.1.15”這個目錄,這個目錄一般是沒有的,需要根據(jù)Linux內核的版本自己創(chuàng)建。

注意這是開發(fā)板的文件系統(tǒng)中的路徑,可以通過串口連接進入開發(fā)板,通過linux指令創(chuàng)建該目錄。

4.3.2 發(fā)送文件到開發(fā)板(TFTP傳輸)

此次測試首先需要將ubuntu中編譯的文件傳輸?shù)桨遄?/strong>中運行,怎么傳輸呢?可以使用TFTP傳輸服務。

之前的文章i.MX6ULL嵌入式Linux開發(fā)2-uboot移植實踐)中已經(jīng)介紹了如何在ubuntu中搭建TFTP服務器

搭建好TFTP服務后,開始傳輸文件到開發(fā)板具體的傳輸步驟為:

  • 開發(fā)板連接網(wǎng)線,與ubuntu虛擬機處于同一局域網(wǎng)內

  • 確保ubuntu已安裝的TFTP服務,并設置了TFTP服務文件夾

  • 將ubuntu中編譯好的文件復制到ubuntu的TFTP服務文件夾中?。?!

    mv chrdevbaseAPP ~/myTest/tftpboot/ mv chrdevbase.ko ~/myTest/tftpboot/

    注:編譯完程序,在傳輸?shù)桨遄又埃欢ㄒ浀冒盐募葟椭频絋FTP文件夾中,否則板子獲取到的可能是TFTP文件夾中的舊文件。

  • 開發(fā)板的串口中通過如下指令來將ubuntu中的文件傳輸?shù)介_發(fā)板中

    cd /lib/modules/4.1.15 ? /*確保在要下載文件的目錄中,若已在,則忽略*/ tftp -g -r chrdevbaseAPP 192.168.5.101 /*獲取chrdevbaseAPP文件*/ tftp -g -r chrdevbase.ko 192.168.5.101 /*獲取chrdevbase.ko文件*/

    這里的代表get,即下載文件,代表remote file,即遠程主機的文件名,然后是要下載的文件名,最后的遠程主機ubuntu的IP地址。

    輸入該指令后,可以看到文件傳輸進度,如下圖:

4.3.3 開始測試

驅動文件chrdevbase.ko和應用文件chrdevbaseAPP傳輸?shù)桨遄又械?lib/modules/4.1.15目錄后,就可以測試了。

首先使用命令來加載驅動,然后使用查看當前的驅動(只有一個我們剛加載的字符驅動),再使用使用指令查看devices 信息,確認系統(tǒng)中是否已經(jīng)列舉了該設備,3條指令如下:

insmod chrdevbase.ko ?lsmod cat /proc/devices

具體是輸出信息:

可以看出,系統(tǒng)中存在chrdevbase設備,主設備號為程序中設定的200。

驅動加載后,還要在/dev目錄下創(chuàng)建一個對應的設備節(jié)點文件(應用程序就是通過該節(jié)點文件實現(xiàn)對設備的操作)。

輸入如下2條命令創(chuàng)建/dev/chrdevbase這個設備節(jié)點文件,并查看結果:

mknod /dev/chrdevbase c 200 0 ?ls /dev/chrdevbase -l

至此,字符設備驅動已經(jīng)加載完成,可以測試我們的應用程序了,也就是

按照上面程序的設定,1是讀,2是寫:

./chrdevbaseAPP /dev/chrdevbase 1 ? ./chrdevbaseAPP /dev/chrdevbase 2

  • 先來看“讀測試”,注意要給chrdevbaseAPP可執(zhí)行的權限,否則無法運行。

圖中下部是程序輸出信息,但似乎只有BSP驅動程序的的輸出,沒有APP應用程序的輸出,應該是內核打印printk與應用的打印printf沖突了,導致APP的打印被擠掉了。

  • 再來看“寫測試'',同樣也是只有BSP的打印

4.3.4 打印沖突問題規(guī)避

對于打印沖突問題,我們可以先在每個printf前后加個sleep(1)的1秒延時,這樣可以先避免打印沖突。

增加延時后再次測試,打印正常:

測試完,最后是rmmod命令卸載模塊:

5 總結

本篇介紹了嵌入式Linux驅動開發(fā)中的基礎驅動——字符驅動開發(fā)的基本模式,使用了一個虛擬的字符設備驅動進行測試,了解驅動程序與應用程序之間的調用關系。


【i.MX6ULL】驅動開發(fā)1——字符設備開發(fā)模板的評論 (共 條)

分享到微博請遵守國家法律
泽州县| 河北省| 靖宇县| 万盛区| 平和县| 揭东县| 肥西县| 鄢陵县| 南漳县| 施秉县| 晋中市| 榕江县| 荔浦县| 黄骅市| 邹城市| 北流市| 洛浦县| 广宗县| 大埔区| 石楼县| 通辽市| 富蕴县| 江城| 丰宁| 东源县| 三明市| 灵宝市| 二手房| 志丹县| 六枝特区| 龙川县| 凤翔县| 静海县| 南平市| 含山县| 阜宁县| 福贡县| 建宁县| 桃园县| 偃师市| 贵州省|