韋東山嵌入式linux系列-第一個實驗

1?前言

筆者使用的是韋東山STM32MP157 Pro的板子,環境搭建部分按照說明文檔配置完成。配置橋接網卡實現板子、windows、ubuntu的通信,也在開發板掛載 Ubuntu 的NFS目錄?,這里就不再贅述了。

板子: 192.168.5.9
windows: 192.168.5.10
ubuntu: 192.168.5.11

在板子上執行

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs/ /mnt

2?開發板的第 1 APP 個實驗

hello.c

/*************************************************************************> File Name: hello.c> Author: Winter> Created Time: Sat 06 Jul 2024 04:44:00 AM EDT************************************************************************/#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>int main(int argc, char* argv[])
{if (argc >= 2)printf("Hello, %s!\n", argv[1]);elseprintf("Hello, world!\n");return 0;
}

在ubuntu上編譯運行

這個程序不是能直接在開發板上運行的,需要使用arm版的工具鏈

在ubuntu上使用開發板的工具鏈重新編譯,就可以在開發板上執行了

arm-buildroot-linux-gnueabihf-gcc hello.c

3?開發板的第 1 驅動實驗

為什么編譯驅動程序之前要先編譯內核?

驅動程序要用到內核文件內核/設備樹/其他驅動程序

比如驅動程序中這樣包含頭文件: #include <asm/io.h>,其中的 asm 是一個鏈接文件,指向 asm-arm 或 asm-mips,這需要先配置、編譯內核才會生成 asm 這個鏈接文件。

編譯驅動時用的內核、開發板上運行到內核,要一致放到板子上

開發板上運行到內核是出廠時燒錄的,你編譯驅動時用的內核是你自己編譯的,這兩個內核不一致時會導致一些問題。所以我們編譯驅動程序前,要把自己編譯出來到內核放到板子上去,替代原來的內核。

更換板子上的內核后,板子上的其他驅動也要更換編譯測試第一個驅動程序

板子使用新編譯出來的內核時,板子上原來的其他驅動也要更換為新編譯出來的。所以在編譯我們自己的第 1 個驅動程序之前,要先編譯內核、模塊,并且放到板子上去

3.1?編譯內核

不同的開發板對應不同的配置文件, 配置文件位于內核源碼arch/arm/configs/目錄。 kernel 的編譯過程如下:

cd 100ask_stm32mp157_pro-sdk/Linux-5.4/
make 100ask_stm32mp157_pro_defconfig

編譯內核

make uImage LOADADDR=0xC2000040 -j10

等待,結果如下

編譯設備樹

make dtbs

編譯完成后, 在 arch/arm/boot 目錄下生成 uImage 內核文件, 在arch/arm/boot/dts 目錄下生成設備樹的二進制文件 stm32mp157c-100ask-512d-v1.dtb。把這 2 個文件復制到/home/book/nfs_rootfs 目錄下備用

cp arch/arm/boot/uImage ~/nfs_rootfs/
cp arch/arm/boot/dts/stm32mp157c-100ask-512d-v1.dtb ~/nfs_rootfs/

3.2?編譯安裝內核模塊

進入內核源碼目錄后,就可以編譯內核模塊了:

cd /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4
make ARCH=arm CROSS_COMPILE=arm-buildroot-linux-gnueabihf- modules -j10

內核模塊編譯完成后如圖

安裝內核模塊到 Ubuntu 某個目錄下備用

可以先把內核模塊安裝到 nfs 目錄(/home/book/nfs_rootfs)。注意: 后面會使用 tree 命令查看目錄結構, 如果提示沒有該命令, 需要執行以下命令安裝 tree 命令:

sudo apt install tree

把模塊安裝在 nfs 目錄“ /home/book/nfs_rootfs/” 下

make ARCH=arm INSTALL_MOD_PATH=/home/book/nfs_rootfs INSTALL_MOD_STRIP=1 modules_install

安裝好驅動后的/home/book/nfs_rootfs/目錄結構如圖

tree /home/book/nfs_rootfs/

3.3 安裝內核和模塊到開發板上

假設:在 Ubuntu 的/home/book/nfs_rootfs 目錄下, 已經有了 zImage、dtb 文件,并且有 lib/modules 子目錄(里面含有各種模塊)。 接下來要把這些文件復制到開發板上。假設 Ubuntu IP 為 192.168.5.11,在開發板上執行以下命令:

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
mount /dev/mmcblk2p2 /boot
cp /mnt/uImage /boot					# 內核
cp /mnt/*.dtb /boot						# 設備樹
cp /mnt/lib/modules /lib -rfd			# 模塊
sync
reboot

后面#是注釋,不用粘上去

最后重啟開發板,它就使用新的 zImage、 dtb、模塊了

這里有個問題,圖中標出來的地方,問題不大,參考:vmmcsd_fixed: disabling 自動彈出 - STM32MP157_PRO - 嵌入式開發問答社區

3.4?第一個驅動

怎么編寫驅動程序

① 確定主設備號,也可以讓內核分配

② 定義自己的 file_operations 結構體

③ 實現對應的 drv_open/drv_read/drv_write 等函數,填入 file_operations 結構體

④ 把 file_operations 結構體告訴內核: register_chrdev

⑤ 誰來注冊驅動程序啊?得有一個入口函數:安裝驅動程序時,就會去調用這個入口函數

⑥ 有入口函數就應該有出口函數:卸載驅動程序時,出口函數調用unregister_chrdev

⑦ 其他完善:提供設備信息,自動創建設備節點: class_create,device_create

解釋:需要實現驅動程序對應的open/write/read等函數,將這些函數放在file_operations 結構體里面,再將這個結構體注冊到內核里面(register_chrdev函數),注冊到什么地方呢,由主設備號區分(類似一個數組chrdevs[主設備號])。入口函數調用注冊函數;有入口就有出口函數(卸載驅動程序)。

應用程序調用open函數打開一個文件"/dev/xxx",最終得到一個整數(文件描述符),這個整數對應內核中的一個結構體struct file

lag、mode就會保存在這個結構體的這兩個參數中,還有一個f_op屬性,里面有read/write/open等函數

應用程序打開某個設備節點時/dec/xxx,會根據設備節點的主設備號,在內核的chrdevs數組中,找到file_operation結構體,這個結構體中提供了驅動程序的read/write/open等函數。

參考 driver/char 中的程序,包含頭文件,寫框架,傳輸數據:

  • 驅動中實現 open, read, write, release, APP 調用這些函數時,都打印內核信息

  • APP 調用 write 函數時,傳入的數據保存在驅動中

  • APP 調用 read 函數時,把驅動中保存的數據返回給 APP

放到ubuntu的/home/book/nfs_rootf/01hello_drv下

hello_drv.c

主要還是圍繞

① 確定主設備號,也可以讓內核分配

② 定義自己的 file_operations 結構體

③ 實現對應的 drv_open/drv_read/drv_write 等函數,填入 file_operations 結構體

④ 把 file_operations 結構體告訴內核: register_chrdev

⑤ 誰來注冊驅動程序啊?得有一個入口函數:安裝驅動程序時,就會去調用這個入口函數

⑥ 有入口函數就應該有出口函數:卸載驅動程序時,出口函數調用unregister_chrdev

⑦ 其他完善:提供設備信息,自動創建設備節點: class_create,device_create

/*************************************************************************> File Name: hello.drv.c> Author: Winter> Created Time: Sun 07 Jul 2024 12:35:19 AM EDT************************************************************************/#include <linux/module.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>// 1確定主設備號,也可以讓內核分配
static int major = 0;                           // 讓內核分配
static char kernel_buf[1024];           // 保存應用程序的數據
static struct class *hello_class;#define MIN(a, b) (a < b ? a : b)// 3 實現對應的 drv_open/drv_read/drv_write 等函數,填入 file_operations 結構體
static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 將kernel_buf區的數據拷貝到用戶區數據buf中,即從內核kernel_buf中讀數據err = copy_to_user(buf, kernel_buf, MIN(1024, size));return MIN(1024, size);
}static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 把用戶區的數據buf拷貝到內核區kernel_buf,即向寫到內核kernel_buf中寫數據err = copy_from_user(kernel_buf, buf, MIN(1024, size));return MIN(1024, size);
}static int hello_drv_open (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}static int hello_drv_close (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}// 2定義自己的 file_operations 結構體
static struct file_operations hello_drv = {.owner = THIS_MODULE,.open = hello_drv_open,.read = hello_drv_read,.write = hello_drv_write,.release = hello_drv_close,
};// 4把 file_operations 結構體告訴內核: register_chrdev
// 5誰來注冊驅動程序啊?得有一個入口函數:安裝驅動程序時,就會去調用這個入口函數
static int __init hello_init(void)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 注冊hello_drv,返回主設備號major = register_chrdev(0, "hello", &hello_drv);  /* /dev/hello */// 創建classhello_class = class_create(THIS_MODULE, "hello_class");err = PTR_ERR(hello_class);if (IS_ERR(hello_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "hello");return -1;}// 創建devicedevice_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */return 0;
}// 6有入口函數就應該有出口函數:卸載驅動程序時,出口函數調用unregister_chrdev
static void __exit hello_exit(void)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);device_destroy(hello_class, MKDEV(major, 0));class_destroy(hello_class);// 卸載unregister_chrdev(major, "hello");
}// 7其他完善:提供設備信息,自動創建設備節點: class_create,device_create
module_init(hello_init);
module_exit(hello_exit);MODULE_LICENSE("GPL");

測試程序:hello_drv_test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>/** ./hello_drv_test -w abc* ./hello_drv_test -r*/
int main(int argc, char **argv)
{int fd;char buf[1024];int len;/* 1. 判斷參數 */if (argc < 2) {printf("Usage: %s -w <string>\n", argv[0]);printf("       %s -r\n", argv[0]);return -1;}/* 2. 打開文件 */fd = open("/dev/hello", O_RDWR);if (fd == -1){printf("can not open file /dev/hello\n");return -1;}/* 3. 寫文件或讀文件 */if ((0 == strcmp(argv[1], "-w")) && (argc == 3)){len = strlen(argv[2]) + 1;len = len < 1024 ? len : 1024;write(fd, argv[2], len);}else{len = read(fd, buf, 1024);		buf[1023] = '\0';printf("APP read : %s\n", buf);}close(fd);return 0;
}

Makefile:換成自己的內核

# 1. 使用不同的開發板內核時, 一定要修改KERN_DIR
# 2. KERN_DIR中的內核要事先配置、編譯, 為了能編譯內核, 要先設置下列環境變量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的開發板不同的編譯器上述3個環境變量不一定相同,
#       請參考各開發板的高級用戶使用手冊KERN_DIR = /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4all:make -C $(KERN_DIR) M=`pwd` modules$(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.cclean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f hello_drv_testobj-m   += hello_drv.o

編譯

因為重新編譯安裝了內核,所以要在板子上重新掛載

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs/ /mnt

裝載驅動程序

insmod hello_drv.ko

cat /proc/devices
lsmod

執行測試程序

4?Hello 驅動中的一些補充知識

4.1?module_init/module_exit 的實現

一個驅動程序有入口函數、出口函數,代碼如下

module_init(hello_init);
module_exit(hello_exit);

驅動程序可以被編進內核里,也可以被編譯為 ko 文件后手工加載。 對于這兩種形式,“ module_init/module_exit”這 2 個宏是不一樣的。 在內核文件“ include\linux\module.h”中可以看到這 2 個宏:

/*** module_init() - driver initialization entry point* @x: function to be run at kernel boot time or module insertion** module_init() will either be called during do_initcalls() (if* builtin) or at module insertion time (if a module).  There can only* be one per module.*/
#define module_init(x)	__initcall(x);/*** module_exit() - driver exit entry point* @x: function to be run when driver is removed** module_exit() will wrap the driver clean-up code* with cleanup_module() when used with rmmod when* the driver is a module.  If the driver is statically* compiled into the kernel, module_exit() has no effect.* There can only be one per module.*/
#define module_exit(x)	__exitcall(x);

具體的

/* Each module must use one module_init(). */
#define module_init(initfn)					\static inline initcall_t __maybe_unused __inittest(void)		\{ return initfn; }					\int init_module(void) __copy(initfn) __attribute__((alias(#initfn)));/* This is only required if you want to be unloadable. */
#define module_exit(exitfn)					\static inline exitcall_t __maybe_unused __exittest(void)		\{ return exitfn; }					\void cleanup_module(void) __copy(exitfn) __attribute__((alias(#exitfn)));

編譯驅動程序時,我們執行“ make modules”這樣的命令,它在編譯 c 文件時會定義宏 MODULE

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

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

相關文章

【linux】服務器創建RAID1

【linux】服務器創建RAID1 文章目錄 【linux】服務器創建RAID1一、配置介紹raid介紹raid類型RAID 0:RAID 1:RAID 5:RAID 6:二、配置RAID硬件RAID:軟件RAID:三、軟件配置RAID1(以linux為例)1.先進入管理員模式2.安裝mdadm工具3.創建raid1數組4.查看RAID數組狀態5.格式化和掛載…

機械鍵盤如何挑選

機械鍵盤的選擇是一個關鍵的決策&#xff0c;因為它直接影響到我們每天的打字體驗。在選擇機械鍵盤時&#xff0c;有幾個關鍵因素需要考慮。首先是鍵盤的鍵軸類型。常見的鍵軸類型包括藍軸、紅軸、茶軸和黑軸等。不同的鍵軸類型具有不同的觸發力、觸發點和聲音。藍軸通常具有明…

神經網絡和安全結合:一種基于神經網絡的智能攻擊檢測與防御系統;構建攻擊行為預測模型

目錄 神經網絡和安全結合 摘要 引言 理論基礎 技術實現與創新點 實驗驗證 結論與展望 一種基于神經網絡的智能攻擊檢測與防御系統 一、系統概述 二、主要功能 三、技術特點 四、應用前景 構建攻擊行為預測模型 一、構建攻擊行為預測模型的步驟 1. 數據收集 2. …

單鏈表的學習與基礎運用p

當我們在實際做項目&#xff0c;或者是自主開發一點小東西的時候&#xff0c;往往會儲存一些數據&#xff0c;有時候我們需要添加這些數據&#xff0c;有時候需要刪除&#xff0c;而有時候&#xff0c;僅僅只需要查找到就行。鏈表中的每一個節點都是一個獨立開辟的空間&#xf…

聚類分析方法(一)

目錄 一、聚類分析原理&#xff08;一&#xff09;聚類分析概述&#xff08;二&#xff09;聚類的數學定義&#xff08;三&#xff09;簇的常見類型&#xff08;四&#xff09;聚類框架及性能要求&#xff08;五&#xff09;簇的距離 二、劃分聚類算法&#xff08;一&#xff0…

Java 有什么必看的書?

Java必看經典書有這兩本&#xff1a; 1、Java核心技術速學版&#xff08;第3版&#xff09; 經典Java開發基礎書CoreJava速學版本&#xff01;Java入門優選書籍&#xff0c;更新至Java17&#xff0c;內容皆是精華&#xff0c;讓Java學習更簡單&#xff0c;讓Java知識應用更快速…

【Linux】什么是進程間通信?方式有哪些?本質理解?

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;個人主頁 &#xff1a;阿然成長日記 …

使用 ChronicleMap 擴展高性能內存緩存

1.擴展內存緩存的挑戰 我們用于與各種程序化和需求方平臺 (DSP) 集成的應用程序之一是低延遲、高吞吐量的基于 JVM 的應用程序。這是 付款憑單&#xff08;DV&#xff09;付前前驗證解決方案的核心組件。自多年前成功推出此解決方案以來&#xff0c;我們不斷添加多項關鍵功能&…

【ChatGPT】全面解析 ChatGPT:從起源到未來

ChatGPT 是由 OpenAI 開發的一個基于 GPT&#xff08;Generative Pre-training Transformer&#xff09;架構的聊天機器人。通過自然語言處理&#xff08;NLP&#xff09;技術&#xff0c;ChatGPT 能夠理解和生成語言&#xff0c;與人類進行對話。本文將深入探討其起源、發展、…

SpringSecurity源碼分析-過濾器鏈是如何植入到spring中的

SpringSecurity源碼分析-過濾器鏈是如何植入到spring中的 一切的源頭都是因為在web.xml中配置了這樣一個Filter <!--security--><filter><filter-name>springSecurityFilterChain</filter-name><filter-class>org.springframework.web.filter.…

NoSQL 之 Redis 集群部署

前言&#xff1a; &#xff08;1&#xff09;主從復制&#xff1a;主從復制是高可用Redis的基礎&#xff0c;哨兵和集群都是在主從復制基礎上實現高可用 的。主從復制主要實現了數據的多機備份&#xff0c;以及對于讀操作的負載均衡和簡單的故障恢復。缺陷&#xff1a; 故障…

vue3+antd 實現文件夾目錄右鍵菜單功能

原本的目錄結構&#xff1a; 右鍵菜單&#xff1a; 點擊菜單以后會觸發回調&#xff1a; 完整的前端代碼&#xff1a; <template><a-directory-treev-model:expandedKeys"expandedKeys"v-model:selectedKeys"selectedKeys"multipleshow-li…

在 Docker 容器中運行 Vite 開發環境,有這兩個問題要注意

容器化開發給我們帶來了很多便捷&#xff0c;但是在開發環境下也有一些問題要注意&#xff0c;如果不解決這些問題&#xff0c;你的開發體驗不會很好。 容器啟動正常&#xff0c;卻無法訪問 我們用 Docker 啟動一個 Vite Vue3 項目的開發環境后&#xff0c;發現端口日志一切…

計算機如何存儲浮點數

浮點數組成 在計算機中浮點數通常由三部分組成&#xff1a;符號位、指數位、尾數位。IEEE-754中32位浮點數如下&#xff1a; 上圖32bit浮點數包含1bit的符號位&#xff0c;8比特的指數位和23bit的尾數位。對于一個常規浮點數&#xff0c;我們來看看它是如何存儲和計算的。這里…

conda env pip install error:No space left on device

conda 環境 pip install error&#xff1a;No space left on device 文章目錄 conda 環境 pip install error&#xff1a;No space left on device現象1 實驗2 分析和解決辦法 現象 非root用戶的服務器&#xff0c;需要安裝環境&#xff0c;安裝的環境超過2GB sudo pip insta…

醫療機器人中的具身智能進展——自主超聲策略模型的任務編碼和局部探索

醫療機器人一直是具身智能的研究熱點。醫學圖像、醫療觸診、血壓血氧、心率脈搏和生物電信號等多模態生物醫學信息&#xff0c;不斷豐富著醫療機器人的感知范疇。 自主超聲 “自主超聲”屬于具身智能醫療機器人領域中話題度較高的研究方向。作為臨床檢查的重要手段之一&#…

線性系統理論及應用GUI設計及仿真

目錄 1.控制系統的狀態空間模型 1.1.狀態空間模型 1.2 傳遞函數模型 1.3 傳遞函數轉換為狀態空間模型 1.4.狀態空間模型轉換為傳遞函數 1.5.狀態空間模型轉化為約當標準型 2.線性系統的時域分析 2.1.矩陣指數函數的計算 2.2.線型定常連續系統的狀態空間模型求解 3.線…

ubuntu24.04按關鍵字卸載不需要的apt包

使用的時候發現一個imagemagic無法正常讀取文件&#xff0c;試圖卸載 man apt經過嘗試后&#xff0c;發現list的一個神奇關鍵字&#xff0c;用來顯示已安裝的軟件包 sudo apt list --installed | grep image按image關鍵字過濾&#xff1a; 之后按軟件名卸載即可 sudo apt pu…

開關電源——調制模式和工作模式

一、開關電源的調制模式 開關電源作為一種廣泛應用于電子設備中&#xff0c;用于將一定電壓和電流轉換為另一種電壓和電流的技術&#xff0c;以下是開關電源三種常見的調制模式&#xff1a; 脈沖寬度調制&#xff08;Pulse Width Modulation&#xff09; 脈沖頻率調制&#xff…

上升與下降

目錄 開頭程序程序的流程圖關于上升與下降的動畫(程序的效果)結尾 開頭 大家好&#xff0c;我叫這是我58。今天&#xff0c;我們要來看一個關于上升與下降的動畫和這個動畫相關的內容。 程序 #define _CRT_SECURE_NO_WARNINGS 1 #define HIGH 10 #include <stdio.h> #…