目錄
一、數(shù)據(jù)傳輸
1.1 APP和驅(qū)動(dòng)?
1.2 驅(qū)動(dòng)和硬件
二、APP使用驅(qū)動(dòng)的4種方式
2.1 非阻塞(查詢)
2.2 阻塞(休眠+喚醒)
2.3 POLL(休眠+喚醒+超時(shí)時(shí)間)
2.3.1?POLL機(jī)制流程
2.3.2?POLL執(zhí)行流程
2.3.3?POLL應(yīng)用和驅(qū)動(dòng)編程?
2.4 異步通知
2.4.1 異步通知流程
2.4.1 異步通知應(yīng)用和驅(qū)動(dòng)編程
一、數(shù)據(jù)傳輸
1.1 APP和驅(qū)動(dòng)?
APP和驅(qū)動(dòng)之間的數(shù)據(jù)訪問(wèn)是不能通過(guò)直接訪問(wèn)對(duì)方的內(nèi)存地址來(lái)操作的,這里涉及Linux系統(tǒng)中的MMU(內(nèi)存管理單元)。在驅(qū)動(dòng)程序中通過(guò)這兩個(gè)函數(shù)來(lái)獲得APP和傳給APP數(shù)據(jù):
- copy_to_user
- copy_from_user
簡(jiǎn)單來(lái)講,應(yīng)用程序與內(nèi)核/驅(qū)動(dòng)程序在物理空間上是隔離開的,應(yīng)用程序和驅(qū)動(dòng)程序是不可能互相訪問(wèn)到的。驅(qū)動(dòng)程序里的copy_from_user得到應(yīng)用層傳來(lái)的數(shù)據(jù),驅(qū)動(dòng)程序可以使用copy_to_user把數(shù)據(jù)發(fā)給應(yīng)用程序,即應(yīng)用程序和驅(qū)動(dòng)程序通過(guò)這兩個(gè)函數(shù)交換數(shù)據(jù)。
1.2 驅(qū)動(dòng)和硬件
- 各個(gè)子系統(tǒng)函數(shù)
- 通過(guò)ioremap映射寄存器地址后,直接訪問(wèn)寄存器
驅(qū)動(dòng)程序操作硬件可以通過(guò)子系統(tǒng)的方式(調(diào)用函數(shù))來(lái)操作硬件;或者用最原始的辦法ioremap,映射寄存器的地址(不是直接操作寄存器地址),這樣在驅(qū)動(dòng)程序里就可以訪問(wèn)寄存器了。
二、APP使用驅(qū)動(dòng)的4種方式
驅(qū)動(dòng)程序:提供能力,不提供策略(驅(qū)動(dòng)程序提供各種作用的函數(shù),供應(yīng)用程序抉擇并使用)。
2.1 非阻塞(查詢)
如果在應(yīng)用程序里open這個(gè)argv[1](設(shè)備節(jié)點(diǎn))時(shí),指定了非阻塞,表示讀數(shù)據(jù)時(shí),如果沒有數(shù)據(jù)并且這個(gè)文件的flag是非阻塞,則立刻返回一個(gè)錯(cuò)誤。APP指定了非阻塞方式,驅(qū)動(dòng)程序是否判斷它的flag完全由用戶決定。
//應(yīng)用程序
//O_RDWR可讀可寫,O_NONBLOCK非阻塞方式
fd = open(argv[1], O_RDWR | O_NONBLOCK);
//驅(qū)動(dòng)程序的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里沒有數(shù)據(jù),APP調(diào)用讀函數(shù),驅(qū)動(dòng)程序讀函數(shù)會(huì)進(jìn)入wait_event_interruptible里休眠(放棄運(yùn)行,不是死等),等待被喚醒。所以我們經(jīng)常看到read函數(shù)很久沒有返回,是因?yàn)樵隍?qū)動(dòng)程序里休眠了。該事件會(huì)記錄在gpio_wait隊(duì)列中。
//應(yīng)用程序
//O_RDWR可讀可寫,不設(shè)置非阻塞
fd = open(argv[1], O_RDWR);
//驅(qū)動(dòng)程序的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;
}
通常配合中斷+定時(shí)器的方式來(lái)喚醒該隊(duì)列里面等待喚醒的進(jìn)程/線程。
//中斷函數(shù)
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);
//定時(shí)器 用來(lái)消除抖動(dòng)
//修改定時(shí)器的超時(shí)時(shí)間= jiffies(當(dāng)前時(shí)間) + 赫茲/5
mod_timer(&gpio_desc->key_timer, jiffies + HZ/5);
return IRQ_HANDLED;//成功處理
}
//定時(shí)器超時(shí)函數(shù)
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);//按鍵值放入環(huán)形緩沖區(qū)
//喚醒隊(duì)列中的進(jìn)程/線程
wake_up_interruptible(&gpio_wait);
kill_fasync(&button_fasync, SIGIO, POLL_IN);
}
2.3 POLL(休眠+喚醒+超時(shí)時(shí)間)
2.3.1?POLL機(jī)制流程
使用休眠-喚醒的方式等待某個(gè)事件發(fā)生時(shí),有一個(gè)缺點(diǎn): 等待的時(shí)間可能很久。我們可以加上一個(gè)超時(shí)時(shí)間,這時(shí)就可以使用 poll 機(jī)制。poll機(jī)制流程如下6步:
①APP不知道驅(qū)動(dòng)程序中是否有數(shù)據(jù),可以先調(diào)用poll函數(shù)查詢一下,poll函數(shù)可以傳入超時(shí)時(shí)間;
②APP進(jìn)入內(nèi)核態(tài),調(diào)用到驅(qū)動(dòng)程序的poll函數(shù),如果有數(shù)據(jù)的話立刻返回;
③如果發(fā)現(xiàn)沒有數(shù)據(jù)時(shí)就休眠一段時(shí)間;
④當(dāng)有數(shù)據(jù)時(shí),比如當(dāng)按下按鍵時(shí),驅(qū)動(dòng)程序的中斷服務(wù)程序和定時(shí)器超時(shí)函數(shù)被調(diào)用,它會(huì)記錄數(shù)據(jù)、喚醒APP;
⑤當(dāng)超時(shí)時(shí)間到了之后,內(nèi)核也會(huì)喚醒APP;
⑥APP根據(jù)poll函數(shù)的返回值就可以知道是否有數(shù)據(jù),如果有數(shù)據(jù)就調(diào)用read得到數(shù)據(jù)。
2.3.2?POLL執(zhí)行流程
?????????????????????????????????????????????????????????????????圖1 poll機(jī)制
函數(shù)執(zhí)行流程如上圖①~⑧所示,重點(diǎn)從③開始看。假設(shè)一開始無(wú)按鍵數(shù)據(jù):
③APP調(diào)用poll之后,進(jìn)入內(nèi)核態(tài);
④在循環(huán)中執(zhí)行程序,致驅(qū)動(dòng)程序的drv_poll被調(diào)用;注意,drv_poll要把自己這個(gè)線程掛入等待隊(duì)列 wq 中!,并沒有休眠,且無(wú)數(shù)據(jù)返回0,有數(shù)據(jù)返回POLLIN;
⑤當(dāng)前沒有數(shù)據(jù),則在內(nèi)核態(tài)中休眠一會(huì),等待超時(shí)內(nèi)核喚醒或中斷+定時(shí)器喚醒;
中斷+定時(shí)器喚醒情況:
⑥過(guò)程中,按下了按鍵,發(fā)生了中斷+定時(shí)器超時(shí)函數(shù),在定時(shí)器超時(shí)函數(shù)里記錄了按鍵值,并且從gpio_wait隊(duì)列中把線程喚醒了;
⑦從休眠中被喚醒,繼續(xù)執(zhí)行 for 循環(huán),再次調(diào)用 drv_poll,在drv_poll中返回?cái)?shù)據(jù)狀態(tài)(POLLIN);
⑧有數(shù)據(jù)返回到內(nèi)核態(tài),內(nèi)核態(tài)返回到應(yīng)用態(tài);
⑨APP調(diào)用read函數(shù)讀數(shù)據(jù)。
超時(shí)內(nèi)核喚醒情況:接著上面的⑤
⑥在休眠過(guò)程中,一直沒有按下了按鍵,超時(shí)時(shí)間到,內(nèi)核把這個(gè)線程喚醒;
⑦線程從休眠中被喚醒,繼續(xù)執(zhí)行 for 循環(huán),再次調(diào)用 drv_poll,drv_poll返回?cái)?shù)據(jù)狀態(tài)
⑧還是沒有數(shù)據(jù),但是超時(shí)時(shí)間到了,那從內(nèi)核態(tài)返回到應(yīng)用態(tài);
⑨APP不能調(diào)用 read 函數(shù)讀數(shù)據(jù)。
需要注意一下幾點(diǎn)?。?!
- drv_poll 要把線程掛入隊(duì)列g(shù)pio_wait,但是并不是在 drv_poll 中進(jìn)入休眠,而是在調(diào)用 drv_poll 之后休眠
- drv_poll 要返回?cái)?shù)據(jù)狀態(tài)
- APP 調(diào)用一次 poll,有可能會(huì)導(dǎo)致 drv_poll 被調(diào)用 2 次
- 線程被喚醒的原因有 2個(gè):中斷(+定時(shí)器)發(fā)生了去隊(duì)列g(shù)pio_wait中把它喚醒,超時(shí)時(shí)間到了內(nèi)核把它喚醒
- APP 要判斷 poll 返回的原因:有數(shù)據(jù),還是超時(shí)。有數(shù)據(jù)時(shí)再去調(diào)用read函數(shù)
2.3.3?POLL應(yīng)用和驅(qū)動(dòng)編程?
驅(qū)動(dòng)程序中的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;
}
?驅(qū)動(dòng)程序中的中斷觸發(fā)函數(shù):按鍵消抖+修改了定時(shí)器超時(shí)時(shí)間
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);
//定時(shí)器 用來(lái)消除抖動(dòng)
mod_timer(&gpio_desc->key_timer, jiffies + HZ/5);//修改定時(shí)器的超時(shí)時(shí)間= jiffies(當(dāng)前時(shí)間) + 赫茲/5
return IRQ_HANDLED;//成功處理
}
?定時(shí)器超時(shí)函數(shù):獲取按鍵值+儲(chǔ)存按鍵值+喚醒線程
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);//按鍵值放入環(huán)形緩沖區(qū)
wake_up_interruptible(&gpio_wait);//喚醒隊(duì)列里的線程
kill_fasync(&button_fasync, SIGIO, POLL_IN);
}
應(yīng)用程序代碼:
struct pollfd fds[1]
int timeout_ms = 5000;
int ret;
int fd;
fds[0].fd = fd; //查詢fd這個(gè)文件
fds[0].events = POLLIN; //POLLIN表示查詢這個(gè)文件有沒有數(shù)據(jù)讓我讀進(jìn)來(lái)
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結(jié)構(gòu)體中有文件滿足返回條件,且返回的事件是這個(gè)文件有數(shù)據(jù)讓我讀進(jìn)來(lái)POLLIN
if((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機(jī)制時(shí),都需要休眠等待某個(gè)事件發(fā)生時(shí),它們的差別在于后者可以指定休眠的時(shí)長(zhǎng)。如果APP不想休眠怎么辦?也有類似的方法:驅(qū)動(dòng)程序有數(shù)據(jù)時(shí)主動(dòng)通知APP,APP收到信號(hào)后執(zhí)行信息處理函數(shù),這就是異步通知。
圖2 異步通知的信號(hào)流程
重點(diǎn)從②開始:
② APP 給 SIGIO 這個(gè)信號(hào)注冊(cè)信號(hào)處理函數(shù) func,以后 APP 收到 SIGIO信號(hào)時(shí),這個(gè)函數(shù)會(huì)被自動(dòng)調(diào)用;
③ 把 APP 的 PID(進(jìn)程 ID)告訴驅(qū)動(dòng)程序,這個(gè)調(diào)用不涉及驅(qū)動(dòng)程序,在內(nèi)核的文件系統(tǒng)層次記錄 PID;
④ 讀取驅(qū)動(dòng)程序文件 Flag;
⑤ 設(shè)置 Flag 里面的 FASYNC 位為 1:當(dāng) FASYNC 位發(fā)生變化時(shí),會(huì)導(dǎo)致驅(qū)動(dòng)程序的 fasync 被調(diào)用;
⑥⑦ 調(diào) 用 faync_helper , 它會(huì)根據(jù)FAYSNC的值決定是否設(shè)置button_async->fa_file=驅(qū)動(dòng)文件 filp:驅(qū)動(dòng)文件 filp 結(jié)構(gòu)體里面含有之前設(shè)置的 PID。
⑧ APP 可以做其他事;
⑨⑩ 按下按鍵,發(fā)生中斷,驅(qū)動(dòng)程序的中斷服務(wù)程序被調(diào)用,里面調(diào)用kill_fasync 發(fā)信號(hào);
??? APP 收到信號(hào)后,它的信號(hào)處理函數(shù)被自動(dòng)調(diào)用,可以在里面調(diào)用read 函數(shù)讀取按鍵。
2.4.1 異步通知應(yīng)用和驅(qū)動(dòng)編程
應(yīng)用程序:信號(hào)處理函數(shù)+注冊(cè)信號(hào)處理函數(shù)+打開驅(qū)動(dòng)+把進(jìn)程ID告訴驅(qū)動(dòng)+使能驅(qū)動(dòng)的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()); //告訴驅(qū)動(dòng)程序,要給誰(shuí)發(fā)信號(hào)
flags = fcntl(fd, F_GETFL); //獲得之前的flags
fcntl(fd, F_SETFL, flags | FASYNC); //這是新的flags并使能驅(qū)動(dòng)的FASYNC功能(使能異步通知)
驅(qū)動(dòng)程序中的fasync被調(diào)用:使能異步通知后會(huì)調(diào)用這個(gè)輔助函數(shù)來(lái)構(gòu)造結(jié)構(gòu)體,結(jié)構(gòu)體里存放進(jìn)程id
//構(gòu)造button_fasync結(jié)構(gòu)體,結(jié)構(gòu)體里存放進(jìn)程id
static int gpio_drv_fasync(int fd, struct file *file, int on)
{
if (fasync_helper(fd, file, on, &button_fasync) >= 0)
return 0;
else
return -EIO;
}
?驅(qū)動(dòng)程序中的中斷觸發(fā)函數(shù):按鍵消抖+修改了定時(shí)器超時(shí)時(shí)間文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-636901.html
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);
//定時(shí)器 用來(lái)消除抖動(dòng)
mod_timer(&gpio_desc->key_timer, jiffies + HZ/5);//修改定時(shí)器的超時(shí)時(shí)間= jiffies(當(dāng)前時(shí)間) + 赫茲/5
return IRQ_HANDLED;//成功處理
}
?定時(shí)器超時(shí)函數(shù):最后一行發(fā)送信號(hào)SIGIO給進(jìn)程,button_fasync結(jié)構(gòu)體中有進(jìn)程信息,發(fā)送信號(hào)后,應(yīng)用程序中收到信號(hào)會(huì)打斷while循環(huán)并先執(zhí)行對(duì)應(yīng)的信號(hào)處理函數(shù),再回到while循環(huán)。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-636901.html
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);//按鍵值放入環(huán)形緩沖區(qū)
wake_up_interruptible(&gpio_wait);//喚醒隊(duì)列里的線程
kill_fasync(&button_fasync, SIGIO, POLL_IN);
}
到了這里,關(guān)于【IMX6ULL驅(qū)動(dòng)開發(fā)學(xué)習(xí)】04.應(yīng)用程序和驅(qū)動(dòng)程序數(shù)據(jù)傳輸和交互的4種方式:非阻塞、阻塞、POLL、異步通知 一、數(shù)據(jù)傳輸?shù)奈恼戮徒榻B完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!