同步和互斥
一、基本概念
1、临界资源
该资源的访问是受限,一个进程访问了该资源,其他进程就不能访问该资源,得不到该资源的进程,该进程有什么动作:
1)进程就产生阻塞--->进入睡眠状态,使用机制:信号量和互斥锁
2)进程就会进入忙等待--->进程还是运行状态,使用机制:自旋锁
3)进程就会退出
临界资源举例:
request_irq(int irq, ...), 同一中断号就是临界资源,只能申请一次
free_irq() ---> 释放资源
申请GPIO口
申请物理内存区
2、临界区:
访问临界资源的代码
3、竞争
多个进程访问同一个资源,就会产生竞争
4、同步
让多个进程之间有序的访问临界资源,避免产生竞争
5、为什么会产生竞争
1)linux内核是抢占式内核,高优先级的进程可以打断低优先级的进程
2)进程与中断服务程序之间也会有竞争
3)在多核处理器的条件下,不同的CPU上运行的进程也可能访问同一个临界资源。
6、使用关中断的方法,实现同步
应用环境的条件:
在单CPU,非抢占式内核中,才可以使用关中断的方法
---------------------------------------------------------------------------
linux内核中多线程同步机制常用的有哪些?
1、在单CPU,非抢占式内核中,才可以使用关中断的方法
2、原子操作
3、原子位操作
4、自旋锁
5、信号量
6、互斥锁
7、等待队列
-----------------------------------------------------------------------------------------------
一、原子操作:
1、在内核中,多个进程(线程)共享的一个计数值,或则进程和ISR之间共享一个计数值的时候,对该计数值的操作,可能会造成计数出错。
1 例如有一段代码两个进程同时访问: 2 int g_a = 0; 3 { 4 g_a = g_a+1; 5 } 6 对c语言代码的访问最终都转化成汇编代码: 7 p1 p2 8 LDR R1 [R0] LDR R1 [R0] 9 ADD R1,R1,#1 ADD R1,R1,#1 10 STR R1,[R0] STR R1,[R0] 11 12 加入在p1进程执行add操作之前,突然被p2进程抢占了;p2进程对g_a进行了+1操作,然后又回到p1进程,这
时p1进程仍然执行刚保存在R1中的值,也就是进行了两次+1操作,但实际上只有进行了一次+1操作的效果,计
数出错。所以需要原子操作。 13 14 原子操作的核心就是,在对公共资源操作过程中不会被打断,直到其操作完成。
2、如何解决共享计数值产生竞争的问题
思路:
将该计数值定义成一个原子变量,对该变量的操作使用原子操作。
3、如何定义原子变量
typedef struct {
int counter; //计数值
} atomic_t;
例:
atimic_t key_count; //key_count是一个原子变量,该原子变量是用来作为一个共享的计数值。
4、什么是原子操作?
我们使用内核提供的接口函数来访问原子变量,可以保证该访问过程是一个原子过程。
1、声明一个原子变量
atimic_t key_count;
v=v+i v=v-i
二、原子位操作
常见的位操作:按位与、按位或、按位取反
使一个位操作的过程变成一个原子过程。
下面位操作过程不是一个原子过程:
int a;
a |= (1<<10);
a &= ~(1<<11);
a ^= (1<<12);
如何实现一个原子位操作的过程?
void set_bit(int nr, unsigned long *addr)
void clear_bit(int nr, volatile unsigned long *addr)
void change_bit(int nr, volatile unsigned long *addr)
例:
int a;
set_bit(10, &a); //原子过程将a的第10位置1
clear_bit(11, &a);//将a的第11位清0
change_bit(12, &a);//将a的第12位取反
三、
四、自旋锁(spin lock)
1、基本概念
1)自旋锁是一个二值的锁 0 1
2)自旋锁是一个等待锁
3)当一个进程已经获得了自旋锁,另外一个进程也获得该自旋锁,则第二进程就会“原地自旋”,直到第一个进程释放该自旋锁。
4)自旋锁不是睡眠锁,不会产生阻塞。
3、自旋锁的用法
1)自旋锁的定义及初始化
(1)采用宏函数的方式静态定义:
static DEFINE_SPINLOCK(wdt_lock);
(2)动态的方式定义及初始化一个自旋锁
static spinlock_t wdt_lock;
spin_lock_init(&wdt_lock);
2)自旋锁上锁
void spin_lock(spinlock_t *lock)
void spin_lock_irq(spinlock_t *lock) //自旋锁上锁的同时关闭中断
spin_lock_irqsave(spinlock_t *lock, unsigned long flags) //自旋锁上锁的同时关闭中断,并保存中断的状态到flags中
3)自旋锁解锁
void spin_unlock(spinlock_t *lock)
void spin_unlock_irq(spinlock_t *lock)
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
4、自旋锁的使用注意事项
1)自旋锁适合于使用在保护的临界区时间比较短的情况下,如果时间比较长,就需要使用信号量或互斥锁。
2)自旋锁使用在多核处理器的环境下,或者在单核处理器且抢占式内核的环境下。
3)在一个进程获得了自旋锁之后,这个时候,抢占器会被关闭,高优先级的进程不会抢占低优先级的进程。
分析:
static inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock->rlock);
}
#define raw_spin_lock(lock) _raw_spin_lock(lock)
void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
{
__raw_spin_lock(lock);
}
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
preempt_disable(); //关闭抢占器
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
4)自旋锁不能递归调用,如果递归调用就会产生死锁。
5)我们在用锁的时候,要注意用锁的顺序:上锁-->解锁 ,上锁--->解锁。当一个进程已经拿到了一个锁,在释放该锁之前,不能去拿新的锁,否则也可以产生死锁。
6)因为自旋锁是一种等待锁,所以自旋锁可以使用在中断上下文。而信号量(或互斥锁)是阻塞锁,得不到该锁的时候会造成睡眠,所以信号量(或互斥锁)是不能使用在中断上下文的。
提示:
中断上下文:ISR的运行环境
Tasklet的运行环境
7)自旋锁保护的临界区是一个原子过程,在过程中,不能使用可能产生阻塞的函数。
------------------------------------------------------------------------------------------------
五、信号量(semaphore)
1、基本概念
1)信号量是一个多值的锁,当一个进程得到信号量就可以访问临界资源,信号量的值就会减1。当信号量间到0,在有进程获得信号量,该进程就会产生阻塞,进入睡眠状态。
2)信号量是一种阻塞锁,当进程得不信号量的时候,就会进入睡眠状态。
3)信号量不能使用中断上下文(原子过程中)。
2、信号量的用法
#include <linux/semaphore.h>
1)定义
struct semaphore my_sema;
2)初始化
void sema_init(struct semaphore *sem, int val)
sema_init(&my_sema, 50);
3)获得一个信号量(P操作)
void down(struct semaphore *sem) //如果进程得不信号量,进入不可中断睡眠(深睡眠:D)
int down_interruptible(struct semaphore *sem) //如果进程得不到信号量,进入可中断睡眠(浅睡眠:S)
4)释放一个信号量
void up(struct semaphore *sem)
-------------------------------------------------------------------------------
六、互斥锁(mutex)
1、基本概念
1)互斥锁又叫互斥体,是信号量的特例,是一个二值的信号量,只有上锁和解锁两个状态。
2)信号量有的特性,互斥锁也有。
3)当一个进程得不到互斥锁(信号量),就会产生阻塞,进入睡眠状态。随眠在该互斥锁的等待队列。当该进程得到了互斥锁,就会进入运行队列,在运行队列中等待系统的调度。
4)自旋锁是没有等待队列的。
2、使用举例
1)定义并初始化一个互斥锁
static DEFINE_MUTEX(adc_mutex);
2)互斥锁上锁和解锁
int s3c_adc_get_adc_data(int channel)
{
int adc_value = 0;
int cur_adc_port = 0;
mutex_lock(&adc_mutex); //上锁
cur_adc_port = adc_port;
adc_port = channel;
adc_value = s3c_adc_convert();
adc_port = cur_adc_port;
mutex_unlock(&adc_mutex); //解锁
pr_debug("%s : Converted Value: %03d\n", __func__, adc_value);
return adc_value;
}
EXPORT_SYMBOL(s3c_adc_get_adc_data);
3、互斥锁的使用过程
1)互斥锁的定义和初始化
(1)静态的方式
static DEFINE_MUTEX(adc_mutex);
(2)动态的方式
void mutex_init(struct mutex *lock);
struct mutex adc_mutex; //定义一个结构体变量,在内存中会给这个结构体变量分配空间
mutex_init(&adc_mutex); //向该空间内填写内容
思考:
strcuct mutex *adc_mutex; //定义一个结构体指针,在内存中没有该结构体的空间
mutex_init(adc_mutex); //向结构体的空间填内容,会出现“段错误(Segmentation fault)”,野指针。
如何解决?
strcuct mutex *adc_mutex;
adc_mutex=kmalloc(sizeof(struct mutex),GFP_KERNEL)
if(adc_mutex == NULL){
return -ENOMEM;
}
mutex_init(adc_mutex);
2)获得一个互斥锁
mutex_lock(struct mutex *lock)
int __sched mutex_lock_interruptible(struct mutex *lock)
3)释放一个互斥锁
void __sched mutex_unlock(struct mutex *lock)
------------------------------------------------------------------------------
七、等待队列
1、概念
我们在使用信号量(互斥锁)的时候,如果一个那不到信号或则拿不到互斥锁,就会产生阻塞,该进程就进入随眠状态。当该信号量(互斥锁)被其他进程释放,则该进程就进入运行队列,等待调度器调度该进程运行。
有一个等待条件:能不能获得一个信号量或互斥锁
有一个等待队列:创建信号量或互斥锁的时候,已经创建了等待队列。
2、需求
能不能自己定义等待队列,并自己设置一个等待条件。当条件满足的时候,进程就继续工作,如果条件不满足,进程就睡眠。
1、等待队列的用法
#include <linux/wait.h>
1)定义一个等待队列,并做等待队列的初始化
(1)静态的方法
DECLARE_WAIT_QUEUE_HEAD(key_wait);
(2)动态的方法
void init_waitqueue_head(wait_queue_head_t *q)
wait_queue_head_t key_wait;
init_waitqueue_head(&key_wait);
2)判断等待条件(所有函数都应配套使用)
wait_event(wait_queue_head_t wq, int condition)//deepsleep状态,能收到信号但不会响应
wait_event_interruptible(wait_queue_head_t wq, int condition) // 可以被kill 掉 top状态为sleep,能收到信号并相应
3)唤醒等待队列中的进程
wake_up(wait_queue_head_t *q)
wake_up_interruptible(wait_queue_head_t *q)
example:
1 #include <linux/init.h> 2 #include <linux/kernel.h> 3 #include <linux/module.h> 4 #include <linux/cdev.h> 5 #include <linux/uaccess.h> 6 #include <linux/fs.h> 7 #include <linux/ioport.h> 8 #include <asm/io.h> 9 #include <linux/device.h> 10 #include <linux/gpio.h> 11 #include <linux/delay.h> 12 #include <linux/interrupt.h> 13 #include <linux/wait.h> 14 15 static wait_queue_head_t key_wait; 16 //等待队列的条件,0-->没有按键按下;1-->有按键按下 17 static int key_flags = 0; 18 19 #define BUF_SIZE 4 20 #define KEY_SUM 8 21 static struct cdev key_dev; 22 static unsigned int key_major = 0; 23 static unsigned int key_minor = 0; 24 static dev_t key_num; 25 static char qbuf[8]={0,0,0,0,0,0,0,0}; 26 27 static struct class * gec210_key_class; 28 static struct device * gec210_key_device; 29 30 struct key_int{ 31 unsigned int const int_num; 32 unsigned int const gpio_num; 33 unsigned long flags; 34 const char int_name[12]; 35 }; 36 37 static const struct key_int gec210_key[KEY_SUM] = { 38 { 39 .int_num = IRQ_EINT(16), 40 .gpio_num = S5PV210_GPH2(0), 41 .flags = IRQF_TRIGGER_FALLING, 42 .int_name = "key2_eint16", 43 }, 44 { 45 .int_num = IRQ_EINT(17), 46 .gpio_num = S5PV210_GPH2(1), 47 .flags = IRQF_TRIGGER_FALLING, 48 .int_name = "key3_eint17", 49 }, 50 { 51 .int_num = IRQ_EINT(18), 52 .gpio_num = S5PV210_GPH2(2), 53 .flags = IRQF_TRIGGER_FALLING, 54 .int_name = "key4_eint18", 55 }, 56 { 57 .int_num = IRQ_EINT(19), 58 .gpio_num = S5PV210_GPH2(3), 59 .flags = IRQF_TRIGGER_FALLING, 60 .int_name = "key5_eint19", 61 }, 62 { 63 .int_num = IRQ_EINT(24), 64 .gpio_num = S5PV210_GPH3(0), 65 .flags = IRQF_TRIGGER_FALLING, 66 .int_name = "key6_eint24", 67 }, 68 { 69 .int_num = IRQ_EINT(25), 70 .gpio_num = S5PV210_GPH3(1), 71 .flags = IRQF_TRIGGER_FALLING, 72 .int_name = "key7_eint25", 73 }, 74 { 75 .int_num = IRQ_EINT(26), 76 .gpio_num = S5PV210_GPH3(2), 77 .flags = IRQF_TRIGGER_FALLING, 78 .int_name = "key8_eint26", 79 }, 80 { 81 .int_num = IRQ_EINT(27), 82 .gpio_num = S5PV210_GPH3(3), 83 .flags = IRQF_TRIGGER_FALLING, 84 .int_name = "key9_eint27", 85 }, 86 }; 87 88 static ssize_t key_read(struct file *file, char __user *buf, size_t size , loff_t *offset) 89 { 90 int i=0; 91 //判断等待条件,key_flags=1,进程向下执行,key_flags=0进程阻塞 92 wait_event_interruptible(key_wait, key_flags); 93 94 if(size != KEY_SUM){ 95 printk("size != KEY_SUM\n"); 96 return -EINVAL; 97 } 98 else if(copy_to_user(buf, qbuf, size)){ 99 printk("copy from user error \n"); 100 return -EFAULT; 101 } 102 for(i=0;i<KEY_SUM;i++) 103 qbuf[i]=0; 104 key_flags = 0; //重置条件 105 106 return size; 107 } 108 static const struct file_operations key_fops = { 109 .owner = THIS_MODULE, 110 .read = key_read, 111 }; 112 113 //八个按键公用的一个中断 114 static irqreturn_t key_interrupt(int irq, void *dev_id) 115 { 116 switch (irq){ 117 case IRQ_EINT(16): 118 qbuf[0]=1; 119 //printk("%s is pressing\n",gec210_key[0].int_name); 120 break; 121 case IRQ_EINT(17): 122 qbuf[1]=1; 123 //printk("%s is pressing\n",gec210_key[1].int_name); 124 break; 125 case IRQ_EINT(18): 126 qbuf[2]=1; 127 //printk("%s is pressing\n",gec210_key[2].int_name); 128 break; 129 case IRQ_EINT(19): 130 qbuf[3]=1; 131 //printk("%s is pressing\n",gec210_key[3].int_name); 132 break; 133 case IRQ_EINT(24): 134 qbuf[4]=1; 135 //printk("%s is pressing\n",gec210_key[4].int_name); 136 break; 137 case IRQ_EINT(25): 138 qbuf[5]=1; 139 //printk("%s is pressing\n",gec210_key[5].int_name); 140 break; 141 case IRQ_EINT(26): 142 qbuf[6]=1; 143 //printk("%s is pressing\n",gec210_key[6].int_name); 144 break; 145 case IRQ_EINT(27): 146 qbuf[7]=1; 147 //intk("%s is pressing\n",gec210_key[7].int_name); 148 break; 149 default: 150 return -ENOIOCTLCMD; 151 } 152 key_flags = 1; 153 wake_up_interruptible(&key_wait); 154 155 return IRQ_HANDLED; 156 } 157 158 static int __init gec210_key_init(void) 159 { 160 int ret, i; 161 if(key_major == 0) //动态分配 162 ret = alloc_chrdev_region(&key_num, key_minor, 1,"key_device"); 163 else{ //静态注册 164 key_num = MKDEV(key_major,key_minor); 165 ret = register_chrdev_region(key_num , 1, "key_device"); 166 } 167 if(ret < 0){ 168 printk("can not register region\n"); 169 return ret; //返回一个负数的错误码 170 } 171 cdev_init(&key_dev, &key_fops); 172 173 ret = cdev_add(&key_dev,key_num, 1); 174 if(ret <0){ 175 printk("cdev_add faikey \n"); 176 goto err_cdev_add; 177 } 178 for(i=0;i<KEY_SUM;i++){ 179 ret = request_irq(gec210_key[i].int_num, key_interrupt, gec210_key[i].flags, 180 gec210_key[i].int_name, NULL); 181 if(ret < 0){ 182 printk("request int failed ,key_name = %s",gec210_key[i].int_name); 183 goto err_request_irq; 184 } 185 //gpio_direction_input(gec210_key[i].gpio_num); 186 } 187 gec210_key_class = class_create(THIS_MODULE, "keys_class"); 188 if(gec210_key_class == NULL){ 189 printk("class create error\n"); 190 ret = -EBUSY; 191 goto err_class_create; 192 } 193 194 gec210_key_device = device_create(gec210_key_class,NULL,key_num,NULL,"key_drv"); 195 if(gec210_key_device == NULL){ 196 printk("device create error\n"); 197 ret = -EBUSY; 198 goto err_device_create; 199 } 200 201 init_waitqueue_head(&key_wait); 202 203 printk("key driver init ok !\n"); 204 return 0; 205 206 err_device_create: 207 class_destroy(gec210_key_class); 208 err_class_create: 209 err_request_irq: 210 while(i--){ 211 free_irq(gec210_key[i].int_num, NULL); 212 } 213 214 cdev_del(&key_dev); 215 err_cdev_add: 216 unregister_chrdev_region(key_num, 1); 217 218 return ret; 219 } 220 221 static void __exit gec210_key_exit(void) 222 { 223 int i=KEY_SUM; 224 unregister_chrdev_region(key_num, 1); 225 cdev_del(&key_dev); 226 while(i--){ 227 free_irq(gec210_key[i].int_num, NULL); 228 } 229 230 device_destroy(gec210_key_class,key_num); 231 class_destroy(gec210_key_class); 232 233 printk("key driver exit ok! \n"); 234 } 235 236 module_init(gec210_key_init); //module的入口 237 module_exit(gec210_key_exit); //module的出口 238 239 MODULE_AUTHOR("fengbaoxiang@gec.com"); 240 MODULE_DESCRIPTION("the driver of keys"); 241 MODULE_LICENSE("GPL"); 242 MODULE_VERSION("V1.0.0");
test.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <fcntl.h> 4 5 char buf[8]={0,0,0,0,0,0,0,0}; 6 7 int main() 8 { 9 int key_fd; 10 int key_ret; 11 int i; 12 key_fd = open("/dev/key_drv", O_RDWR); 13 if(key_fd <0 ){ 14 perror("key open"); 15 return -1; 16 } 17 while(1) 18 { 19 //有按键按下,read就去按键的值;没有按键按下,read就睡眠 20 key_ret = read(key_fd, buf, sizeof(buf)); 21 if(key_ret < 0) 22 { 23 perror("key read"); 24 return -1; 25 } 26 /* key2~key9 */ 27 for(i=0;i<8;i++) 28 { 29 if(buf[i] == 1) 30 printf("key%d is pressing\n", i+2); 31 } 32 } 33 close(key_fd); 34 return 0; 35 }