linux 内核读写锁学习记录

读写锁示例

net/ipx/ipx_route.c

ipx 的读写锁用于处理对 ipx 路由表的并发访问问题。这里的路由表是一组链表,包含了多个路由项目。读路由表的情况要比更新路由表的情况多得多,这里使用读写锁能够提高性能。

相关代码如下:

LIST_HEAD(ipx_routes);
DEFINE_RWLOCK(ipx_routes_lock);

extern struct ipx_interface *ipx_internal_net;

extern struct ipx_interface *ipxitf_find_using_net(__be32 net);
extern int ipxitf_demux_socket(struct ipx_interface *intrfc,
			       struct sk_buff *skb, int copy);
extern int ipxitf_demux_socket(struct ipx_interface *intrfc,
			       struct sk_buff *skb, int copy);

struct ipx_route *ipxrtr_lookup(__be32 net)
{
	struct ipx_route *r;

	read_lock_bh(&ipx_routes_lock);
	list_for_each_entry(r, &ipx_routes, node)
		if (r->ir_net == net) {
			ipxrtr_hold(r);
			goto unlock;
		}
	r = NULL;
unlock:
	read_unlock_bh(&ipx_routes_lock);
	return r;
}

/*
 * Caller must hold a reference to intrfc
 */
int ipxrtr_add_route(__be32 network, struct ipx_interface *intrfc,
		     unsigned char *node)
{
	struct ipx_route *rt;
	int rc;

	/* Get a route structure; either existing or create */
	rt = ipxrtr_lookup(network);
	if (!rt) {
		rt = kmalloc(sizeof(*rt), GFP_ATOMIC);
		rc = -EAGAIN;
		if (!rt)
			goto out;

		atomic_set(&rt->refcnt, 1);
		ipxrtr_hold(rt);
		write_lock_bh(&ipx_routes_lock);
		list_add(&rt->node, &ipx_routes);
		write_unlock_bh(&ipx_routes_lock);
	} else {
		rc = -EEXIST;
		if (intrfc == ipx_internal_net)
			goto out_put;
	}

	rt->ir_net 	= network;
	rt->ir_intrfc 	= intrfc;
	if (!node) {
		memset(rt->ir_router_node, '\0', IPX_NODE_LEN);
		rt->ir_routed = 0;
	} else {
		memcpy(rt->ir_router_node, node, IPX_NODE_LEN);
		rt->ir_routed = 1;
	}

	rc = 0;
out_put:
	ipxrtr_put(rt);
out:
	return rc;
}

ipxrtr_lookupipx route的读函数,在读取过程中获取读锁,读写锁允许多个执行流同时读取,但是在同一时刻只能有一个执行流来修改。

ipxrtr_add_route 用来创建一条路由项目,它会更新路由表,在更新的时候需要获取写锁。写锁保证了如下两个条件:

  1. 所有的读都完成了
  2. 同一时刻只有一个执行流来写

上述代码调用如下函数来完成读写锁的申请与释放。

read_lock_bh
read_unlock_bh

write_lock_bh
write_unlock_bh

上面的这种接口只是内核提供的读写锁接口中的一种,它会在获取读写锁的同时关闭本地中断。这说明 ipx route 表存在同时在进程上下文与中断上下文同时访问的情况。

这种读写锁优先于读,适合于读比写多的场景中。当然读写锁也有优先写的实现,linux 中的顺序锁——seqlock 就是一个典型的写多于读的实现。

顺序锁示例

kernel/time/jiffies.c 中相关代码截取如下:

#if (BITS_PER_LONG < 64)
u64 get_jiffies_64(void)
{
	unsigned long seq;
	u64 ret;

	do {
		seq = read_seqbegin(&jiffies_lock);
		ret = jiffies_64;
	} while (read_seqretry(&jiffies_lock, seq));
	return ret;
}
EXPORT_SYMBOL(get_jiffies_64);
#endif

这里读取 jiffies_64变量就是在占用了写锁的情况下执行的,在 seqlock中写优于读,一次获取读锁可能失败,如果此时有人在写,按照优先写的方案此时其它人不能再读,因此在获取读锁的时候要失败后重试。

更新 jiffies_64的代码可以在 kernel/time/timekeeping.c中找到,截取部分代码如下:

/*
 * Must hold jiffies_lock
 */
void do_timer(unsigned long ticks)
{
	jiffies_64 += ticks;
	calc_global_load(ticks);
}
/**
 * xtime_update() - advances the timekeeping infrastructure
 * @ticks:	number of ticks, that have elapsed since the last call.
 *
 * Must be called with interrupts disabled.
 */
void xtime_update(unsigned long ticks)
{
	write_seqlock(&jiffies_lock);
	do_timer(ticks);
	write_sequnlock(&jiffies_lock);
	update_wall_time();
}

可以看到在函数 do_timer中更新 jiffies_64的值,注释中明确指出在调用这个函数时需要占用 jiffies_lock,下面的 xtime_update函数中首先调用 write_seqlock函数获取写锁,然后更新 jiffies_64然后释放写锁,就完成了整个过程。

/*
 * Periodic tick
 */
static void tick_periodic(int cpu)
{
	if (tick_do_timer_cpu == cpu) {
		write_seqlock(&jiffies_lock);

		/* Keep track of the next tick event */
		tick_next_period = ktime_add(tick_next_period, tick_period);

		do_timer(1);
		write_sequnlock(&jiffies_lock);
		update_wall_time();
	}

	update_process_times(user_mode(get_irq_regs()));
	profile_tick(CPU_PROFILING);
}
/*
 * Called from the timer interrupt handler to charge one tick to the current
 * process.  user_tick is 1 if the tick is user time, 0 for system.
 */
void update_process_times(int user_tick)
{
	struct task_struct *p = current;

	/* Note: this timer irq context must be accounted for as well. */
	account_process_tick(p, user_tick);
	run_local_timers();
	rcu_check_callbacks(user_tick);
#ifdef CONFIG_IRQ_WORK
	if (in_irq())
		irq_work_tick();
#endif
	scheduler_tick();
	run_posix_cpu_timers(p);
}
sched/sched.h:1214:extern const struct sched_class stop_sched_class;
sched/sched.h:1215:extern const struct sched_class dl_sched_class;
sched/sched.h:1216:extern const struct sched_class rt_sched_class;
sched/sched.h:1217:extern const struct sched_class fair_sched_class;
sched/sched.h:1218:extern const struct sched_class idle_sched_class;

rcu 机制

此机制用于提高读操作远多于写操作时的性能。基本理念是读线程不需要加锁,但是写线程会变得更加复杂,它们会在数据结构的一份副本上执行更新操作,并代替读者看到的指针。

rcu 机制我研究的不多,就不做进一步阐述了。

总结

linux 内核提供了多种不同的读写锁机制,编写驱动程序时需要根据特定的场景选择适合的锁,在保证功能正常的前提下尽可能的提高锁的性能。不过我们也能够看到一些无锁的实现方案,这些方案的复杂性很高,但同时它也带来了更好的性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值