韋東山嵌入式Liunx入門驅動開發五

文章目錄

      • 一、驅動程序基石
        • 1-1 休眠與喚醒
        • 1-2 POLL機制
        • 1-3 異步通知
          • (1) 異步通知程序解析
          • (2) 異步通知機制內核代碼詳解
        • 1-4 阻塞與非阻塞
        • 1-5 定時器
          • (1) 內核函數
          • (2) 定時器時間單位
        • 1-6 中斷下半部 tasklet

本人學習完韋老師的視頻,因此來復習鞏固,寫以筆記記之。
韋老師的課比較難,第一遍不知道在說什么,但是堅持看完一遍,再來復習,基本上就水到渠成了。
看完視頻復習的同學觀看最佳!
基于 IMX6ULL-PRO
參考視頻 Linux快速入門到精通視頻
參考資料:01_嵌入式Linux應用開發完全手冊V5.1_IMX6ULL_Pro開發板.pdf

一、驅動程序基石

1-1 休眠與喚醒

當應用程序必須等待某個事件發生,比如必須等待按鍵被按下時, 可以使用休眠-喚醒機制
① APP調用read等函數試圖讀取數據,比如讀取按鍵;
② APP進入內核態,也就是調用驅動中的對應函數,發現有數據則復制到用戶空間并馬上返回;
③ 如果APP在內核態,也就是在驅動程序中發現沒有數據,則APP休眠;
④ 當有數據時,比如當按下按鍵時,驅動程序的中斷服務程序被調用,它會記錄數據、喚醒APP
⑤ APP繼續運行它的內核態代碼,也就是驅動程序中的函數,復制數據到用戶空間并馬上返回。
在這里插入圖片描述
驅動框架
在這里插入圖片描述
休眠,直到condition 為真;休眠期間是可被打斷的,可以被信號打斷

wait_event_interruptible(wq, condition)

喚醒wq隊列中狀態為“ TASK_INTERRUPTIBLE ”的線程,只喚醒其中的一個線程

wake_up_interruptible(wq)

要休眠的線程,放在wq 隊列里,中斷處理函數從wq隊列里把它取出來喚
醒。
① 初始化wq隊列
② 在驅動的read函數中,調用 wait_event_interruptible。它本身會判斷
event是否為 FALSE ,如果為FASLE表示無數據,則休眠。
當從wait_event_interruptible 返回后,把數據復制回用戶空間。
③ 在中斷服務程序里:設置event 為TRUE,并調用wake_up_interruptible 喚醒線程。

1-2 POLL機制

使用休眠喚醒的方式等待某個事件發生時,有一個缺點:等待的時間可能
很久
。我們可以加上一個超時時間,這時就可以使用poll機制。
① APP不知道驅動程序中是否有數據,可以先調用 poll函數查詢一下, poll函數可以傳入超時時間
② APP進入內核態,調用驅動程序的poll函數,有數據的話立刻返回;
③ 如果發現沒有數據時就休眠一段時間
④ 當有數據時,比如當按下按鍵時,驅動程序的中斷服務程序被調用,它會記錄數據、喚醒APP
⑤ 當超時時間到了之后,內核也會喚醒APP
⑥ APP根據poll函數的返回值就可以知道是否有數據,如果有數據就調用
read得到數據。

在這里插入圖片描述
drv_poll函數需要做的事
① 把當前線程掛入隊列wq:poll_wait
②返回設備狀態:drv_poll 要返回自己的當前狀態:P OLLIN | POLLRDNORM) 或 POLLOUT | POLLWRNORM) 。

static unsigned int gpio_key_drv_poll(struct file *fp, poll_table * wait)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);poll_wait(fp, &gpio_key_wait, wait);return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;}

button_test.c

int main(int argc, char **argv)
{int fd;int val;struct pollfd fds[1];int timeout_ms = 5000;int ret;/* 1. 判斷參數 */if (argc != 2) {printf("Usage: %s <dev>\n", argv[0]);return -1;}/* 2. 打開文件 */fd = open(argv[1], O_RDWR);if (fd == -1){printf("can not open file %s\n", argv[1]);return -1;}fds[0].fd = fd;fds[0].events = POLLIN;while (1){/* 3. 讀文件 */ret = poll(fds, 1, timeout_ms);if((ret == 1) && (fds[0].revents & POLLIN)){read(fd, &val, 4);printf("get button : 0x%x\n", val);}else{printf("timeout\n");}}close(fd);return 0;
}
1-3 異步通知
(1) 異步通知程序解析

異步通知流程如下:
在這里插入圖片描述

① APP給SIGIO這個信號注冊信號處理函數func,以后APP收到SIGIO信號時,這個函數會被自動調用。
② 把APP的PID(進程 ID)告訴驅動程序,這個調用不涉及驅動程序,在內核的文件系統層次記錄PID。

filp->f_owner.pid = get_pid(pid);

③ 讀取驅動程序文件Flag
④ 設置Flag里面的FASYNC位為1;當 FASYNC位發生變化時,會導致驅動程序的fasync被調用
⑤ 調用faync_helper ,它會根據 FAYSNC的值決定是否設置
button_async -->fa_file=filp(內含PID);open文件時,會在內核文件系統中有一個struct file *filp結構體,filp->f_owner.pid里面含有之前設置的PID 。
⑥ APP做其他事;當按下按鍵,發生中斷,驅動程序的中斷服務程序被調用,里面調用kill_fasync 發信號
⑦ APP收到信號后,它的信號處理函數被自動調用,可以在里面調用
read函數讀取按鍵。

驅動程序中提供對應的drv_fasync函數,并在FAYNC變化時,調用fasync_helper函數,使得button_fasync->fa_file = filp或者NULL

struct fasync_struct *button_fasync;
static int gpio_key_drv_fasync(int fd, struct file *file, int on)
{if (fasync_helper(fd, file, on, &button_fasync) >= 0)return 0;elsereturn -EIO;
}

在GPIO中斷服務程序中,若button_fasync->fa_file非空,則獲得PID,并發信號給上層應用

kill_fasync(&button_fasync, SIGIO, POLL_IN);

上層應用程序
在這里插入圖片描述

(2) 異步通知機制內核代碼詳解

上層應用執行fcntl函數,內核會調用fs/fcntl.c 的如下函數。
在這里插入圖片描述
進入do_fcntl函數,flag標志對應函數的cmd
在這里插入圖片描述

以下分別對三種flag進行代碼演示
① 當flag是F_SETOWN時,內核do_fcntl中調用f_setown函數,最終將pid給filp

fcntl(fd, F_SETOWN, getpid());

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
② 當flag是F_GETFL時,獲取文件的狀態標志

flags = fcntl(fd, F_GETFL);

在這里插入圖片描述
③ 當flag是F_SETFL時,設置文件支持異步通知功能

fcntl(fd, F_SETFL, flags | FASYNC);

在這里插入圖片描述
在這里插入圖片描述
啟動了FASYNC 功能的話,驅動程序的 button_fasync 就被設置了,它指向的 fasync_struct 結構體里含有 filp里含有PID

static int gpio_key_drv_fasync(int fd, struct file *file, int on)
{if (fasync_helper(fd, file, on, &button_fasync) >= 0)return 0;elsereturn -EIO;
}

在這里插入圖片描述
從button_fasync 指針中,取出 fasync_struct 結構體,從這個結構體的 fa_file 中得到接收方的PID ,然后使用 send_sigio函數發送信號。根據 PID找到進程在內核的 task_struct結構體, 修改里面的某些成員表示收到了信號。

kill_fasync(&button_fasync, SIGIO, POLL_IN);

在這里插入圖片描述

1-4 阻塞與非阻塞

所謂阻塞,就是等待某件事情發生。比如調用read讀取按鍵時,如果沒有按鍵數據則read函數不會返回,它會讓線程休眠等待。
使用poll時,傳入超時時間不為0(阻塞);設置超時時間為0,沒有數據立即返回(非阻塞)
如何設置阻塞與非阻塞呢?
① open時

int fd = open(/dev/xxx”, O_RDWR | O_NONBLOCK); 	/* 非阻塞方式*/
int fd = open(/dev/xxx”, O_RDWR ); 	/* 阻塞方式*/

② open后

int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK); 	/* 非阻塞方式*/
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); 	/* 阻塞方式*/

驅動程序中,當APP打開某個驅動時,在內核中會有一個struct file 結構體的f _flags對應打開文件時的標記位;可以設置f _flasgs 的O_NONBLOCK 位,表示非阻塞;也可以清除這個位表示阻塞。

/* 實現對應的open/read/write等函數,填入file_operations結構體*/                
static ssize_t gpio_key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);int err;int key;if(is_key_buf_empty() && (file->f_flags & O_NONBLOCK))return -EAGAIN;else{wait_event_interruptible(gpio_key_wait, !is_key_buf_empty());key = get_key();err = copy_to_user(buf, &key, 4);return 4;}
}
1-5 定時器
(1) 內核函數

所謂定時器,就是鬧鐘,時間到后你就要做某些事。有2個要素:時間、做事;換成程序員的話就是:超時時間、函數。
內核源碼:include\linux\timer.h

1、設置定時器,主要是初始化timer_list結構體,設置其中的函數、參數。

setup_timer(timer, fn, data);

2、向內核添加定時器。 timer–>expires 表示超時時間。
當超時時間到達,內核就會調用這個函數:timer->function(timer -->data) 。

void add_timer(struct timer_list *timer)

在這里插入圖片描述
3、修改定時器的超時時間

int mod_timer(struct timer_list *timer, unsigned long expires):

4、刪除定時器

int del_timer(struct timer_list *timer)
(2) 定時器時間單位

這表示內核每秒中會發生100次系統滴答中斷 (tick),這是Linux系統的心跳。每發生一次tick中斷,全局變量jiffies累加1。即:每個滴答是10ms。

CONFIG_HZ=100

按鍵觸發中斷,進入中斷處理函數,若不斷發生機械振動,會不斷進入中斷處理函數更新定時器超時時間,時間到后進入定時器處理函數,打印GPIO端口信息
probe函數設置定時器

static int gpio_key_probe(struct platform_device *pdev)
{/* 設置定時器*/setup_timer(&gpio_keys_100ask[i].key_timer, key_timer_expire, &gpio_keys_100ask[i]);/*設置超時時間*/gpio_keys_100ask[i].key_timer.expires = ~0;add_timer(&gpio_keys_100ask[i].key_timer);
}

中斷處理函數修改定時器超時時間

static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{struct gpio_key *gpio_key = dev_id;printk("gpio_key_isr %d irq happened\n", gpio_key->gpio);mod_timer(&gpio_key->key_timer, jiffies + HZ/50); //20ms  HZ = 1sreturn IRQ_HANDLED;
}

定時器處理函數中打印GPIO信息和喚醒線程

struct timer_list key_timer;
/*定時器處理函數*/
static void key_timer_expire(unsigned long data)
{/*data ==> gpio*/struct gpio_key *gpio_key = data;int val;int key;val = gpiod_get_value(gpio_key->gpiod);printk("key_timer_expire %d %d\n", gpio_key->gpio, val);key = (gpio_key->gpio << 8) | val;put_key(key);wake_up_interruptible(&gpio_key_wait); 			/*喚醒線程*/kill_fasync(&button_fasync, SIGIO, POLL_IN);	/*發信號*/}
1-6 中斷下半部 tasklet

在上半部處理緊急的事情時,在處理過程中,中斷是被禁止的;在下半部處理耗時的事情時,在處理過程中,中斷是使能的。
內核源碼:include\linux\interrupt.h

struct tasklet_struct
{struct tasklet_struct *next;unsigned long state;atomic_t count;void (*func)(unsigned long);unsigned long data;
};

state用于表示 tasklet 的狀態,一共有2位。
bit0 表示 TASKLET_STATE_SCHED
等于1時,表示已經執行了 tasklet_schedule把tasklet放入隊列;
bit1 表示 TASKLET_STATE_RUN
等于1時,表示正在運行 tasklet 中的func函數;函數執行完后內核會把該位清0。

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

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

相關文章

華為OD技術面試案例7-2024年

記錄一下我面試od的面試過程. 1、第一個是hr電話面試, 其實也就是od的hr致電, 簡單了解一下個人情況, 問我要一些個人信息, 這塊沒啥問題; 2、第二個就是機考了, 根據我提供的信息, od的hr給我發了一個機考的鏈接, 并告訴我7天內有效, 可以在考試之前先刷刷題, 刷題地址參考…

《幻獸帕魯》游戲對服務器性能的具體要求是什么?

《幻獸帕魯》游戲對服務器性能的具體要求是什么&#xff1f; CPU&#xff1a;官方最低要求為i5-3570K&#xff0c;但在多人游玩時可能會有明顯卡頓。此外&#xff0c;還有建議選擇4核或更高性能的處理器&#xff0c;以確保游戲運行流暢。 內存&#xff1a;對于不同人數的聯機&…

超越想象:人工智能的奇跡與可能性

超越想象&#xff1a;人工智能的奇跡與可能性 人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;作為當今科技領域的熱門話題&#xff0c;其奇跡和可能性正在不斷被揭示和拓展&#xff0c;超越了人們的想象。從智能機器人到自動駕駛汽車&#xff0c;從語…

蘋果ios群控軟件開發常用源代碼分享!

在移動軟件開發領域&#xff0c;蘋果設備由于其封閉性和安全性受到了廣大開發者的青睞&#xff0c;然而&#xff0c;這也為開發者帶來了一些挑戰&#xff0c;特別是在進行群控軟件開發時。 群控軟件是指可以同時控制多臺設備的軟件&#xff0c;這在自動化測試、批量操作等場景…

數據要素:數字化轉型中的新“金礦”及其發展潛力

作為一名在數字化轉型項目中摸爬滾打的實踐者&#xff0c;我們見證了數據從簡單的信息處理工具逐漸演變為驅動經濟社會發展的關鍵要素。近日&#xff0c;多部門聯合發布的《“數據要素”三年行動計劃&#xff08;2024—2026年&#xff09;》更是將數據要素的重要性提升到了新的…

C++ //練習 10.15 編寫一個lambda,捕獲它所在函數的int,并接受一個int參數。lambda應該返回捕獲的int和int參數的和。

C Primer&#xff08;第5版&#xff09; 練習 10.15 練習 10.15 編寫一個lambda&#xff0c;捕獲它所在函數的int&#xff0c;并接受一個int參數。lambda應該返回捕獲的int和int參數的和。 環境&#xff1a;Linux Ubuntu&#xff08;云服務器&#xff09; 工具&#xff1a;v…

Linux:進入vim編輯模式

vim 是一個強大的文本編輯器。 三種模式&#xff1a; 普通模式&#xff08;Normal mode&#xff09; 插入模式&#xff08;Insert mode&#xff09; 命令行模式&#xff08;Command-line mode&#xff09; 當你打開一個文件時&#xff0c;vim 默認處于普通模式。 插入模式&a…

十六、異常和File

異常和File 一、異常1.1異常的分類1.2 異常的作用1.3 異常的處理方式1.3.1 JVM默認的處理方式1.3.2 自己處理&#xff08;捕獲異常&#xff09;1.3.3 自己處理&#xff08;靈魂四問&#xff09; 1.4 異常中的常見方法1.5 拋出異常綜合練習&#xff08;鍵盤錄入數據&#xff09;…

基于springboot+vue的社區養老服務平臺

博主主頁&#xff1a;貓頭鷹源碼 博主簡介&#xff1a;Java領域優質創作者、CSDN博客專家、阿里云專家博主、公司架構師、全網粉絲5萬、專注Java技術領域和畢業設計項目實戰&#xff0c;歡迎高校老師\講師\同行交流合作 ?主要內容&#xff1a;畢業設計(Javaweb項目|小程序|Pyt…

黑馬點評-商戶查詢業務

緩存原理 本文的業務就是redis的經典應用&#xff0c;標準的操作方式就是查詢數據庫之前先查詢緩存&#xff0c;如果緩存數據存在&#xff0c;則直接從緩存中返回&#xff0c;如果緩存數據不存在&#xff0c;再查詢數據庫&#xff0c;然后將數據存入redis。 緩存更新策略 根據…

Spring重點記錄

文章目錄 1.Spring的組成2.Spring優點3.IOC理論推導4.IOC本質5.IOC實現&#xff1a;xml或者注解或者自動裝配&#xff08;零配置&#xff09;。6.hellospring6.1beans.xml的結構為&#xff1a;6.2.Spring容器6.3對象的創建和控制反轉 7.IOC創建對象方式7.1以有參構造的方式創建…

【OneAPI】貓狗類別檢測API

OneAPI新接口發布&#xff1a;貓狗類別檢測 45種狗狗類別和15種貓貓類別檢測。 API地址&#xff1a;POST https://oneapi.coderbox.cn/openapi/api/detect/dogcat 請求參數&#xff08;body&#xff09; 參數名類型必填含義說明imageUrlstring是圖片地址網絡圖片地址&#…

Vue路由(黑馬程序員)

路由介紹 將資代碼/vue-project(路由)/vue-project/src/views/tlias/DeptView.vue拷貝到我們當前EmpView.vue同級&#xff0c;其結構如下&#xff1a; 此時我們希望&#xff0c;實現點擊側邊欄的部門管理&#xff0c;顯示部門管理的信息&#xff0c;點擊員工管理&#xff0c;顯…

【周總結平淡但不平凡的周末】

上周總結 根據系統生產環境的日志文件&#xff0c;寫了個腳本統計最近使用我們系統的用戶的手機型號以及系統&#xff0c;幫助聚焦主要測試的機型&#xff0c;以及系統類型 依然是根據時區不同對項目進行改造&#xff0c;還有一個開發好的接口需要下周聯調 2024/3/3 晴…

QT Mingw32/64編譯ffmpeg源碼生成32/64bit庫以及測試

文章目錄 前言下載msys2ysamFFmpeg 搭建編譯環境安裝msys2安裝QT Mingw編譯器到msys環境中安裝ysam測試 編譯FFmpeg測試 前言 FFmpeg不像VLC有支持QT的庫文件&#xff0c;它僅提供源碼&#xff0c;需要使用者自行編譯成對應的庫&#xff0c;當使用QTFFmpeg實現播放視頻以及視頻…

連接 mongodb集群的集中方式

mongodb 連接到復制集 mongodb://node1,node2,node3.../database?[options]mongodb 連接到分片集 mongodb://mongos1,mongos2,mongos3.../database?[options]使用 mongosrv 通過域名解析得到所有的 mongos 或 節點的地址, 而不是把這些寫在連接字符串中. mongodbsrv://se…

經典的算法面試題(1)

題目&#xff1a; 給定一個整數數組 nums&#xff0c;編寫一個算法將所有的0移到數組的末尾&#xff0c;同時保持非零元素的相對順序。 示例: 輸入: [0,1,0,3,12] 輸出: [1,3,12,0,0] 注意&#xff1a;必須在原數組上操作&#xff0c;不能拷貝額外的數組。盡量減少操作次數。 這…

數據處理——一維數組轉列向量(分割時間序列為數據塊時的問題)

記錄在處理數據時被磕絆了一下的一個處理細節。 1.想要達到的要求 在某次滑動窗口取樣時間序列數據時&#xff0c;我得到如下一個以一維數組為元素的列表&#xff1a; 對于如上輸出列表中的每個一維數組&#xff0c;我希望將其轉換為下圖中的形式&#xff0c;簡單說就是希望他…

編程筆記 Golang基礎 042 文件處理

編程筆記 Golang基礎 042 文件處理 一、文件處理二、Go語言文件處理創建文件和寫入內容打開文件并按模式讀寫讀取文件內容更高級的文件和IO操作改變文件權限目錄操作 小結 一、文件處理 文件處理是指在計算機科學中&#xff0c;對存儲在磁盤或其他持久性存儲介質上的文件進行的…

Android Jni添加打印(C++打印)

Android Jni添加打印&#xff08;C打印&#xff09; 文章目錄 Android Jni添加打印&#xff08;C打印&#xff09;一、前言二、添加日志實現1、在某個類上面定義類型和方法2、把日志方法定義在.h文件中定義 myLog.h3、引用打印頭文件的示例代碼&#xff08;1&#xff09; MainA…