select和poll,驅動等待隊列,poll和select編程

 2023-10-06 阅读 17 评论 0

摘要:一、等待隊列 1.定義一個等待隊列及初始化 1)動態初始化 wait_queue_head_t wq; //全局變量 init_waitqueue_head(&wq); //安裝模塊時候執行了初始化 select和poll、2)靜態初始化 DECLARE_WAIT_QUEUE_HEAD(wq); //這句等效于上面兩句 功能:建立一個等

一、等待隊列

1.定義一個等待隊列及初始化
1)動態初始化

wait_queue_head_t  wq;       //全局變量
init_waitqueue_head(&wq);   //安裝模塊時候執行了初始化

select和poll、2)靜態初始化

DECLARE_WAIT_QUEUE_HEAD(wq);    //這句等效于上面兩句

功能:建立一個等待隊列,并且初始化好
參數:
wq:是一個類型wait_queue_head_t的變量,就是驅動自己定義的等待隊列頭。

select編程。2.等待隊列睡眠

wait_event(wq, condition)
//功能:建立不可以殺進程(信號不能喚醒,效果和msleep相同)。wait_event_interruptible(wq, condition)
//功能:它可以被信號喚醒。休眠過程中,進程可以接收信號,收到后不管條件如何,直接返回。wait_event_timeout(wq, condition, timeout)
//功能:休眠期間效果和 wait_event ,但是有一個超時時間 ,時間到不管條件如何,直接返回。wait_event_interruptible_timeout(wq, condition, timeout)
//功能:休眠期間效果和 wait_event_interruptible相同。區別是有超時功能,時間到不管條件如何,直接返回。

參數:
wq: 是一個類型wait_queue_head_t的變量,就是驅動自己定義的等待隊列頭。
Condition :可以為任何類型,通常定義為整形,值為0進入休眠,值為非0直接返回。
Timeout :超時時間。

3.等待隊列的喚醒

wake_up(wq)	//常用
//功能:用于喚醒各種方式進入休眠的進程,只喚醒隊列上的一個進程。wake_up_all(wq)
//功能:效果和wake_up相同,只是能喚醒隊列上所有的進程。wake_up_interruptible(wq)	//常用
//功能:只能用于喚醒一個 使用wait_event_interruptible*休眠的進程。wake_up_interruptible_all(wq)
//功能:能喚醒隊列所有 使用wait_event_interruptible*休眠的進程。

參數:
wq: 是一個類型wait_queue_head_t指針。

代碼例子:
驅動層;

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <asm/uaccess.h>
#include <linux/delay.h>      //msleep//1)第一步: 添加相關頭文件
#include <linux/wait.h>        //等待隊列相關數據結構及函數
#include <linux/sched.h>       //進程狀態宏定義//2)定義一個等待隊列頭變量,并且初始化
static DECLARE_WAIT_QUEUE_HEAD(wq);//按鍵數量
#define BTN_SIZE   4
//按鍵緩沖區,'0'表示沒有按鍵,'1'表示按下了
static char keybuf[] = {"0000"};
/*把一個當成一個對象來看待,方便編程,定義一個描述按鍵結構*/
struct button_desc {int  gpio;   //存放io口編號int  number; //存放按鍵編號,根據自己需要設計,char *name;  //按鍵名字,隨便,但是要有意義
};
/* 定義4個按鍵的信息 */
static struct button_desc buttons[] = {{ EXYNOS4_GPX3(2), 0, "KEY0" },{ EXYNOS4_GPX3(3), 1, "KEY1" },{ EXYNOS4_GPX3(4), 2, "KEY2" },{ EXYNOS4_GPX3(5), 3, "KEY3" },
};
init_waitqueue_head/* 按鍵動作標志,在中斷程序中置1,read成功復制數據后清0 */
int press = 0;//中斷服務函數
irqreturn_t key_isr(int irq, void* dev)
{//存放按鍵狀態int dn = 0;int index = 0;//這里進行還原原來的類型struct button_desc *bdata = (struct button_desc *)dev;//把讀取到的結果取邏輯非,因為程序設計使用正邏輯。'1'表示按下。dn = !gpio_get_value(bdata->gpio);//取得當前中斷對應 的按鍵編號index = bdata->number;//把按鍵狀態更新到對應的按緩沖中keybuf[index] = dn + '0';//輸出按鍵提示//  printk("%s %s\r\n", bdata->name, dn ? "down" : "up");//4) 在等待條件變成真的地方調用  wake_up*函數喚醒休眠的進程press = 1;wake_up_interruptible(&wq) ;//通知內核到wq這個隊列頭上的鏈表去查詢每一個休眠的進程 的條件是否變成了真。//如果進程等待的條件還沒有是真,則繼續休眠。return IRQ_HANDLED;
}static ssize_t tiny4412_read (struct file *flp,char __user *buff,size_t count,loff_t * off)
{int ret = 0;//用戶傳遞0,直接返回if(!count) {return 0;}//修正參數if(count > BTN_SIZE ) {count = BTN_SIZE;}/* 沒有按鍵動作( 按下和松開時候 )*/if (!press) {if (flp->f_flags & O_NONBLOCK) { //調用open("/dev/button",flags) --》flags 存放在filp->f_flags = flagsreturn -EAGAIN;} else {//3)在需要休眠的地方使用wait_event*函數進行進行。//休眠,等待有按鍵動作喚醒進程。wait_event_interruptible(wq, press);}}/* 清標志 */press = 0;//復制數據到用戶空間ret = copy_to_user(buff, keybuf, count);if(ret) {printk("error:copy_to_user\r\n");return -EFAULT;}return count;
}static const struct file_operations dev_fops = {.read   =   tiny4412_read,.owner  =   THIS_MODULE,
};#define LEDS_MAJOR  255   //255
#define DEVICE_NAME  "mybtn"
static struct miscdevice misc = {.minor = LEDS_MAJOR, //次設備號.name  = DEVICE_NAME,//設備名.fops  = &dev_fops,  //文件操作方法
};static int __init btn_init(void)
{int ret;int irq;int i;int flags;flags = IRQ_TYPE_EDGE_BOTH; //設置為雙邊觸發for ( i = 0; i < 4 ; i++ ) {//得到中斷號irq = gpio_to_irq(buttons[i].gpio); //keyX//注冊中斷ret = request_irq(irq, key_isr, flags, buttons[i].name, (void*)&buttons[i]);if(ret < 0) {break;}}//如果不是全部成功,則反向注銷已經注冊的中斷if(ret < 0) {for ( --i; i; i-- ) {irq = gpio_to_irq(buttons[i].gpio); //keyXdisable_irq(irq);free_irq(irq, (void*)&buttons[i]);}return ret;}//注冊雜項設備ret = misc_register(&misc);       //注冊混雜設備return ret;
}static void __exit btn_exit(void)
{int i = 0;int irq;//注銷中斷for (i = 0; i < 4; i++) {irq = gpio_to_irq(buttons[i].gpio); //keyXdisable_irq(irq);free_irq(irq, (void*)&buttons[i]);}//注銷雜項設備misc_deregister(&misc);
}module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");

應用層:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>    //lseek
#include <sys/ioctl.h> //ioctl
#include <poll.h>      //pollchar save_buf[10] = {"0000"};   //存放數據使用int main(void)
{int fd;                      //存放文件描述符號int ret;int i;struct pollfd fds[1];//以非阻塞方式打開//fd = open("/dev/mybtns", O_RDWR | O_NONBLOCK );//以讀寫方式進行打開,默認是阻塞方式打開的fd = open("/dev/mybtns", O_RDWR );if(fd < 0) {printf("open error\r\n");return -1;}//實際程序需要循環讀取按鍵動作,然后根據動作完成不同事情while(1) {char cur_buf[10] = {0};   //臨時存放數據使用fds[0].fd     = fd;fds[0].events = POLLIN;  //要監測讀事件//ret = poll(fds, 1, -1);   //   永遠阻塞直到有變化 //ret = poll(fds, 1, 0);      //   非阻塞ret = poll(fds, 1, 2000); //   2秒超時//判斷查詢結果if(ret < 0) {perror("poll");exit(0);} else if(ret == 0) {printf("timeout\r\n");continue;} else {//分別判斷每個fdif(fds[0].revents & POLLIN) {//回讀當前的4個燈狀態read(fd, cur_buf, 4);for(i = 0; i < 4; i++) {if(cur_buf[i] != save_buf[i]) {save_buf[i] = cur_buf[i] ; //更新當前按鍵狀態if(save_buf[i] == '1') {printf("K%d press\r\n", i + 1);} else {printf("K%d up\r\n", i + 1);}printf("keys:%s\r\n", save_buf);}}printf("keys:%s\r\n", save_buf);}}}//關閉文件close(fd);return 0;
}

二、poll接口

1.驅動層

void poll_wait(struct file * pfile, wait_queue_head_t * wait_address, poll_table *p)

參數:
pfile:由unsigned int xxxx_poll(struct file *filp,struct poll_table_struct *wait)第一個參數傳遞
wait_address:上面的定義并且初始化的等待隊列頭wq
p:由unsigned int xxxx_poll(struct file *filp,struct poll_table_struct *wait)第二個參數傳遞
返回值:成功:返回設備的狀態掩碼( 正數):可讀,可寫,出錯掩碼;失敗:負數,錯誤碼。

返回值掩碼含義
POLLIN如果設備無阻塞的讀,就返回該值
POLLRDNORM通常的數據已經準備好,可以讀了,就返回該值。
POLLERR如果設備發生錯誤,就返回該值。
POLLOUT如果設備可以無阻塞地寫,就返回該值
POLLWRNORM設備已經準備好,可以寫了,就返回該值。

設備可讀,通常返回: (POLLIN | POLLRDNORM)
設備可寫,通常返回: (POLLOUT | POLLWRNORM)

2.應用層

int poll(struct pollfd fd[], nfds_t nfds, int timeout)

功能:可以阻塞/非阻塞地監測多個文件的可讀、 可寫、 錯誤事件發生。 poll 函數退出后, struct pollfd 變量的fd,events 值被清零,需要重新設置, revents 變量包含了監測結果。
參數:
fd:表示被監視的文件描述符(不用申明,需要定義和初始化賦值),結構如下:

struct pollfd {
int fd; //文件描述符
short events; //請求的事件
short revents; //返回的事件
};

fd:打開文件的文件描述符
events:傳入需要監測事件
revents:傳出返回的事件

nfds:要監視的文件描述符的數量。
timeout:大于0 :等待指定數目的毫秒數;0 :立即返回,不阻塞進程;-1 :永遠等待, 直到有任何一個監測的文件描述符發生變化
返回值:
大于0 : fd 數組中準備好讀,寫或出錯狀態的那些文件描述符號的總數量( 我們要關心這種情況)
等于0 : 超時
小于0 : 調用函數失敗

返回值意義
POLLIN普通或優先級帶數據可讀
POLLRDNORM普通數據可讀
POLLRDBAND優先級帶數據可讀
POLLPRI高優先級數據可讀
POLLOUT普通數據可寫
POLLWRNORM普通數據可寫
POLLWRBAND優先級帶數據可寫
POLLERR發生錯誤
POLLHUP發生掛起
POLLNVAL描述字不是一個打開的文件

注意:后三個只能作為描述字的返回結果存儲在 revents 中,而不能作為測試條件用于 events 中。

三、select函數

1.應用層 (對應設備驅動的 poll接口)

int select(int nfds,fd_set *readset, fd_set *writeset,fd_set *exceptset, struct timeval*timeout)
//exceptset 三個集合中的 fd, 所以如果想檢測它們, 則需要在返回后再次添加。 

參數說明:
ndfs: select 監視的文件文件描述符中值最大值+1。
readset: select 監視的可讀文件描述符集合。可以傳入 NULL 值,表示不關心任何文件的讀變化。
writeset: select 監視的可寫文件描述符集合。可以傳入 NULL 值,表示不關心任何文件的寫變化。
exceptset: select 監視的異常文件描述符集合。
timeout:本次 select()的超時結束時間。
時間結構定義如下:

struct timeval{
long tv_sec; /*秒 */
long tv_usec; /*微秒 */
}

返回值:
大于 0:執行成功則返回文件描述符狀態已改變的個數;
等于0:代表已超過 timeout 時間, 文件描述符狀態還沒有發生改變;
等于-1:函數有錯誤發生錯誤原因存于 errno,此時參數 readset, writeset, exceptset 和 timeout 的值變成不可預測。

代碼例子:
驅動層代碼:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <asm/uaccess.h>
#include <linux/delay.h>      //msleep
#include <linux/poll.h>        //poll接口的相關定義及函數//1)第一步: 添加相關頭文件
#include <linux/wait.h>        //等待隊列相關數據結構及函數
#include <linux/sched.h>       //進程狀態宏定義//2)定義一個等待隊列頭變量,并且初始化
static DECLARE_WAIT_QUEUE_HEAD(wq);//按鍵數量
#define BTN_SIZE   4//按鍵緩沖區,'0'表示沒有按鍵,'1'表示按下了
static char keybuf[] = {"0000"};/*把一個當成一個對象來看待,方便編程,定義一個描述按鍵結構*/
struct button_desc {int  gpio;   //存放io口編號int  number; //存放按鍵編號,根據自己需要設計,char *name;  //按鍵名字,隨便,但是要有意義
};/* 定義4個按鍵的信息 */
static struct button_desc buttons[] = {{ EXYNOS4_GPX3(2), 0, "KEY0" },{ EXYNOS4_GPX3(3), 1, "KEY1" },{ EXYNOS4_GPX3(4), 2, "KEY2" },{ EXYNOS4_GPX3(5), 3, "KEY3" },
};/* 按鍵動作標志,在中斷程序中置1,read成功復制數據后清0 */
int press = 0;//中斷服務函數
irqreturn_t key_isr(int irq, void* dev)
{//存放按鍵狀態int dn = 0;int index = 0;//這里進行還原原來的類型struct button_desc *bdata = (struct button_desc *)dev;//把讀取到的結果取邏輯非,因為程序設計使用正邏輯。'1'表示按下。dn = !gpio_get_value(bdata->gpio);//取得當前中斷對應 的按鍵編號index = bdata->number;//把按鍵狀態更新到對應的按緩沖中keybuf[index] = dn + '0';//輸出按鍵提示//  printk("%s %s\r\n", bdata->name, dn ? "down" : "up");//4) 在等待條件變成真的地方調用  wake_up*函數喚醒休眠的進程press = 1;wake_up_interruptible(&wq) ;//通知內核到wq這個隊列頭上的鏈表去查詢每一個休眠的進程 的條件是否變成了真。//如果進程等待的條件還沒有是真,則繼續休眠。return IRQ_HANDLED;
}static ssize_t tiny4412_read (struct file *flp, char __user *buff,size_t count,loff_t * off)
{int ret = 0;//用戶傳遞0,直接返回if(!count) {return 0;}//修正參數if(count > BTN_SIZE ) {count = BTN_SIZE;}/* 沒有按鍵動作( 按下和松開時候 )*/if (!press) {if (flp->f_flags & O_NONBLOCK) { //調用open("/dev/button",flags) --》flags 存放在filp->f_flags = flagsreturn -EAGAIN;} else {//  while(press == 0) {  //這個循環中不能是獨占類型代碼,否則進程會死循環。 不能是while(press==0);//      msleep(5);       //休眠,進程放棄CPU,這種休眠后不可被信號中斷。// }//3)在需要休眠的地方使用wait_event*函數進行進行。//休眠,等待有按鍵動作喚醒進程。wait_event_interruptible(wq, press);}}/* 清標志 */press = 0;//復制數據到用戶空間ret = copy_to_user(buff, keybuf, count);if(ret) {printk("error:copy_to_user\r\n");return -EFAULT;}return count;
}
//輪詢接口
//這個函數執行時候不會引起阻塞,
unsigned int tiny4412_poll (struct file *pfile, struct poll_table_struct *wait)
{unsigned int mask = 0;       //一定要初始化為0,因為mask是局部變量//1)調用 poll_wait 把當前進程添加到等待隊列中poll_wait(pfile, &wq, wait); //這個函數不會引起進程的阻塞//2)返回設備狀態掩碼(是否可讀可寫標志)if(press) {mask = POLLIN | POLLRDNORM;}return mask;
}static const struct file_operations dev_fops = {.read   =   tiny4412_read,.poll   =   tiny4412_poll,.owner  =   THIS_MODULE,
};#define LEDS_MAJOR  255   //255
#define DEVICE_NAME  "mybtns"static struct miscdevice misc = {.minor = LEDS_MAJOR, //次設備號.name  = DEVICE_NAME,//設備名.fops  = &dev_fops,  //文件操作方法
};static int __init btn_init(void)
{int ret;int irq;int i;int flags;flags = IRQ_TYPE_EDGE_BOTH; //設置為雙邊觸發for ( i = 0; i < 4 ; i++ ) {//得到中斷號irq = gpio_to_irq(buttons[i].gpio); //keyX//注冊中斷ret = request_irq(irq, key_isr, flags, buttons[i].name, (void*)&buttons[i]);if(ret < 0) {break;}}//如果不是全部成功,則反向注銷已經注冊的中斷if(ret < 0) {for ( --i; i; i-- ) {irq = gpio_to_irq(buttons[i].gpio); //keyXdisable_irq(irq);free_irq(irq, (void*)&buttons[i]);}return ret;}//注冊雜項設備ret = misc_register(&misc);       //注冊混雜設備return ret;
}static void __exit btn_exit(void)
{int i = 0;int irq;//注銷中斷for (i = 0; i < 4; i++) {irq = gpio_to_irq(buttons[i].gpio); //keyXdisable_irq(irq);free_irq(irq, (void*)&buttons[i]);}//注銷雜項設備misc_deregister(&misc);
}module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");

應用層代碼:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>    //lseek
#include <sys/ioctl.h> //ioctl
#include <poll.h>      //poll//select
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>#define   poll_or_select    0//poll:1;select:0char save_buf[10] = {"0000"};   //存放數據使用int main(void)
{int fd;                      //存放文件描述符號int ret;int i;
#if      poll_or_selectstruct pollfd fds[1];
#elsefd_set readset ;             //定義監測讀集合struct timeval timeout; 
#endif    //以非阻塞方式打開//fd = open("/dev/mybtns", O_RDWR | O_NONBLOCK );//以讀寫方式進行打開,默認是阻塞方式打開的fd = open("/dev/mybtns", O_RDWR );if(fd < 0) {printf("open error\r\n");return -1;}//實際程序需要循環讀取按鍵動作,然后根據動作完成不同事情while(1) {char cur_buf[10] = {0};   //臨時存放數據使用
#if  poll_or_select    fds[0].fd     = fd;fds[0].events = POLLIN;  //要監測讀事件//ret = poll(fds, 1, -1);   //   永遠阻塞直到有變化 //ret = poll(fds, 1, 0);      //   非阻塞ret = poll(fds, 1, 2000); //   2秒超時
#else//必須每重新設備,因為每次select返回時。timeout變成值會清成為0timeout.tv_sec   = 2;timeout.tv_usec  = 0;//一般在重新添加監測對象時候前需要前0全部fdFD_ZERO(&readset);        //清集合//必須每次重新添加要監測的對象,因為每次select返回時。readset變成部分值會清成為0FD_SET(fd, &readset);     //添加監測對象到集合//永遠阻塞直到有文件狀態發生變化ret = select(fd+1,&readset,NULL,NULL,NULL);//2秒超時//ret = select(fd + 1, &readset, NULL, NULL, &timeout);
#endif        //判斷查詢結果if(ret < 0) {perror("poll"); perror("select");exit(0);} else if(ret == 0) {printf("timeout\r\n");continue;} else {//分別判斷每個fd
#if  poll_or_select              if(fds[0].revents & POLLIN) 
#else                if(FD_ISSET(fd, &readset))
#endif                 {//回讀當前的4個燈狀態read(fd, cur_buf, 4);for(i = 0; i < 4; i++) {if(cur_buf[i] != save_buf[i]) {save_buf[i] = cur_buf[i] ; //更新當前按鍵狀態if(save_buf[i] == '1') {printf("K%d press\r\n", i + 1);} else {printf("K%d up\r\n", i + 1);}printf("keys:%s\r\n", save_buf);}}printf("keys:%s\r\n", save_buf);}}}//關閉文件close(fd);return 0;
}

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/123369.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息