linux deepin内核头文件解析(二)——WRITE_ONCE函数和list.h

前文书道:linux deepin内核头文件解析(一)——list.h

前文代码

static inline void __list_add(struct list_head *new,
			      struct list_head *prev,
			      struct list_head *next)
{
	if (!__list_add_valid(new, prev, next))
		return;
 
	next->prev = new;
	new->next = next;
	new->prev = prev;
	WRITE_ONCE(prev->next, new);
}

出现了函数WRITE_ONCE()来将prev->next和new链接,现在来仔细研究WRITE_ONCE函数。

函数定义在文件include/linux/compile.h

#define WRITE_ONCE(x, val) \
({							\
	union { typeof(x) __val; char __c[1]; } __u =	\
		{ .__val = (__force typeof(x)) (val) }; \
	__write_once_size(&(x), __u.__c, sizeof(x));	\
	__u.__val;					\
})

c源码为了高效率使用了宏定义的写法,现在为了提高可读性我来改写代码(降低了效率):

template<class T>
void WRITE_ONCE(T x, T var) {
    union { 
        typeof(x) __val; 
        char __c[1]; 
    } __u = { .__val = (__force typeof(x)) (val) };
    __write_once_size(&(x), __u.__c, sizeof(x));
    __u.__val;			
}

这里居然又出现了函数__write_once_size(),追根溯源把,转到__write_once_size的定义:

static __always_inline void __write_once_size(volatile void *p, void *res, int size)
{
	switch (size) {
	case 1: *(volatile __u8 *)p = *(__u8 *)res; break;
	case 2: *(volatile __u16 *)p = *(__u16 *)res; break;
	case 4: *(volatile __u32 *)p = *(__u32 *)res; break;
	case 8: *(volatile __u64 *)p = *(__u64 *)res; break;
	default:
		barrier();
		__builtin_memcpy((void *)p, (const void *)res, size);
		barrier();
	}
}

这里很容易可以看出函数__write_once_size(void *a, void *b, int size)目的是为了安全地使指针b赋值给指针a,而且不会出现指针类型错误,而且用了volatile关键字保证编译器不回因为优化而忽略这些代码,这很鲁棒!

所以语句

WRITE_ONCE(prev->next, new);

的真正作用就是安全地将prev->next指向new!

下一个函数

static inline void list_add(struct list_head *new, struct list_head *head)
{
	__list_add(new, head, head->next);
}

作用一目了然,也是链表添加节点,但参数只有两个,新节点地址和链表头emmmm,果然懒惰是人类进步的驱动力,双向链表嘛,不严谨的说每一个节点都可以是头节点。

下一个函数是往尾节点后添加新节点:

static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
	__list_add(new, head->prev, head);
}

下面进入节点的删除环节,注释是这么说的:

/*
 * Delete a list entry by making the prev/next entries
 * point to each other.
 *
 * 通过给出prev和next来删除他们互相指向的入口(节点)
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 * 这是链表的内部操作,必须在我们已经知晓prev和next入口的前提下
 */
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
	next->prev = prev;
	WRITE_ONCE(prev->next, next);
}

 上图!

我执行代码__list_del(&n, &(n+2));

结果是什么呢,我把节点(n+2)的prev指向了节点n,把节点n的next指向了节点(n+2),现在节点(n+1)已经被孤立出链表的逻辑结构了!无论节点(n+1)还在不在内存里!

如下:

static inline void __list_del_entry(struct list_head *entry)
{
	if (!__list_del_entry_valid(entry))
		return;

	__list_del(entry->prev, entry->next);
}

__list_del_entry()函数传入一个节点(入口)地址,其中又出现新函数__list_del_entry_valid(),定位它:

static inline bool __list_del_entry_valid(struct list_head *entry)
{
	return true;
}

emmm,也就是说,如果传入的entry参数是list_head类型的指针,那么返回true,那上面的代码调用这个函数其实是确保安全性,保证不会传入野指针。

下一个函数

static inline void list_del(struct list_head *entry)
{
	__list_del_entry(entry);
	entry->next = LIST_POISON1;
	entry->prev = LIST_POISON2;
}

这个函数其实是对__list_del()函数的完善,__list_del函数虽然将节点entry孤立出链表体系之外,但是entry的prev和next依然指向链表,斩草要除根,这里的LIST_POISON1和LIST_POISON2定义在poison.h里,这里不展开讲,可以理解为在程序不会占用到的内存地址,把entry的头和脚都放逐到了程序之外,这才是斩草除根嘛。


未完,下面还有list.h的查找和更改部分,涉及到了linux内核设计最精彩的算法部分,下章再说

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值