• 同步和互斥


    一、基本概念

    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 ", __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
    ");
     96         return -EINVAL;
     97     }
     98     else if(copy_to_user(buf, qbuf, size)){
     99         printk("copy from user error 
    ");
    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
    ",gec210_key[0].int_name);
    120         break;
    121     case IRQ_EINT(17):
    122         qbuf[1]=1;
    123         //printk("%s is pressing
    ",gec210_key[1].int_name);
    124         break;
    125     case IRQ_EINT(18):
    126         qbuf[2]=1;
    127         //printk("%s is pressing
    ",gec210_key[2].int_name);
    128         break;
    129     case IRQ_EINT(19):
    130         qbuf[3]=1;
    131         //printk("%s is pressing
    ",gec210_key[3].int_name);
    132         break;
    133         case IRQ_EINT(24):
    134         qbuf[4]=1;
    135         //printk("%s is pressing
    ",gec210_key[4].int_name);
    136         break;
    137     case IRQ_EINT(25):
    138         qbuf[5]=1;
    139         //printk("%s is pressing
    ",gec210_key[5].int_name);
    140         break;    
    141     case IRQ_EINT(26):
    142         qbuf[6]=1;
    143         //printk("%s is pressing
    ",gec210_key[6].int_name);
    144         break;
    145     case IRQ_EINT(27):
    146         qbuf[7]=1;
    147         //intk("%s is pressing
    ",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
    ");
    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 
    ");
    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
    ");
    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
    ");
    197         ret = -EBUSY;
    198         goto err_device_create;
    199     }
    200 
    201     init_waitqueue_head(&key_wait);
    202     
    203     printk("key driver init ok !
    ");
    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! 
    ");    
    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
    ", i+2);
    31         }
    32     }
    33     close(key_fd);
    34     return 0;    
    35 }
  • 相关阅读:
    Android中TextView设置下划线
    BottomSheetDialogFragment 如何设置高度和禁止滑动(Kotlin)
    [iOS]使用GCD创建定时器
    [iOS]定时器NSTimer、CADisplayLink的内存管理
    [iOS]dispatch_after()中self和weakself的使用
    [Flutter]在Mac上安装Flutter运行环境
    wx小程序反编译为js代码
    Android | 玩转AppBarLayout,设置scrollFlags滑动属性详解
    玩转微信 | 炫酷的聊天满屏掉爱心系列,赶紧收藏
    Android使用更简单的方式实现滑块拼图验证码功能
  • 原文地址:https://www.cnblogs.com/defen/p/5935287.html
Copyright © 2020-2023  润新知