字符设备驱动程序之poll机制(九)

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_polldo_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

猜你喜欢

转载自blog.csdn.net/xiaodingqq/article/details/80318076
今日推荐