One article to understand|Kernel sequential locks

The Linux kernel has many lock mechanisms, such as spin locks, read-write locks, semaphores, and RCU locks. This article introduces a lock mechanism that is similar to read-write locks: sequential lock (seqlock).

Sequential locks, like read-write locks, are lock mechanisms for more reading, less writing, and fast processing. The difference between sequential locks and read-write locks is that the read lock of a read-write lock blocks the write lock, but the read lock of a sequential lock does not block the write lock.

Read lock principle

In order to prevent the read lock from blocking the write lock, the read lock will not actually perform the locking operation. So how does the read lock prevent the data from being modified by other processes when reading critical section data?

To solve this problem, sequential locks use a mechanism similar to version numbers: 序号. The serial number is a counter that only increases but does not decrease. It can be seen from the definition of the sequential lock object, as shown in the following code:

typedef struct {
     struct seqcount seqcount; // 序号
     spinlock_t lock;          // 自旋锁,写锁上锁时使用
} seqlock_t;

Before reading the critical section data, you first need to call  read_seqbegin() a function to obtain the read lock. read_seqbegin() The core logic of the function is to read the sequence number of the sequential lock. The code looks like this:

static inline unsigned read_seqbegin(const seqlock_t *sl)
{
    unsigned ret;

repeat:
    // 读取顺序锁的序号
    ret = sl->sequence;

    // 如果序号是单数,需要重新获取
    if (unlikely(ret & 1)) {
        ...
        goto repeat;
    }
    ...
    return ret;
}

As can be seen from the above code, read_seqbegin() the function only obtains the sequence number of the sequential lock and does not perform a locking operation, so the read lock does not block the write lock.

Note: The reason why the serial number needs to be reacquired when it is an odd number will be explained when analyzing the write lock implementation principle.

Since the read lock does not perform a locking operation, what if the data is modified when reading the critical section data? The answer is: when exiting the critical section, compare the sequence number of the current sequence lock with the sequence number read previously. If consistent, it means that the data has not been modified, otherwise it means that the data has been modified. If the data is modified, the data in the critical section needs to be re-read.

You can use  read_seqretry() functions to compare whether the serial numbers are consistent, so the correct usage of read lock is as shown in the following code:

do {
    // 获取顺序锁序号
    unsigned seq = read_seqbegin(&seqlock);
    // 读取临界区数据
    ...
} while (read_seqretry(&seqlock, seq)); // 对比序号是否一致?

read_seqretry() The implementation of the function is very simple and looks like this:

static inline unsigned 
read_seqretry(const seqlock_t *sl, unsigned start)
{
    ...
    return sl->sequence != start;
}

As can be seen from the above code, read_seqretry() the function simply compares whether the current serial number is consistent with the previously read serial number.

Write lock principle

From the above analysis, it can be seen that the read lock determines whether the data has been modified by comparing whether the serial numbers before and after are consistent. So when was the serial number modified? The answer is: when acquiring the write lock.

Obtaining the write lock is  write_seqlock() implemented through a function, and its implementation is relatively simple. The code is as follows:

static inline void write_seqlock(seqlock_t *sl)
{
    spin_lock(&sl->lock);

    sl->sequence++;
    ...
}

write_seqlock() The function first acquires the spin lock (so the write lock and write lock are mutually exclusive), and then adds one to the sequence number. Therefore, before modifying the critical section data, the write lock will first increase the value of the sequence number, which will cause the sequence numbers acquired twice before and after the read lock to be inconsistent. We can illustrate this situation with the following diagram:

seqlock principle

 Information Direct: Linux kernel source code technology learning route + video tutorial kernel source code

Learning Express: Linux Kernel Source Code Memory Tuning File System Process Management Device Driver/Network Protocol Stack

It can be seen that when the sequence number values ​​obtained before and after reading the critical section are inconsistent, it means that the data has been modified, and the modified data needs to be re-read.

Unlocking the write lock is also very simple. The code is as follows:

static inline void write_sequnlock(seqlock_t *sl)
{
  ...
 s->sequence++;
 spin_unlock(&sl->lock);
}

Unlocking also requires adding one to the serial number and then releasing the spin lock.

Since  write_seqlock() functions and  write_sequnlock() functions will add one to the serial number, after unlocking, the value of the serial number must be an even number.

When we analyzed the read lock, we saw that if the serial number is an odd number, the serial number will be reacquired until the serial number is an even number. This is because when the sequence number is odd, it means that the data is being updated. It is meaningless to read the value of the critical section at this time, so you need to wait until the update is completed before reading.

Original author: Things about the Linux kernel

Guess you like

Origin blog.csdn.net/youzhangjing_/article/details/132693265