读写锁示例
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_lookup
是 ipx route
的读函数,在读取过程中获取读锁,读写锁允许多个执行流同时读取,但是在同一时刻只能有一个执行流来修改。
ipxrtr_add_route 用来创建一条路由项目,它会更新路由表,在更新的时候需要获取写锁。写锁保证了如下两个条件:
- 所有的读都完成了
- 同一时刻只有一个执行流来写
上述代码调用如下函数来完成读写锁的申请与释放。
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 内核提供了多种不同的读写锁机制,编写驱动程序时需要根据特定的场景选择适合的锁,在保证功能正常的前提下尽可能的提高锁的性能。不过我们也能够看到一些无锁的实现方案,这些方案的复杂性很高,但同时它也带来了更好的性能。