【IMX6ULL驅動開發學習】04.應用程序和驅動程序數據傳輸和交互的4種方式:非阻塞、阻塞、POLL、異步通知

一、數據傳輸

1.1 APP和驅動?

APP和驅動之間的數據訪問是不能通過直接訪問對方的內存地址來操作的,這里涉及Linux系統中的MMU(內存管理單元)。在驅動程序中通過這兩個函數來獲得APP和傳給APP數據:

  • copy_to_user
  • copy_from_user

簡單來講,應用程序與內核/驅動程序在物理空間上是隔離開的,應用程序和驅動程序是不可能互相訪問到的。驅動程序里的copy_from_user得到應用層傳來的數據,驅動程序可以使用copy_to_user把數據發給應用程序,即應用程序和驅動程序通過這兩個函數交換數據。

1.2 驅動和硬件

  • 各個子系統函數
  • 通過ioremap映射寄存器地址后,直接訪問寄存器

驅動程序操作硬件可以通過子系統的方式(調用函數)來操作硬件;或者用最原始的辦法ioremap,映射寄存器的地址(不是直接操作寄存器地址),這樣在驅動程序里就可以訪問寄存器了。

二、APP使用驅動的4種方式

驅動程序:提供能力,不提供策略(驅動程序提供各種作用的函數,供應用程序抉擇并使用)。

2.1 非阻塞(查詢)

如果在應用程序里open這個argv[1](設備節點)時,指定了非阻塞,表示讀數據時,如果沒有數據并且這個文件的flag是非阻塞,則立刻返回一個錯誤。APP指定了非阻塞方式,驅動程序是否判斷它的flag完全由用戶決定。

//應用程序
//O_RDWR可讀可寫,O_NONBLOCK非阻塞方式
fd = open(argv[1], O_RDWR | O_NONBLOCK);
//驅動程序的read
static ssize_t gpio_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int err;int key;if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))return -EAGAIN;wait_event_interruptible(gpio_wait, !is_key_buf_empty());key = get_key();err = copy_to_user(buf, &key, 4);	return 4;
}

2.2 阻塞(休眠+喚醒)

如果一開始buf里沒有數據,APP調用讀函數,驅動程序讀函數會進入wait_event_interruptible里休眠(放棄運行,不是死等),等待被喚醒。所以我們經常看到read函數很久沒有返回,是因為在驅動程序里休眠了。該事件會記錄在gpio_wait隊列中。

//應用程序
//O_RDWR可讀可寫,不設置非阻塞
fd = open(argv[1], O_RDWR);
//驅動程序的read
static ssize_t gpio_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int err;int key;if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))return -EAGAIN;wait_event_interruptible(gpio_wait, !is_key_buf_empty());key = get_key();err = copy_to_user(buf, &key, 4);	return 4;
}

通常配合中斷+定時器的方式來喚醒該隊列里面等待喚醒的進程/線程。

//中斷函數
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{struct gpio_desc *gpio_desc = dev_id;printk("gpio_key_isr key %d irq happened\n", gpio_desc->gpio);//定時器 用來消除抖動//修改定時器的超時時間= jiffies(當前時間) + 赫茲/5mod_timer(&gpio_desc->key_timer, jiffies + HZ/5);return IRQ_HANDLED;//成功處理
}
//定時器超時函數
static void key_timer_expire(unsigned long data)
{struct gpio_desc *gpio_desc = (struct gpio_desc *)data;int val;int key;val = gpio_get_value(gpio_desc->gpio);key = (gpio_desc->key) | (val<<8);put_key(key);//按鍵值放入環形緩沖區//喚醒隊列中的進程/線程wake_up_interruptible(&gpio_wait);kill_fasync(&button_fasync, SIGIO, POLL_IN);
}

2.3 POLL(休眠+喚醒+超時時間)

2.3.1?POLL機制流程

使用休眠-喚醒的方式等待某個事件發生時,有一個缺點: 等待的時間可能很久。我們可以加上一個超時時間,這時就可以使用 poll 機制。poll機制流程如下6步:

①APP不知道驅動程序中是否有數據,可以先調用poll函數查詢一下,poll函數可以傳入超時時間;

②APP進入內核態,調用到驅動程序的poll函數,如果有數據的話立刻返回;

③如果發現沒有數據時就休眠一段時間;

④當有數據時,比如當按下按鍵時,驅動程序的中斷服務程序和定時器超時函數被調用,它會記錄數據、喚醒APP;

⑤當超時時間到了之后,內核也會喚醒APP;

⑥APP根據poll函數的返回值就可以知道是否有數據,如果有數據就調用read得到數據。

2.3.2?POLL執行流程

?????????????????????????????????????????????????????????????????圖1 poll機制

函數執行流程如上圖①~⑧所示,重點從③開始看。假設一開始無按鍵數據:

③APP調用poll之后,進入內核態;

④在循環中執行程序,致驅動程序的drv_poll被調用;注意,drv_poll要把自己這個線程掛入等待隊列 wq 中!,并沒有休眠,且無數據返回0,有數據返回POLLIN;

⑤當前沒有數據,則在內核態中休眠一會,等待超時內核喚醒中斷+定時器喚醒;

中斷+定時器喚醒情況:

⑥過程中,按下了按鍵,發生了中斷+定時器超時函數,在定時器超時函數里記錄了按鍵值,并且從gpio_wait隊列中把線程喚醒了;

⑦從休眠中被喚醒,繼續執行 for 循環,再次調用 drv_poll,在drv_poll中返回數據狀態(POLLIN);

⑧有數據返回到內核態,內核態返回到應用態;

⑨APP調用read函數讀數據。


超時內核喚醒情況:接著上面的⑤

⑥在休眠過程中,一直沒有按下了按鍵,超時時間到,內核把這個線程喚醒;

⑦線程從休眠中被喚醒,繼續執行 for 循環,再次調用 drv_poll,drv_poll返回數據狀態

⑧還是沒有數據,但是超時時間到了,那從內核態返回到應用態;

⑨APP不能調用 read 函數讀數據。

需要注意一下幾點!!!

  • drv_poll 要把線程掛入隊列gpio_wait,但是并不是在 drv_poll 中進入休眠,而是在調用 drv_poll 之后休眠
  • drv_poll 要返回數據狀態
  • APP 調用一次 poll,有可能會導致 drv_poll 被調用 2 次
  • 線程被喚醒的原因有 2個:中斷(+定時器)發生了去隊列gpio_wait中把它喚醒,超時時間到了內核把它喚醒
  • APP 要判斷 poll 返回的原因:有數據,還是超時。有數據時再去調用read函數

2.3.3?POLL應用和驅動編程?

驅動程序中的poll代碼

static unsigned int gpio_drv_poll(struct file *fp, poll_table * wait)
{poll_wait(fp, &gpio_wait, wait);return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}

?驅動程序中的中斷觸發函數:按鍵消抖+修改了定時器超時時間

static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{struct gpio_desc *gpio_desc = dev_id;printk("gpio_key_isr key %d irq happened\n", gpio_desc->gpio);//定時器 用來消除抖動mod_timer(&gpio_desc->key_timer, jiffies + HZ/5);//修改定時器的超時時間= jiffies(當前時間) + 赫茲/5return IRQ_HANDLED;//成功處理
}

?定時器超時函數:獲取按鍵值+儲存按鍵值+喚醒線程

static void key_timer_expire(unsigned long data)
{struct gpio_desc *gpio_desc = (struct gpio_desc *)data;int val;int key;val = gpio_get_value(gpio_desc->gpio);key = (gpio_desc->key) | (val<<8);put_key(key);//按鍵值放入環形緩沖區wake_up_interruptible(&gpio_wait);//喚醒隊列里的線程kill_fasync(&button_fasync, SIGIO, POLL_IN);
}

應用程序代碼:

struct pollfd fds[1]
int timeout_ms = 5000;
int ret;
int fd;
fds[0].fd = fd;           //查詢fd這個文件
fds[0].events = POLLIN;   //POLLIN表示查詢這個文件有沒有數據讓我讀進來 fd = open(argv[1], O_RDWR);
if(fd == -1)
{printf("can not open file %s\n", argv[1]);
}while(1)
{ret = poll(fds, 1, timeout_ms);//ret為1表示fds結構體中有文件滿足返回條件,且返回的事件是這個文件有數據讓我讀進來POLLINif((ret == 1) && (fds[0].revents & POLLIN)){read(fd, &val, 4);printf("get button : 0x%x\n", val);}else{printf("timeout\n");}}

?

2.4 異步通知

2.4.1 異步通知流程

使用休眠-喚醒、POLL機制時,都需要休眠等待某個事件發生時,它們的差別在于后者可以指定休眠的時長。如果APP不想休眠怎么辦?也有類似的方法:驅動程序有數據時主動通知APP,APP收到信號后執行信息處理函數,這就是異步通知。


圖2 異步通知的信號流程

重點從②開始:
② APP 給 SIGIO 這個信號注冊信號處理函數 func,以后 APP 收到 SIGIO信號時,這個函數會被自動調用;
③ 把 APP 的 PID(進程 ID)告訴驅動程序,這個調用不涉及驅動程序,在內核的文件系統層次記錄 PID;
④ 讀取驅動程序文件 Flag;
⑤ 設置 Flag 里面的 FASYNC 位為 1:當 FASYNC 位發生變化時,會導致驅動程序的 fasync 被調用;
⑥⑦ 調 用 faync_helper , 它會根據FAYSNC的值決定是否設置button_async->fa_file=驅動文件 filp:驅動文件 filp 結構體里面含有之前設置的 PID。
⑧ APP 可以做其他事;
⑨⑩ 按下按鍵,發生中斷,驅動程序的中斷服務程序被調用,里面調用kill_fasync 發信號;
??? APP 收到信號后,它的信號處理函數被自動調用,可以在里面調用read 函數讀取按鍵。

2.4.1 異步通知應用和驅動編程

應用程序:信號處理函數+注冊信號處理函數+打開驅動+把進程ID告訴驅動+使能驅動的FASYNC功能

static void sig_func(int sig)
{int val;read(fd, &val, 4);printf("get button : 0x%x\n", val);
}signal(SIGIO, sig_func);fd = open(argv[1], O_RDWR);
if(fd == -1)
{printf("can not open file %s\n", argv[1]);
}fcntl(fd, F_SETOWN, getpid());         //告訴驅動程序,要給誰發信號
flags = fcntl(fd, F_GETFL);            //獲得之前的flags
fcntl(fd, F_SETFL, flags | FASYNC);    //這是新的flags并使能驅動的FASYNC功能(使能異步通知)

驅動程序中的fasync被調用:使能異步通知后會調用這個輔助函數來構造結構體,結構體里存放進程id

//構造button_fasync結構體,結構體里存放進程id
static int gpio_drv_fasync(int fd, struct file *file, int on)
{if (fasync_helper(fd, file, on, &button_fasync) >= 0)return 0;elsereturn -EIO;
}

?驅動程序中的中斷觸發函數:按鍵消抖+修改了定時器超時時間

static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{struct gpio_desc *gpio_desc = dev_id;printk("gpio_key_isr key %d irq happened\n", gpio_desc->gpio);//定時器 用來消除抖動mod_timer(&gpio_desc->key_timer, jiffies + HZ/5);//修改定時器的超時時間= jiffies(當前時間) + 赫茲/5return IRQ_HANDLED;//成功處理
}

?定時器超時函數:最后一行發送信號SIGIO給進程,button_fasync結構體中有進程信息,發送信號后,應用程序中收到信號會打斷while循環并先執行對應的信號處理函數,再回到while循環。

static void key_timer_expire(unsigned long data)
{struct gpio_desc *gpio_desc = (struct gpio_desc *)data;int val;int key;val = gpio_get_value(gpio_desc->gpio);key = (gpio_desc->key) | (val<<8);put_key(key);//按鍵值放入環形緩沖區wake_up_interruptible(&gpio_wait);//喚醒隊列里的線程kill_fasync(&button_fasync, SIGIO, POLL_IN);
}

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

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

相關文章

24屆近3年上海電力大學自動化考研院校分析

今天給大家帶來的是上海電力大學控制考研分析 滿滿干貨&#xff5e;還不快快點贊收藏 一、上海電力大學 學校簡介 上海電力大學&#xff08;Shanghai University of Electric Power&#xff09;&#xff0c;位于上海市&#xff0c;是中央與上海市共建、以上海市管理為主的全日…

stack 、 queue的語法使用及底層實現以及deque的介紹【C++】

文章目錄 stack的使用queue的使用適配器queue的模擬實現stack的模擬實現deque stack的使用 stack是一種容器適配器&#xff0c;具有后進先出&#xff0c;只能從容器的一端進行元素的插入與提取操作 #include <iostream> #include <vector> #include <stack&g…

Layui列表復選框根據條件禁用

// 禁用客服回訪id有值的復選框res.data.forEach(function (item, i) {if (item.feedbackEmpId) {let index res.data[i][LAY_TABLE_INDEX];$(".layui-table tr[data-index"index"] input[typecheckbox]").prop(disabled,true);$(".layui-table tr[d…

【WebRTC---源碼篇】(二十四)GCC獲取碼率后的分配

RtpTransportControllerSend::PostUpdates 配置碼率 // Contains updates of network controller comand state. Using optionals to // indicate whether a member has been updated. The array of probe clusters // should be used to send out probes if not empty. // 包…

【SpringBoot】89、SpringBoot中使用@Transactional進行事務管理

事務是一組組合成邏輯工作單元的操作,雖然系統中可能會出錯,但事務將控制和維護事務中每個操作的一致性和完整性。 1、SpringBoot 引用說明 新建的 Spring Boot 項目中,一般都會引用 spring-boot-starter 或者 spring-boot-starter-web,而這兩個起步依賴中都已經包含了對…

EV 錄屏修復小工具

參考這篇文章, EV錄制文件損壞-修復方法, 我用 C# 寫了一個小程序. 倉庫: github.com/SlimeNull/EvRepair 下載: github.com/SlimeNull/EvRepair/Releases 鏡像: gitee.com/slimenull/EvRepair/releases 覺得還不錯的話, 點個星星 推薦使用的幾個理由: 內嵌 ffmpeg 和 recov…

Linux學習之初識Linux

目錄 一.Linux的發展歷史及概念 1.什么是Linux UNIX發展的歷史&#xff1a; Linux發展歷史&#xff1a; 2. 開源 商業化發行版本 二. 如何搭建Linux環境 Linux 環境的搭建方式主要有三種&#xff1a; 1. 直接安裝在物理機上 2. 使用虛擬機軟件 3. 使用云服務器 三. …

沒學C++,如何從C語言絲滑過度到python【python基礎萬字詳解】

大家好&#xff0c;我是紀寧。 文章將從C語言出發&#xff0c;深入介紹python的基礎知識&#xff0c;也包括很多python的新增知識點詳解。 文章目錄 1.python的輸入輸出&#xff0c;重新認識 hello world&#xff0c;重回那個激情燃燒的歲月1.1 輸出函數print的規則1.2 輸入函…

idea 使用debug 啟動項目的時候 出現 Method breakpoints may dramatically slow down debugging

問題: 1. 寫了一段時間的代碼&#xff0c;在debug啟動項目后提示&#xff1a;Method breakpoints may dramatically slow down debugging 但是正常啟動是可以的&#xff0c;debug不行。 2. idea 里面的項目&#xff0c;很多地方都有斷點&#xff0c;現在想要取消全部的斷點…

Redis——hash類型詳解

概述 Redis本身就是鍵值對結構&#xff0c;而Redis中的value可以是哈希類型&#xff0c;為了區分這兩個鍵值對&#xff0c;Redis中的鍵值對是key-value&#xff0c;而value中的哈希鍵值對則是field-value&#xff0c;其中value必須是字符串 下面介紹一些Redis的hash類型的常用…

Vue中拖動排序功能,引入SortableJs,前端拖動排序。

背景&#xff1a; 作為一名前端開發人員&#xff0c;在工作中難免會遇到拖拽功能&#xff0c;分享一個github上一個不錯的拖拽js庫&#xff0c;能滿足我們在項目開發中的需要&#xff0c;支持Vue和React&#xff0c;下面是我在vue后臺項目中中使用SortableJS的使用詳細流程&am…

html實現iphone同款開關

一、背景 想實現一個開關的按鈕&#xff0c;來觸發一些操作&#xff0c;網上找了總感覺看著別扭&#xff0c;忽然想到iphone的開關挺好&#xff0c;搞一個 二、代碼實現 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8&qu…

HDFS原理剖析

一、概述 HDFS是Hadoop的分布式文件系統&#xff08;Hadoop Distributed File System&#xff09;&#xff0c;實現大規模數據可靠的分布式讀寫。HDFS針對的使用場景是數據讀寫具有“一次寫&#xff0c;多次讀”的特征&#xff0c;而數據“寫”操作是順序寫&#xff0c;也就是…

STM32 LL庫+STM32CubeMX--LED呼吸燈

一、前期準備 硬件&#xff1a;STM32F103C8T6開發板調試工具&#xff1a;DAPLink(本次使用)或USB-TTL開發環境&#xff1a;STM32CubeMX、Keil、Vscode(可選)LED&#xff1a;使用PA0(TIM2_CH1)輸出PWM&#xff0c;LED的陰極接GND 二、使用定時器中斷產生PWM STM32F103C8T6在72…

scope,deep穿透的實際應用

一.父組件代碼 <template><div id"app"><h1 class"box"><pageName> </pageName></h1></div> </template><script> import pageName from "../src/components/pageName.vue"; export de…

Java中的==和equals():區別詳解

大家好&#xff01;在 Java 編程中&#xff0c;比較對象的相等性是一個常見的任務。然而&#xff0c;你是否知道在 Java 中有兩種不同的方法來比較對象的相等性&#xff1a; 操作符和 equals() 方法&#xff1f;本文將深入探討這兩種方法之間的區別以及何時使用它們。 操作符 …

arcgis pro3.0-3.0.1-3.0.2安裝教程大全及安裝包下載

一. 產品介紹&#xff1a; ArcGIS Pro 這一功能強大的單桌面 GIS 應用程序是一款功能豐富的軟件&#xff0c;采用 ArcGIS Pro 用戶社區提供的增強功能和創意進行開發。 ArcGIS Pro 支持 2D、3D 和 4D 模式下的數據可視化、高級分析和權威數據維護。 支持通過 Web GIS 在一系列 …

KafkaStream:基本使用

簡介&#xff1a; kafkaStream&#xff1a;提供了對存儲在kafka中的數據進行流式處理和分析的功能 特點&#xff1a; KafkasSream提供了一個非常簡單輕量的Library&#xff0c;它可以非常方便的嵌入到java程序中&#xff0c;也可以任何方式打包部署 入門案例&#xff1a; 1、…

jenkins自動化部署Jenkinsfile文件配置

簡介 使用jenkins部署時會讀取項目中Jenkinsfile文件&#xff0c;文件配置不對會導致部署失敗 文件內容 pipeline {agent anyparameters {string(name: project_name, defaultValue: xxx1, description: 項目jar名稱)string(name: version, defaultValue: xxx2, description…

【Apollo】阿波羅自動駕駛:塑造自動駕駛技術的未來

前言 Apollo (阿波羅)是一個開放的、完整的、安全的平臺&#xff0c;將幫助汽車行業及自動駕駛領域的合作伙伴結合車輛和硬件系統&#xff0c;快速搭建一套屬于自己的自動駕駛系統。 開放能力、共享資源、加速創新、持續共贏是 Apollo 開放平臺的口號。百度把自己所擁有的強大、…