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  阅读(553)  评论(0编辑  收藏  举报

导航