内核中读写信号量(rwsem)实现的关键注释

本文详细解析了Linux内核3.10.0-327.el7版本中的读写锁机制,包括读锁与写锁的具体实现过程,特别关注了在尝试获取锁失败后如何进入慢速路径进行处理,以及不同情况下锁的分配策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文代码摘自于linux-3.10.0-327.el7版本内核:

读锁实现注释:
读锁在获取时主入口时down_read,在这个函数中会先尝试__down_read_trylock,如果__down_read_trylock失败,就会执行__down_read,在这个函数中实际运行的是慢速路径,它的逻辑实现在如下:

struct rw_semaphore __sched *rwsem_down_read_failed(struct rw_semaphore *sem)
{
    //1.默认是减去这个值,在运行到这里之前,会先try,在try时增加了read标记,但是获取锁失败了,所以要undo这个操作
    long count, adjustment = -RWSEM_ACTIVE_READ_BIAS;
    struct rwsem_waiter waiter;
    struct task_struct *tsk = current;

    /* set up my own style of waitqueue */
    waiter.task = tsk;
    waiter.type = RWSEM_WAITING_FOR_READ;
    get_task_struct(tsk);

    raw_spin_lock_irq(&sem->wait_lock);
    //2.如果list是空的,那么我们将要添加一个waiter,因此需要添加wait flag到count中.
    if (list_empty(&sem->wait_list))
        adjustment += RWSEM_WAITING_BIAS;
    list_add_tail(&waiter.list, &sem->wait_list);

    /* we're now waiting on the lock, but no longer actively locking */

    //3.此时我们已经添加到了wait_list,因此需要把前面获取到的adjustment更新到count标记
    count = rwsem_atomic_update(adjustment, sem);

    /* If there are no active locks, wake the front queued process(es).
     *
     * If there are no writers and we are first in the queue,
     * wake our own waiter to join the existing active readers !
     */
    //4.唤醒wait_list中前面的进程,可能为一个写者,或者多个读者(可能包含自己)。
    //如果唤醒本进程,会把对应的读waiter中的task设置为NULL
    if (count == RWSEM_WAITING_BIAS ||
        (count > RWSEM_WAITING_BIAS &&
         adjustment != -RWSEM_ACTIVE_READ_BIAS))
        sem = __rwsem_do_wake(sem, RWSEM_WAKE_ANY);

    raw_spin_unlock_irq(&sem->wait_lock);

    /* wait to be given the lock */
    while (true) {
        set_task_state(tsk, TASK_UNINTERRUPTIBLE);
        //判断task为NULL,说明不用继续等待了,可以持锁了.
        if (!waiter.task)
            break;
        schedule();
    }   

    tsk->state = TASK_RUNNING;

    return sem;
}

写锁实现注释:
写锁在获取时主入口时down_write,在这个函数中会先尝试__down_write_trylock,如果__down_write_trylock失败,就会执行__down_write,在这个函数中实际运行的是慢速路径,它的逻辑实现在如下:

struct rw_semaphore __sched *rwsem_down_write_failed(struct rw_semaphore *sem)
{
    //1.默认是减去这个值,在try时增加了write标记,但是获取锁失败了,所以要undo这个操作
    long count, adjustment = -RWSEM_ACTIVE_WRITE_BIAS;
    struct rwsem_waiter waiter;
    struct task_struct *tsk = current;

    /* set up my own style of waitqueue */
    waiter.task = tsk;
    waiter.type = RWSEM_WAITING_FOR_WRITE;

    raw_spin_lock_irq(&sem->wait_lock);
    //2.如果list是空的,那么我们将要添加一个waiter,因此需要添加wait flag到count中.
    if (list_empty(&sem->wait_list))
        adjustment += RWSEM_WAITING_BIAS;
    list_add_tail(&waiter.list, &sem->wait_list);

    /* we're now waiting on the lock, but no longer actively locking */

    //3.此时我们已经添加到了wait_list,因此需要把前面获取到的adjustment更新到count标记
    count = rwsem_atomic_update(adjustment, sem);

    /* If there were already threads queued before us and there are no
     * active writers, the lock must be read owned; so we try to wake
     * any read locks that were queued ahead of us. */
    //判断如果wait_list中已经存在等待的读者时,
    //为了加快读者执行完成临界区执行,需要先把他们唤醒
    //这样当前写者才能更快的获取到写锁
    if (count > RWSEM_WAITING_BIAS &&
        adjustment == -RWSEM_ACTIVE_WRITE_BIAS)
        sem = __rwsem_do_wake(sem, RWSEM_WAKE_READERS);

    /* wait until we successfully acquire the lock */
    set_task_state(tsk, TASK_UNINTERRUPTIBLE);
    while (true) {
        if (!(count & RWSEM_ACTIVE_MASK)) {
            /* Try acquiring the write lock. */
            //4.没有active时,尝试获取锁,添加write active标记
            count = RWSEM_ACTIVE_WRITE_BIAS;
            if (!list_is_singular(&sem->wait_list))
                count += RWSEM_WAITING_BIAS;

            if (sem->count == RWSEM_WAITING_BIAS &&
                cmpxchg(&sem->count, RWSEM_WAITING_BIAS, count) ==
                            RWSEM_WAITING_BIAS)
                break;
        }

        raw_spin_unlock_irq(&sem->wait_lock);

        /* Block until there are no active lockers. */
        do {
            schedule();
            set_task_state(tsk, TASK_UNINTERRUPTIBLE);
        } while ((count = sem->count) & RWSEM_ACTIVE_MASK);

        raw_spin_lock_irq(&sem->wait_lock);
    }

    list_del(&waiter.list);  
    raw_spin_unlock_irq(&sem->wait_lock);
    tsk->state = TASK_RUNNING;

    return sem;
}

读写锁的实现总结:

  • 当读者持有锁时,且没有写者等待时,后续的读者可以直接获取锁成功;
  • 当有一个写者等待时,后续的写者和读者都不能获取锁;
  • 当写者持有锁时,后续的写者和读者都不能获取锁;
  • 等待wait_list上,第一个都是写者;
  • 当写者释放锁时,等待wait_list上的第一个将会获取锁(不管其是读者还是写者);

参考:http://linux.laoqinren.net/kernel/rw-semaphore/

### Linux内核读写锁的实现与使用 在Linux内核中,读写锁(read-write lock)是一种同步机制,用于保护共享资源。它允许多个读者同时访问资源,但只允许一个写者独占访问资源。这种机制可以显著提高并发性能,尤其是在读操作远多于写操作的情况下。 #### 1. 读写锁的基本概念 读写锁的核心思想是区分读和写两种访问模式。读锁允许多个线程或进程同时持有锁进行读操作,而写锁则是排他性的,只有单个线程或进程可以持有写锁[^5]。这种方式可以减少竞争,从而提高系统性能。 #### 2. 内核中的读写实现 Linux内核提供了`rwlock_t`类型来定义读写锁,并通过以下函数对其进行操作: - **初始化读写锁**: ```c void rwlock_init(rwlock_t *lock); ``` 这个函数用于初始化一个读写锁实例。 - **获取读锁**: ```c void read_lock(rwlock_t *lock); ``` 获取读锁时,允许其他线程也获取读锁,但阻止任何线程获取写锁。 - **释放读锁**: ```c void read_unlock(rwlock_t *lock); ``` - **获取写锁**: ```c void write_lock(rwlock_t *lock); ``` 获取写锁时,阻止其他线程获取任何类型的锁。 - **释放写锁**: ```c void write_unlock(rwlock_t *lock); ``` 这些函数确保了读写锁的正确性和安全性。此外,内核还提供了带中断屏蔽功能的版本,例如`write_lock_irqsave`和`read_lock_bh`等,以适应不同的应用场景[^6]。 #### 3. 使用场景 读写锁通常用于需要频繁读取、偶尔写入的场景。例如,文件系统的元数据缓存、网络协议栈中的连接跟踪表等。以下是一个简单的示例代码: ```c #include <linux/rwsem.h> rwlock_t my_rwlock; void init_my_rwlock() { rwlock_init(&my_rwlock); } void reader_function() { read_lock(&my_rwlock); // 获取读锁 // 执行读操作 read_unlock(&my_rwlock); // 释放读锁 } void writer_function() { write_lock(&my_rwlock); // 获取写锁 // 执行写操作 write_unlock(&my_rwlock); // 释放写锁 } ``` #### 4. 注意事项 尽管读写锁能够提高并发性能,但在某些情况下也可能导致问题: - **死锁风险**:如果多个线程以不一致的顺序获取锁,可能会导致死锁。 - **性能瓶颈**:当写操作频繁时,读写锁可能成为性能瓶颈,因为所有写操作都需要等待之前的读操作完成。 因此,在设计时需要权衡读写操作的比例以及锁的竞争情况。 ### 总结 Linux内核中的读写锁通过区分读和写操作,提供了一种高效的同步机制。其核心在于允许多个读者同时访问资源,而写者则独占资源。这种机制适用于读操作远多于写操作的场景,但在使用时需要注意避免死锁和性能瓶颈。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值