linux 内核驱动的poll,嵌入式Linux驱动开发(五)——poll机制原理以及驱动实现...
前情回顧:
再開始今天的內(nèi)容之前,先簡單review一下,我們都用了什么方案來獲取按鍵值,他們的特點都是什么。只有不斷地理清了思路,我們才能夠更好的理解,為何會出現(xiàn)如此多的解決方案,當遇到問題的時候,才可以對癥下藥。
第一種方案,也是最粗暴的一種,在應用層通過一個死循環(huán)的read函數(shù),來不斷地查看底層read函數(shù),在底層我們并未做阻塞,每次應用層read,底層都會給上層應用返回值。那么,這個簡單粗暴的方式,顯然是最不合理的,因為read函數(shù)不斷地在讀取,使得CPU的使用率被死循環(huán)占用了99%,這基本上是災難式的解決方案。雖然簡單,但是代價也是顯而易見的。
為了降低CPU的使用率,那么就要避免使用死循環(huán)不斷地來從底層獲取信息,那么,也就產(chǎn)生了兩種解決思路,也分別就是第二篇文章中的兩種解決方案了。
第二種方案,直接舍棄了上層應用read的操作,全部交給底層,通過中斷來實現(xiàn)。(request_irq 和 free_irq函數(shù)來注冊和卸載中斷)只要按鍵按下,進入中斷模式,調(diào)用底層的中斷處理函數(shù),來將按鍵信息進行打印。雖然上層應用沒有什么影響,但是,這樣的方案也并不是我們想要的,因為,底層相對穩(wěn)定的部分,參與了他不該參與的部分——具體中斷如何解決。這樣其實也是災難性的,難道因為上層應用程序的需求發(fā)生了改變,還需要修改底層的驅(qū)動程序嗎?!這顯然要把從用戶到應用層開發(fā)再到底層應用開發(fā)人員給煩死。但是這個方案雖然不能解決需求,但它也并不是一無是處的,正是因為他的存在,才出現(xiàn)了第三種解決的方案。
第三種方案,實際從底層解決思路的角度來看,是通過睡眠的方式解決的,從上層來看是通過阻塞來解決的。也就,上層應用通過調(diào)用了阻塞式的read函數(shù)(這也是大部分 read 函數(shù)的使用場景),如果沒有數(shù)據(jù)返回來,就讓 read 阻塞在這里,上層應用可以放心的死循環(huán)來調(diào)用 read, 不必擔心它會不停地調(diào)用底層而耗費大量資源。對于底層如何來實現(xiàn)阻塞式的 read 呢?其實也很簡單是通過睡眠的方式來實現(xiàn)的。
static DECLARE_WAIT_QUEUE_HEAD(wait_queue);
wake_up_interruptible(&wait_queue);
wait_event_interruptible(wait_queue, condition);
通過以上的函數(shù),來實現(xiàn)底層應用的睡眠功能,結(jié)合第二種方案,在上層 read 的時候直接讓驅(qū)動睡眠,而不是返回。通過注冊中斷處理函數(shù),在其中,來喚醒并通過copytouser函數(shù)來把數(shù)據(jù)返回到上層應用的 read 函數(shù)中,從而實現(xiàn)了上層的阻塞功能。不但可以檢測到中斷的具體發(fā)生情況,同時也避免了 CPU 的高使用率的發(fā)生。
通過以上一系列的方法,初步實現(xiàn)了獲取按鍵值的需求,但是,還不能夠滿足我們的所有需求,比如,最后的解決方案是將 read 阻塞在了那里,這不一定能滿足我們所有的需求,因此,也誕生了以下的方案——poll機制。
延續(xù)之前文章中提到的最后一種方案,一直阻塞在那里可能會限制我們的需求,那么,是否可以通過一種方案,讓他不要一直阻塞,而是阻塞一段時間,如果沒有變化就先讓它返回,不要一直阻塞在那里。
對于Linux應用開發(fā)來說,poll 機制的使用和 open、read函數(shù)的使用大同小異,只需要調(diào)用 poll 函數(shù)即可。關(guān)于這個函數(shù)的具體使用方法,可以通過man poll來查看具體的函數(shù)使用方法。
poll 函數(shù)的原型如下:
#include
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
根據(jù)man手冊的信息,可以在應用層實現(xiàn) poll 函數(shù)和 read 函數(shù)的調(diào)用。
#include
#include
#include
#include
#include
#include
int main()
{
//打開設備文件
int fd = open("/dev/poll_eint", O_RDWR);
if(fd < 0)
{
printf("open error\n");
return -1;
}
#if 0
/*
poll 函數(shù)所接收的第一個參數(shù),該結(jié)構(gòu)體的內(nèi)容
*/
struct pollfd {
int fd; /* file descriptor 文件描述符*/
short events; /* requested events 輸入?yún)?shù)*/
short revents; /* returned events 輸出參數(shù),事件發(fā)生后由內(nèi)核來修改*/
};
#endif
//創(chuàng)建 struct polld 結(jié)構(gòu)體,并為其中的成員賦值
struct pollfd fds[1];
fds[0].fd = fd; //指定文件描述
fds[0].events = POLLIN;//有數(shù)據(jù)可以讀的時候返回
int ret = 0; //poll 函數(shù)的返回值,依據(jù)返回值判斷是否有數(shù)據(jù)可讀
unsigned char value = 0; //讀取的按鍵值
//循環(huán)查詢
while(1)
{
// int poll(struct pollfd *fds, nfds_t nfds, int timeout);
/*
fds:struct pollfd 數(shù)組,其中管理了文件描述以及輸入?yún)?shù)
ndfs: 需要通過 poll 機制管理的文件描述符數(shù)量
timeout:超時時間,單位為毫秒
*/
//調(diào)用 poll 函數(shù)
ret = poll(fds, 1, 5000);
//如果返回值為0,打印超時
if(!ret)
printf("timeout.....%d\n\n", ret);
else{
//返回值不為0,說明有數(shù)據(jù)可以讀取,通過 read 來讀取數(shù)據(jù)并打印
read(fd, &value, 1);
printf("value: %x\t ret: %d\n", value, ret);
}
}
return 0;
}
以上就是一個簡單的通過 poll 查詢的應用程序示例代碼。但是,Linux 系統(tǒng)是如何來實現(xiàn) poll 函數(shù)的,驅(qū)動程序如何來寫是接下來要討論的話題。
和 open 一樣,上層調(diào)用 open 函數(shù),進入內(nèi)核空間,調(diào)用 sys_open 系統(tǒng)調(diào)用,在逐層向下調(diào)用,一直到調(diào)用到驅(qū)動程序中 file_operations 中注冊的 open函數(shù)為止。poll 函數(shù)也同樣,根據(jù)內(nèi)核源代碼,具體 poll 函數(shù)的調(diào)用原理如下。
poll機制的調(diào)用原理
應用層通過調(diào)用: poll函數(shù)
進入到內(nèi)核空間的系統(tǒng)調(diào)用: sys_poll(位于/linux/sys_poll.h 文件中)
do_sys_poll(...., timeout_jiffies)
poll_initwait(&table)
init_poll_funcptr(&pwq->pt, __pollwait)
pt->qproc = qproc //相當于table->qproc = __pollwait
do_poll(nfds, head, &table, timeout)
for(;;) //死循環(huán)
if(do_pollfd(pfd, pt))
//do_pollfd中,調(diào)用mask = file->f_op->poll(file, pwait); return mask;
//實際此時就會調(diào)用到驅(qū)動程序中的 poll 函數(shù)
p->qproc(filp, wait_address, p);//驅(qū)動程序中的 poll 函數(shù),需要調(diào)用 poll_wait 函數(shù),poll_wait 函數(shù)中執(zhí)行了這個條語句
__pollwait()//把當前進程掛到隊列中去管理,并不休眠
count++; //如果驅(qū)動的poll返回非0值,那么count++
pt = NULL;
if(count || !*timeout || signal_pending(current))
break; //break的三個條件:count非0;超時;有信號等待處理
//如果條件不成立,會進入到休眠狀態(tài)
//驅(qū)動的poll:p->qproc(filp, wait_address, p) 把當錢進程掛到wait_address隊列中去
__timeout = schedule_timeout(__timeout) //休眠__timeout的時間
根據(jù)以上的調(diào)用路線可以看到其中有一句f_op->poll(....),可以看出這個句話實際是從之前我們很熟悉的struct file_operations中取出了poll的函數(shù)指針,并且添加到一個超時隊列中來進行調(diào)用管理的。
那么,我們只需要看看具體的 poll 函數(shù)原型是怎么樣的也就可以來寫poll 機制的驅(qū)動了。
struct file_operations {
// .........
unsigned int (*poll) (struct file *, struct poll_table_struct *);
//.........
};
驅(qū)動程序使用 poll 機制以及中斷機制來獲取按鍵值
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define EINT_PIN_COUNT 4
static const char* dev_name = "poll_eint";
static volatile unsigned int major = 0;
static struct class* poll_class;
static struct class_device* poll_class_device;
struct pin_desc
{
unsigned int pin;
unsigned int value;
};
//%kernel%\include\asm-arm\arch\irqs.h
//#define IRQ_EINT0 S3C2410_IRQ(0) /* 16 */
//中斷號數(shù)組
static const int eints[EINT_PIN_COUNT] =
{
IRQ_EINT0,
IRQ_EINT2,
IRQ_EINT11,
IRQ_EINT19
};
static struct pin_desc pins[4] =
{
{S3C2410_GPF0, 0x1},
{S3C2410_GPF2, 0x2},
{S3C2410_GPG3, 0x3},
{S3C2410_GPG11, 0x4},
};
unsigned int status = 0;
unsigned int condition = 0;
unsigned char value = 0;
static DECLARE_WAIT_QUEUE_HEAD(wait_queue); //初始化中斷等待序列
//中斷處理函數(shù)
static irqreturn_t irq_handler(int irq, void *dev_id)
{
//獲取按鍵值,如果按下則與0x80
struct pin_desc* desc = (struct pin_desc*) dev_id;
status = s3c2410_gpio_getpin(desc->pin);
if(status)
value = desc->value | 0x80;
else
value = desc->value;
//從睡眠隊列中喚醒
wake_up_interruptible(&wait_queue);
condition = 1;
return 0;
}
static ssize_t poll_read (struct file *file, char __user *buff, size_t size, loff_t *ppos)
{
//read 的時候直接進入睡眠狀態(tài),有中斷發(fā)生的時候在中斷處理函數(shù)中喚醒
printk(".........read\n\n");
wait_event_interruptible(wait_queue, condition);
condition = 0;
//喚醒后,講數(shù)據(jù)發(fā)送到用戶空間
printk("read.........\n\n");
copy_to_user(buff, &value, 1);
return 0;
}
static unsigned int poll_poll (struct file *file, struct poll_table_struct *pts)
{
unsigned int mask = 0;
printk("poll<<<<<<<<<<<<<
// static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
poll_wait(file, &wait_queue, pts); //將需要管理的文件描述符、休眠的隊列告訴內(nèi)核,內(nèi)核會將該進程放到管理隊列中,但并不會在這里進行休眠
printk("poll>>>>>>>>>>>>>>\n\n");
//如果按鍵被按下,返回非零值,以便應用層判斷poll 返回值
if(condition)
mask |= POLLIN | POLLRDNORM;
return mask;
}
static int poll_open (struct inode *inode, struct file *file)
{
// 在 open 的時候注冊按鍵中斷
int i;
for(i = 0; i < EINT_PIN_COUNT; ++i){
request_irq(eints[i], irq_handler, IRQT_BOTHEDGE, dev_name, &pins[i]);
}
printk("interrupt register\n");
return 0;
}
static int poll_release (struct inode *inode, struct file *file)
{
//釋放之前注冊的中斷
//void free_irq(unsigned int irq, void *dev_id)
int i = 0;
for(;i < EINT_PIN_COUNT; ++i){
free_irq(eints[i], &pins[i]);
}
printk("button released\n");
return 0;
}
struct file_operations poll_fops =
{
.owner = THIS_MODULE,
.open = poll_open,
.read = poll_read,
.poll = poll_poll,
.release = poll_release,
};
static int __init poll_init(void)
{
major = register_chrdev(major, dev_name, &poll_fops);
poll_class = class_create(THIS_MODULE, dev_name);
poll_class_device = class_device_create(poll_class, NULL, MKDEV(major, 0), NULL, dev_name);
printk("init\n");
return 0;
}
static void __exit poll_exit(void)
{
unregister_chrdev(major, dev_name);
class_device_unregister(poll_class_device);
class_destroy(poll_class);
printk("exit\n");
}
module_init(poll_init);
module_exit(poll_exit);
MODULE_AUTHOR("Ethan Lee <4128127@qq.com>");
MODULE_LICENSE("GPL");
通過以上的代碼,就可以實現(xiàn)通過 poll 機制和中斷的方式來實現(xiàn)不完全阻塞的狀態(tài)來查詢按鍵值了。
那么以上的方法實際都是通過應用層查詢的方式來實現(xiàn)了具體的按鍵值獲取任務,是不是還有其他的方式來獲取按鍵值呢?
假設把應用程序主動查看的方式比作一個人通過不斷地打電話的方式來詢問快遞是否送達,那么,是不是可以讓驅(qū)動程序來給應用程序打電話,通知他狀態(tài)發(fā)生了變化呢?
這個當然是可以的,肯定需要用到進程間的通訊方式。
具體的實現(xiàn)過程,在下一篇文章里面我們來探討吧。
總結(jié)
以上是生活随笔為你收集整理的linux 内核驱动的poll,嵌入式Linux驱动开发(五)——poll机制原理以及驱动实现...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 猪肉条上有蓝色印章是什么?
- 下一篇: 为何英国盛产上好的威士忌?