Linux内核—llist
基于 Linux-5.10.81
一、llist概述
部分使用场景无需锁保护的以NULL结尾的单链表。实现文件为 lib/llist.c 和 include/linux/llist.h,后者包含导出的函数和便利宏。
(1) 不需要加锁的情况:
如果有多个生产者和多个消费者,则可以在生产者中使用 llist_add,在消费者中同时使用 llist_del_all,无需锁定。此外,如果单个消费者多个生产者,单个消费者可以使用 llist_del_first,而多个生产者同时使用 llist_add,无需任何锁定。
(2) 需要加锁的情况:
如果我们有多个消费者,其中一个消费者使用 llist_del_first, 其他消费者使用 llist_del_first 或 llist_del_all,则需要锁。这是因为 llist_del_first 依赖于 list->first->next 不会改变,但是没有锁保护,如果在删除操作中间发生抢占并且被抢占,
则无法确定 list->first 与之前导致 llist_del_first 中的 cmpxchg 成功的相同。 例如,当一个消费者正在进行 llist_del_first 操作时,另一个消费者中的 llist_del_first、llist_add、llist_add(或 llist_del_all、llist_add、llist_add)序列可能会导致违规。
是否需要加锁总结起来如下:
* | add | del_first | del_all * add | - | - | - * del_first | | L | L * del_all | | | -
其中,特定行的操作可以与列的操作同时发生,“-”表示不需要锁,而“L”表示需要锁。
这个链表是基于long类型的 cmpxchg 原子操作。在没有 NMI-safe 的 cmpxchg 实现的架构上,该列表不能在 NMI handlers 中使用。 因此,在 NMI 处理程序中使用列表的代码应该依赖于CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG(Arm64架构默认使能)。
二、相关数据结构
1. llist_head 和 llist_node
struct llist_head { struct llist_node *first; }; struct llist_node { struct llist_node *next; };
三、相关函数
一些常规的便利、判空的操作跳过,看其特有的函数。
1. 初始化
直接将 head->first 设置为NULL
//1 #define LLIST_HEAD_INIT(name) { NULL } #define LLIST_HEAD(name) struct llist_head name = LLIST_HEAD_INIT(name) //include/linux/llist.h //2 static inline void init_llist_head(struct llist_head *list) //include/linux/llist.h { list->first = NULL; }
2. 添加一个元素
/** * llist_add - add a new entry * @new: new entry to be added * @head: the head for your lock-less list * * Returns true if the list was empty prior to adding this entry. */ static inline bool llist_add(struct llist_node *new, struct llist_head *head) { return llist_add_batch(new, new, head); } /** * llist_add_batch - add several linked entries in batch * @new_first: first entry in batch to be added * @new_last: last entry in batch to be added * @head: the head for your lock-less list * * Return whether list is empty before adding. * * 头插法,插入一段链表。llist_add传参:(new, new, head) */ bool llist_add_batch(struct llist_node *new_first, struct llist_node *new_last, struct llist_head *head) { struct llist_node *first; do { new_last->next = first = READ_ONCE(head->first); /* 对 head->first 的判断和赋值是原子操作,又只支持头插法,因此多个任务同时对 head->first 操作是安全的 */ } while (cmpxchg(&head->first, first, new_first) != first); return !first; } EXPORT_SYMBOL_GPL(llist_add_batch);
只支持头插法,通过对 head->first 的原子操作来保证安全性。
3. 删除一个元素
/** * llist_del_first - delete the first entry of lock-less list * @head: the head for your lock-less list * * If list is empty, return NULL, otherwise, return the first entry * deleted, this is the newest added one(无锁只支持头插发插入元素). * * 翻译: * 无锁下,只有一个 llist_del_first 用户可以和多个 llist_add 用户同时使用。 * 否则其他用户中的 llist_del_first、llist_add、llist_add(或 llist_del_all、 * llist_add、llist_add)序列可能会更改@head->first->next,但保留@head->first。 * 如果需要多个消费者,请使用 llist_del_all 或在消费者之间使用锁。 */ struct llist_node *llist_del_first(struct llist_head *head) { struct llist_node *entry, *old_entry, *next; entry = smp_load_acquire(&head->first); for (;;) { if (entry == NULL) return NULL; old_entry = entry; next = READ_ONCE(entry->next); entry = cmpxchg(&head->first, old_entry, next); /* 若不相等,说明 head->first 有改动过了 */ if (entry == old_entry) break; } return entry; } EXPORT_SYMBOL_GPL(llist_del_first);
也是从头部删除,原子操作 head->first。
4. 删除所有元素
/** * llist_del_all - delete all entries from lock-less list * @head: the head of lock-less list to delete all entries * * If list is empty, return NULL, otherwise, delete all entries and * return the pointer to the first entry. The order of entries * deleted is from the newest to the oldest added one. */ static inline struct llist_node *llist_del_all(struct llist_head *head) { /* 原子的先返回 head->first,然后将其 head->first=NULL*/ return xchg(&head->first, NULL); }
5. reverse 链表所有元素
/** * llist_reverse_order - reverse order of a llist chain * @head: first item of the list to be reversed * * Reverse the order of a chain of llist entries and return the * new first entry. */ struct llist_node *llist_reverse_order(struct llist_node *head) { struct llist_node *new_head = NULL; while (head) { struct llist_node *tmp = head; head = head->next; tmp->next = new_head; new_head = tmp; } return new_head; } EXPORT_SYMBOL_GPL(llist_reverse_order);
因为只提供了头插法和从头删除的接口,类似于栈的实现。若是想使用成队列,就需要对齐进行reverse。
四、总结
可以看到,所谓的无锁操作都是依赖对 head->first 的原子操作实现的,因此实际使用时要考虑到这一点,来判断你当前的无锁使用是否是安全的。
五、使用Demo
1. 示例一般的无锁使用
//链表节点 struct demo_desc { ... struct llist_node llnode; ... }; struct demo_desc d1; //链表头 LLIST_HEAD(demo_head); //无锁插入元素 llist_add(&d1.demo_node, struct &demo_head); //无锁取出所有链表元素 struct llist_node *llnode = llist_del_all(demo_head); //reverse后做队列使用 llnode = llist_reverse_order(llnode); //之后就可以使用llist.h中的遍历函数对llnode进行安全遍历了,可参考 flush_smp_call_function_queue() 中的使用。
posted on 2022-06-27 16:44 Hello-World3 阅读(535) 评论(0) 编辑 收藏 举报