《Android深度探索 卷1 HAL与驱动开发》笔记之Linux驱动程序中的并发操作(6)——读-复制-更新(RCU)机制API

RCU使用方法

(1) 读锁定

static inline void rcu_read_lock(void)
static inline void rcu_read_lock_bh(void)

(2) 读解锁

static inline void rcu_read_unlock(void)
static inline void rcu_read_unlock_bh(void)

(3) 使用RCU读模式的标准参考代码

rcu_read_lock();
... /* 读临界区的代码 */
rcu_read_unlock();

**rcu_read_lock()rcu_read_unlock()**函数实际上只是禁止和打开内核的进程抢占,主要目的是为了防止在执行临界区的代码的时候被其他的进程打断,从这两个函数的源代码就可以看出这一点。

  • 读模式代码定义
static inline void rcu_read_lock(void)
{
	__rcu_read_lock(); /* 关闭进程抢占 */
	__acquire(RCU);
	rcu_lock_acquire(&rcu_lock_map);
	rcu_lockdep_assert(rcu_is_watching(),
			   "rcu_read_lock() used illegally while idle");
}

static inline void rcu_read_unlock(void)
{
	rcu_lockdep_assert(rcu_is_watching(),
			   "rcu_read_unlock() used illegally while idle");
	rcu_lock_release(&rcu_lock_map);
	__release(RCU);
	__rcu_read_unlock(); /* 打开进程抢占 */
}

__rcu_read_lock()__rcu_read_unlock() 函数分别用来关闭和打开进程抢占。

static inline void __rcu_read_lock(void)
{
	preempt_disable();
}

static inline void __rcu_read_unlock(void)
{
	preempt_enable();
}

衍生函数 rcu_read_lock_bh()rcu_read_unlock_bh() 函数也使用了类似的实现方法。这两个函数的实现代码如下:

static inline void rcu_read_lock_bh(void)
{
	local_bh_disable(); /* 禁止软中断 */
	__acquire(RCU_BH);
	rcu_lock_acquire(&rcu_bh_lock_map);
	rcu_lockdep_assert(rcu_is_watching(),
			   "rcu_read_lock_bh() used illegally while idle");
}

static inline void rcu+read_unlock_bh(void)
{
	rcu_lockdep_assert(rcu_is_watching(),
			   "rcu_read_unlock_bh() used illegally while idle");
	rcu_lock_release(&rcu_bh_lock_map);
	__release(RCU_BH);
	local_bh_enable(); /* 打开软中断 */
}

(4) 同步RCU

synchronize_rcu();

RCU的核心理念是读者访问的同时,写者可以更新访问对象的副本,但写者需要等待所有读者完成访问之后,才能删除老对象。这个过程实现的关键和难点就在于如何判断所有的读者已经完成访问。通常把写者开始更新,到所有读者完成访问这段时间叫做宽限期(Grace Period)。内核中实现宽限期等待的函数是synchronize_rcu
其定义如下:

static inline void synchronize_rcu(void)
{
	synchronize_sched();
}

synchronize_rcu() 函数由RCU写线程调用,它将阻塞写线程,知道经过grace period后,即所有的杜金城已经执行完临界区的代码并退出,写进程才可以继续下一步的操作。如果有多个RCU写进程调用这个哈数,它们将在一个grace period之后全部被唤醒(至于哪个写线程会执行,由它们采用的锁机制所决定)。

(5) 挂接回调函数

void call_rcu(struct rcu_head *head,
	      void (*func)(struct rcu_head *head));

struct callback_head {
	struct callback_head *next;
	void (*func)(struct callback_head *head);
};
#define rcu_head callback_head

call_rcu() 函数也是由RCU写线程调用,这个函数不会使写线程发生阻塞,所以可以在中断上下文或者软中断中使用,而**synchronize_rcu()**只能在进程上下文总使用。这个函数把函数func挂接到RCU回调函数链上,然后立刻返回。一旦所有的CPU都已经完成了读临界区操作,fun函数就会被调用来释放旧数据。head参数用来记录回调函数function,一般这个结构会被作为RCU保护的数据结构的一个字段,以便于省去单独为这个结构体分配内存的操作。

RCU的示例代码

1. 使用rwlock机制的读端代码

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

上述代码如果修改成RCU机制后会变成如下的形式:

static enum audi_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)) {
			rcu_read_unlock();
			return state;
		}
	}
	rcu_read_unlock();
	return AUDIT_BUILD_CONTEXT;
}

这种转换非常直接,使用rcu_read_lock、rcu_read_unlock、list_for_each_entry_rcu 分别替换 read_lock、read_unlock、list_for_each_entry 即可。

2. 使用rwlock机制的写端代码

/* 删除rule */
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 */
}

/* 添加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机制的形式可以得到如下代码。

/* 删除rule */
static inline int audit_del_rule(struct audit_rule *rule, struct list_head *list)
{
	struct audit_entry *e;
	list_for_each_entry_rcu(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 */
}

/* 添加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_rcucall_rcu ,这是因为被删除的链表项可能还在被其他的读进程引用,所以不能够立刻删除,必须等到所有的读进程经历一个quiescent state 才可以删除。

发布了23 篇原创文章 · 获赞 3 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/karaskass/article/details/102530223
今日推荐