回顾:
linux内核的并发和竞态:
概念:
并发:多个执行单元同时发生
竞态:
共享资源:
互斥访问:
临界区:
原子性:
内核执行路径:
产生竞态的几种情况:
多核:
进程与线程
中断与进程
中断与中断
linux内核避免竞态的方法:
中断屏蔽
原子操作
自旋锁
信号量:睡眠锁
linux内核等待队列机制:
等待队列机制:
目的:就是让进程在内核空间进行休眠操作!
问:进程在用户空间如何显示进行休眠?
答:sleep();
问:为什么需要让用户进程在内核空间进行休眠呢?
答:
进程没有权利和资格在空间进行访问硬件设备,必须利用系统调用陷入内核空间才能访问硬件设备;一旦进程陷入内核空间访问硬件设备(串口、按键),进程发现硬件设备不可用(串口没有接收到数据或者按键没有人为操作),那么内核就让用户进程在内核空间进行休眠,等待硬件设备可用;
一旦硬件设备可用了(通过中断判断硬件设备可用)唤醒之前休眠的进程,进程再次对设备进行访问操作!
特点:如果一个进程需要在内核空间休眠,等待队列机制就将这个进程放入一个等待队列(休眠队列)中去,一旦进程被唤醒,进程也会从队列(休眠队列)中进行移除!
比如信号量机制就是利用等待队列机制来实现进程休眠的!
问:等待队列和工作队列的区别?
答:工作队列的本质是延后执行,底半部的一个机制
等待队列的本质就是让进程进行休眠!
调度器:属于linux内核进程管理子系统的重要部件,负责进程之间的切换、调度、抢占,也负责进程之间CPU资源的转换工作!
等待队列机制让用户进程由运行进入休眠,由休眠进入运行,整个过程就需要调度器参与!
等待队列机制如何使用?
调度器<->老鹰
等待队列头<->鸡妈妈
休眠的进程<->鸡宝宝
游戏规则:如果一个进程要休眠,只需给这个休眠的进程分配一个容器,将此进程填充到容器中,然后把这个容器再添加到以鸡妈妈代表的休眠队列中去,进行休眠!一旦这个进程被唤醒,调度器(老鹰)将这个进程从休眠队列中移除,让这个进程投入运行!
linux内核等待队列的实现过程:
等待队列头(鸡妈妈)的数据结构:wait_queue_head_t
存放休眠进程的容器的数据结构:wait_queue_t
让进程在内核空间休眠的步骤:
1,分配等待队列头
wait_queue_head_t wq;
2,初始化等待队列头对象
init_waitqueue_head(&wq)
3,分配存放休眠进程的容器(一个进程一个容器)
wait_queue_t wait;
4,初始化存放进程的容器:
init_waitqueut_entry(&wait,current);
注意:current是内核全局变量,代表当前进程(正在使用CPU资源的进程),指向当前的struct task struct 结构体(linux系统对于每一个进程或者线程是否分配一个struct task_struct结构体对象来描述当前进程或者线程的属性)
例如:利用current打印当前进程的信息:
printk(“当前进程名%s,当前进程pid:%d \n”,current->pid,current->comm);
5,将要休眠的进程添加到等待队列头所在休眠队列中去。
add_wait_queue(&wq,&wait);
注意:当前进程还没有正式休眠!
6,设置当前进程的状态为休眠状态
set_currnet_state(TASK_INTERRUPTIBLE);//设置为可中断的休眠状态
7,让当前进程进入真正的休眠状态(当前进程释放CPU资源)
scedhule();//才算是完成进程的休眠
注意:进程一旦调用此函数进行休眠,代码就执行到此处不再往下执行了!
注意:这个函数的休眠是永久休眠!
如果这个进程被唤醒,这个函数立即返回
8,等待进程被唤醒,进程被唤醒的方法途径有两种:
驱动主动唤醒(设备可用产生中断,有中断来唤醒)等待队列中休眠的进程;
给休眠的进程发送信号(kill)唤醒等待队列中休眠的进程
问:驱动如何主动唤醒wq等待队列中休眠的进程呢?
9,一旦进程被唤醒以后,将当前进程从等待队列中进行移除!
remove_wait_queut(&wq,&wait);
10,设置当前进程的状态为运行状态,进程正式投入运行!
set current_state(TASK_RUNNING);
11,判断进程被唤醒的原因,如果是驱动主动唤醒 ,进程就应该合理的,正常的,有理由的继续访问设备(比如读取串口设备);如果是接收到kill信号引起的唤醒,进程就应该处理这个信号,而不是继续访问这个设备!
if(signal_pending(current)){
printk("进程接收到信号引起的唤醒");
return ERETARTSYS;
}else{
printk("进程由驱动主动唤醒");
//继续访问设备
}
用schedule_timeout函数让进程在内核空间休眠的步骤:
1,分配等待队列头
wait_queue_head_t wq;
2,初始化等待队列头对象
init_waitqueue_head(&wq)
3,分配存放休眠进程的容器(一个进程一个容器)
wait_queue_t wait;
4,初始化存放进程的容器:
init_waitqueut_entry(&wait,current);
注意:current是内核全局变量,代表当前进程(正在使用CPU资源的进程),指向当前的struct task struct 结构体(linux系统对于每一个进程或者线程是否分配一个struct task_struct结构体对象来描述当前进程或者线程的属性)
例如:利用current打印当前进程的信息:
printk(“当前进程名%s,当前进程pid:%d \n”,current->pid,current->comm);
5,将要休眠的进程添加到等待队列头所在休眠队列中去。
add_wait_queue(&wq,&wait);
注意:当前进程还没有正式休眠!
6,设置当前进程的状态为休眠状态
set_currnet_state(TASK_INTERRUPTIBLE);//设置为可中断的休眠状态
7,让当前进程进入真正的休眠状态(当前进程释放CPU资源)
schedhule_timeout();//才算是完成进程的休眠
注意:进程一旦调用此函数进行休眠,代码就执行到此处不再往下执行了!
注意:这个函数的休眠不是永久休眠,而是指定了休眠的时间!
schedhule_timeout(5*HZ)//休眠5秒
如果这个进程被唤醒,这个函数立即返回
8,等待进程被唤醒,进程被唤醒的方法途径有两种:
驱动主动唤醒(设备可用产生中断,有中断来唤醒)等待队列中休眠的进程;
给休眠的进程发送信号(kill)唤醒等待队列中休眠的进程
如果超时时间到期(5秒到期),那么这个函数主动返回,进程主动被唤醒(由称超时唤醒)!如果是超时唤醒, schedhule_timeout返回为0,否则返回为非0!
9,一旦进程被唤醒以后,将当前进程从等待队列中进行移除!
remove_wait_queut(&wq,&wait);
10,设置当前进程的状态为运行状态,进程正式投入运行!
set current_state(TASK_RUNNING);
11,判断进程被唤醒的原因,如果是驱动主动唤醒 ,进程就应该合理的,正常的,有理由的继续访问设备(比如读取串口设备);如果是接收到kill信号引起的唤醒,进程就应该处理这个信号,而不是继续访问这个设备!如果以上的唤醒都是不成立,则就是超时唤醒
if("如果是超时唤醒"){
printk("进程是由超时引起的唤醒");
return -EAGAIN;
}else{//驱动唤醒和接收信号唤醒
if(signal_pending(current)){
printk("进程接收到信号引起的唤醒");
return ERETARTSYS;
}else{
printk("进程由驱动主动唤醒");
//继续访问设备
}
}
问:驱动如何主动唤醒休眠的进程呢?
答:“主动唤醒”:主动唤醒的前提是设备可用;
“如何判断设备可用”:设备可用,必然产生中断,通过中断处理函数的是否调用来判断设备是否可用!
案例:回顾之前的按键驱动
仅仅实现了一个中断处理函数!
案例:按键驱动,要求写进程唤醒读进程!
btn_drv.c
#include <linux/init.h>
#include <linux/moudule.h>
#include <linux/miscdevice.h>
#include <linux/irq.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/input.h>//标准的键值KEY_UP,KEY_DOWN....
#include <linux/sched.h>//TASK_*
#include <linux/wait.h>//add_wait_queue....
//1,分配读进程的等待队列头对象
static wait_queue_head_t rwq;//里面保存休眠的读进程
//当应用程序调用read的时候,通过软中断到这里
//让读进程进入休眠状态(这里用schedule函数休眠)
static ssize_t btn_read(struct file *file,char __user *buf,size_t count,lofft_t *ppos){
printk("读进程[%d]:PID[%d],将进入休眠状态\n",current->comm,current->pid);
//1,分配保存当前进程的容器
wait_queue_t wait;
//2,初始化保存进程的容器,将当前进程填充到这个容器中
init_waitqueue_entry(&wait,current);
//3,将当前进程添加到睡眠队列中去
add_wait_queue(&rwq,&wait);
//4,设置当前进程的状态为可中断的休眠状态
set_current_state(TASK_INTERRUPTIBLE);
//5,当前进程进入真正的休眠状态
schedule();//代码停止不动!静静的等待着被唤醒
//6,一旦被唤醒 ,进程从schedule函数返回,设置当前进程的状态为运行
set_current_state(TASK_RUNNING);
//7,一旦被唤醒,进程从睡眠队列中进行移除
remove_wait_queue(&rwq,&wait);
//8,判断是哪种原因引起的进程唤醒
if(signal_pending(current)){//判断当前进程是否接收到信号
printk("读进程[%s]:PID[%d]接收到的信号\n",current->comm,current->pid);
return -ERSETARTSYS;
}
//9,如果8不成立,说明是驱动主动唤醒
printk("读进程[%s]:PID[%d]被写进程主动唤醒 \n",current->comm,current->pid);
return count;
}
static ssize_t btn_write(struct file *file,char __user *buf,size_t count,lofft_t *ppos){
printk("写进程[%s]:PID[%d]将唤醒读进程 \n",current->comm,current->pid);
//唤醒读等待队列中休眠的读进程
wake_up_interruptible(&rwq);
return count;
}
//分配硬件操作集合
static struct file_opeartons btn_fops={
.owner = THIS_MODULE,
.read = btn_read,
.write = btn_write
}
//分配初始化混杂设备对象
static struct miscdevice btn_misc = {
.minor = MISC_DYNAMDIC_MINOR,//动态分配次设备号
.name = "mybtn",// /dev/mybtn
.fops = &btn_fops
}
static int btn_init(void){
//注册混杂设备
misc_register(&btn_misc);
//2,初始化读进程的等待队列头
init_waitqueue_head(&rwq);
return 0;
}
static void btn_exit(void){
misc_deregister(&led_misc);//卸载混杂设备
}
module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");
应用程序测试代码:btn_test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char *argv[]){
int fd;
int data;
if(argc<2){
printf("Usage \n %s <r|w> \n",argv[0]);
return -1;
}
fd = open("/dev/mybtn",O_RDWR);
if(fd<0){
printf("open fail \n");
return -1;
}
if(!strcmp(argv[1],"r")){//制造一个读进程
//读进程,会休眠
read(fd,&data,4);
}eles if(!strcmp(argv[1],"w")){
//写进程,不会休眠
write(fd,"hello",6);
}
close(fd);
return 0;
}
案例:按键驱动,要求写进程唤醒读进程,读进程获取某个按键的按键值和按键的状态:
#include <linux/init.h>
#include <linux/moudule.h>
#include <linux/miscdevice.h>
#include <linux/irq.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/input.h>//标准的键值KEY_UP,KEY_DOWN....
#include <linux/sched.h>//TASK_*
#include <linux/wait.h>//add_wait_queue....
//上报按键信息的数据结构
struct btn_event{
int code;//上报的按钮的值
int state;//上报的按键状态
}
//分配上报的按键信息
static struct btn_event g_data;
//1,分配读进程的等待队列头对象
static wait_queue_head_t rwq;//里面保存休眠的读进程
//当应用程序调用read的时候,通过软中断到这里
//让读进程进入休眠状态(这里用schedule函数休眠)
static ssize_t btn_read(struct file *file,char __user *buf,size_t count,lofft_t *ppos){
printk("读进程[%d]:PID[%d],将进入休眠状态\n",current->comm,current->pid);
//1,分配保存当前进程的容器
wait_queue_t wait;
//2,初始化保存进程的容器,将当前进程填充到这个容器中
init_waitqueue_entry(&wait,current);
//3,将当前进程添加到睡眠队列中去
add_wait_queue(&rwq,&wait);
//4,设置当前进程的状态为可中断的休眠状态
set_current_state(TASK_INTERRUPTIBLE);
//5,当前进程进入真正的休眠状态
schedule();//代码停止不动!静静的等待着被唤醒
//6,一旦被唤醒 ,进程从schedule函数返回,设置当前进程的状态为运行
set_current_state(TASK_RUNNING);
//7,一旦被唤醒,进程从睡眠队列中进行移除
remove_wait_queue(&rwq,&wait);
//8,判断是哪种原因引起的进程唤醒
if(signal_pending(current)){//判断当前进程是否接收到信号
printk("读进程[%s]:PID[%d]接收到的信号\n",current->comm,current->pid);
return -ERSETARTSYS;
}
//9,如果8不成立,说明是驱动主动唤醒
printk("读进程[%s]:PID[%d]被写进程主动唤醒 \n",current->comm,current->pid);
//10,上报按键信息
copy_to_user(buf,&g_data,sizeof(g_data));
return count;
}
static ssize_t btn_write(struct file *file,char __user *buf,size_t count,lofft_t *ppos){
printk("写进程[%s]:PID[%d]将唤醒读进程 \n",current->comm,current->pid);
//赋值的代码要放到唤醒代码的前面
g_data.code = KEY_UP;
g_data.state = 1;//假如1表示按下
//唤醒读等待队列中休眠的读进程
wake_up_interruptible(&rwq);
return count;
}
//分配硬件操作集合
static struct file_opeartons btn_fops={
.owner = THIS_MODULE,
.read = btn_read,
.write = btn_write
}
//分配初始化混杂设备对象
static struct miscdevice btn_misc = {
.minor = MISC_DYNAMDIC_MINOR,//动态分配次设备号
.name = "mybtn",// /dev/mybtn
.fops = &btn_fops
}
static int btn_init(void){
//注册混杂设备
misc_register(&btn_misc);
//2,初始化读进程的等待队列头
init_waitqueue_head(&rwq);
return 0;
}
static void btn_exit(void){
misc_deregister(&led_misc);//卸载混杂设备
}
module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
struct btn_event{
int code;
int state;
};
int main(int argc,char *argv[]){
int fd;
struct btn_event data;
if(argc<2){
printf("Usage \n %s <r|w> \n",argv[0]);
return -1;
}
fd = open("/dev/mybtn",O_RDWR);
if(fd<0){
printf("open fail \n");
return -1;
}
if(!strcmp(argv[1],"r")){//制造一个读进程
//读进程,会休眠
read(fd,&data,sizeof(data));//由驱动上报的按键值
printf("code = %d ,state = %d",data.code,data.state);
}eles if(!strcmp(argv[1],"w")){
//写进程,不会休眠
write(fd,"hello",6);
}
close(fd);
return 0;
}
应用程序测试代码:
案例:把以上两个案例的驱动schedule换成schedule_timout(5*HZ)
案例:实现按键驱动,每当用户进行按键操作进,驱动能够上报按键对应的键值和按键状态;
提示:btn_write换成中断处理函数进行唤醒
#include <linux/init.h>
#include <linux/moudule.h>
#include <linux/miscdevice.h>
#include <linux/irq.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/input.h>//标准的键值KEY_UP,KEY_DOWN....
#include <linux/sched.h>//TASK_*
#include <linux/wait.h>//add_wait_queue....
//上报按键信息的数据结构
struct btn_event{
int code;//上报的按钮的值
int state;//上报的按键状态
}
//声明按键硬件相关的数据结构
struct btn_resource{
int irq;//中断号
int gpio;//gpio
int code;//键值
char *name;//按键名称
};
//分配初始化按键信息
static struct btn_resource btn_info[] = {
[0] = {
.irq = IRQ_EINT(0),
.gpio = S5PV210_GPH0(0),
.code = KEY_UP,
.name = "KEY_UP"
},
[1] = {
.irq = IRQ_EINT(1),
.gpio = S5PV210_GPH0(),
.code = KEY_DOWN,
.name = "KEY_DOWN"
}
};
//分配上报的按键信息
static struct btn_event g_data;
//1,分配读进程的等待队列头对象
static wait_queue_head_t rwq;//里面保存休眠的读进程
//当应用程序调用read的时候,通过软中断到这里
//让读进程进入休眠状态(这里用schedule函数休眠)
static ssize_t btn_read(struct file *file,char __user *buf,size_t count,lofft_t *ppos){
printk("读进程[%d]:PID[%d],将进入休眠状态\n",current->comm,current->pid);
//1,分配保存当前进程的容器
wait_queue_t wait;
//2,初始化保存进程的容器,将当前进程填充到这个容器中
init_waitqueue_entry(&wait,current);
//3,将当前进程添加到睡眠队列中去
add_wait_queue(&rwq,&wait);
//4,设置当前进程的状态为可中断的休眠状态
set_current_state(TASK_INTERRUPTIBLE);
//5,当前进程进入真正的休眠状态
schedule();//代码停止不动!静静的等待着被唤醒
//6,一旦被唤醒 ,进程从schedule函数返回,设置当前进程的状态为运行
set_current_state(TASK_RUNNING);
//7,一旦被唤醒,进程从睡眠队列中进行移除
remove_wait_queue(&rwq,&wait);
//8,判断是哪种原因引起的进程唤醒
if(signal_pending(current)){//判断当前进程是否接收到信号
printk("读进程[%s]:PID[%d]接收到的信号\n",current->comm,current->pid);
return -ERSETARTSYS;
}
//9,如果8不成立,说明是驱动主动唤醒
printk("读进程[%s]:PID[%d]被写进程主动唤醒 \n",current->comm,current->pid);
//10,上报按键信息
copy_to_user(buf,&g_data,sizeof(g_data));
return count;
}
static irqreturn_r button_isr(int irq,void *dev_id){
//1,获取对应按键的结构体的首地址
struct btn_resource *pdata = (struct btn_resource *)dev_id
g_data.code = pdata->code;
g_data.state = !gpio_get_value(pdata->gpio);//返回:按下是1,松开是0
//唤醒读等待队列中休眠的读进程
wake_up_interruptible(&rwq);
return IRQ_HANDLE;
}
//分配硬件操作集合
static struct file_opeartons btn_fops={
.owner = THIS_MODULE,
.read = btn_read,
.write = btn_write
}
//分配初始化混杂设备对象
static struct miscdevice btn_misc = {
.minor = MISC_DYNAMDIC_MINOR,//动态分配次设备号
.name = "mybtn",// /dev/mybtn
.fops = &btn_fops
}
static int btn_init(void){
int i;
//注册混杂设备
misc_register(&btn_misc);
//2,初始化读进程的等待队列头
init_waitqueue_head(&rwq);
//注册中断处理函数和申请GPIO资源
for(i=0;i<ARRAY_SIZE(btn_info);i++){
gpio_request(btn_info[i].gpio,btn_info[i].name);
request_irq(btn_info[i].irq,
button_isr,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
&btn_info[i]);
}
return 0;
}
static void btn_exit(void){
//释放资源
int i;
for(i=0;i<ARRAY_SIZE(btn_info);i++){
gpio_free(btn_info[i].gpio);
free_irq(btn_info[i].irq,&btn_info[i]);
}
misc_deregister(&led_misc);//卸载混杂设备
}
module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");
对应的测试程序为:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
struct btn_event{
int code;
int state;
};
int main(int argc,char *argv[]){
int fd;
struct btn_event data;
if(argc<2){
printf("Usage \n %s <r|w> \n",argv[0]);
return -1;
}
fd = open("/dev/mybtn",O_RDWR);
if(fd<0){
printf("open fail \n");
return -1;
}
while(1){
read(fd,&data,sizeof(data));//读的时候会休眠,
printf("code = %d ,state = %d",data.code,data.state);
}
close(fd);
return 0;
}