S3C2440按键中断驱动程序的设计.docx
《S3C2440按键中断驱动程序的设计.docx》由会员分享,可在线阅读,更多相关《S3C2440按键中断驱动程序的设计.docx(29页珍藏版)》请在冰豆网上搜索。
S3C2440按键中断驱动程序的设计
S3C2440按键驱动的设计(内核2.6.30.4)
下图为S3C2440的按键连接电路图:
在开始设计程序之前介绍一下与Linux设备中断处理程序相关的知识。
首先是申请与释放IRQ的APIrequest_irq()和free_irq(),request_irq()的原型为:
intrequest_irq(unsignedintirq,
void(*handler)(intirq,void*dev_id,structpt_regs*regs),
unsignedlongirqflags,
constchar*devname,
void*dev_id);
irq是要申请的硬件中断号;
handler是向系统登记的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递;
irqflags是中断处理的属性,若设置SA_INTERRUPT,表明中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,而慢速处理程序不屏蔽;若设置SA_SHIRQ,则多个设备共享中断;
dev_id在中断共享时会用到,一般设置为这个设备的device结构本身或者NULL。
free_irq()的原型为:
voidfree_irq(unsignedintirq,void*dev_id);
下面我就开始写驱动程序,源码如下(详解在源码后面说明):
#include
#defineDEVICE_NAME"IRQ-Test"
structbutton_irq_desc{
intirq;
intpin;
intpin_setting;
intnumber;
char*name;
};
staticstructbutton_irq_descbutton_irqs[]={
{IRQ_EINT1,S3C2410_GPF1,S3C2410_GPF1_EINT1,0,"KEY1"},/*K1*/
{IRQ_EINT4,S3C2410_GPF4,S3C2410_GPF4_EINT4,1,"KEY2"},/*K2*/
{IRQ_EINT2,S3C2410_GPF2,S3C2410_GPF2_EINT2,2,"KEY3"},/*K3*/
{IRQ_EINT0,S3C2410_GPF0,S3C2410_GPF0_EINT0,3,"KEY4"},/*K4*/
staticvolatilecharkey_values[]={'0','0','0','0'};
staticDECLARE_WAIT_QUEUE_HEAD(button_waitq);
staticvolatileintev_press=0;
staticirqreturn_tirq_interrupt(intirq,void*dev_id)
{
structbutton_irq_desc*button_irqs=(structbutton_irq_desc*)dev_id;
intdown;
down=!
s3c2410_gpio_getpin(button_irqs->pin);
if(down!
=(key_values[button_irqs->number]&1))
key_values[button_irqs->number]='0'+down;
ev_press=1;
wake_up_interruptible(&button_waitq);
}
returnIRQ_RETVAL(IRQ_HANDLED);
staticinttq2440_irq_open(structinode*inode,structfile*file)
inti;
interr=0;
for(i=0;i{if(button_irqs[i].irq<0)continue;err=request_irq(button_irqs[i].irq,irq_interrupt,IRQ_TYPE_EDGE_BOTH,button_irqs[i].name,(void*)&button_irqs[i]);if(err)break;}if(err){i--;for(;i>=0;i--){if(button_irqs[i].irq<0)continue;disable_irq(button_irqs[i].irq);free_irq(button_irqs[i].irq,(void*)&button_irqs[i]);}return-EBUSY;}ev_press=1;return0;} staticinttq2440_irq_close(structinode*inode,structfile*file){inti;for(i=0;i{if(button_irqs[i].irq<0)continue;free_irq(button_irqs[i].irq,(void*)&button_irqs[i]);}return0;} staticinttq2440_irq_read(structfile*filp,char__user*buff,size_tcount,loff_t*offp){unsignedlongerr;if(!ev_press){if(filp->f_flags&O_NONBLOCK)return-EAGAIN;elsewait_event_interruptible(button_waitq,ev_press);}ev_press=0;err=copy_to_user(buff,(constvoid*)key_values,min(sizeof(key_values),count));returnerr?-EFAULT:min(sizeof(key_values),count);}staticunsignedinttq2440_irq_poll(structfile*file,structpoll_table_struct*wait){unsignedintmask=0;poll_wait(file,&button_waitq,wait);if(ev_press)mask|=POLLIN|POLLRDNORM;returnmask;} staticstructfile_operationsdev_fops={.owner=THIS_MODULE,.open=tq2440_irq_open,.release=tq2440_irq_close,.read=tq2440_irq_read,.poll=tq2440_irq_poll,};staticstructmiscdevicemisc={.minor=MISC_DYNAMIC_MINOR,.name=DEVICE_NAME,.fops=&dev_fops,};staticint__initdev_init(void){intret;ret=misc_register(&misc);printk(DEVICE_NAME"initialized\n");returnret;}staticvoid__exitdev_exit(void){misc_deregister(&misc);}module_init(dev_init);module_exit(dev_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("www.MODULE_DESCRIPTION("IRQTestforEmbedSkyTQ2440Board"); 下面是对这个驱动代码的的详细解释:头文件的解释:#include所有模块都需要这个头文件#includeinit和exit相关宏#include声明了printk()这个内核态用的函数#include文件系统有关的,结构体file_operations在头文件linux/fs.h中定义#include寄存器设置#includeLinux中的用户和内核态内存交互函数(这些函数在include/asm/uaccess.h中被声明):unsignedlongcopy_from_user(void*to,constvoid*from,unsignedlongn);unsignedlongcopy_to_user(void*to,void*from,unsignedlonglen)#include在这个头文件中主要是misc(混合)设备注册和注销linux中用structmiscdevice来描述一个混杂设备#includeLinux中断定义#includearm中断定义,如中断号的定义IRQ_EINT1#includeinterrupt.h中包含了与中断相关的大部分宏及struct结构的定义,如request_irq(unsignedintirq,irq_handler_thandler,unsignedlongflags,constchar*name,void*dev);#includeIO空间的物理地址定义#include延迟函数的定义声明(本程序未用延迟函数,所以这个头文件不是必须的)#include本例中定义了POLLIN|POLLRDNORM#include通常在Linux中,把SoC系统中集成的独立外设单元(如:I2C、IIS、RTC、看门狗等)都被当作平台设备来处理。在Linux中用platform_device结构体来描述一个平台设备,在2.6.30.4内核中定义在:include/linux/platform_device.h中(本例中这个头文件是非必须的,因为没有把这个中断实现定义为平台设备,如果要注册为平台设备,这个头文件就需要包含进来了)#include注册字符设备(在本例中,这个头文件也是非必须的) 下面三段代码在设计中不是必须的:MODULE_LICENSE("GPL")用于声明模块的许可证MODULE_AUTHOR("www.声明模块的作者MODULE_DESCRIPTION("IRQTestforEmbedSkyTQ2440Board")对模块的描述代码的说明:#defineDEVICE_NAME"IRQ-Test"加载模式后,执行“cat/proc/devices"命令看到的设备名称structbutton_irq_desc{intirq;intpin;intpin_setting;intnumber;char*name;};定义了一个类型名为button_irq_desc的结构体类型,由它把按钮中断的信息综合起来intirq中断号,中断号唯一表示一个中断intpin;中断控制的寄存器,该寄存器的值由中断引脚设置,我们希望从该寄存器读出控制信息intpin_setting;中断的引脚,该引脚的电平由按键来控制,从而最终我们由按键控制了寄存器的值intnumber;编号char*name;名称staticstructbutton_irq_descbutton_irqs[]={{IRQ_EINT1,S3C2410_GPF1,S3C2410_GPF1_EINT1,0,"KEY1"},/*K1*/{IRQ_EINT4,S3C2410_GPF4,S3C2410_GPF4_EINT4,1,"KEY2"},/*K2*/{IRQ_EINT2,S3C2410_GPF2,S3C2410_GPF2_EINT2,2,"KEY3"},/*K3*/{IRQ_EINT0,S3C2410_GPF0,S3C2410_GPF0_EINT0,3,"KEY4"},/*K4*/};定义了一个button_irq_desc结构体类型的数组变量button_irq,并对结构体的各个元素赋值。注:static的说明:Linux内核是一个十分庞大的系统。随着Linux内核的发展,其核心代码包括几千个文件,代码量超过百万行。对于用C语言编写的大型程序,需要解决的一个问题就是“名字空间污染”。由于C语言的函数和全局变量的作用空间都是全局的,在另外一个文件中,使用extern关键字,就可以实现对于其他文件中的全局变量和函数的访问。虽然这样很方便,但是源码中多个文件全局变量和函数定义的名称不允许重复;否则,编译器在连接时,就会报告命名重复的错误。这就是“名字空间污染”的问题。当系统庞大并且由多个程序员共同开发时,这个问题就更加突出。因此,需要引用一些封装的特性,限制源码中函数和变量作用的空间。C语言中的static关键字解决了这个问题。从上面代码中可以看到所有的全局变量和函数都是用static关键字修饰的。这是一个很好的习惯。被static关键字修饰的函数和全局变量,其名字作用的范围仅仅是当前的文件,而不是整个系统。staticvolatilecharkey_values[]={'0','0','0','0'};数组中是否有数据的标志,0表示无数据可读,1表示有数据可读。注:volatile就象大家更熟悉的const一样,volatile是一个类型修饰符(typespecifier)。它是被设计用来修饰被不同线程访问和修改的变量。如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。下面我们来一个个说明。 考虑下面的代码:classGadget { public: voidWait() { while(!flag_) { Sleep(1000);//sleepsfor1000milliseconds} } voidWakeup() { flag_=true; } ... private: boolflag_; }; 上面代码中Gadget::Wait的目的是每过一秒钟去检查一下flag_成员变量,当flag_被另一个线程设为true时,该函数才会返回。至少这是程序作者的意图,然而,这个Wait函数是错误的。 假设编译器发现Sleep(1000)是调用一个外部的库函数,它不会改变成员变量flag_,那么编译器就可以断定它可以把flag_缓存在寄存器中,以后可以访问该寄存器来代替访问较慢的主板上的内存。这对于单线程代码来说是一个很好的优化,但是在现在这种情况下,它破坏了程序的正确性:当你调用了某个Gadget的Wait函数后,即使另一个线程调用了Wakeup,Wait还是会一直循环下去。这是因为flag_的改变没有反映到缓存它的寄存器中去。编译器的优化未免有点太……乐观了。 在大多数情况下,把变量缓存在寄存器中是一个非常有价值的优化方法,如果不用的话很可惜。C和C++给你提供了显式禁用这种缓存优化的机会。如果你声明变量是使用了volatile修饰符,编译器就不会把这个变量缓存在寄存器里——每次访问都将去存取变量在内存中的实际位置。这样你要对Gadget的Wait/Wakeup做的修改就是给flag_加上正确的修饰: classGadget { public: ...asabove... private: volatileboolflag_; }; staticDECLARE_WAIT_QUEUE_HEAD(button_waitq);睡眠的介绍对于一个进程"睡眠"意味着什么?当一个进程被置为睡眠,它被标识为处于一个特殊的状态并且从调度器的运行队列中去除.直到发生某些事情改变了那个状态。这个进程将不被在任何CPU上调度,并且,因此,将不会运行.一个睡着的进程已被搁置到系统的一边,等待以后发生事件.对于一个Linux驱动使一个进程睡眠是一个容易做的事情.但是,有几个规则必须记住以安全的方式编码睡眠.这些规则的第一个是:当你运行在原子上下文时不能睡眠.我们在第5章介绍过原子操作;一个原子上下文只是一个状态,这里多个步骤必须在没有任何类型的并发存取的情况下进行.这意味着,对于睡眠,是你的驱动在持有一个自旋锁,seqlock,或者RCU锁时不能睡眠.如果你已关闭中断你也不能睡眠.在持有一个旗标时睡眠是合法的,但是你应当仔细查看这样做的任何代码.如果代码在持有一个旗标时睡眠,任何其他的等待这个旗标的线程也睡眠.因此发生在持有旗标时的任何睡眠应当短暂,并且你应当说服自己,由于持有这个旗标,你不能阻塞这个将最终唤醒你的进程.另一件要记住的事情是,当你醒来,你从不知道你的进程离开CPU多长时间或者同时已经发生了什么改变.你也常常不知道是否另一个进程已经睡眠等待同一个事件;那个进程可能在你之前醒来并且获取了你在等待的资源.结果是你不能关于你醒后的系统状态做任何的假设,并且你必须检查来确保你在等待的条件是,确实,真的.一个另外的相关的点,当然,是你的进程不能睡眠除非确信其他人,在某处的,将唤醒它.做唤醒工作的代码必须也能够找到你的进程来做它的工作.确保一个唤醒发生,是深入考虑你的代码和对于每次睡眠,确切知道什么系列的事件将结束那次睡眠.使你的进程可能被找到,真正地,通过一个称为等待队列的数据结构实现的.一个等待队列就是它听起来的样子:一个进程列表,都等待一个特定的事件.在Linux中,一个等待队列由一个"等待队列头"来管理,一个wait_queue_head_t类型的结构,定义在中.一个等待队列头可被定义和初始化,使用:DECLARE_WAIT_QUEUE_HEAD(name);或者动态地,如下:wait_queue_head_tmy_queue;init_waitqueue_head(&my_queue);我们将很快返回到等待队列结构,但是我们知道了足够多的来首先看看睡眠和唤醒.6.2.2.简单睡眠当一个进程睡眠,它这样做以期望某些条件在以后会成真.如我们之前注意到的,任何睡眠的进程必须在它再次醒来时检查来确保它在等待的条件真正为真.Linux内核中睡眠的最简单方式是一个宏定义,称为wait_event(有几个变体);它结合了处理睡眠的细节和进程在等待的条件的检查.wait_event的形式是:wait_event(queue,condition)wait_event_interruptible(queue,condition)wait_event_timeout(queue,condition,timeout)wait_event_interruptible_timeout(queue,condition,timeout)在所有上面的形式中,queue是要用的等待队列头.注意它是"通过值"传递的.条件是一个被这个宏在睡眠前后所求值的任意的布尔表达式;直到条件求值为真值,进程继续睡眠.注意条件可能被任意次地求值,因此它不应当有任何边界效应.如果你使用wait_event,你的进程被置为不可中断地睡眠,如同我们之前已经提到的,它常常不是你所要的.首选的选择是wait_event_interruptible,它可能被信号中断.这个版本返回一个你应当检查的整数值;一个非零值意味着你的睡眠被某些信号打断,并且你的驱动可能应当返回-ERESTARTSYS.最后的版本(wait_event_timeout和wait_event_interruptible_timeout)等待一段有限的时间;在这个时间期间(以嘀哒数表达的,我们将在第7章讨论)超时后,这个宏返回一个0值而不管条件是如何求值的.图片的另一半,当然,是唤
if(button_irqs[i].irq<0)
continue;
err=request_irq(button_irqs[i].irq,irq_interrupt,IRQ_TYPE_EDGE_BOTH,
button_irqs[i].name,(void*)&button_irqs[i]);
if(err)
break;
i--;
for(;i>=0;i--)
disable_irq(button_irqs[i].irq);
free_irq(button_irqs[i].irq,(void*)&button_irqs[i]);
return-EBUSY;
return0;
staticinttq2440_irq_close(structinode*inode,structfile*file)
for(i=0;i{if(button_irqs[i].irq<0)continue;free_irq(button_irqs[i].irq,(void*)&button_irqs[i]);}return0;} staticinttq2440_irq_read(structfile*filp,char__user*buff,size_tcount,loff_t*offp){unsignedlongerr;if(!ev_press){if(filp->f_flags&O_NONBLOCK)return-EAGAIN;elsewait_event_interruptible(button_waitq,ev_press);}ev_press=0;err=copy_to_user(buff,(constvoid*)key_values,min(sizeof(key_values),count));returnerr?-EFAULT:min(sizeof(key_values),count);}staticunsignedinttq2440_irq_poll(structfile*file,structpoll_table_struct*wait){unsignedintmask=0;poll_wait(file,&button_waitq,wait);if(ev_press)mask|=POLLIN|POLLRDNORM;returnmask;} staticstructfile_operationsdev_fops={.owner=THIS_MODULE,.open=tq2440_irq_open,.release=tq2440_irq_close,.read=tq2440_irq_read,.poll=tq2440_irq_poll,};staticstructmiscdevicemisc={.minor=MISC_DYNAMIC_MINOR,.name=DEVICE_NAME,.fops=&dev_fops,};staticint__initdev_init(void){intret;ret=misc_register(&misc);printk(DEVICE_NAME"initialized\n");returnret;}staticvoid__exitdev_exit(void){misc_deregister(&misc);}module_init(dev_init);module_exit(dev_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("www.MODULE_DESCRIPTION("IRQTestforEmbedSkyTQ2440Board"); 下面是对这个驱动代码的的详细解释:头文件的解释:#include所有模块都需要这个头文件#includeinit和exit相关宏#include声明了printk()这个内核态用的函数#include文件系统有关的,结构体file_operations在头文件linux/fs.h中定义#include寄存器设置#includeLinux中的用户和内核态内存交互函数(这些函数在include/asm/uaccess.h中被声明):unsignedlongcopy_from_user(void*to,constvoid*from,unsignedlongn);unsignedlongcopy_to_user(void*to,void*from,unsignedlonglen)#include在这个头文件中主要是misc(混合)设备注册和注销linux中用structmiscdevice来描述一个混杂设备#includeLinux中断定义#includearm中断定义,如中断号的定义IRQ_EINT1#includeinterrupt.h中包含了与中断相关的大部分宏及struct结构的定义,如request_irq(unsignedintirq,irq_handler_thandler,unsignedlongflags,constchar*name,void*dev);#includeIO空间的物理地址定义#include延迟函数的定义声明(本程序未用延迟函数,所以这个头文件不是必须的)#include本例中定义了POLLIN|POLLRDNORM#include通常在Linux中,把SoC系统中集成的独立外设单元(如:I2C、IIS、RTC、看门狗等)都被当作平台设备来处理。在Linux中用platform_device结构体来描述一个平台设备,在2.6.30.4内核中定义在:include/linux/platform_device.h中(本例中这个头文件是非必须的,因为没有把这个中断实现定义为平台设备,如果要注册为平台设备,这个头文件就需要包含进来了)#include注册字符设备(在本例中,这个头文件也是非必须的) 下面三段代码在设计中不是必须的:MODULE_LICENSE("GPL")用于声明模块的许可证MODULE_AUTHOR("www.声明模块的作者MODULE_DESCRIPTION("IRQTestforEmbedSkyTQ2440Board")对模块的描述代码的说明:#defineDEVICE_NAME"IRQ-Test"加载模式后,执行“cat/proc/devices"命令看到的设备名称structbutton_irq_desc{intirq;intpin;intpin_setting;intnumber;char*name;};定义了一个类型名为button_irq_desc的结构体类型,由它把按钮中断的信息综合起来intirq中断号,中断号唯一表示一个中断intpin;中断控制的寄存器,该寄存器的值由中断引脚设置,我们希望从该寄存器读出控制信息intpin_setting;中断的引脚,该引脚的电平由按键来控制,从而最终我们由按键控制了寄存器的值intnumber;编号char*name;名称staticstructbutton_irq_descbutton_irqs[]={{IRQ_EINT1,S3C2410_GPF1,S3C2410_GPF1_EINT1,0,"KEY1"},/*K1*/{IRQ_EINT4,S3C2410_GPF4,S3C2410_GPF4_EINT4,1,"KEY2"},/*K2*/{IRQ_EINT2,S3C2410_GPF2,S3C2410_GPF2_EINT2,2,"KEY3"},/*K3*/{IRQ_EINT0,S3C2410_GPF0,S3C2410_GPF0_EINT0,3,"KEY4"},/*K4*/};定义了一个button_irq_desc结构体类型的数组变量button_irq,并对结构体的各个元素赋值。注:static的说明:Linux内核是一个十分庞大的系统。随着Linux内核的发展,其核心代码包括几千个文件,代码量超过百万行。对于用C语言编写的大型程序,需要解决的一个问题就是“名字空间污染”。由于C语言的函数和全局变量的作用空间都是全局的,在另外一个文件中,使用extern关键字,就可以实现对于其他文件中的全局变量和函数的访问。虽然这样很方便,但是源码中多个文件全局变量和函数定义的名称不允许重复;否则,编译器在连接时,就会报告命名重复的错误。这就是“名字空间污染”的问题。当系统庞大并且由多个程序员共同开发时,这个问题就更加突出。因此,需要引用一些封装的特性,限制源码中函数和变量作用的空间。C语言中的static关键字解决了这个问题。从上面代码中可以看到所有的全局变量和函数都是用static关键字修饰的。这是一个很好的习惯。被static关键字修饰的函数和全局变量,其名字作用的范围仅仅是当前的文件,而不是整个系统。staticvolatilecharkey_values[]={'0','0','0','0'};数组中是否有数据的标志,0表示无数据可读,1表示有数据可读。注:volatile就象大家更熟悉的const一样,volatile是一个类型修饰符(typespecifier)。它是被设计用来修饰被不同线程访问和修改的变量。如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。下面我们来一个个说明。 考虑下面的代码:classGadget { public: voidWait() { while(!flag_) { Sleep(1000);//sleepsfor1000milliseconds} } voidWakeup() { flag_=true; } ... private: boolflag_; }; 上面代码中Gadget::Wait的目的是每过一秒钟去检查一下flag_成员变量,当flag_被另一个线程设为true时,该函数才会返回。至少这是程序作者的意图,然而,这个Wait函数是错误的。 假设编译器发现Sleep(1000)是调用一个外部的库函数,它不会改变成员变量flag_,那么编译器就可以断定它可以把flag_缓存在寄存器中,以后可以访问该寄存器来代替访问较慢的主板上的内存。这对于单线程代码来说是一个很好的优化,但是在现在这种情况下,它破坏了程序的正确性:当你调用了某个Gadget的Wait函数后,即使另一个线程调用了Wakeup,Wait还是会一直循环下去。这是因为flag_的改变没有反映到缓存它的寄存器中去。编译器的优化未免有点太……乐观了。 在大多数情况下,把变量缓存在寄存器中是一个非常有价值的优化方法,如果不用的话很可惜。C和C++给你提供了显式禁用这种缓存优化的机会。如果你声明变量是使用了volatile修饰符,编译器就不会把这个变量缓存在寄存器里——每次访问都将去存取变量在内存中的实际位置。这样你要对Gadget的Wait/Wakeup做的修改就是给flag_加上正确的修饰: classGadget { public: ...asabove... private: volatileboolflag_; }; staticDECLARE_WAIT_QUEUE_HEAD(button_waitq);睡眠的介绍对于一个进程"睡眠"意味着什么?当一个进程被置为睡眠,它被标识为处于一个特殊的状态并且从调度器的运行队列中去除.直到发生某些事情改变了那个状态。这个进程将不被在任何CPU上调度,并且,因此,将不会运行.一个睡着的进程已被搁置到系统的一边,等待以后发生事件.对于一个Linux驱动使一个进程睡眠是一个容易做的事情.但是,有几个规则必须记住以安全的方式编码睡眠.这些规则的第一个是:当你运行在原子上下文时不能睡眠.我们在第5章介绍过原子操作;一个原子上下文只是一个状态,这里多个步骤必须在没有任何类型的并发存取的情况下进行.这意味着,对于睡眠,是你的驱动在持有一个自旋锁,seqlock,或者RCU锁时不能睡眠.如果你已关闭中断你也不能睡眠.在持有一个旗标时睡眠是合法的,但是你应当仔细查看这样做的任何代码.如果代码在持有一个旗标时睡眠,任何其他的等待这个旗标的线程也睡眠.因此发生在持有旗标时的任何睡眠应当短暂,并且你应当说服自己,由于持有这个旗标,你不能阻塞这个将最终唤醒你的进程.另一件要记住的事情是,当你醒来,你从不知道你的进程离开CPU多长时间或者同时已经发生了什么改变.你也常常不知道是否另一个进程已经睡眠等待同一个事件;那个进程可能在你之前醒来并且获取了你在等待的资源.结果是你不能关于你醒后的系统状态做任何的假设,并且你必须检查来确保你在等待的条件是,确实,真的.一个另外的相关的点,当然,是你的进程不能睡眠除非确信其他人,在某处的,将唤醒它.做唤醒工作的代码必须也能够找到你的进程来做它的工作.确保一个唤醒发生,是深入考虑你的代码和对于每次睡眠,确切知道什么系列的事件将结束那次睡眠.使你的进程可能被找到,真正地,通过一个称为等待队列的数据结构实现的.一个等待队列就是它听起来的样子:一个进程列表,都等待一个特定的事件.在Linux中,一个等待队列由一个"等待队列头"来管理,一个wait_queue_head_t类型的结构,定义在中.一个等待队列头可被定义和初始化,使用:DECLARE_WAIT_QUEUE_HEAD(name);或者动态地,如下:wait_queue_head_tmy_queue;init_waitqueue_head(&my_queue);我们将很快返回到等待队列结构,但是我们知道了足够多的来首先看看睡眠和唤醒.6.2.2.简单睡眠当一个进程睡眠,它这样做以期望某些条件在以后会成真.如我们之前注意到的,任何睡眠的进程必须在它再次醒来时检查来确保它在等待的条件真正为真.Linux内核中睡眠的最简单方式是一个宏定义,称为wait_event(有几个变体);它结合了处理睡眠的细节和进程在等待的条件的检查.wait_event的形式是:wait_event(queue,condition)wait_event_interruptible(queue,condition)wait_event_timeout(queue,condition,timeout)wait_event_interruptible_timeout(queue,condition,timeout)在所有上面的形式中,queue是要用的等待队列头.注意它是"通过值"传递的.条件是一个被这个宏在睡眠前后所求值的任意的布尔表达式;直到条件求值为真值,进程继续睡眠.注意条件可能被任意次地求值,因此它不应当有任何边界效应.如果你使用wait_event,你的进程被置为不可中断地睡眠,如同我们之前已经提到的,它常常不是你所要的.首选的选择是wait_event_interruptible,它可能被信号中断.这个版本返回一个你应当检查的整数值;一个非零值意味着你的睡眠被某些信号打断,并且你的驱动可能应当返回-ERESTARTSYS.最后的版本(wait_event_timeout和wait_event_interruptible_timeout)等待一段有限的时间;在这个时间期间(以嘀哒数表达的,我们将在第7章讨论)超时后,这个宏返回一个0值而不管条件是如何求值的.图片的另一半,当然,是唤
staticinttq2440_irq_read(structfile*filp,char__user*buff,size_tcount,loff_t*offp)
unsignedlongerr;
if(!
ev_press)
if(filp->f_flags&O_NONBLOCK)
return-EAGAIN;
else
wait_event_interruptible(button_waitq,ev_press);
ev_press=0;
err=copy_to_user(buff,(constvoid*)key_values,min(sizeof(key_values),count));
returnerr?
-EFAULT:
min(sizeof(key_values),count);
staticunsignedinttq2440_irq_poll(structfile*file,structpoll_table_struct*wait)
unsignedintmask=0;
poll_wait(file,&button_waitq,wait);
if(ev_press)
mask|=POLLIN|POLLRDNORM;
returnmask;
staticstructfile_operationsdev_fops={
.owner=THIS_MODULE,
.open=tq2440_irq_open,
.release=tq2440_irq_close,
.read=tq2440_irq_read,
.poll=tq2440_irq_poll,
staticstructmiscdevicemisc={
.minor=MISC_DYNAMIC_MINOR,
.name=DEVICE_NAME,
.fops=&dev_fops,
staticint__initdev_init(void)
intret;
ret=misc_register(&misc);
printk(DEVICE_NAME"initialized\n");
returnret;
staticvoid__exitdev_exit(void)
misc_deregister(&misc);
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.
MODULE_DESCRIPTION("IRQTestforEmbedSkyTQ2440Board");
下面是对这个驱动代码的的详细解释:
头文件的解释:
所有模块都需要这个头文件
init和exit相关宏
声明了printk()这个内核态用的函数
文件系统有关的,结构体file_operations在头文件linux/fs.h中定义
寄存器设置
Linux中的用户和内核态内存交互函数(这些函数在include/asm/uaccess.h中被声明):
unsignedlongcopy_from_user(void*to,constvoid*from,unsignedlongn);unsignedlongcopy_to_user(void*to,void*from,unsignedlonglen)
在这个头文件中主要是misc(混合)设备注册和注销
linux中用structmiscdevice来描述一个混杂设备
Linux中断定义
arm中断定义,如中断号的定义IRQ_EINT1
interrupt.h中包含了与中断相关的大部分宏及struct结构的定义,如
request_irq(unsignedintirq,irq_handler_thandler,unsignedlongflags,
constchar*name,void*dev);
IO空间的物理地址定义
延迟函数的定义声明(本程序未用延迟函数,所以这个头文件不是必须的)
本例中定义了POLLIN|POLLRDNORM
通常在Linux中,把SoC系统中集成的独立外设单元(如:
I2C、IIS、RTC、看门狗等)都被当作平台设备来处理。
在Linux中用platform_device结构体来描述一个平台设备,在2.6.30.4内核中定义在:
include/linux/platform_device.h中(本例中这个头文件是非必须的,因为没有把这个中断实现定义为平台设备,如果要注册为平台设备,这个头文件就需要包含进来了)
注册字符设备(在本例中,这个头文件也是非必须的)
下面三段代码在设计中不是必须的:
MODULE_LICENSE("GPL")
用于声明模块的许可证
声明模块的作者
MODULE_DESCRIPTION("IRQTestforEmbedSkyTQ2440Board")
对模块的描述
代码的说明:
加载模式后,执行“cat/proc/devices"命令看到的设备名称
定义了一个类型名为button_irq_desc的结构体类型,由它把按钮中断的信息综合起来
intirq中断号,中断号唯一表示一个中断
intpin;中断控制的寄存器,该寄存器的值由中断引脚设置,我们希望从该寄存器读出控制信息
intpin_setting;中断的引脚,该引脚的电平由按键来控制,从而最终我们由按键控制了寄存器的值
intnumber;编号
char*name;名称
定义了一个button_irq_desc结构体类型的数组变量button_irq,并对结构体的各个元素赋值。
注:
static的说明:
Linux内核是一个十分庞大的系统。
随着Linux内核的发展,其核心代码包括几千个文件,代码量超过百万行。
对于用C语言编写的大型程序,需要解决的一个问题就是“名字空间污染”。
由于C语言的函数和全局变量的作用空间都是全局的,在另外一个文件中,使用extern关键字,就可以实现对于其他文件中的全局变量和函数的访问。
虽然这样很方便,但是源码中多个文件全局变量和函数定义的名称不允许重复;否则,编译器在连接时,就会报告命名重复的错误。
这就是“名字空间污染”的问题。
当系统庞大并且由多个程序员共同开发时,这个问题就更加突出。
因此,需要引用一些封装的特性,限制源码中函数和变量作用的空间。
C语言中的static关键字解决了这个问题。
从上面代码中可以看到所有的全局变量和函数都是用static关键字修饰的。
这是一个很好的习惯。
被static关键字修饰的函数和全局变量,其名字作用的范围仅仅是当前的文件,而不是整个系统。
数组中是否有数据的标志,0表示无数据可读,1表示有数据可读。
volatile
就象大家更熟悉的const一样,volatile是一个类型修饰符(typespecifier)。
它是被设计用来修饰被不同线程访问和修改的变量。
如果没有volatile,基本上会导致这样的结果:
要么无法编写多线程程序,要么编译器失去大量优化的机会。
下面我们来一个个说明。
考虑下面的代码:
classGadget
public:
voidWait()
while(!
flag_)
Sleep(1000);//sleepsfor1000milliseconds
voidWakeup()
flag_=true;
...
private:
boolflag_;
上面代码中Gadget:
:
Wait的目的是每过一秒钟去检查一下flag_成员变量,当flag_被另一个线程设为true时,该函数才会返回。
至少这是程序作者的意图,然而,这个Wait函数是错误的。
假设编译器发现Sleep(1000)是调用一个外部的库函数,它不会改变成员变量flag_,那么编译器就可以断定它可以把flag_缓存在寄存器中,以后可以访问该寄存器来代替访问较慢的主板上的内存。
这对于单线程代码来说是一个很好的优化,但是在现在这种情况下,它破坏了程序的正确性:
当你调用了某个Gadget的Wait函数后,即使另一个线程调用了Wakeup,Wait还是会一直循环下去。
这是因为flag_的改变没有反映到缓存它的寄存器中去。
编译器的优化未免有点太……乐观了。
在大多数情况下,把变量缓存在寄存器中是一个非常有价值的优化方法,如果不用的话很可惜。
C和C++给你提供了显式禁用这种缓存优化的机会。
如果你声明变量是使用了volatile修饰符,编译器就不会把这个变量缓存在寄存器里——每次访问都将去存取变量在内存中的实际位置。
这样你要对Gadget的Wait/Wakeup做的修改就是给flag_加上正确的修饰:
...asabove...
volatileboolflag_;
睡眠的介绍
对于一个进程"睡眠"意味着什么?
当一个进程被置为睡眠,它被标识为处于一个特殊的状态并且从调度器的运行队列中去除.直到发生某些事情改变了那个状态。
这个进程将不被在任何CPU上调度,并且,因此,将不会运行.一个睡着的进程已被搁置到系统的一边,等待以后发生事件.
对于一个Linux驱动使一个进程睡眠是一个容易做的事情.但是,有几个规则必须记住以安全的方式编码睡眠.
这些规则的第一个是:
当你运行在原子上下文时不能睡眠.我们在第5章介绍过原子操作;一个原子上下文只是一个状态,这里多个步骤必须在没有任何类型的并发存取的情况下进行.这意味着,对于睡眠,是你的驱动在持有一个自旋锁,seqlock,或者RCU锁时不能睡眠.如果你已关闭中断你也不能睡眠.在持有一个旗标时睡眠是合法的,但是你应当仔细查看这样做的任何代码.如果代码在持有一个旗标时睡眠,任何其他的等待这个旗标的线程也睡眠.因此发生在持有旗标时的任何睡眠应当短暂,并且你应当说服自己,由于持有这个旗标,你不能阻塞这个将最终唤醒你的进程.
另一件要记住的事情是,当你醒来,你从不知道你的进程离开CPU多长时间或者同时已经发生了什么改变.你也常常不知道是否另一个进程已经睡眠等待同一个事件;那个进程可能在你之前醒来并且获取了你在等待的资源.结果是你不能关于你醒后的系统状态做任何的假设,并且你必须检查来确保你在等待的条件是,确实,真的.
一个另外的相关的点,当然,是你的进程不能睡眠除非确信其他人,在某处的,将唤醒它.做唤醒工作的代码必须也能够找到你的进程来做它的工作.确保一个唤醒发生,是深入考虑你的代码和对于每次睡眠,确切知道什么系列的事件将结束那次睡眠.使你的进程可能被找到,真正地,通过一个称为等待队列的数据结构实现的.一个等待队列就是它听起来的样子:
一个进程列表,都等待一个特定的事件.
在Linux中,一个等待队列由一个"等待队列头"来管理,一个wait_queue_head_t类型的结构,定义在中.一个等待队列头可被定义和初始化,使用:
DECLARE_WAIT_QUEUE_HEAD(name);
或者动态地,如下:
wait_queue_head_tmy_queue;
init_waitqueue_head(&my_queue);
我们将很快返回到等待队列结构,但是我们知道了足够多的来首先看看睡眠和唤醒.
6.2.2.简单睡眠
当一个进程睡眠,它这样做以期望某些条件在以后会成真.如我们之前注意到的,任何睡眠的进程必须在它再次醒来时检查来确保它在等待的条件真正为真.Linux内核中睡眠的最简单方式是一个宏定义,称为wait_event(有几个变体);它结合了处理睡眠的细节和进程在等待的条件的检查.wait_event的形式是:
wait_event(queue,condition)
wait_event_interruptible(queue,condition)
wait_event_timeout(queue,condition,timeout)
wait_event_interruptible_timeout(queue,condition,timeout)
在所有上面的形式中,queue是要用的等待队列头.注意它是"通过值"传递的.条件是一个被这个宏在睡眠前后所求值的任意的布尔表达式;直到条件求值为真值,进程继续睡眠.注意条件可能被任意次地求值,因此它不应当有任何边界效应.
如果你使用wait_event,你的进程被置为不可中断地睡眠,如同我们之前已经提到的,它常常不是你所要的.首选的选择是wait_event_interruptible,它可能被信号中断.这个版本返回一个你应当检查的整数值;一个非零值意味着你的睡眠被某些信号打断,并且你的驱动可能应当返回-ERESTARTSYS.最后的版本(wait_event_timeout和wait_event_interruptible_timeout)等待一段有限的时间;在这个时间期间(以嘀哒数表达的,我们将在第7章讨论)超时后,这个宏返回一个0值而不管条件是如何求值的.
图片的另一半,当然,是唤
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1