zoukankan      html  css  js  c++  java
  • 锁与RCU数据共享机制

    RCU的原理

    在很多系统(如操作系统,数据中心)中,数据一致性访问是一个非常重要的部分,通常我们可以采用锁机制实现数据的一致性访问。例如,semaphore、spinlock、rwlock机 制,在访问共享数据时,首先访问锁资源,在获取锁资源的前提下才能实现数据的访问。这种原理很简单,根本的思想就是在访问临界资源时,首先访问一个全局的变量(锁),通过全局变量的状态来控制线程对临界资源的访问。但是,这种思想是需要硬件支持的,硬件需要配合实现全局变量(锁)的读-修改-写,现代CPU都会提供这样的原子化指令。采用锁机制实现数据访问的一致性存在如下两个问题:

    1.  效率问题。锁机制的实现需要对内存的原子化访问,这种访问操作会破坏流水线操作,降低了流水线效率,这是影响性能的一个因素。另外,在采用读写锁机制的情况下,写锁是排他锁,无法实现写锁与读锁的并发操作,在某些应用下回降低性能。
    2. 扩展性问题。例如,当系统中CPU数量增多的时候,采用锁机制实现数据的同步访问效率偏低。并且随着CPU数量的增多,效率降低,由此可见锁机制实现的数据一致性访问扩展性差。

    下面以 Linux-2.6 内核中对数据一致性的实现思想来阐述解决方案:

    为了解决上述问题,Linux中引进了RCU机制。该机制在多CPU的平台上比较适用,对于读多写少的应用尤其适用。RCU的思路实际上很简单,下面对其进行描述:

    1. 对于读操作,可以直接对共享资源进行访问,但是前提是需要CPU支持访存操作的原子化,现代CPU对这一点都做了保证。但是RCU的读操作上下文是不可抢占的(这一点在下面解释),所以读访问共享资源时可以采用read_rcu_lock(),该函数的工作是停止抢占。
    2. 对于写操作,其需要将原来的老数据作一次备份(copy),然后对备份数据进行修改,修改完毕之后再用新数据更新老数据,更新老数据时采用了rcu_assign_pointer()宏,在该函数中首先屏障一下memory,然后修改老数据。这个操作完成之后,需要进行老数据资源的回收。操作线程向系统注册回收方法,等待回收。采用数据备份的方法可以实现读者与写者之间的并发操作,但是不能解决多个写着之间的同步,所以当存在多个写者时,需要通过锁机制对其进行互斥,也就是在同一时刻只能存在一个写者。
    3. 在RCU机制中存在一个垃圾回收的daemon,当共享资源被update之后,可以采用该daemon实现老数据资源的回收。回收时间点就是在update之前的所有的读者全部退出。由此可见写者在update之后是需要睡眠等待的,需要等待读者完成操作,如果在这个时刻读者被抢占或者睡眠,那么很可能会导致系统死锁。因为此时写者在等待读者,读者被抢占或者睡眠,如果正在运行的线程需要访问读者和写者已经占用的资源,那么死锁的条件就很有可能形成了。

    RCU(Read-Copy Update),顾名思义就是读-拷贝修改,它是基于其原理命名的。对于被RCU保护的共享数据结构,读者不需要获得任何锁就可以访问它,但写者在访问它 时首先拷贝一个副本,然后对副本进行修改,最后使用一个回调(callback)机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据。这个时机就是所有引用该数据的CPU都退出对共享数据的操作。

    因此RCU实际上是一种改进的rwlock,读者几乎没有什么同步开销,它不需要锁,不使用原子指令,而且在除alpha的所有架构上也不需要内存 栅(Memory Barrier),因此不会导致锁竞争,内存延迟以及流水线停滞。不需要锁也使得使用更容易,因为死锁问题就不需要考虑了。写者的同步开销比较大,它需要 延迟数据结构的释放,复制被修改的数据结构,它也必须使用某种锁机制同步并行的其它写者的修改操作。读者必须提供一个信号给写者以便写者能够确定数据可以 被安全地释放或修改的时机。有一个专门的垃圾收集器来探测读者的信号,一旦所有的读者都已经发送信号告知它们都不在使用被RCU保护的数据结构,垃圾收集 器就调用回调函数完成最后的数据释放或修改操作。 RCU与rwlock的不同之处是:它既允许多个读者同时访问被保护的数据,又允许多个读者和多个写者同时访问被保护的数据(注意:是否可以有多个写者并 行访问取决于写者之间使用的同步机制),读者没有任何同步开销,而写者的同步开销则取决于使用的写者间同步机制。但RCU不能替代rwlock,因为如果 写比较多时,对读者的性能提高不能弥补写者导致的损失。

    读者在访问被RCU保护的共享数据期间不能被阻塞,这是RCU机制得以实现的一个基本前提,也就说当读者在引用被RCU保护的共享数据期间,读者所 在的CPU不能发生上下文切换,spinlock和rwlock都需要这样的前提。写者在访问被RCU保护的共享数据时不需要和读者竞争任何锁,只有在有 多于一个写者的情况下需要获得某种锁以与其他写者同步。写者修改数据前首先拷贝一个被修改元素的副本,然后在副本上进行修改,修改完毕后它向垃圾回收器注 册一个回调函数以便在适当的时机执行真正的修改操作。等待适当时机的这一时期称为grace period,而CPU发生了上下文切换称为经历一个quiescent state,grace period就是所有CPU都经历一次quiescent state所需要的等待的时间。垃圾收集器就是在grace period之后调用写者注册的回调函数来完成真正的数据修改或数据释放操作的。

    以下以链表元素删除为例详细说明这一过程:

    写者要从链表中删除元素 B,它首先遍历该链表得到指向元素 B 的指针,然后修改元素 B 的前一个元素的 next 指针指向元素 B 的 next 指针指向的元素C,修改元素 B 的 next 指针指向的元素 C 的 prep 指针指向元素 B 的 prep指针指向的元素 A,在这期间可能有读者访问该链表,修改指针指向的操作是原子的,所以不需要同步,而元素 B 的指针并没有去修改,因为读者可能正在使用 B 元素来得到下一个或前一个元素。写者完成这些操作后注册一个回调函数以便在 grace period 之后删除元素 B,然后就认为已经完成删除操作。垃圾收集器在检测到所有的CPU不在引用该链表后,即所有的 CPU 已经经历了 quiescent state,grace period 已经过去后,就调用刚才写者注册的回调函数删除了元素 B。

    RCU 实现机制

    按照前面所讲原理,对于读者,RCU 仅需要抢占失效,因此获得读锁和释放读锁分别定义为:

    #define rcu_read_lock()         preempt_disable()
    #define rcu_read_unlock() preempt_enable()

    它们有一个变种:

    #define rcu_read_lock_bh()      local_bh_disable()
    #define rcu_read_unlock_bh() local_bh_enable()

    这个变种只在修改是通过 call_rcu_bh 进行的情况下使用,因为 call_rcu_bh 将把 softirq 的执行完毕也认为是一个 quiescent state,因此如果修改是通过 call_rcu_bh 进行的,在进程上下文的读端临界区必须使用这一变种。

    每一个 CPU 维护两个数据结构rcu_data,rcu_bh_data,它们用于保存回调函数,函数call_rcu和函数call_rcu_bh用户注册回调函 数,前者把回调函数注册到rcu_data,而后者则把回调函数注册到rcu_bh_data,在每一个数据结构上,回调函数被组成一个链表,先注册的排 在前头,后注册的排在末尾。

    当在CPU上发生进程切换时,函数rcu_qsctr_inc将被调用以标记该CPU已经经历了一个quiescent state。该函数也会被时钟中断触发调用。

    时钟中断触发垃圾收集器运行,它会检查:

    1. 否在该CPU上有需要处理的回调函数并且已经经过一个grace period;
    2. 否没有需要处理的回调函数但有注册的回调函数;
    3. 否该CPU已经完成回调函数的处理;
    4. 否该CPU正在等待一个quiescent state的到来;

    如果以上四个条件只要有一个满足,它就调用函数rcu_check_callbacks。

    函数rcu_check_callbacks首先检查该CPU是否经历了一个quiescent state,如果:

    1. 当前进程运行在用户态;

    2. 当前进程为idle且当前不处在运行softirq状态,也不处在运行IRQ处理函数的状态;

    那么,该CPU已经经历了一个quiescent state,因此通过调用函数rcu_qsctr_inc标记该CPU的数据结构rcu_data和rcu_bh_data的标记字段 passed_quiesc,以记录该CPU已经经历一个quiescent state。

    否则,如果当前不处在运行softirq状态,那么,只标记该CPU的数据结构rcu_bh_data的标记字段passed_quiesc,以记录该CPU已经经历一个quiescent state。注意,该标记只对rcu_bh_data有效。

    然后,函数rcu_check_callbacks将调用tasklet_schedule,它将调度为该CPU设置的tasklet rcu_tasklet,每一个CPU都有一个对应的rcu_tasklet。

    在时钟中断返回后,rcu_tasklet将在softirq上下文被运行。

    rcu_tasklet将运行函数rcu_process_callbacks,函数rcu_process_callbacks可能做以下事情:

    1. 开始一个新的grace period;这通过调用函数rcu_start_batch实现。
    2.  运行需要处理的回调函数;这通过调用函数rcu_do_batch实现。
    3.  检查该CPU是否经历一个quiescent state;这通过函数rcu_check_quiescent_state实现

    如果还没有开始grace period,就调用rcu_start_batch开始新的grace period。调用函数rcu_check_quiescent_state检查该CPU是否经历了一个quiescent state,如果是并且是最后一个经历quiescent state的CPU,那么就结束grace period,并开始新的grace period。如果有完成的grace period,那么就调用rcu_do_batch运行所有需要处理的回调函数。函数rcu_process_callbacks将对该CPU的两个数据 结构rcu_data和rcu_bh_data执行上述操作。

    RCU API

    rcu_read_lock()

    读者在读取由RCU保护的共享数据时使用该函数标记它进入读端临界区。

    rcu_read_unlock()

    该函数与rcu_read_lock配对使用,用以标记读者退出读端临界区。夹在这两个函数之间的代码区称为"读端临界区"(read-side critical section)。读端临界区可以嵌套,如下图,临界区2被嵌套在临界区1内。

    synchronize_rcu()

    该函数由RCU写端调用,它将阻塞写者,直到经过grace period后,即所有的读者已经完成读端临界区,写者才可以继续下一步操作。如果有多个RCU写端调用该函数,他们将在一个grace period之后全部被唤醒。注意,该函数在2.6.11及以前的2.6内核版本中为synchronize_kernel,只是在2.6.12才更名为 synchronize_rcu,但在2.6.12中也提供了synchronize_kernel和一个新的函数synchronize_sched, 因为以前有很多内核开发者使用synchronize_kernel用于等待所有CPU都退出不可抢占区,而在RCU设计时该函数只是用于等待所有CPU 都退出读端临界区,它可能会随着RCU实现的修改而发生语意变化,因此为了预先防止这种情况发生,在新的修改中增加了专门的用于其它内核用户的 synchronize_sched函数和只用于RCU使用的synchronize_rcu,现在建议非RCU内核代码部分不使用 synchronize_kernel而使用synchronize_sched,RCU代码部分则使用 synchronize_rcu,synchronize_kernel之所以存在是为了保证代码兼容性。

    synchronize_kernel()

    其他非RCU的内核代码使用该函数来等待所有CPU处在可抢占状态,目前功能等同于synchronize_rcu,但现在已经不建议使用,而使用synchronize_sched。

    synchronize_sched()

    该函数用于等待所有CPU都处在可抢占状态,它能保证正在运行的中断处理函数处理完毕,但不能保证正在运行的softirq处理完毕。注意,synchronize_rcu只保证所有CPU都处理完正在运行的读端临界区。 注:在2.6.12内核中,synchronize_kernel和synchronize_sched都实际使用synchronize_rcu,因此当前它们的功能实际是完全等同的,但是将来将可能有大的变化,因此务必根据需求选择恰当的函数。

    void fastcall call_rcu(struct rcu_head *head,
    void (*func)(struct rcu_head *rcu))
    struct rcu_head {
    struct rcu_head *next;
    void (*func)(struct rcu_head *head);
    };

    函数 call_rcu 也由 RCU 写端调用,它不会使写者阻塞,因而可以在中断上下文或 softirq 使用,而 synchronize_rcu、synchronize_kernel 和synchronize_shced 只能在进程上下文使用。该函数将把函数 func 挂接到 RCU回调函数链上,然后立即返回。一旦所有的 CPU 都已经完成端临界区操作,该函数将被调用来释放删除的将绝不在被应用的数据。参数 head 用于记录回调函数 func,一般该结构会作为被 RCU 保护的数据结构的一个字段,以便省去单独为该结构分配内存的操作。需要指出的是,函数 synchronize_rcu 的实现实际上使用函数call_rcu。

    void fastcall call_rcu_bh(struct rcu_head *head,
                                    void (*func)(struct rcu_head *rcu))
    

    函数call_ruc_bh功能几乎与call_rcu完全相同,唯一差别就是它把softirq的完成也当作经历一个quiescent state,因此如果写端使用了该函数,在进程上下文的读端必须使用rcu_read_lock_bh。

    #define rcu_dereference(p)     ({ \
                                    typeof(p) _________p1 = p; \
                                    smp_read_barrier_depends(); \
                                    (_________p1); \
                                    })
    

    该宏用于在RCU读端临界区获得一个RCU保护的指针,该指针可以在以后安全地引用,内存栅只在alpha架构上才使用。

    除了这些API,RCU还增加了链表操作的RCU版本,因为对于RCU,对共享数据的操作必须保证能够被没有使用同步机制的读者看到,所以内存栅是非常必要的。

    static inline void list_add_rcu(struct list_head *new, struct list_head *head) 该函数把链表项new插入到RCU保护的链表head的开头。使用内存栅保证了在引用这个新插入的链表项之前,新链表项的链接指针的修改对所有读者是可见的。

    static inline void list_add_tail_rcu(struct list_head *new,
                                            struct list_head *head)
    

    该函数类似于list_add_rcu,它将把新的链表项new添加到被RCU保护的链表的末尾。

    static inline void list_del_rcu(struct list_head *entry)
    

    该函数从RCU保护的链表中移走指定的链表项entry,并且把entry的prev指针设置为LIST_POISON2,但是并没有把entry的next指针设置为LIST_POISON1,因为该指针可能仍然在被读者用于便利该链表。

    static inline void list_replace_rcu(struct list_head *old, struct list_head *new)
    

    该函数是RCU新添加的函数,并不存在非RCU版本。它使用新的链表项new取代旧的链表项old,内存栅保证在引用新的链表项之前,它的链接指针的修正对所有读者可见。

    list_for_each_rcu(pos, head)
    

    该宏用于遍历由RCU保护的链表head,只要在读端临界区使用该函数,它就可以安全地和其它_rcu链表操作函数(如list_add_rcu)并发运行。

    list_for_each_safe_rcu(pos, n, head)
    

    该宏类似于list_for_each_rcu,但不同之处在于它允许安全地删除当前链表项pos。

    list_for_each_entry_rcu(pos, head, member)
    

    该宏类似于list_for_each_rcu,不同之处在于它用于遍历指定类型的数据结构链表,当前链表项pos为一包含struct list_head结构的特定的数据结构。

    list_for_each_continue_rcu(pos, head)
    

    该宏用于在退出点之后继续遍历由RCU保护的链表head。

    static inline void hlist_del_rcu(struct hlist_node *n)
    

    它从由RCU保护的哈希链表中移走链表项n,并设置n的ppre指针为LIST_POISON2,但并没有设置next为LIST_POISON1,因为该指针可能被读者使用用于遍利链表。

    static inline void hlist_add_head_rcu(struct hlist_node *n,
                                            struct hlist_head *h)
    

    该函数用于把链表项n插入到被RCU保护的哈希链表的开头,但同时允许读者对该哈希链表的遍历。内存栅确保在引用新链表项之前,它的指针修正对所有读者可见。

    hlist_for_each_rcu(pos, head)
    

    该宏用于遍历由RCU保护的哈希链表head,只要在读端临界区使用该函数,它就可以安全地和其它_rcu哈希链表操作函数(如hlist_add_rcu)并发运行。

    hlist_for_each_entry_rcu(tpos, pos, head, member)
    

    类似于hlist_for_each_rcu,不同之处在于它用于遍历指定类型的数据结构哈希链表,当前链表项pos为一包含struct list_head结构的特定的数据结构。

    应用

    下面部分将就 RCU 的几种典型应用情况详细讲解。

    1.只有增加和删除的链表操作

    在这种应用情况下,绝大部分是对链表的遍历,即读操作,而很少出现的写操作只有增加或删除链表项,并没有对链表项的修改操作,这种情况使用RCU非 常容易,从rwlock转换成RCU非常自然。路由表的维护就是这种情况的典型应用,对路由表的操作,绝大部分是路由表查询,而对路由表的写操作也仅仅是 增加或删除,因此使用RCU替换原来的rwlock顺理成章。系统调用审计也是这样的情况。

    这是一段使用rwlock的系统调用审计部分的读端代码:

           static enum audit_state audit_filter_task(struct task_struct *tsk)
            {
                    struct audit_entry *e;
                    enum audit_state   state;
                    read_lock(&auditsc_lock);
                    /* Note: audit_netlink_sem held by caller. */
                    list_for_each_entry(e, &audit_tsklist, list) {
                            if (audit_filter_rules(tsk, &e->rule, NULL, &state)) {
                                    read_unlock(&auditsc_lock);
                                    return state;
                            }
                    }
                    read_unlock(&auditsc_lock);
                    return AUDIT_BUILD_CONTEXT;
            }
            


    使用RCU后将变成:

           static enum audit_state audit_filter_task(struct task_struct *tsk)
            {
                    struct audit_entry *e;
                    enum audit_state   state;
                    rcu_read_lock();
                    /* Note: audit_netlink_sem held by caller. */
                    list_for_each_entry_rcu(e, &audit_tsklist, list) {
                            if (audit_filter_rules(tsk, &e->rule, NULL, &state)) {
                                    rcu_read_unlock();
                                    return state;
                            }
                    }
                    rcu_read_unlock();
                    return AUDIT_BUILD_CONTEXT;
            }
            


    这种转换非常直接,使用rcu_read_lock和rcu_read_unlock分别替换read_lock和read_unlock,链表遍历函数使用_rcu版本替换就可以了。

    使用rwlock的写端代码:

           static inline int audit_del_rule(struct audit_rule *rule,
                                             struct list_head *list)
            {
                    struct audit_entry  *e;
                    write_lock(&auditsc_lock);
                    list_for_each_entry(e, list, list) {
                            if (!audit_compare_rule(rule, &e->rule)) {
                                    list_del(&e->list);
                                    write_unlock(&auditsc_lock);
                                    return 0;
                            }
                    }
                    write_unlock(&auditsc_lock);
                    return -EFAULT;         /* No matching rule */
            }
            static inline int audit_add_rule(struct audit_entry *entry,
                                             struct list_head *list)
            {
                    write_lock(&auditsc_lock);
                    if (entry->rule.flags & AUDIT_PREPEND) {
                            entry->rule.flags &= ~AUDIT_PREPEND;
                            list_add(&entry->list, list);
                    } else {
                            list_add_tail(&entry->list, list);
                    }
                    write_unlock(&auditsc_lock);
                    return 0;
            }
            


    使用RCU后写端代码变成为:

           static inline int audit_del_rule(struct audit_rule *rule,
                                             struct list_head *list)
            {
                    struct audit_entry  *e;
                    /* Do not use the _rcu iterator here, since this is the only
                     * deletion routine. */
                    list_for_each_entry(e, list, list) {
                            if (!audit_compare_rule(rule, &e->rule)) {
                                    list_del_rcu(&e->list);
                                    call_rcu(&e->rcu, audit_free_rule, e);
                                    return 0;
                            }
                    }
                    return -EFAULT;         /* No matching rule */
            }
            static inline int audit_add_rule(struct audit_entry *entry,
                                             struct list_head *list)
            {
                    if (entry->rule.flags & AUDIT_PREPEND) {
                            entry->rule.flags &= ~AUDIT_PREPEND;
                            list_add_rcu(&entry->list, list);
                    } else {
                            list_add_tail_rcu(&entry->list, list);
                    }
                    return 0;
            }
            


    对于链表删除操作,list_del替换为list_del_rcu和call_rcu,这是因为被删除的链表项可能还在被别的读者引用,所以不能 立即删除,必须等到所有读者经历一个quiescent state才可以删除。另外,list_for_each_entry并没有被替换为list_for_each_entry_rcu,这是因为,只有一 个写者在做链表删除操作,因此没有必要使用_rcu版本。

    通常情况下,write_lock和write_unlock应当分别替换成spin_lock和spin_unlock,但是对于只是对链表进行 增加和删除操作而且只有一个写者的写端,在使用了_rcu版本的链表操作API后,rwlock可以完全消除,不需要spinlock来同步读者的访问。 对于上面的例子,由于已经有audit_netlink_sem被调用者保持,所以spinlock就没有必要了。

    这种情况允许修改结果延后一定时间才可见,而且写者对链表仅仅做增加和删除操作,所以转换成使用RCU非常容易。

    2.写端需要对链表条目进行修改操作

    如果写者需要对链表条目进行修改,那么就需要首先拷贝要修改的条目,然后修改条目的拷贝,等修改完毕后,再使用条目拷贝取代要修改的条目,要修改条目将被在经历一个grace period后安全删除。

    对于系统调用审计代码,并没有这种情况。这里假设有修改的情况,那么使用rwlock的修改代码应当如下:

           static inline int audit_upd_rule(struct audit_rule *rule,
                                             struct list_head *list,
                                             __u32 newaction,
                                             __u32 newfield_count)
            {
                    struct audit_entry  *e;
                    struct audit_newentry *ne;
                    write_lock(&auditsc_lock);
                    /* Note: audit_netlink_sem held by caller. */
                    list_for_each_entry(e, list, list) {
                            if (!audit_compare_rule(rule, &e->rule)) {
                                    e->rule.action = newaction;
                                    e->rule.file_count = newfield_count;
                                    write_unlock(&auditsc_lock);
                                    return 0;
                            }
                    }
                    write_unlock(&auditsc_lock);
                    return -EFAULT;         /* No matching rule */
            }
            


    如果使用RCU,修改代码应当为;

          static inline int audit_upd_rule(struct audit_rule *rule,
                                             struct list_head *list,
                                             __u32 newaction,
                                             __u32 newfield_count)
            {
                    struct audit_entry  *e;
                    struct audit_newentry *ne;
                    list_for_each_entry(e, list, list) {
                            if (!audit_compare_rule(rule, &e->rule)) {
                                    ne = kmalloc(sizeof(*entry), GFP_ATOMIC);
                                    if (ne == NULL)
                                            return -ENOMEM;
                                    audit_copy_rule(&ne->rule, &e->rule);
                                    ne->rule.action = newaction;
                                    ne->rule.file_count = newfield_count;
                                    list_replace_rcu(e, ne);
                                    call_rcu(&e->rcu, audit_free_rule, e);
                                    return 0;
                            }
                    }
                    return -EFAULT;         /* No matching rule */
            }
            


    3.修改操作立即可见

    前面两种情况,读者能够容忍修改可以在一段时间后看到,也就说读者在修改后某一时间段内,仍然看到的是原来的数据。在很多情况下,读者不能容忍看到 旧的数据,这种情况下,需要使用一些新措施,如System V IPC,它在每一个链表条目中增加了一个deleted字段,标记该字段是否删除,如果删除了,就设置为真,否则设置为假,当代码在遍历链表时,核对每一 个条目的deleted字段,如果为真,就认为它是不存在的。

    还是以系统调用审计代码为例,如果它不能容忍旧数据,那么,读端代码应该修改为:

           static enum audit_state audit_filter_task(struct task_struct *tsk)
            {
                    struct audit_entry *e;
                    enum audit_state   state;
                    rcu_read_lock();
                    list_for_each_entry_rcu(e, &audit_tsklist, list) {
                            if (audit_filter_rules(tsk, &e->rule, NULL, &state)) {
                                    spin_lock(&e->lock);
                                    if (e->deleted) {
                                            spin_unlock(&e->lock);
                                            rcu_read_unlock();
                                            return AUDIT_BUILD_CONTEXT;
                                    }
                                    rcu_read_unlock();
                                    return state;
                            }
                    }
                    rcu_read_unlock();
                    return AUDIT_BUILD_CONTEXT;
            }
            


    注意,对于这种情况,每一个链表条目都需要一个spinlock保护,因为删除操作将修改条目的deleted标志。此外,该函数如果搜索到条目,返回时应当保持该条目的锁,因为只有这样,才能看到新的修改的数据,否则,仍然可能看到就的数据。

    写端的删除操作将变成:

           static inline int audit_del_rule(struct audit_rule *rule,
                                             struct list_head *list)
            {
                    struct audit_entry  *e;
                    /* Do not use the _rcu iterator here, since this is the only
                     * deletion routine. */
                    list_for_each_entry(e, list, list) {
                            if (!audit_compare_rule(rule, &e->rule)) {
                                    spin_lock(&e->lock);
                                    list_del_rcu(&e->list);
                                    e->deleted = 1;
                                    spin_unlock(&e->lock);
                                    call_rcu(&e->rcu, audit_free_rule, e);
                                    return 0;
                            }
                    }
                    return -EFAULT;         /* No matching rule */
            }
            


    删除条目时,需要标记该条目为已删除。这样读者就可以通过该标志立即得知条目是否已经删除。

    参考资料

    [1] Linux RCU实现者之一Paul E. McKenney的RCU资源链接,http://www.rdrop.com/users/paulmck/rclock/

    [2] Paul E. McKenney的博士论文,"Exploiting Deferred Destruction: An Analysis of Read-Copy Update Techniques in Operating System Kernels",http://www.rdrop.com/users/paulmck/rclock/RCUdissertation.2004.07.14e1.pdf

    [3] Paul E. McKenney's paper in Ottawa Linux Summit 2002, Read-Copy Update,http://www.rdrop.com/users/paulmck/rclock/rcu.2002.07.08.pdf

    [4] Linux Journal在2003年10月对RCU的简介, Kernel Korner - Using RCU in the Linux 2.5 Kernel,http://linuxjournal.com/article/6993

    [5] Scaling dcache with RCU, http://linuxjournal.com/article/7124

    [6] Patch: Real-Time Preemption and RCU,http://lwn.net/Articles/128228/

    [7] Using Read-Copy Update Techniques for System V IPC in the Linux 2.5 Kernel, http://www.rdrop.com/users/paulmck/rclock/rcu.FREENIX.2003.06.14.pdf


  • 相关阅读:
    Code Forces 650 C Table Compression(并查集)
    Code Forces 645B Mischievous Mess Makers
    POJ 3735 Training little cats(矩阵快速幂)
    POJ 3233 Matrix Power Series(矩阵快速幂)
    PAT 1026 Table Tennis (30)
    ZOJ 3609 Modular Inverse
    Java实现 LeetCode 746 使用最小花费爬楼梯(递推)
    Java实现 LeetCode 745 前缀和后缀搜索(使用Hash代替字典树)
    Java实现 LeetCode 745 前缀和后缀搜索(使用Hash代替字典树)
    Java实现 LeetCode 745 前缀和后缀搜索(使用Hash代替字典树)
  • 原文地址:https://www.cnblogs.com/haippy/p/2272255.html
Copyright © 2011-2022 走看看