• xv6---spinlock自旋锁


     Spinlock

    目录

     Spinlock

    自旋锁的结构体

    自旋锁的方法

    initlock-----设置锁的参数,名字

    acquire----循环,直到可以拿到锁。

    release---释放锁

    holding---检查当前的cpu是否拿到了锁

    死锁问题:

    中断问题:


    参考:xv6 Kernel-4 Spinlocks_哔哩哔哩_bilibili

    1. // Mutual exclusion lock.
    2. struct spinlock {
    3. uint locked; // Is the lock held?
    4. // For debugging:
    5. char *name; // Name of lock.
    6. struct cpu *cpu; // The cpu holding the lock.
    7. };
    • 自旋锁的方法

    • initlock-----设置锁的参数,名字

    1. void
    2. initlock(struct spinlock *lk, char *name)
    3. {
    4. lk->name = name;
    5. lk->locked = 0;
    6. lk->cpu = 0;
    7. }
    • acquire----循环,直到可以拿到锁。

    伪代码如下

    1. func:
    2. if (*lock == 0) // spinlock空闲
    3. {
    4. *lock = 1;
    5. } else { //spinlock不空闲,循环等待
    6. goto func;
    7. }
    • 上述的代码必须保证原子性,否则当同一个cpu的两个线程同时满足 *lock == 0,那么两个线程都会拿到spinlock,这肯定是不行的。
    • 在xv6中,原子操作指令就是ampswap指令,对应到c语言就是__sync_lock_test_and_set函数来保证原子性。
    • __sync_synchronize();   避免编译优化改变代码执行顺序。

    1. // Acquire the lock.
    2. // Loops (spins) until the lock is acquired.
    3. void
    4. acquire(struct spinlock *lk)
    5. {
    6. push_off(); // disable interrupts to avoid deadlock.
    7. if(holding(lk))
    8. panic("acquire");
    9. { //执行原子操作,避免出现并发问题
    10. // On RISC-V, sync_lock_test_and_set turns into an atomic swap:
    11. // a5 = 1
    12. // s1 = &lk->locked
    13. // amoswap.w.aq a5, a5, (s1)
    14. while(__sync_lock_test_and_set(&lk->locked, 1) != 0)
    15. ;
    16. }
    17. // Tell the C compiler and the processor to not move loads or stores
    18. // past this point, to ensure that the critical section's memory
    19. // references happen strictly after the lock is acquired.
    20. // On RISC-V, this emits a fence instruction.
    21. __sync_synchronize();
    22. // Record info about lock acquisition for holding() and debugging.
    23. lk->cpu = mycpu();
    24. }
    • release---释放锁

    释放锁:等价于操作lk->locked = 0 ,用__sync_lock_release(&lk->locked);实现。也是原子操作

    1. // Release the lock.
    2. void
    3. release(struct spinlock *lk)
    4. {
    5. if(!holding(lk)) // 正常是当前cpu有spinlock, 所以才不会有panic("release");
    6. panic("release");
    7. lk->cpu = 0; //因为随后会释放锁,所以不妨设为空,表示当前没有cpu拿到锁
    8. // Tell the C compiler and the CPU to not move loads or stores
    9. // past this point, to ensure that all the stores in the critical
    10. // section are visible to other CPUs before the lock is released,
    11. // and that loads in the critical section occur strictly before
    12. // the lock is released.
    13. // On RISC-V, this emits a fence instruction.
    14. __sync_synchronize();
    15. { // amoswap 原子操作
    16. // Release the lock, equivalent to lk->locked = 0.
    17. // This code doesn't use a C assignment, since the C standard
    18. // implies that an assignment might be implemented with
    19. // multiple store instructions.
    20. // On RISC-V, sync_lock_release turns into an atomic swap:
    21. // s1 = &lk->locked
    22. // amoswap.w zero, zero, (s1)
    23. __sync_lock_release(&lk->locked);
    24. }
    25. pop_off();
    26. }
    • holding---检查当前的cpu是否拿到了锁

    1. // Check whether this cpu is holding the lock.
    2. // Interrupts must be off.
    3. int
    4. holding(struct spinlock *lk)
    5. {
    6. int r;
    7. r = (lk->locked && lk->cpu == mycpu()); //获取当前cpu是否拿到了锁
    8. return r;
    9. }

    进程A:键盘打字---产生中断---acquired 等待spinlock----写入数据

    进程B:某个调用拿到了spinlock,  等待键盘打字--写入的数据。   

    ------>产生死锁

    解决方法:acquire()的时候disable interupt   ,  release的时候,enable interupt

    额外的好处:可以让拿到spinlock的线程不会太久。

    所以代码的push_off()和pop_off()中分别会打开/关闭中断来避免死锁

    1. void
    2. push_off(void)
    3. {
    4. ...
    5. intr_off(); // 关闭中断
    6. ...
    7. }
    8. void
    9. pop_off(void)
    10. {
    11. ...
    12. intr_on(); //打开中断
    13. ...
    14. }
    • 中断问题:

    如果此时有三个调用去执行acquire(),其中一个调用拿到了spinlock, diable中断在执行结束后,会在release的时候去enable中断。查看spinlock的acquire实现(如下面的代码)

    可知:如果在release的时候打开了中断,那么其他cpu会立刻拿到spinlock, 且这个时候已经关闭过了中断,然后中断又被打开了,这肯定不行!!!

    1. void
    2. acquire(struct spinlock *lk)
    3. {
    4. push_off(); // disable interrupts to avoid deadlock.
    5. if(holding(lk))
    6. panic("acquire");
    7. // 原子操作---等待直到拿到spinlock
    8. while(__sync_lock_test_and_set(&lk->locked, 1) != 0)
    9. ;
    10. ............
    11. }

    解决方案:加上计数器,实现如下:acquire一次,中断计数就增加(noff变量)

    release一次就减1,直到为0才恢复中断(不一定会打开,需要根据,int old = intr_get()的值判断,old == 1代表acquires spinlock之前中断是打开的,反之用完了锁不打开)。

    1. void
    2. push_off(void)
    3. {
    4. int old = intr_get(); // 调用push_off之前 打开了中断 if old != 0
    5. intr_off();
    6. if(mycpu()->noff == 0) // 第一次调用acquire
    7. mycpu()->intena = old; //intena保存锁之前的中断状态
    8. mycpu()->noff += 1;
    9. }
    10. void
    11. pop_off(void)
    12. {
    13. struct cpu *c = mycpu();
    14. if(intr_get())
    15. panic("pop_off - interruptible");
    16. if(c->noff < 1)
    17. panic("pop_off");
    18. c->noff -= 1;
    19. if(c->noff == 0 && c->intena) //直到aqcuire对应的release次数为0 && 调用push_off之前打开了中断
    20. intr_on(); //打开中断
    21. }

  • 相关阅读:
    面经-框架-Spring refresh 流程
    lua-web-utils和proxy程序示例
    【吴恩达机器学习笔记】十三、异常检测
    Linux自建mysql访问show databases只有information_schema和test两个数据库
    .NET 6上的WebView2体验
    AI绘图—对中文拟合度很高,值得一试
    vscode 设置左边文件栏缩进
    Java:Java 对机器学习和数据科学有好处吗?
    分布式架构设计思路和要点
    Day1_9 Java学习之DQL语言与完整性约束
  • 原文地址:https://blog.csdn.net/m0_37844072/article/details/127681005