首页 > 代码库 > 同步和互斥

同步和互斥

一、基本概念

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,#110 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 = http://www.mamicode.com/0;

         int cur_adc_port = 0;

 

mutex_lock(&adc_mutex); //上锁

 

         cur_adc_port = adc_port;

         adc_port = channel;

         adc_value = http://www.mamicode.com/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 }

 

同步和互斥