链接:https://pan.baidu.com/s/1V0E9IHSoLbpiWJsncmFgdA?pwd=1688
提取码:1688
教学内容:
1 、内核中断
Linux操作系统下同裸机程序一样,需要利用中断机制来处理硬件的异步事件,但用户态不允许中断事件,因此中断必须由设备驱动程序来接收与处理,如果CPU接收到一个中断,它会停止一切工作,调用中断处理函数,此时进程调度也会停止,所以就要求我们的中断处理一定要快。
中断处理程序运行在中断上下文中,注意事项如下:
- 中断处理程序不能停止运行
- 不能使用导致睡眠的处理机制(互斥锁、信号量等),用自旋锁代替
- 中断处理函数不能与用户空间直接交互数据
- 中断处理函数不必是可重用的
- 中断处理函数可以被嵌套(不同优先级之间)
- 中断处理函数执行时间尽可能短,中断处理通常分两个部分:
顶半部:为下半部做准备,并表示已经服务了此中断(相当于登记中断)
底半部:完成重大工作负载,执行过程中所有中断都是使能的(具体处理中断)
对于,底半部的中断处理,为了不占用CPU太多时间,可以采用队列和微线程的方式处理底半部中断程序,顶半部的登记工作一般在__init或open函数中完成。
#include <linux/interrupt.h> //申请中断
int request_irq(unsigned int irq,
irqreturn_t(*handler)(int,void*),
unsigned long irqflag,
const char *devname, void *dev_id);
参数1:中断号,所申请的中断向量,比如EXIT0中断等并定义在mach/irqs.h
参数2:中断服务程序
参数3:中断属性设置,定义在linux/interrupt.h
IRQF_DISABLED, 用于保证中断不被打断和嵌套(值为0)
IRQF_SHARED, 申请子中断时,共享中断源
IRQF_SAMPLE_RANDOM, 中断可能被用来产生随机数
参数4:中断名字,cat /proc/interrupts 可察看系统中断申请与使用情况
参数5:中断参数,作为共享中断时用于区别不同中断的参数irqreturn_t的第二个参数
返回0为成功,失败返回-INVAL或-EBUSY
void free_irq(unsigned int irq, void *dev_id); // 释放中断
参数1:所要清除中断服务程序的中断号
参数2:中断参数,同申请时的值一致
void enable_irq(unsigned int irq); // 使能中断
参数:中断号
void disable_irq(unsigned int irq); // 关闭中断,并等待中断处理完成后返回
参数:中断号
void disable_irq_nosync(unsigned int irq); // 关闭中断,立即返回
参数:中断号
注意:当用户在中断处理函数中关闭中断时应当使用disable_irq_nosync,否则会形成死锁
如果中断程序比较短,就不必要把中断分为二个部分,
对于中断的设置,可以使用
#include <linux/irq.h>
int set_irq_type(int irq, int edge);
参数1:中断号 #include <mach/irqs.h>
参数2:外部中断触发方式定义在linux/irq.h
IRQ_TYPE_LEVEL_LOW,
IRQ_TYPE_LEVEL_HIGH,
IRQ_TYPE_EDGE_FALLING,
IRQ_TYPE_EDGE_RISING,
IRQ_TYPE_EDGE_BOTH
具体程序如下:
//**************************************************
#include <linux/module.h> /*module_init()*/
#include <linux/kernel.h> /* printk() */
#include <linux/init.h> /* __init __exit */
#include <linux/fs.h> /* file_operation */
#include <asm/uaccess.h> /* copy_to_user, copy_from_user */
#include <linux/device.h> /*class ,class_create ,device_create 等*/
#include <linux/errno.h> /* Error number */
#include <linux/delay.h> /* udelay */
#include <mach/regs-gpio.h> /*S3C2410_GPGCON ,S3C2410_GPIO_IRQ*/
#include <linux/pci.h> /*S3C24XX_VA_GPIO*/
#include <linux/irq.h> //set_irq_type ,IRQ_TYPE_EDGE_FALLING
#include <linux/interrupt.h> //request_irq , free_irq
#include <mach/irqs.h> //IRQ_EINT2
#include <linux/gpio.h> //s3c2410_gpio_cfgpin();
#define DRIVER_NAME "key13_eint"
static int MAJOR_NR = 0; /* Driver Major Number */
static int MINOR_NR = 0; //次设备起始号
struct class *my_class;
static struct semaphore readable; //信息号,阻塞read调用,后续知识
static unsigned char key;
static irqreturn_t buttons_interrupt(int irq, void *dev_id) //和request_irq第二参数对应
{
key = (unsigned int)dev_id;
up(&readable);
return 0;
}
static int keyDriver_open(struct inode *inode, struct file *file)
{return 0;}
static int keyDriver_release(struct inode *inode, struct file *file)
{ return 0;}
static int keyDriver_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
int ret = 0;
if(down_interruptible(&readable) != 0){return 0;}
ret = copy_to_user(buff, &key, sizeof(key));
if(ret != 0){
PRINTK("in function read,copy_to_user function run failed!\n");
return ret;
}
return sizeof(key);
}
static struct file_operations keyDriver_fops = {
.owner = THIS_MODULE,
.read = keyDriver_read,
.open = keyDriver_open,
.release = keyDriver_release,
};
static int __init myModule_init(void)
{
int ret;
/* Module init code */
PRINTK("keyDriver_init\n");
sema_init(&readable, 0); //信号量初始化
ret = set_irq_type(IRQ_EINT2,IRQ_TYPE_EDGE_FALLING);//设置外部中断
if(ret != 0){
PRINTK("IRQ_EINT2 set irq type failed!\n");
return ret;
}
ret = set_irq_type(IRQ_EINT3,IRQ_TYPE_EDGE_FALLING);
if(ret != 0){
PRINTK("IRQ_EINT3 set irq type failed!\n");
return ret;
}
ret = set_irq_type(IRQ_EINT4,IRQ_TYPE_EDGE_FALLING);
if(ret != 0){
PRINTK("IRQ_EINT4 set irq type failed!\n");
return ret;
}
ret = request_irq(IRQ_EINT2,buttons_interrupt, IRQF_DISABLED,"KEY1", (void *)1);
ret = request_irq(IRQ_EINT3,buttons_interrupt, 0,"KEY2", (void *)2);
ret = request_irq(IRQ_EINT4,buttons_interrupt, 0,"KEY3", (void *)3);
MAJOR_NR = register_chrdev(MAJOR_NR, DRIVER_NAME, &keyDriver_fops);
if(MAJOR_NR < 0)
{
PRINTK("register char device fail!\n");
return MAJOR_NR;
}
my_class=class_create(THIS_MODULE,"udev_key13_eint");
device_create(my_class,NULL, MKDEV(MAJOR_NR, MINOR_NR), NULL,DRIVER_NAME);
PRINTK("register myDriver OK! Major = %d\n", MAJOR_NR);
return 0;
}
static void __exit myModule_exit(void)
{
PRINTK("exit in\n");
free_irq(IRQ_EINT2, (void *)1);
free_irq(IRQ_EINT3, (void *)2);
free_irq(IRQ_EINT4, (void *)3);
if(MAJOR_NR > 0)
{
unregister_chrdev(MAJOR_NR, DRIVER_NAME);
device_destroy(my_class,MKDEV(MAJOR_NR, MINOR_NR));
class_destroy(my_class);
PRINTK("myModule_exit ok\n");
}
return;
}
module_init(myModule_init);
module_exit(myModule_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("HB");
//**************************************************
如果中断处理程序比较大,可以使用上下文分开的形式,对于下文的执行,可以使用队列或者微线程方式。
2 、内核微线程:(参考 7 、嵌入式 linux 设备驱动 \02 、字符设备驱动 \somecode\1_basic_cdriver\2_advanced_cdriver\1_interrupt\1_key13_eint )
创建方法一:
//直接利用宏直接创建
DECLARE_TASKLET(name, func, data);
参数分别为:微线程名称、任务处理函数、任务处理函数的参数
//微线程调度函数
void tasklet_schedule(struct tasklet_struct * );
函数功能:将tasklet加入tasklet_(hi_)vec后,激发软件中断,并立即返回
//微线程销毁函数
void tasklet_kill(struct tasklet_struct * );
//*****************************************
#include <linux/interrupt.h>
void tasklet_function( unsigned long data );//声明已定义的任务处理函数
DECLARE_TASKLET(my_tasklet, tasklet_function, tasklet_data);
tasklet_schedule(&my_tasklet); //中断处理程序中调用
......
//使用完毕后,调用以下函数销毁已初始化的微线程
tasklet_kill(&key_tasklet);
//*****************************************
创建方法二:
//微线程初始化函数
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data);
参数1:用户已定义tasklet_struct 变量的地址
参数2:tasklet的任务处理函数
参数3:func函数的参数
//微线程调度函数
void tasklet_schedule(struct tasklet_struct * );
函数功能:将tasklet加入tasklet_(hi_)vec后,激发软件中断,并立即返回
//微线程销毁函数
void tasklet_kill(struct tasklet_struct * );
//**************
其他的功能:
微线程禁止、打开、激发软中断:
//禁止微线程调度,其中tasklet_disable_nosync( )函数和disable_irq_nosync( )类似
void tasklet_disable(struct tasklet_struct * );
void tasklet_disable_nosync(struct tasklet_struct * );
//允许微线程调度
void tasklet_enable(struct tasklet_struct * );
3 、工作队列:
对于队列方式可以实现阻塞进程的唤醒,具体队列程序如下:
1)、定义工作结构体和自定义队列结构体
static struct workqueue_struct *key_workqueue;
static struct work_struct key_work;
//static struct workqueue_struct *key_workqueue;
//struct delayed_work key_work;
2)、在open或者__init中创建队列和初始化任务
key_workqueue = create_workqueue("key_queue");
INIT_WORK(&key_work,keyScan_Handler);
//key_workqueue = create_workqueue("key_queue");
//INIT_DELAYED_WORK(&dwork, func);
3)、在中断(适当的地址)中把任务添加到队列中去
queue_work(key_workqueue,&key_work);
//int queue_delayed_work(key_workqueue,&key_work,unsigned long delay);
4)、在退出时候,把队列任务清空,销毁队列(在__exit)
flush_workqueue(key_workqueue);
destroy_workqueue(key_workqueue);
注意:在队列中也可以不使用自定义队列,使用全局内核队列,这样就不需要定义、创建、清空、销毁等动作,只需把任务添加全局队列即可;绿色的为带延时的队列。
//添加任务到内核全局工作队列
int schedule_work(struct work_struct *work);
参数:已初始化的工作
4 、内核并发处理
当多个进程、线程或中断、正常用户程序同时访问同一个资源,可能导致错误,因此内核需要提供并发控制机制,对公共资源的访问进行同步控制,确保共享资源的安全访问。
一般linux中包含了众多的互斥与同步机制,包括信号量、互斥体、自旋锁、原子操作、读写锁等来实现并发机制的实现。
情况一、单核处理器
对于单核处理器来说,进程只是在宏观上并行,而在微观上是串行的,所以对于一个公共区域操作,几个进程之间对区域操作不存在竞发状态,只要防止在进程对区域读写的时候,不被打断即可,为此分二种情况,一种是抢占性内核、即高优先级进程能够打断低优先级进程,另一种是非抢占性内核,只有中断才可能打断进程;所以在抢占性单核情况要使用自旋锁和关中断,在非抢占内核情况下仅仅只要关中断。
情况二、多核处理器(SMP系统)
对于上述单核的情况只能防止一个处理器的竞发,如果多核的情况就不能防止了,为此,只能在临界区段(即公共区域)加上自旋锁。
手段一:中断屏蔽
Local_irq_disable(); //屏蔽所有中断
...... //临界区代码
Local_irq_enable(); //开放所有中断
注:使用该函数有时会出现问题,如果在屏蔽中断前,中断已被关闭,那么在开放中断时之前默认处于关闭状态的中断被打开。
我们可以通过下面的方法避免这种情况:
Local_irq_save(); //保存中断状态,关闭所有中断
...... //临界区代码
Local_irq_restore(); //打开中断,恢复关闭前中断状态
手段二:自旋锁
1)、定义自旋锁
static struct spinlock my_spin_lock;
//static spinlock_t my_spin_lock;
2)、初始化自旋锁
spin_lock_init(&my_spin_lock);
3)、在程序区加锁,离开解锁,加解锁有四种形式
//获得自旋锁,(可自旋等待,可被软、硬件中断)
void spin_lock(spinlock_t *my_spinlock);
//获得自旋锁,(可自旋等待,保存中断状态并关闭软、硬件中断)
void spin_lock_irqsave(spinlock_t *my_spinlock, unsigned long flags);
//获得自旋锁,(可自旋等待,不保存中断状态关闭软、硬件中断)
void spin_lock_irq(spinlock_t *my_spinlock);
//获得自旋锁,(可自旋等待,不保存中断状态关闭硬件中断)
void spin_lock_bh(spinlock_t *lock);
注意:进程在获取自旋锁失败自旋等待时都不可以被系统消息打断
/释放自旋锁,退出临界区,严格与上面加锁对应
void spin_unlock(spinlock_t *lock)
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
void spin_unlock_irq(spinlock_t *lock)
void spin_unlock_bh(spinlock_t *lock)
//尝试获得自旋锁,(不自旋等待,成功返回1、失败则返回0)
int spin_trylock(spinlock_t *lock)
//*************************************
#include <linux/spinlock.h>
spinlock_t my_spinlock;
spin_lock_init(&my_spinlock);
//或者spinlock_t my_spinlock = SPIN_LOCK_UNLOCKED;
spin_lock(&my_spinlock);
....... //临界区代码
spin_unlock(&my_spinlock);
//***************************************
手段三:互斥和信号量(和 linux 系统编程类似)
1)、定义信号量
struct semaphore sem;
2)、初始化信号量
void sema_init(struct semaphore *sem, int val);
val:信号值
3)、加或减信号量
// 获取(等待)信号量,(减操作,不能被系统消息打断,会导致调用者睡眠)
void down(struct semaphore *sem);
// 获取(等待)信号量,(减操作,可以被系统消息打断,会导致调用者睡眠)
int down_interruptible(struct semaphore *sem);
//尝试获得信号量,成功返回0,失败返回非0,不会导致调用者睡眠
int down_trylock(struct semaphore *sem);
// 释放信号量,即使信号量加1(如果线程睡眠,将其唤醒)
void up(struct semaphore *sem);
//************************************
#include <linux/semaphore.h>
struct semaphore my_sema; //信号量
sema_init(&my_sema, 1); //信号量初始化为1,与init_MUTEX(my_sema)等效
//定义一个semaphore变量,并初始化为1
//DECLARE_MUTEX(my_sema);
down_interruptible(&my_sema); //获取信号量(减操作)
..... //临界区代码
up(&bufflock); //释放信号量(加操作)
//**************************************
1)、互斥锁定义和初始化
struct mutex mut;
mutex_init(&mut);
//DEFINE_MUTEX(mutexname) //一步定义和初始化
2)、加、解锁
//获得互斥体(加锁),(不能被系统消息打断,若获取该锁失败则进入睡眠)
extern void mutex_lock(struct mutex *lock);
//获得互斥体(加锁),(可以被系统消息打断,若获取该锁失败则进入睡眠)
extern int mutex_lock_interruptible(struct mutex *lock);
//尝试获得互斥体(加锁),成功返回0,失败返回错误值
extern int mutex_trylock(struct mutex *lock);
//释放互斥体(解锁)
extern void mutex_unlock(struct mutex *lock);
//***********************************
#include <linux/mutex.h>
struct mutex my_mutex;
mutex_init(&my_mutex);
//或者static DEFINE_MUTEX(my_mutex);
mutex_lock(&my_mutex); //获取信号量(减操作)
..... //临界区代码
mutex_unlock(&my_mutex); //释放信号量(加操作)
//***************************************
5 、延时与内核定时器
Linux系统定时器能以可编程的频率中断处理器,此频率为每秒的定时器节拍数,对应内核变量HZ,即1秒中产生的节拍数。
每当系统定时器中断产生,内部计数器值就加1,此计数器是一个64位的变量"jiffies_64",开发者常使用unsigned long型的变量"jiffies",此或者和jiffies_64相同或者为jiffies_64的低32位(jiffies和jiffies_64相同与否取决于平台),该变量记录了自上次操作系统引导以来的时钟滴答数。
内核定时器:
#include <linux/timer.h>
// 定时器结构体类型
struct timer_list
{
struct list_head entry;
//用来形成链表,由内核管理,申请一个定时器,自动加入链表
unsigned long expires;
//定时器到期时间(指定一个时刻,即, jiffies计数值)
void (*function)(unsigned long);
// 定时器处理函数
unsigned long data;
// 作为参数被传入定时器处理函数
......
};
// 初始化定时器
void init_timer(struct timer_list *timer);
//添加定时器。定时器开始计时
void add_timer(struct timer_list * timer);
//删除定时器,在定时器到期前禁止一个已注册定时器
int del_timer(struct timer_list * timer);
//如果定时器函数正在执行则在函数执行完后返回,其它情况同上
int del_timer_sync(struct timer_list *timer);
//更新定时器到期时间,并开启定时器
int mod_timer(struct timer_list *timer, unsigned long expires);
//查看定时器是否正在被调度运行//return value: 1 if the timer is pending, 0 if not.
int timer_pending(const struct timer_list *timer);
设计步骤:
1)、创建struct timer_list结构体,并且把赋值定时时间,函数入口,函数参数等。
struct timer_list my_timer;
my_timer.data = 0L;
my_timer.function = timer_handler;
my_timer.expires = jiffies + 1*HZ;
2)、初始化定时器
init_timer(&my_timer);
3)、启动定时
add_timer(&my_timer);
4)、如需重复执行, 需要重新初始化并启动定时器
my_timer.expires = jiffies + 1 * HZ/n; //1/n秒定时
add_timer(&my_timer);
//mod_timer(&my_timer, jiffies+1*HZ);
5)、关闭或者卸载的时候把定时器销毁
del_timer_sync(&my_timer);
//**************************************
struct timer_list myTimer;
init_timer(&myTimer);
myTimer.expires = jiffies + 3 * HZ;
myTimer.data = 0L;
myTimer.function = timerHandler;
add_timer(&myTimer); //添加定时器到内核
// 定时器处理函数,计数次数 n = t*HZ(t单位为秒)
void timerHandler(unsigned long data)
{
// 如需重复执行, 需要重新初始化并启动定时器
myTimer.expires = jiffies + 3 * HZ;
add_timer(&myTimer);
}
......
del_timer_sync(&myTimer); //使用完毕删除定时器
//*********************************************
内核延时:
内核延时分为,等待延时和睡眠延时,对于等待延时很短,在延时期间会一直忙等待(即占据CPU);另一种如果延时较长,进程会进入睡眠延时(即让出CPU)
短延迟 **-**不依赖于时钟滴答,与具体的CPU类型相关
#include <linux/delay.h>
void ndelay(unsigned long nsecs); //纳秒级延时
void udelay(unsigned long usecs); //微秒级延时
void mdelay(unsigned long msecs); //毫秒级延时
注:这三个延时函数使任务进入忙等待,一般不太长的时间可以用它
长延时 **-**长于一个时钟滴答,为了提高系统运行效率,当前进程需要进入睡眠,让出处理器。
void msleep(unsigned int millisecs);
unsigned long msleep_interruptible(unsigned int millisecs);
void ssleep(unsigned int seconds);
以上三个函数本质都是依赖于等待队列来实现进程睡眠,让出处理器的。
队列实现一:(自建队列)
#include<linux/wait.h>
long wait_event_timeout(wait_queue_head_t q, condition,long timeout);
long wait_event_interruptible_timeout(wait_queue_head_t q, condition,long timeout);
/*这些函数在给定队列上睡眠, 但是它们在超时(以 jiffies 表示)到后返回。如果超时,函数返回 0; 如果这个进程被其他事件唤醒,则返回以 jiffies 表示的剩余的延迟实现;返回值从不会是负值*/
//*****************
#include <linux/wait.h>
wait_queue_head_t wait;
init_waitqueue_head(&wait);
wait_event_interruptible_timeout(wait,0,delay);
//********************
队列实现二:
#include <linux/sched.h>
signed long schedule_timeout(signed long timeout);
注:等待队列wait_event_timeout函数内部也是通过该函数实现,使用schedule_timeout可以避免声明和使用多余的等待队列头
/*timeout 是要延时的 jiffies 数。除非这个函数在给定的 timeout 流失前返回,否则返回值是 0 。schedule_timeout 要求调用者首先设置当前的进程状态。为获得一个不可中断的延迟, 可使用 TASK_UNINTERRUPTIBLE 代替。如果你忘记改变当前进程的状态, 调用 schedule_time 如同调用 shcedule,建立一个不用的定时器。*/
使用实例:
//*************************
#include <linux/sched.h>
......
long delay = 100;
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(delay);
.....
//*************************
set_current_state为设置当前进程的状态,调度器在定时器到时且进程状态变为TASK_RUNNING时才运行该进程,若实现不可中断的延迟可用TASK_UNINTERRUPTIBLE。若使用前忘记改变进程的状态,则定时器不起作用,不能实现延迟
6 、 Linux 驱动阻塞与非阻塞
阻塞操作,是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作条件后再进行各项操作,实现阻塞一般使用等待队列或信号量的方法。
非阻塞,进程在不能进行设备操作时,并不挂起,它或者放弃,或者不停地查询,直到可以进行操作为止,对于非阻塞而言,如果要知道能不能操作,就必须不断的查询,一般使用轮询操作。
阻塞实现:
等待队列在Linux内核中用来阻塞或唤醒一个进程,也可以用来同步对系统资源的访问,还可以实现延迟功能。
实现方法 1--- 信号量:
1)、定义信号并初始化
static struct semaphore readable;
sema_init(&readable, 0); //信号量初始化为0
2)、在需要阻塞的系统调用中,使用down操作(假设read阻塞)
if(down_interruptible(&readable) != 0) //read阻塞实现
return 0;
3)、在把数据放入之后,能够让用户层能读取到字符的地方,up操作(假设在中断中)
up(&readable);
注:down_interruptible会在没信号量的情况下,阻塞,而睡眠。
实现方法 2-- 等待队列:
单任务情况:
1)、定义和初始化等待队列头
//定义一个等待队列头
wait_queue_head_t my_queue;
//初始一个等待队列头
init_waitqueue_head(&my_queue);
//定义并初始化一个等待队列头
//DECLARE_WAIT_QUEUE_HEAD(my_queue);
2)、定义一个条件flag,非0为真,0为假;依据条件把当前进程加入队列
static int flag = 0;
wait_event_interruptible(my_queue,flag);
3)、在需要的地址唤醒进程
flag = 1;
wake_up_interruptible(&my_queue);
//*******************************
#include <linux/wait.h>
//定义等待队列头
static DECLARE_WAIT_QUEUE_HEAD(my_queue);
static int flag = 0;
.....
//判断flag条件如果为假,将当前进程推入等待队列并将其睡眠
wait_event_interruptible(my_queue,flag);
flag = 0;
.....
void func()
{
flag = 1;
wake_up_interruptible(&my_queue);
}
//******************************
1)、定义和初始化等待队列头
static DECLARE_WAIT_QUEUE_HEAD(key_waitqueue_head);
2)、将当前进程推入等待队列将其睡眠
interruptible_sleep_on(&key_waitqueue_head);
3)、在需要的地址唤醒进程
wake_up_interruptible(&key_waitqueue_head);
非阻塞实现:
select()函数是提供给用户的接口函数,该函数通过系统调用最终会引发设备驱动中的poll()函数被执行,该机制可以实现一个用户进程对多个设备驱动文件的监测,在设备驱动中的轮询编程poll()函数,为file_operation成员之一。
轮询方法:
#include <sys/select.h>
//在应用程序中调用的文件描述符监测函数
int select(int numfds, fd_set *readfds,
fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout);
参数numfds:待监听中最大描述符值加一
参数readfds:监听读操作的文件描述符集合
参数writefds:监听写操作的文件描述符集合
参数exceptfds:监听异常处理的文件描述符集合
参数timeout:监听等待超时退出select()
返回值 如果参数timeout设为NULL则表示select()没有timeout。
错误代码 执行成功则返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间,当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。
//文件描述符集合的变量的定义
fd_set fds;
//清空描述符集合
FD_ZERO(fd_set *set);
//加入一个文件描述符到集合中
FD_SET(int fd, fd_set *set);
//从集合中清除一个文件描述符
FD_CLR(int fd, fd_set *set);
//判断文件描述符是否被置位
FD_ISSET(int fd, fd_set *set);
返回非0,表示置位(该文件描述集合中有文件可进行读写操作,或产生错误)
//添加等待队列到wait参数指定的轮询列表中
void poll_wait(struct file *filp, wait_queue_heat_t *wq,poll_table *wait);
程序设计步骤:
1)、定义和初始化头队列
static DECLARE_WAIT_QUEUE_HEAD(key_waitq);
2)、在file_operations中添加poll函数
.poll = keyDriver_poll,
3)、在poll函数中把添加等待队列到wait参数指定的轮询列表
poll_wait(file, &key_waitq, wait);
4)、在中断等需要地方,唤醒等待
wake_up_interruptible(&key_waitq);
7 、 Linux 内核线程
内核线程类似于用户进程,通常用于并发处理些工作,它是一种在内核空间实现后台任务的方式,并且可以参与时间片轮转调度。
线程一般在__init函数里面就定义、开启,而结束线程一般是在__exit函数中停止。
//创建内核线程(方法一),返回值为创建线程的指针
struct task_struct *kthread_create(int (*threadfn)(void *data),
void *data,const char namefmt[], ...);
参数1:线程函数指针,线程开启后将运行此函数
参数2:函数参数data,传递给线程函数
参数3:线程名称,这个函数可以像printk一样传入某种格式的线程名
//内核线程创建后不会马上运行,需要通过以下函数启动
int wake_up_process(struct task_struct *p);
//***************************
static int key_kthread_create(void)
{
int err = 0;
key_thread = kthread_create(key_thread_func, &key, "key13_task");
if(IS_ERR(key_thread)){
PRINTK("Unable to start kernel thread.\n");
err = PTR_ERR(key_thread);
key_thread = NULL;
return err;
}
wake_up_process(key_thread);
return 0;
}
//*****************************
static void key_kthread_stop(void)
{
if(key_thread){
kthread_stop(key_thread);
key_thread = NULL;
}
return;
}
//*****************************
static int key_thread_func(void *data)
{
PRINTK("in function key_thread!\n");
//daemonize("key_thread_func");
//allow_signal(SIGKILL);
for(;;){ //while循环实现线程任务
if(kthread_should_stop()) //检查是否stop
// if(signal_pending(current))
break;
if(key = translateKey(GPFDAT & 0x1c)){
wake_up_interruptible(&my_queue);
condition = 1;
}
schedule(); //进程间切换
}
return 0;
}
//***********************************************
disable_irq关闭中断并等待中断处理完后返回, 而disable_irq_nosync立即返回
下面从内核代码来找一下原因:
先看一下disable_irq_nosync,内核代码中是这样解释的:
/**
* disable_irq_nosync - disable an irq without waiting
* @irq: Interrupt to disable
*
* Disable the selected interrupt line. Disables and Enables are
* nested.
* Unlike disable_irq(), this function does not ensure existing
* instances of the IRQ handler have completed before returning.
*
* This function may be called from IRQ context.
*/
void disable_irq_nosync(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);
unsigned long flags;
if (!desc)
return;
chip_bus_lock(irq, desc);
spin_lock_irqsave(&desc->lock, flags);
__disable_irq(desc, irq, false);
spin_unlock_irqrestore(&desc->lock, flags);
chip_bus_sync_unlock(irq, desc);
}
关闭中断后程序返回, 如果在中断处理程序中, 那么会继续将中断处理程序执行完.
/**
* disable_irq - disable an irq and wait for completion
* @irq: Interrupt to disable
*
* Disable the selected interrupt line. Enables and Disables are
* nested.
* This function waits for any pending IRQ handlers for this interrupt
* to complete before returning. If you use this function while
* holding a resource the IRQ handler may need you will deadlock.
*
* This function may be called - with care - from IRQ context.
*/
void disable_irq(unsigned int irq)
{
struct irq_desc *desc = irq_desc + irq;
if (irq >= NR_IRQS)
return;
disable_irq_nosync(irq);
if (desc->action)
synchronize_irq(irq);
}
关闭中断并等待中断处理完后返回.从代码中可以看到, disable_irq先是调用了disable_irq_nosync, 然后检测desc->action是否为1. 在中断处理程序中, action是置1的, 所以进入synchronize_irq函数中.
/**
* synchronize_irq - wait for pending IRQ handlers (on other CPUs)
* @irq: interrupt number to wait for
*
* This function waits for any pending IRQ handlers for this interrupt
* to complete before returning. If you use this function while
* holding a resource the IRQ handler may need you will deadlock.
*
* This function may be called - with care - from IRQ context.
*/
void synchronize_irq(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);
unsigned int status;
if (!desc)
return;
do {
unsigned long flags;
/*
* Wait until we're out of the critical section. This might
* give the wrong answer due to the lack of memory barriers.
*/
while (desc->status & IRQ_INPROGRESS)
cpu_relax();
/* Ok, that indicated we're done: double-check carefully. */
spin_lock_irqsave(&desc->lock, flags);
status = desc->status;
spin_unlock_irqrestore(&desc->lock, flags);
/* Oops, that failed? */
} while (status & IRQ_INPROGRESS);
/*
* We made sure that no hardirq handler is running. Now verify
* that no threaded handlers are active.
*/
wait_event(desc->wait_for_threads, !atomic_read(&desc->threads_active));
}
注释中说明该函数是在等待中断处理程序的结束, 这也是disable_irq与disable_irq_nosync不同的主要所在. 但是在中断处理函数中调用会发生什么情况呢? 进入中断处理函数前IRQ_INPROGRESS会被__setup_irq设置, 所以程序会一直陷在while循环中, 而此时内核以经被独占, 这就导致系统死掉.
总结:
由于在disable_irq中会调用synchronize_irq函数等待中断返回, 所以在中断处理程序中不能使用disable_irq, 否则会导致cpu被synchronize_irq独占而发生系统崩溃.
头尾介绍了,当然还的说说中断的心脏了,即中断服务程序,即申请中断时的第二个参数
irqreturn_t handler(int irq,void *dev_id)
{
...... // 中断处理
return IRQ_HANDLED;
}
该中断服务程序可以被多个中断调用,所以使用的时候,为防止产生冲突,需要加上自旋锁,此时即涉及到内核的并发控制
抢占内核:必须配置 make menuconfig
kernel feature
freemption model