https://blog.csdn.net/fengyuwuzu0519/article/details/71046343
https://blog.csdn.net/cs953575/article/details/60590183
字符设备驱动程序之poll机制
测试程序:死循环read
如果没有按键按下,read永远会在那里等待。
优化目的(poll):5秒钟之内,如果没有按键按下,就给我返回。
poll机制:(查询机制)
它在查询的过程中,可以休眠,在这一段时间内休眠。在这一段时间内,如果有按键按下,它就立刻返回。如果一直没有按键按下,等这段时间到了之后,就返回。
在驱动程序里poll,可以仿照内核里写的poll函数:
对于系统调用poll或select,它们对应的内核函数是sys_poll。从sys_poll函数里,分析poll的机制:(※※※下图解析很详细)
前方高能预警
1、之前是在read函数里,wait_event_interruptible(button_waitq, ev_press),将进程挂在队列里的。
当使用poll机制时,在sys_poll的do_poll函数里:
__pollwait->qproc(filp, &button_waitq, p);(把当前的进程挂到button_waitq队列里去,它并不会休眠)
将进程挂到button_waitq队列里。中断服务程序再从队列里唤醒。
2、应用程序调用poll时,内核就会调用了sys_poll(驱动中的forth_drv_poll是sys_poll中的一部分,应用程序调用poll,并非简单的调用驱动程序中的poll函数)。
3、驱动程序的poll函数里,poll_wait(file, &button_waitq, wait); ,poll_wait来调用上面说到的__pollwait函数,这代码只不过是让这个进程挂到button_waitq队列里,但是现在不会立即休眠。
4、※※※为什么要把进程放到button_waitq队列里去呢?
如果在休眠时间5秒之内,已经有了按键发生。中断处理函数有按键发生了,中断处理函数就可以唤醒这个队列,来触发这个进程,之前是在read里的wait_event_interruptible让进程挂到队列里去的。当用poll机制时,在sys_poll里面,把进程挂到队列button_waitq里面去的,中断程序就从队列里唤醒。(你休眠我才能唤醒你;我唤醒你,你必须在那里休眠)
5、do_poll里面的死循环,退出的条件是:(count非0,超时,有信号等待处理),※※※如果前面的条件不满足,进程就会进入休眠。那么,谁来唤醒进程呢?除了休眠到指定时间被系统唤醒外,还可以被驱动程序唤醒(中断服务程序),这就是为什么驱动的poll里要调用poll_wait的原因。
这三个条件之一就是刚才的count,可见如果相关的fd可读,程序就会跳出do_poll函数,停止睡眠。其他两个条件:一是,如果在timeout这段时间内没有其他进程去唤醒它,那么第二次执行判断的时候就会跳出死循环。二是,如果在这段时间内有其他进程唤醒它,那么也可以跳出死循环返回(例如我们可以利用中断处理函数去唤醒它,这样的话一有数据可读,就可以让它立即返回)。可见,驱动程序里与poll相关的地方有两处:一是构造file_operation结构时,要定义自己的poll函数。二是通过poll_wait来调用上面说到的__pollwait函数,
6、执行poll函数是不会引起休眠的,__timeout = schedule_timeout(__timeout);,这才是真正的休眠的函数,在这里之后,进程才会休眠。
7、forth_drv_poll函数:
static unsigned forth_drv_poll(struct file *file, poll_table *wait)
{
unsigned int mask = 0;
//poll_wait会调用sys_poll的__pollwait函数
poll_wait(file, &button_waitq, wait); //不会立即休眠,这只是让进程挂到队列里面去。
//休眠是在"do_poll"中的"schedule_timeout()"
//ev_press=0,休眠,ev_press=1,唤醒
if (ev_press) //如果当前有数据可以返回应用程序,否则mask=0
mask |= POLLIN | POLLRDNORM;
return mask;//如果返回0,do_poll的count++就不会执行,往下就会休眠schedule_timeout()
}
8、应用程序使用poll机制或者select机制,是一样道理,最终都会调用驱动程序里面的poll函数(syspoll)。
驱动程序forth_drv.c
/*
一、驱动框架:
1.先定义file_operations结构体,其中有对设备的打开,读和写的操作函数。
2.分别定义相关的操作函数
3.定义好对设备的操作函数的结构体(file_operations)后,将其注册到内核的file_operations结构数组中。
此设置的主设备号为此结构在数组中的下标。
4.定义出口函数:卸载注册到内核中的设备相关资源
5.修饰 入口 和 出口函数
6.给系统提供更多的内核消息,在sys目录下提供设备的相关信息。应用程序udev可以据此自动创建设备节点,
创建一个class设备类,在此类下创建设备
*/
#include <linux/module.h> //内涵头文件,含有一些内核常用函数的原形定义。
#include <linux/kernel.h> //最基本的文件,支持动态添加和卸载模块。Hello World驱动要这一个文件就可以。
#include <linux/fs.h> //包含了文件操作相关的struct的定义,例如struct file_operations
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h> //包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义
#include <asm/irq.h>
#include <asm/io.h> //包含了ioremap、ioread等内核访问IO内存等函数的定义
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
static struct class *forthdrv_class; //一个类
static struct class_device *forthdrv_class_dev; //一个类里面再建立一个设备
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;
/* 下面两个是定义休眠函数的参数 */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中断时间标志,中断服务程序将它置1,forth_drv_read将它清0 */
static volatile int ev_press=0;
/* 引脚描述的结构体 */
struct pin_desc{
unsigned int pin;
unsigned int key_val;
};
/* 键值:按下时,0x01,0x02,0x03,0x04 */
/* 键值:松开时,0x81,0x82,0x83,0x84 */
static unsigned char keyval; //键值
/* 在request_irq函数中把结构体传进去 */
struct pin_desc pins_desc[4] = { //键值先赋初始值0x01,0x02,0x03,0x04
{S3C2410_GPF0, 0x01}, //pin=S3C2410_GPF0, key_val(按键值)=0x01
{S3C2410_GPF2, 0x02}, //pin=S3C2410_GPF2, key_val(按键值)=0x02
{S3C2410_GPG3, 0x03}, //pin=S3C2410_GPF3, key_val(按键值)=0x03
{S3C2410_GPG11, 0x04}, //pin=S3C2410_GPF11, key_val(按键值)=0x04
};
/*
* 确定按键值
*/
static irqreturn_t button_irq(int irq, void *dev_id) //中断处理函数
{
/* irq = IRQ_EINT0 …… */
/* dev_id = 结构体struct pins_desc */
struct pin_desc * pindesc = (struct pin_desc *)dev_id;
unsigned int pinval;
/* 读取引脚PIN值 */
pinval = s3c2410_gpio_getpin(pindesc->pin);
/* 确定按键值,按下管脚低电平,松开管脚高电平 */
if(pinval)
{
/* 松开 */
keyval = 0x80 | pindesc->key_val; //规定的:0x8X
}
else
{
/* 按下 */
keyval = pindesc->key_val; //0x0X
}
/* 唤醒 */
ev_press = 1; /* 表示中断发生了 */
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程,去button_wq队列,把挂在队列下的进程唤醒 */
return IRQ_RETVAL(IRQ_HANDLED);
}
static int forth_drv_open(struct inode *inode, struct file *file)
{
/* 配置GPF0,2为输入引脚 */
/* 配置GPF3,11为输入引脚 */
/* request_irq函数的第五个参数是void *,为无类型指针,可以指向任何数据类型 */
request_irq(IRQ_EINT0, button_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
request_irq(IRQ_EINT2, button_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
request_irq(IRQ_EINT11, button_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
request_irq(IRQ_EINT19, button_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);
return 0;
}
ssize_t forth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
if (size != 1)
return -EINVAL;
/* 如果没有按键动作,休眠,休眠:让出CPU */
/* 休眠时,把进程挂在button_wq 队列里 */
/* 如果休眠后被唤醒,就会从这里继续往下执行 */
/* 一开始没有按键按下,ev_press = 0 */
wait_event_interruptible(button_waitq, ev_press);//ev_press=0,休眠,让我们的测试程序休眠;ev_press!=0,直接往下运行
/* 如果有按键动作,返回键值 */
copy_to_user(buf, &keyval, 1); //把键值 拷回去
ev_press = 0; //清零,如果不清零,下次再读,立马往下执行,返回原来的值
return 1;
}
int forth_drv_close(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT0, &pins_desc[0]);
free_irq(IRQ_EINT2, &pins_desc[1]);
free_irq(IRQ_EINT11, &pins_desc[2]);
free_irq(IRQ_EINT19, &pins_desc[3]);
return 0;
}
static unsigned forth_drv_poll(struct file *file, poll_table *wait)
{
unsigned int mask = 0;
//poll_wait会调用sys_poll的__pollwait函数
poll_wait(file, &button_waitq, wait); //不会立即休眠,这只是让进程挂到队列里面去。
//休眠是在"do_poll"中的"schedule_timeout()"
//ev_press=0,休眠,ev_press=1,唤醒
if (ev_press) //如果当前有数据可以返回应用程序,否则mask=0
mask |= POLLIN | POLLRDNORM;
return mask;//如果返回0,do_poll的count++就不会执行,往下就会休眠schedule_timeout()
}
static struct file_operations forth_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = forth_drv_open,
.read = forth_drv_read,
.release = forth_drv_close,
.poll = forth_drv_poll,
};
int major;
static int forth_drv_init(void)
{
major = register_chrdev(0, "forth_drv", &forth_drv_fops);
//创建一个类
forthdrv_class = class_create(THIS_MODULE, "firstdrv");
//在这个类下面再创建一个设备
//mdev是udev的一个简化版本
//mdev应用程序,就会被内核调用,会根据类和类下面的设备这些信息
forthdrv_class_dev = class_device_create(forthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons");/* /dev/buttons */
//建立地址映射:物理地址->虚拟地址
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); //指向的是虚拟地址,第一个参数是物理开始地址,第二个是长度(字节)
gpfdat = gpfcon + 1; //加1,实际加4个字节
gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16); //指向的是虚拟地址,第一个参数是物理开始地址,第二个是长度(字节)
gpgdat = gpgcon + 1; //加1,实际加4个字节
return 0;
}
static void forth_drv_exit(void)
{
unregister_chrdev(major, "forth_drv");
class_device_unregister(forthdrv_class_dev);
class_destroy(forthdrv_class);
iounmap(gpfcon);
iounmap(gpgcon);
return 0;
}
module_init(forth_drv_init);
module_exit(forth_drv_exit);
MODULE_LICENSE("GPL");
测试程序forthdrvtest.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
/* forthdrvtest
*/
int main(int argc, char **argv)
{
int fd;
unsigned char key_val;
int ret;
struct pollfd fds[1];//只查询一个驱动程序
fd = open("/dev/buttons", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
fds[0].fd =fd; //查询的文件
fds[0].events = POLLIN; //有数据等待你读取
while (1)
{
//read,如果没有按键按下,永远不会返回
//目的:在一定时间内,如果没有按键发生,给我返回
//如果在5秒之内,没有按键发生,就返回0
ret = poll(fds, 1, 5000); //参数:fds,查询几个文件,超时时间(以毫秒为单位) 返回值:A value of 0 indicates that the call timed out
if (ret == 0)
{
printf("time out\n");
}
else //如果按下按键,立刻返回,返回值为非0
{
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
}
return 0;
}
1、使用命令man poll来查看poll函数的用法:
包含头文件:#include <poll.h>
poll函数:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数:
struct pollfd *fds:(可以同时查询多个文件,按键、网卡,所以定义了一个数组)
nfds_t nfds:查询几个文件
int timeout:超时时间(以毫秒为单位)
struct pollfd {
int fd; /* file descriptor查询的文件 */
short events; /* requested events POLLIN期待获得的值,表示有数据等待你读取 */
short revents; /* returned events */
};
events:POLLIN There is data to read.(有数据等待你读取)
返回值:如果是0,表示有超时。
2、使用top命令,可耗的CPU资源很少。即使poll查询方式,也会导致进程休眠。
Makefile文件
KERN_DIR = /work/system/linux-2.6.22.6
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += forth_drv.o