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