在Linux系统中应用层和驱动层之间的交互方式有多种:查询、休眠、poll、和异步通知。
查询方式(非阻塞方式):
查询方式就是通过简单的应用层和驱动层之间交互的一种方式,应用层以非阻塞的方式打开设备文件;
在应用程序中如果使用open打开一个设备文件,就会在当前进程里分配一个file结构体;
应用层使用open函数以非阻塞的方式打开一个文件方式如下:
底层驱动fileoperation结构体中的read和write函数中第一个参数就是应用层在打开该设备文件时在进程中分配的file结构体,在这个file结构体中会将应用层open中flag相关参数设置进去,在驱动的read和write函数中可以根据file结构体中的相关信息进行完善驱动代码。
以查询的方式进行应用层和底层进行交互,如果应用层不断调用read函数此时CPU占用较高是一种耗费资源的方式。
以总线设备驱动模型的驱动按键代码为例,设备信息在设备树中进行定义:
设备树中添加的代码
Key{
conpatible = "board_Key_control";
gpios = <&gpio4 1 GPIO_ACTIVE_HIGH>;
}
Key_drv.c
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <asm/current.h>
//主设备号
static int major = 0;
//创建设备类的结构体
static struct class Key_Class;
//用于从设备树中记录设备信息的结构体
static struct gpio_desc Key_device;
static ssize_t Key_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
int val = gpiod_get_value(&Key_device); //获取gpio发送过来的数据
int len;
if(size < 4){
len = size;
}else{
len = 4;
}
copy_to_user(buf, &val, size); //将获取的数据传送至应用层
return len;
}
static struct file_operations Key_control_fops = {
.owner = THIS_MODULE,
.read = Key_read,
};
static int Key_probe(struct platform_device *pdev) //驱动程序和设备树中含有conpatible的设备匹配成功后调用此函数
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
Key_device = gpiod_get(pdev->dev, NULL, 0); //记录设备信息放置在全局结构体Key_device中
gpiod_direction_input(Key_device); //设置方向
device_create(Key_Class, NULL, MKDEV(major,0), NULL, "/dev/key_control"); //创建设备节点,在此是可以让驱动和设备匹配成功后创建设备节点,不用再入口函数中进行初始化。
}
static int Key_remove(struct platform_device *pdev)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
device_destroy(Key_Class, MKDEV(major,0)); //销毁在probe中创建的设备
}
static const struct of_device_id board_control[] = { //用于匹配所有总线设备中与该驱动相匹配的设备
{ .compatible = "board_Key_control" }, //对应到设备树中的设备节点设置的conpatible属性
{ },
};
static struct platform_driver Key_platform_drv = { //总线驱动结构体
.probe = Key_probe,
.remove = Key_remove,
.driver = {
.name = "key_control",
.of_match_table = board_control, //对需要匹配的设备进行定义
},
};
static int __init Key_drv_init(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0,"key_control",&Key_control_fops); //注册字符设备驱动
Key_Class = class_create(THIS_MODULE, "key_class"); //创建对应设备的类
if (IS_ERR(Key_Class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "key_control");
return PTR_ERR(Key_Class);
}
int err = platform_driver_register(&Key_platform_drv); //注册总线驱动
return err;
}
static void __exit Key_drv_exit(void) //出口函数
{
platform_driver_unregister(&Key_platform_drv); //卸载总线驱动
class_destroy(&Key_Class); //销毁类,device_destroy是在总线驱动结构体的remove中调用
unregister_chrdev(major, "key_control"); //卸载字符设备驱动
}
module_init(Key_drv_init);
module_exit(Key_drv_exit);
MODULE_LICENSE("GPL");
在jz2440中进行验证:
应用程序:
#include <fcntl.h>
#include <stdio.h>
#include <unsitd.h>
int main(int argc,char **argv)
{
int fd;
int val = 0;
int time = 0;
fd = open("/dev/led",O_RDWR);
if(fd<0){
printf("open faile\n");
}else{
printf("open success\n");
}
while(1){
read(fd,&val,4);
printf("val = %d\n",val);
time++;
if(time == 20){
break;
}
}
return 0;
}
驱动程序:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static int major = 0;
static struct class *led_class;
static struct class_device *firstdrv_class_dev;
volatile unsigned long *gpfcon =NULL;
volatile unsigned long *gpfdat =NULL;
volatile unsigned long *gpgcon =NULL;
volatile unsigned long *gpgdat =NULL;
static int led_driver_open(struct inode * inode,struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
*gpfcon &= ~( (0x3) | (0x3<<(2*2))|(0x3<<(4*2)) |(0x3<<(5*2))|(0x3<<(6*2))); //GPIOF对应LED位清零
*gpfcon |= ( (0x1<<(4*2)) |(0x1<<(5*2))|(0x1<<(6*2))); //对应位配置为输出
*gpgcon &= ~((0x3<<(2*3)) | (0x3<<(11*2)));
*gpfdat |= (1<<4) | (1<<5) |(1<<6);
//*gpfdat &= ~((1<<4) | (1<<5) |(1<<6)) ;
return 0;
}
static ssize_t led_driver_read(struct file *file,const char __user *buf,size_t size,loff_t *ppos)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
int val = 1;
int regfval = *gpfdat;
int reggval = *gpgdat;
if(!(regfval & (1<<0))){ //判断那个按键被按下
*gpfdat &= ~(1<<4);
val = 1;
}else if(!(regfval & (1<<2))){
*gpfdat &= ~(1<<5);
val = 2;
}else if(!(reggval & (1<<11))){
*gpfdat &= ~(1<<6);
val = 3;
}else if(!(reggval & (1<<3))){
*gpfdat |= (1<<4) | (1<<5) |(1<<6);
val = 0;
}
copy_to_user(buf,&val,4);
return 0;
}
static struct file_operations led_drv_fops = {
.owner = THIS_MODULE,
.open = led_driver_open,
.read = led_driver_read,
};
static int __init led_drv_init(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "led_driver", &led_drv_fops);
led_class = class_create(THIS_MODULE,"led_class");
firstdrv_class_dev = class_device_create(led_class, NULL, MKDEV(major,0), NULL, "led");
gpfcon = (volatile unsigned long *)ioremap(0x56000050,16);
gpfdat = gpfcon+1;
gpgcon = (volatile unsigned long *)ioremap(0x56000060,16);
gpgdat = gpgcon+1;
return 0;
}
static void __exit led_drv_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
iounmap(gpfcon);
iounmap(gpfdat);
iounmap(gpgdat);
iounmap(gpgdat);
class_device_unregister(firstdrv_class_dev);
class_destroy(led_class);
unregister_chrdev(major, "led_driver");
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
使用top命令查看cpu使用情况
休眠唤醒方式:
使用休眠和唤醒方式,应用层在打开设备文件时可以不添加阻塞的flag,此时默认为阻塞方式打开,阻塞标记位被保存在file结构体中。
在对应的read中使用wait_event_interruptible函数可以使用户态当前任务进入到休眠状态,等待被唤醒,其他任务按照顺序运行,休眠的任务不占用CPU资源。唤醒休眠任务需要使用到wake_up_interruptible函数进行唤醒,唤醒的时候
wait_event_interruptible函数中第一个参数是用来将当前任务放置在队列中,第二个参数是用来判断条件是否成立。
如果需要唤醒使用wake_up_interruptible函数对休眠进行唤醒,从队列中将任务唤醒,这样驱动程序就可以继续执行wait_event_interrputible函数之后的内容:
使用休眠唤醒方式进行应用层和驱动层之间的交互,驱动程序以阻塞的方式打开,当调用wait_event_interruptible函数时就会将用户态进程上下文对应的任务进行休眠,并将任务放入等待队列中。当有驱动程序使用wake_up函数时内核态就会继续从之前调用wait_event_interruptible函数之后继续运行。用户态之前被休眠的任务也会开始运行,但是该任务并不一定是立即执行,有可能是等其他任务运行之后再运行。相比于查询方式,使用休眠唤醒方式进行应用层和驱动层进行交互可以程序在阻塞时长时间占用CPU资源的情况。
key_drv.c
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <asm/current.h>
static int major = 0;
static int has_data = 0; //等待时间条件
static int irq; //中断号
static struct class *key_class;
static struct gpio_desc *key_dev;
static wait_queue_head_t key_wq; //等待队列
static ssize_t key_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
int val;
int len;
wait_event_interruptible(key_wq, has_data); //休眠,等待唤醒
val = gpiod_get_value(key_dev); //读取GPIO信息
copy_to_user(buf, &val, size);
has_data= 0; //将唤醒条件复位
if(size>=4){
len = 4;
}else{
len = size;
}
return len;
}
static irqreturn_t key_isr(int irq, void *dev_id) //中断服务函数
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
has_data = 1; //置位中断条件
wake_up(&key_wq); //唤醒队列
return IRQ_HANDLED;
}
static struct file_operations drv_fops= {
.owner = THIS_MODULE,
.read = key_drv_read,
};
static int key_driver_probe(struct platform_device *pdev)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
key_dev = gpiod_get(pdev->dev,NULL, 0);
gpiod_direction_input(key_dev);
irq = gpiod_to_irq(key_dev); //获取中断号
request_irq(irq, key_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "key_inter", NULL); //注册中断
device_create(key_class, NULL, MKDEV(major,0), 0, "/dev/key");
return 0;
}
static int key_driver_remove(struct platform_device *pdev)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
device_destroy(key_class, MKDEV(major,0));
free_irq(irq, NULL);
gpiod_put(key_dev);
}
static const struct of_device_id key_drv_dev[] = {
{ .compatible = "key_drv", },
{}
};
static struct platform_driver key_plat_drv = {
.probe = key_driver_probe,
.remove = key_driver_remove,
.driver = {
.name = "board_key_driver",
.of_match_table = key_drv_dev,
},
};
static int __init key_drv_init(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "key_driver", &drv_fops);
key_class = class_create(THIS_MODULE, "key_drv_class");
init_waitqueue_head(&key_wq); //初始化等待队列
platform_driver_register(&key_plat_drv);
return 0;
}
static void __exit gpio_key_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
platform_driver_unregister(&key_plat_drv);
class_destroy(key_class);
unregister_chrdev(major, "key_driver");
}
module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");
使用jz2440验证中断功能
应用程序
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc,char **argv)
{
int fd;
int val = 0;
int time = 0;
fd = open("/dev/led",O_RDWR);
if(fd<0){
printf("open faile\n");
}else{
printf("open success\n");
}
while(1){
read(fd,&val,4);
printf("val = %d\n",val);
time++;
if(time == 20){
break;
}
}
return 0;
}
驱动程序
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static int major = 0;
static struct class *led_class;
static struct class_device *firstdrv_class_dev;
static int has_data = 0;
volatile unsigned long *gpfcon =NULL;
volatile unsigned long *gpfdat =NULL;
volatile unsigned long *gpgcon =NULL;
volatile unsigned long *gpgdat =NULL;
static DECLARE_WAIT_QUEUE_HEAD(key_pin_que);
static struct pin_des{
unsigned int pin;
unsigned int val;
};
static struct pin_des key_pin[4] = {
{S3C2410_GPF0,0x01},
{S3C2410_GPF2,0x02},
{S3C2410_GPG3,0x03},
{S3C2410_GPG11,0x04},
};
static irqreturn_t irq_ser(int irq,void *dev_id)
{
if(!s3c2410_gpio_getpin(S3C2410_GPF0)){ //低电平表示被按下
has_data = 1;
}else if(!s3c2410_gpio_getpin(S3C2410_GPF2)){ //低电平表示被按下
has_data = 2;
}else if(!s3c2410_gpio_getpin(S3C2410_GPG3)){ //低电平表示被按下
has_data = 3;
}else if(!s3c2410_gpio_getpin(S3C2410_GPG11)){ //低电平表示被按下
has_data = 4;
}else{
has_data = 0;
}
wake_up(&key_pin_que);
return IRQ_RETVAL(IRQ_HANDLED);
}
static int led_driver_open(struct inode * inode,struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
/* *gpfcon &= ~( (0x3) | (0x3<<(2*2))|(0x3<<(4*2)) |(0x3<<(5*2))|(0x3<<(6*2))); //GPIOF对应LED位清零
*gpfcon |= ( (0x1<<(4*2)) |(0x1<<(5*2))|(0x1<<(6*2))); //对应位配置为输出
*gpgcon &= ~((0x3<<(2*3)) | (0x3<<(11*2)));
*gpfdat |= (1<<4) | (1<<5) |(1<<6);
//*gpfdat &= ~((1<<4) | (1<<5) |(1<<6)) ;
*/
request_irq(IRQ_EINT0, irq_ser,IRQT_BOTHEDGE , "key_pin0", &key_pin[0]);
request_irq(IRQ_EINT2, irq_ser,IRQT_BOTHEDGE , "key_pin2", &key_pin[1]);
request_irq(IRQ_EINT11, irq_ser,IRQT_BOTHEDGE , "key_pin11", &key_pin[2]);
request_irq(IRQ_EINT19, irq_ser,IRQT_BOTHEDGE , "key_pin19", &key_pin[3]);
return 0;
}
static ssize_t led_driver_read(struct file *file,const char __user *buf,size_t size,loff_t *ppos)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
int val = 1;
int regfval = *gpfdat;
int reggval = *gpgdat;
wait_event_interruptible(key_pin_que, has_data);
switch(has_data){
case 1:
s3c2410_gpio_setpin(S3C2410_GPF4, 0);
break;
case 2:
s3c2410_gpio_setpin(S3C2410_GPF5, 0);
break;
case 3:
s3c2410_gpio_setpin(S3C2410_GPF6, 0);
break;
case 4:
s3c2410_gpio_setpin(S3C2410_GPF4, 1);
s3c2410_gpio_setpin(S3C2410_GPF5, 1);
s3c2410_gpio_setpin(S3C2410_GPF6, 1);
break;
}
/*
if(!(regfval & (1<<0))){
*gpfdat &= ~(1<<4);
val = 1;
}else if(!(regfval & (1<<2))){
*gpfdat &= ~(1<<5);
val = 2;
}else if(!(reggval & (1<<11))){
*gpfdat &= ~(1<<6);
val = 3;
}else if(!(reggval & (1<<3))){
*gpfdat |= (1<<4) | (1<<5) |(1<<6);
val = 0;
}
*/
copy_to_user(buf,&has_data,4);
has_data = 0;
return 0;
}
static int led_driver_close(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT0,&key_pin[0]);
free_irq(IRQ_EINT2,&key_pin[1]);
free_irq(IRQ_EINT11,&key_pin[2]);
free_irq(IRQ_EINT19,&key_pin[3]);
return 0;
}
static struct file_operations led_drv_fops = {
.owner = THIS_MODULE,
.open = led_driver_open,
.read = led_driver_read,
.release = led_driver_close,
};
static int __init led_drv_init(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "led_driver", &led_drv_fops);
led_class = class_create(THIS_MODULE,"led_class");
firstdrv_class_dev = class_device_create(led_class, NULL, MKDEV(major,0), NULL, "led");
gpfcon = (volatile unsigned long *)ioremap(0x56000050,16);
gpfdat = gpfcon+1;
gpgcon = (volatile unsigned long *)ioremap(0x56000060,16);
gpgdat = gpgcon+1;
return 0;
}
static void __exit led_drv_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
iounmap(gpfcon);
iounmap(gpfdat);
iounmap(gpgdat);
iounmap(gpgdat);
class_device_unregister(firstdrv_class_dev);
class_destroy(led_class);
unregister_chrdev(major, "led_driver");
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
使用cat/proc/interrupt查看中断信息:
使用ps指令查看程序运情况:
使用top指令查看cpu使用情况:
如果单板不支持中断时,可以使用内核线程来对休眠线程进行唤醒,在内核线程中不断查询设置相关装态,满足条件后使用wake_up函数对休眠的线程进行唤醒:
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <asm/current.h>
#include <linux/kthread.h>
static int major = 0;
static struct class *Remote_Control_Class;
static struct gpio_desc *Key_device;
static int has_data = 0;
static wait_queue_head_t Key_wq;
static struct task_struct *remote_thread; //创建线程函数返回的结构体
static ssize_t remote_control_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
wait_event_interruptible(Key_wq, has_data);
int val = gpiod_get_value(Key_device);
copy_to_user(buf, &val, 4);
has_data = 0;
if(size<4){
return size;
}
return 4;
}
static struct file_operations remote_control_fops = {
.owner = THIS_MODULE,
.read = remote_control_read,
}
static int remote_control_probe(struct platform_device *pdev)
{
*Key_device = gpiod_get(pdev->dev, NULL, 0);
device_create(Remote_Control_Class, NULL, MKDEV(major,0), NULL, "/dev/remote_control");
return 0;
}
static int remote_control_remove(struct platform_device *pdev)
{
gpiod_put(Key_device);
device_destroy(Remote_Control_Class, MKDEV(major,0));
}
static const struct of_device_id board_device[] = {
{ .compatible = "remote_control_device" },
{ },
};
static struct platform_driver plat_drv = {
.probe = remote_control_probe,
.remove = remote_control_remove,
.driver = {
.name = "board_control",
.of_match_table = board_device,
},
}
static int kthread_func(void *data)
{
int data = 0;
while(!kthread_should_stop(void)){ //为了让线程可以stop必须加kthread_should_stop(void)
data = gpiod_get_value(Key_device);
if(data != 0){
has_data = 0x08 | data;
wake_up(&Key_wq);
}
set_current_state(TASK_INTERRUPTIBLE); //schedule_timeout可以运行的前提是设置了当前线程状态
schedule_timeout(HZ/50); //延时
}
return 0;
}
static int __init Remote_drv_init(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "remote_control", const struct file_operations * fops);
Remote_Control_Class = class_create(THIS_MODULE, "remote_control_class");
init_waitqueue_head(&Key_wq);
remote_thread = kthread_run(kthread_func,NULL,"remote_control_d"); //创建线程
platform_driver_register(&plat_drv);
return 0;
}
static void __exit Remote_drv_exit(void)
{
platform_driver_unregister(&plat_drv);
kthread_stop(remote_thread); //线程停止
class_destroy(Remote_Control_Class);
unregister_chrdev major, "remote_control");
}
module_init(Remote_drv_init);
module_exit(Remote_drv_exit);
MODULE_LICENSE("GPL");
poll方式
在poll方式中,驱动程序中需要对fileoperation结构体中的poll函数指针进行初始化。使用poll方式进行交互,任务也会休眠,但是该任务的休眠并不是发生驱动层而是发生内核文件系统中的sys_poll中。在sys_poll中首先会进入到一个循环中,调用到驱动程序中file_operation中定义的poll函数,获取该函数返回的设备状态,然后判断设备状态以及是否超时,当设备状态满足条件或者超时就会返回用户态,否则就会进入休眠,将线程挂到队列中。sys_poll如果进入到休眠期有两种唤醒方式,一种是因超时被内核自动唤醒,另一种是中断服务程序中有使用wake_up_interruptible函数对挂起的线程进行唤醒,进入到下一次循环,调用驱动程序中的poll函数获取设备状态,此时设备状态满足条件立即返回值用户态。
在驱动程序的poll函数中会做两件事情,一个是将线程挂入队列,但是不会进行休眠操作,另一个是返回设备状态由内核文件系统sys_poll取读取设备状态进行判断。
驱动程序中的poll函数:
应用程序:
首先设置相关参数:
系统调用poll和select都对应底层驱动程序中的poll函数。休眠和唤醒方式在等待时间发生时可能会等待很久,并且在这段时间并不知道底层的运行情况,但是使用poll机制时就会给等待某一事件的时间加上一个期限,这样在等待事件发生时就有两种方式进行唤醒,一种是等待超时由内核自动唤醒,另一种是事件发生使用wake_up_interruptible函数对任务进行唤醒。
在应用程序中调用poll其中对应的事件如下:
在jz2440中进行验证:
驱动程序:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
static int major = 0;
static struct class *led_class;
static struct class_device *firstdrv_class_dev;
volatile unsigned long *gpfcon =NULL;
volatile unsigned long *gpfdat =NULL;
volatile unsigned long *gpgcon =NULL;
volatile unsigned long *gpgdat =NULL;
static int has_data = 0;
static DECLARE_WAIT_QUEUE_HEAD(key_pin_que);
static struct pin_des{
unsigned int pin;
unsigned int val;
};
static struct pin_des key_pin[4] = {
{S3C2410_GPF0,0x01},
{S3C2410_GPF2,0x02},
{S3C2410_GPG3,0x03},
{S3C2410_GPG11,0x04},
};
static irqreturn_t irq_ser(int irq,void *dev_id)
{
if(!s3c2410_gpio_getpin(S3C2410_GPF0)){ //低电平表示被按下
has_data = 1;
}else if(!s3c2410_gpio_getpin(S3C2410_GPF2)){ //低电平表示被按下
has_data = 2;
}else if(!s3c2410_gpio_getpin(S3C2410_GPG3)){ //低电平表示被按下
has_data = 3;
}else if(!s3c2410_gpio_getpin(S3C2410_GPG11)){ //低电平表示被按下
has_data = 4;
}else{
has_data = 0;
}
wake_up(&key_pin_que);
return IRQ_RETVAL(IRQ_HANDLED);
}
static int led_driver_open(struct inode * inode,struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
/* *gpfcon &= ~( (0x3) | (0x3<<(2*2))|(0x3<<(4*2)) |(0x3<<(5*2))|(0x3<<(6*2))); //GPIOF对应LED位清零
*gpfcon |= ( (0x1<<(4*2)) |(0x1<<(5*2))|(0x1<<(6*2))); //对应位配置为输出
*gpgcon &= ~((0x3<<(2*3)) | (0x3<<(11*2)));
*gpfdat |= (1<<4) | (1<<5) |(1<<6);
//*gpfdat &= ~((1<<4) | (1<<5) |(1<<6)) ;
*/
request_irq(IRQ_EINT0, irq_ser,IRQT_BOTHEDGE , "key_pin0", &key_pin[0]);
request_irq(IRQ_EINT2, irq_ser,IRQT_BOTHEDGE , "key_pin2", &key_pin[1]);
request_irq(IRQ_EINT11, irq_ser,IRQT_BOTHEDGE , "key_pin11", &key_pin[2]);
request_irq(IRQ_EINT19, irq_ser,IRQT_BOTHEDGE , "key_pin19", &key_pin[3]);
return 0;
}
static ssize_t led_driver_read(struct file *file,const char __user *buf,size_t size,loff_t *ppos)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
int val = 1;
int regfval = *gpfdat;
int reggval = *gpgdat;
wait_event_interruptible(key_pin_que, has_data);
switch(has_data){
case 1:
s3c2410_gpio_setpin(S3C2410_GPF4, 0);
break;
case 2:
s3c2410_gpio_setpin(S3C2410_GPF5, 0);
break;
case 3:
s3c2410_gpio_setpin(S3C2410_GPF6, 0);
break;
case 4:
s3c2410_gpio_setpin(S3C2410_GPF4, 1);
s3c2410_gpio_setpin(S3C2410_GPF5, 1);
s3c2410_gpio_setpin(S3C2410_GPF6, 1);
break;
}
/*
if(!(regfval & (1<<0))){
*gpfdat &= ~(1<<4);
val = 1;
}else if(!(regfval & (1<<2))){
*gpfdat &= ~(1<<5);
val = 2;
}else if(!(reggval & (1<<11))){
*gpfdat &= ~(1<<6);
val = 3;
}else if(!(reggval & (1<<3))){
*gpfdat |= (1<<4) | (1<<5) |(1<<6);
val = 0;
}
*/
copy_to_user(buf,&has_data,4);
has_data = 0;
return 0;
}
static int led_driver_close(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT0,&key_pin[0]);
free_irq(IRQ_EINT2,&key_pin[1]);
free_irq(IRQ_EINT11,&key_pin[2]);
free_irq(IRQ_EINT19,&key_pin[3]);
return 0;
}
static int led_driver_poll(struct file *file,poll_table *wait)
{
unsigned int mask = 0;
poll_wait(file,&key_pin_que, wait); //将应用线程挂起到队列,但不是立即挂起
if(has_data){
mask = POLLIN |POLLRDNORM; //当有按键按下是立即返回POLLIN
}
return mask;
}
static struct file_operations led_drv_fops = {
.owner = THIS_MODULE,
.open = led_driver_open,
.read = led_driver_read,
.release = led_driver_close,
.poll = led_driver_poll,
};
static int __init led_drv_init(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "led_driver", &led_drv_fops);
led_class = class_create(THIS_MODULE,"led_class");
firstdrv_class_dev = class_device_create(led_class, NULL, MKDEV(major,0), NULL, "led");
gpfcon = (volatile unsigned long *)ioremap(0x56000050,16);
gpfdat = gpfcon+1;
gpgcon = (volatile unsigned long *)ioremap(0x56000060,16);
gpgdat = gpgcon+1;
return 0;
}
static void __exit led_drv_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
iounmap(gpfcon);
iounmap(gpfdat);
iounmap(gpgdat);
iounmap(gpgdat);
class_device_unregister(firstdrv_class_dev);
class_destroy(led_class);
unregister_chrdev(major, "led_driver");
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
应用程序:
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <poll.h>
struct pollfd fds[1]; //设置poll中等待的fd集合
int main(int argc,char **argv)
{
int fd;
int val = 0;
int time = 0;
int poll_ret;
fd = open("/dev/led",O_RDWR);
if(fd<0){
printf("open faile\n");
return -1;
}else{
printf("open success\n");
}
fds[0].fd = fd; //设置文件描述符
fds[0].events = POLLIN; //设置为有数据可读的事件
while(1){
poll_ret = poll(fds,1,5000);
if(poll_ret == 0){ //当poll返回为0时表示等待超时
printf("time_out\n");
}else{ //返回值不为零表示有对应的event事件发生
read(fd,&val,4); //对应event来read或者write
printf("val = %d\n",val);
}
}
return 0;
}
程序运行情况:
cpu占用情况:
异步通知方式
应用层使用异步通知方式步骤:
1.注册信号处理函数signal
2.打开驱动程序
3.将进程PID传给底层驱动程序
fcntl(fd,F_SETOWN,getpid());
4.设置flag为FASYNC,启动异步通知功能
Oflags = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,fkags|FASYNC);
注册信号处理函数:
将pid和flag传给底层驱动程序,并设置异步通知功能
这样应用层就可以执行其他的任务等待底层发送信号执行异步通知。当接收到信号的时候就会执行信号处理函数。
步骤3和4都会在内核文件系统中的sys_fcntl中对pid以及flag进行赋值,然后调用驱动程序中的fcntl函数将值传入到底层驱动中。驱动层需要对fileoperation中的fasync函数指针进行初始化,在函数中调用fasync_helper函数来获取应用层传过来的pid和flag,根据flag是否是FASYNC判断是否为异步通知。底层如果想向上层发送信号,可以使用kill_fasync函数。当上层收到底层发送的信号后就可以执行应用层注册的信号处理函数,在信号处理函数中可以去read底层信息。
其中fasync_helper函数中的button_fasync结构体如下:
底层驱动向上层应用层发送信号,如果button_fasync结构体中的file结构体不为空则取出pid发送信号
以jz2440作为验证:
驱动程序:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static int major = 0;
static struct class *led_class;
static struct class_device *firstdrv_class_dev;
volatile unsigned long *gpfcon =NULL;
volatile unsigned long *gpfdat =NULL;
volatile unsigned long *gpgcon =NULL;
volatile unsigned long *gpgdat =NULL;
static int has_data = 0;
static DECLARE_WAIT_QUEUE_HEAD(key_pin_que);
static struct fasync_struct *led_driver_fasync_queue;
static struct pin_des{
unsigned int pin;
unsigned int val;
};
static struct pin_des key_pin[4] = {
{S3C2410_GPF0,0x01},
{S3C2410_GPF2,0x02},
{S3C2410_GPG3,0x03},
{S3C2410_GPG11,0x04},
};
static irqreturn_t irq_ser(int irq,void *dev_id)
{
if(!s3c2410_gpio_getpin(S3C2410_GPF0)){ //低电平表示被按下
has_data = 1;
}else if(!s3c2410_gpio_getpin(S3C2410_GPF2)){ //低电平表示被按下
has_data = 2;
}else if(!s3c2410_gpio_getpin(S3C2410_GPG3)){ //低电平表示被按下
has_data = 3;
}else if(!s3c2410_gpio_getpin(S3C2410_GPG11)){ //低电平表示被按下
has_data = 4;
}else{
has_data = 0;
}
kill_fasync(&led_driver_fasync_queue, SIGIO, POLL_IN); //向应用程序发送信号
wake_up(&key_pin_que);
return IRQ_RETVAL(IRQ_HANDLED);
}
static int led_driver_open(struct inode * inode,struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
/* *gpfcon &= ~( (0x3) | (0x3<<(2*2))|(0x3<<(4*2)) |(0x3<<(5*2))|(0x3<<(6*2))); //GPIOF对应LED位清零
*gpfcon |= ( (0x1<<(4*2)) |(0x1<<(5*2))|(0x1<<(6*2))); //对应位配置为输出
*gpgcon &= ~((0x3<<(2*3)) | (0x3<<(11*2)));
*gpfdat |= (1<<4) | (1<<5) |(1<<6);
//*gpfdat &= ~((1<<4) | (1<<5) |(1<<6)) ;
*/
request_irq(IRQ_EINT0, irq_ser,IRQT_BOTHEDGE , "key_pin0", &key_pin[0]);
request_irq(IRQ_EINT2, irq_ser,IRQT_BOTHEDGE , "key_pin2", &key_pin[1]);
request_irq(IRQ_EINT11, irq_ser,IRQT_BOTHEDGE , "key_pin11", &key_pin[2]);
request_irq(IRQ_EINT19, irq_ser,IRQT_BOTHEDGE , "key_pin19", &key_pin[3]);
return 0;
}
static ssize_t led_driver_read(struct file *file,const char __user *buf,size_t size,loff_t *ppos)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
int val = 1;
int regfval = *gpfdat;
int reggval = *gpgdat;
wait_event_interruptible(key_pin_que, has_data);
switch(has_data){
case 1:
s3c2410_gpio_setpin(S3C2410_GPF4, 0);
break;
case 2:
s3c2410_gpio_setpin(S3C2410_GPF5, 0);
break;
case 3:
s3c2410_gpio_setpin(S3C2410_GPF6, 0);
break;
case 4:
s3c2410_gpio_setpin(S3C2410_GPF4, 1);
s3c2410_gpio_setpin(S3C2410_GPF5, 1);
s3c2410_gpio_setpin(S3C2410_GPF6, 1);
break;
}
copy_to_user(buf,&has_data,4);
has_data = 0;
return 0;
}
static int led_driver_close(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT0,&key_pin[0]);
free_irq(IRQ_EINT2,&key_pin[1]);
free_irq(IRQ_EINT11,&key_pin[2]);
free_irq(IRQ_EINT19,&key_pin[3]);
return 0;
}
static int led_driver_fasync(int fd, struct file *file, int on) //对led_driver_fasync_queue结构体进行初始化
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return fasync_helper(fd, file, on, &led_driver_fasync_queue); //获取应用层传来的pid和falg并初始化led_driver_fasync_queue中
}
static struct file_operations led_drv_fops = {
.owner = THIS_MODULE,
.open = led_driver_open,
.read = led_driver_read,
.release = led_driver_close,
.fasync = led_driver_fasync,
};
static int __init led_drv_init(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "led_driver", &led_drv_fops);
led_class = class_create(THIS_MODULE,"led_class");
firstdrv_class_dev = class_device_create(led_class, NULL, MKDEV(major,0), NULL, "led");
gpfcon = (volatile unsigned long *)ioremap(0x56000050,16);
gpfdat = gpfcon+1;
gpgcon = (volatile unsigned long *)ioremap(0x56000060,16);
gpgdat = gpgcon+1;
return 0;
}
static void __exit led_drv_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
iounmap(gpfcon);
iounmap(gpfdat);
iounmap(gpgdat);
iounmap(gpgdat);
class_device_unregister(firstdrv_class_dev);
class_destroy(led_class);
unregister_chrdev(major, "led_driver");
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
应用程序:
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <signal.h>
int fd;
void sig_function(int signum) //信号处理函数,驱动层有信号发送过来的时候就执行该函数
{
int val;
read(fd,&val,4);
printf("val = %d\n",val);
}
int main(int argc,char **argv)
{
int val = 0;
int time = 0;
int Oflag;
fd = open("/dev/led",O_RDWR);
signal(SIGIO,sig_function); //注册信号
fcntl(fd,F_SETOWN,getpid()); //将进程id号给到驱动程序
Oflag = fcntl(fd,F_GETFL); //获取当前的flag
fcntl(fd,F_SETFL,Oflag | FASYNC); //设置为异步通知的flag
if(fd<0){
printf("open faile\n");
}else{
printf("open success\n");
}
while(1){
}
return 0;
}
Linux单板的中断方式:
中断处理流程:
1.设置中断控制器
2.注册中断处理函数
3.保存现场
4.调用中断处理函数
5.恢复现场
异常虽然与中断处理流程一致,但是中断可以被屏蔽,而异常不能被屏蔽;
中断发生后的过程:发生中断后CPU会指向中断向量表,保存现场,执行中断处理函数,恢复现场。其中保存和恢复现场都是内核实现的,但是执行中断处理函数是由开发人员提供的对应函数。
在驱动中编写中断函数时需要使用到request_irq函数取注册驱动;
第一个参数为中断号,第二个参数为中断处理函数,第三个参数为标志位,第四个为名字,第五个为设备。其中最为重要的就是中断号和中断处理函数。
值得一提的是现在的底层中断驱动程序通常采用GIC协议,在内核代码中的设备树可以看到中断是会分级的,下面以树莓派使用的bcm2835和imx6ull作为例子。
在树莓派设备树中:在gpio作为中断源时其上层节点为soc节点,soc节点的父中断为intc,intc会向cpu发送一条中断信息。
在imx6ull的设备树中可以看到:gpio作为中断源其上层节点是soc节点,soc的父中断是gpc,gpc的父中断是intc,然后intc会向cpu发送一个中断信息。
在设备树中可以设置中断信息,可以不了解整个中断的层级过程,但是必须要指明中断的父节点。
learned_from:韦东山