同步和互斥

一、基本概念

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 }

 

posted @ 2016-10-07 00:12  高傲的monkey  阅读(1978)  评论(0编辑  收藏  举报