Linked List
A linked list is a data structure where each node contains the relevant data and a pointer to the next node.
Generic Linked Lists in C
Two approaches:
- Macro-based with hardcoded member names (
next
,prev
) - Intrusive linked list node type as struct member (Linux kernel approach)
Macro-based Linked List API
For doubly-linked lists, hardcode the structure member names (i.e the nodes to link) to "prev" and "next".
typedef struct item_t item_t; struct item_t { int foo; float bar; /* ... */ item_t* prev; item_t* next; };
The macro-based API then can look like this:
#define DL_FOREACH(head,el) \ for ((el) = (head); el; (el) = (el)->next) #define DL_APPEND(head,add) \ do { \ if (head) { \ (add)->prev = (head)->prev; \ (head)->prev->next = (add); \ (head)->prev = (add); \ (add)->next = NULL; \ } else { \ (head)=(add); \ (head)->prev = (head); \ (head)->next = NULL; \ } \ } while (0) #define DL_DELETE(head,del) \ do { \ assert((head) != NULL); \ assert((del)->prev != NULL); \ if ((del)->prev == (del)) { \ (head)=NULL; \ } else if ((del) == (head)) { \ assert((del)->next != NULL); \ (del)->next->prev = (del)->prev; \ (head) = (del)->next; \ } else { \ (del)->prev->next = (del)->next; \ if ((del)->next) { \ (del)->next->prev = (del)->prev; \ } else { \ (head)->prev = (del)->prev; \ } \ } \ } while (0)
Intrusive linked list - list_head
The Linux kernel approach to having linked lists is a type of intrusive linked list where the links are embedded in the struct that is being linked:
struct list_head { struct list_head *next, *prev; }; #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; } /* api */ void list_add(struct list_head *new, struct list_head *head); void list_add_tail(struct list_head *new, struct list_head *head); void list_del(struct list_head *entry); int list_is_head(const struct list_head *list, const struct list_head *head); int list_empty(const struct list_head *head); #define list_entry(ptr, type, member) container_of(ptr, type, member) // get struct of the entry #define list_first_entry(ptr, type, member) list_entry((ptr)->next, type, member) // get first element from list #define list_next_entry(pos, member) list_entry((pos)->member.next, typeof(*(pos)), member) // get next element in list #define list_entry_is_head(pos, head, member) (&pos->member == (head)) // test if entry is head of list #define list_for_each_entry(pos, head, member) \ // iterate over list of given type for (pos = list_first_entry(head, typeof(*pos), member); \ !list_entry_is_head(pos, head, member); \ pos = list_next_entry(pos, member)) #define list_for_each_entry_safe(pos, n, head, member) \ for (pos = list_first_entry(head, typeof(*pos), member), \ n = list_next_entry(pos, member); \ !list_entry_is_head(pos, head, member); \ pos = n, n = list_next_entry(n, member))
/* usage code */ struct mdev_state { struct vfio_device vdev; /* ... */ struct list_head dmabufs; /* intrusive linked list */ u32 active_id; u32 next_id; }; /* init list */ static int mbochs_init_dev(struct vfio_device *vdev) { /* ... */ INIT_LIST_HEAD(&mdev_state->dmabufs); /* ... */ } /* add to list */ static struct mbochs_dmabuf *mbochs_dmabuf_alloc(struct mdev_state *mdev_state, struct mbochs_mode *mode) { dmabuf = kzalloc(sizeof(struct mbochs_dmabuf), GFP_KERNEL); dmabuf->mode = *mode; dmabuf->id = mdev_state->next_id++; dmabuf->pagecount = DIV_ROUND_UP(mode->size, PAGE_SIZE); dmabuf->pages = kcalloc(dmabuf->pagecount, sizeof(struct page *), GFP_KERNEL); dmabuf->mdev_state = mdev_state; list_add(&dmabuf->next, &mdev_state->dmabufs); return dmabuf; } /* iterate through list */ static struct mbochs_dmabuf* mbochs_dmabuf_find_by_mode(struct mdev_state *mdev_state, struct mbochs_mode *mode) { struct mbochs_dmabuf *dmabuf; list_for_each_entry(dmabuf, &mdev_state->dmabufs, next) { if (mbochs_modes_equal(&dmabuf->mode, mode)) { return dmabuf; } } return NULL; } /* use of container_of, safely iterate and delete */ static void mbochs_close_device(struct vfio_device *vdev) { struct mdev_state *mdev_state = container_of(vdev, struct mdev_state, vdev); struct mbochs_dmabuf *dmabuf, *tmp; list_for_each_entry_safe(dmabuf, tmp, &mdev_state->dmabufs, next) { list_del(&dmabuf->next); if (dmabuf->buf) { /* free in mbochs_release_dmabuf() */ dmabuf->unlinked = true; } else { kfree(dmabuf); } } }