操作系统锁的实现原理

    所谓的锁,说白了就是内存中的一个整型数,拥有两种状态:空闲状态和上锁状态。加锁时,判断锁是否空闲,如果空闲,修改为上锁状态,返回成功;如果已经上锁,则返回失败。解锁时,则把锁状态修改为空闲状态。

    假如我们把加锁过程用如下伪码表示:

  1. read lock;
  2. 判断lock状态;
  3. 如果已经加锁,失败返回;
  4. 把锁状态设置为上锁;
  5. 返回成功。

    假设上述操作每一步都是一个原子操作,那么在什么情况下多个线程会同时获得锁呢?

  • 中断:假设线程A执行完第一步,发生中断,中断返回后,OS调度线程B,线程B也来加锁并且加锁成功,这时OS调度线程A执行,线程从第二步开始执行,也加锁成功。
  • 多核:当然了,想想上面举的例子,描述的就是两个核同时获取到锁的情况。

    先考虑单核场景:

  1. 既然只有中断才能把上锁过程打断,造成多线程操作失败。那就先关中断,在加锁操作完成后再开中断。
  2. 使用test and set指令让加锁成为原子操作。

    通过上面的手段,单核环境下,锁的实现问题得到了圆满的解决。在多核场景下硬件执行时还是得从内存中读取lock,判断并设置状态到内存,这个过程不是原子的。也就是说两个核会并行操作内存而且从操作内存这个调度来看“test and set”不是原子的,需要先读内存然后再写内存,如果我们保证这个内存操作是原子的,就能保证锁的正确性了。确实,硬件提供了锁内存总线的机制,我们在锁内存总线的状态下执行test and set操作,就能保证同时只有一个核来test and set,从而避免了多核下发生的问题。

    硬件实现手段:

    前面提到,cpu会通过对总线加锁的手段来解决多核同时获取锁的情况,它到时是怎么实现的呢? 在cpu芯片上有一个HLOCK Pin,可以通过发送指令来操作,将#HLOCK Pin电位拉低,并持续到这条指令执行完毕,从而将总线锁住,这样同一总线上的其他CPU就不能通过总线来访问内存了。最开始这些功能是用来测试cpu的,后来被操作系统实现而封装成各种功能:关键代码段,信号量等。在加锁的代码编译成汇编后,会有个lock指令前缀,lock会使得紧跟在其后的指令变成atomic instruction,暂时的锁一下总线,指令执行完,总线就解锁了。

    总结一下,在硬件层面,CPU提供了原子操作、关中断、锁内存总线的机制;OS基于这几个CPU硬件机制,就能够实现锁;再基于锁,就能够实现各种各样的同步机制(信号量、消息、Barrier等等等等)。

发布了33 篇原创文章 · 获赞 6 · 访问量 552

猜你喜欢

转载自blog.csdn.net/weixin_43519984/article/details/105093934
今日推荐