RAII technology: implement spin locks with guards in Rust, support a certain degree of compile-time concurrency security checks

Summary

This article introduces a spin lock that uses RAII technology. Cooperating with Rust's life cycle and ownership mechanism, it can solve the problems of "forgetting to release the lock", "double release", etc. of the spin lock while reducing the amount of code. "Access without locking" concurrency security issues. And this kind of spin lock can support the check at compile time, any code that does not meet the above security requirements will fail to compile.

 

foreword

For the locks provided by default in many programming languages, locking and releasing locks need to be done manually. Manual locking is understandable (this is no nonsense), but the timing of manual locking is always difficult to control . For example: in the critical section, during the execution process, if the program makes an error and forgets to release the lock during the exception handling process, other processes will not be able to obtain the lock. The traditional approach is to manually find all possible exception handling paths and add lock-release codes. In this way, the problem can be solved, but it is very cumbersome, especially when there are multiple locks.

Moreover, for traditional languages, there may also be the problem of "double release" of locks, that is, after a lock is released by process A, process B locks it, and then, the error code of process A executes the lock release operation , causing process B's lock to be released prematurely. When we discover such a problem, it may not be the first scene, and debugging is very difficult.

Moreover, for most languages, there is no mechanism for the lock and the data it protects to tell the compiler/interpreter: "This lock protects this data object". Therefore, it is difficult for the compiler to detect "access without locking" bugs , and programmers often make this mistake (especially for novice programmers, it is difficult to deal with the lock problem). For such code, the compiler cannot guarantee its concurrency safety.

For Rust, with its life cycle and ownership mechanism, we can combine it with RAII technology to implement a new spin lock, which can easily solve the above problems.

 

Specific code link: spinlock.rs (revision ec53d23e) - OpenGrok cross reference for /DragonOS/kernel/src/libs/spinlock.rs

What is RAII technology?

RAII , full name Resource Acquisition Is Initialization (English: Resource A acquisition  II initialization), it is an  idiom in some object-oriented languages. RAII originated from C++ and is used in many programming languages.

RAII requires that the validity period of a resource is strictly bound to the life cycle of the object holding the resource, that is, the allocation (acquisition) of the resource is completed by the constructor of the object, and the release of the resource is completed by the destructor. Under this requirement, as long as the object can be destructed correctly, there will be no resource leaks.

train of thought

Since Rust implements the life cycle and ownership mechanism at the language level, it can implement RAII very well and can support compile-time checks . Code that does not meet the security requirements will not pass compilation. Our idea is: hand over the ownership of the data to be protected to the corresponding locks for management , and programmers are no longer required to manually manage the relationship between "locks - data protected by locks". That is to say, this spin lock has the ownership of the data to be protected, and other places that need to access the protected data need to borrow this variable from the spin lock to obtain mutable references/immutable references. This access permission is not directly given to the local variable in the function that needs to use the data, but an object called "guard" is responsible for holding the permission . Every time you access data, you go through this guard (note that thanks to Rust's "zero-cost abstraction", there is no runtime overhead ). When the life cycle of the guard variable ends, its destructor performs the action of "release the lock".

When the spin lock lends access to the data it protects, it performs the locking action and then returns a guard. Please note that the guard will only be initialized after "spinlock locked successfully". Therefore, for a spinlock, there is at most 1 guard. And, as long as the life cycle of the guard is not over, we can access the protected data through this guard.

So, let's summarize the way to solve the above problems based on RAII + ownership + life cycle mechanism of spin lock:

  • Forgot to release the lock/when there is an abnormal exit, the lock is not released: once the life cycle of the guard ends, the lock will be released in the destructor.
  • "Double release" problem: All lock release operations can only be performed by the destructor of the guard object. Since there is only one guard object at most at the same time, and as long as the life cycle of the guard object is not over, the lock must be acquired. Thus avoiding the "double free" problem.
  • The problem of "accessing protected data without locking": Since the protected data belongs to the spin lock, it is a private field. Processes can only access protected data through guards. And there is only one way to get the guard: successfully lock it. Therefore, it can solve the problem of "access without locking". Any code that wants to "access without locking" will fail the compiler's inspection.

accomplish

The idea is mentioned above, so let's introduce its implementation in combination with the specific code:

Structure definition

The following figure is the definition of SpinLock and its guards :

spinlock.rs (revision ec53d23e) - OpenGrok cross reference for /DragonOS/kernel/src/libs/spinlock.rs

For SpinLock, it contains two private member variables:

  • lock: This is a RawSpinlock. The specific function is the same as that of spinlocks in other languages. It needs to be locked and released manually. It has the most basic functions of spinlocks. It does not have the feature of concurrent security check at compile time.
  • data: This field is the data protected by the spinlock. When the spin lock is initialized, the data to be protected will be placed in this UnsafeCell. Note that UnsafeCell supports internal mutability, that is, the value of the protected data can be modified.

For the guard SpinLockGuard, it has only one member variable, which is the immutable reference of SpinLock. Moreover, SpinLockGuard has no constructor, it can only be generated after locking through SpinLock's lock() method.

SpinLock implementation

SpinLock has only two member methods: new() and lock(). As shown below:

  

spinlock.rs (revision ec53d23e) - OpenGrok cross reference for /DragonOS/kernel/src/libs/spinlock.rs

  • new() method: Initialize the lock field and put the data into the data field. Please note that since the value passed in is not a reference, the ownership of the value is moved to the data field after the new() function ends . Other parts of the program no longer have ownership of this value. In other external functions, any attempt to access the value will be blocked by the compiler .
  • lock() method: This method first locks the spin lock, and then returns a guard. Note that the lock() function is the only way to get a guard.

At the same time, we implement the Sync Trait for SpinLock, so that the compiler knows that SpinLock is thread-safe and can be shared among several threads. (Of course, we require T to implement Send Trait, because only in this way, it means that it can be sent from one process to another)

spinlock.rs (revision ec53d23e) - OpenGrok cross reference for /DragonOS/kernel/src/libs/spinlock.rs

SpinLockGuard implementation

The implementation of SpinLockGuard is also very simple, we implemented 3 traits for it: Deref, DerefMut, Drop.

spinlock.rs (revision ec53d23e) - OpenGrok cross reference for /DragonOS/kernel/src/libs/spinlock.rs

  • Deref: When we access SpinLockGuard, it is equivalent to accessing variables protected by spin locks (immutable references)
  • DerefMut: When we access SpinLockGuard, it is equivalent to accessing variables protected by spin locks (mutable references)
  • Drop: When the life cycle of SpinLockGuard ends, the lock will be released automatically.

How to use such a spinlock?

Compared with the traditional SpinLock, which needs to repeatedly confirm that the variable is under the protection of the lock, the use of SpinLock is very simple, just do this:

In the above example, we declared a SpinLock and passed in the data to be protected: a Vec array. Then, on line 3, we acquire the lock. In the next few lines, we use this guard to insert data into the Vec. After leaving the inner closure (wrapped by "{}"), in the last line, we can find that the lock is automatically released by printing.

For variables inside the structure, we can use SpinLock for fine-grained locking , that is, use SpinLock to wrap member variables that need to be carefully locked, such as this:

pub struct a {
  pub data: SpinLock<data_struct>,
}

Then, access to the data field of the data_struct type must be locked first, otherwise it cannot be accessed.

Of course, we can also lock the entire structure :

struct MyStruct {
  pub data: data_struct,
}
/// 被全局加锁的结构体
pub struct LockedMyStruct(SpinLock<MyStruct>);

Summarize

The spin lock introduced in this article uses RAII technology, combined with Rust's life cycle and ownership mechanism. The lock is bound to the data it protects, enabling it to support compile-time checking. It reduces the generation of BUG, ​​and also reduces the burden on programmers to manually maintain the "lock-data protected by lock" relationship.

appendix

Please indicate the source of the reprint: RAII technology: implement a spin lock with guards in Rust, support a certain degree of compile-time concurrent security checks – Longjin's blog icon-default.png?t=MBR7https://longjin666.cn/?p=1678

Welcome to pay attention to my public account " Denglong ", let us know more things together~

Guess you like

Origin blog.csdn.net/qq_34026204/article/details/128704279