"Linux Kernel Design and Implementation" Reading Notes-Kernel Data Structure

Linked list

Simple linked list:

Doubly linked list:

Circular linked list:

If the main operation on the data set is to traverse the data, use a linked list.

 

Doubly linked list

Linux does not stuff data structures into the linked list, but stuffs the nodes of the linked list into the data structure.

Linked list node (include\linux\list.h):

/*
 * Simple doubly linked list implementation.
 *
 * Some of the internal functions ("__xxx") are useful when
 * manipulating whole lists rather than single entries, as
 * sometimes we already know the next/prev entries and we can
 * generate better code by using them directly rather than
 * using the generic single-entry routines.
 */
struct list_head {
    struct list_head *next, *prev;
};

list_head itself has no meaning, it needs to be embedded in the data structure to take effect:

struct clk {
    struct list_head node;
    const char *name;
    int id;
    struct module *owner;
    struct clk *parent;
    struct clk_ops *ops;
    struct kref kref;
    unsigned long rate;
    unsigned long flags;
};

Use the macro container_of() to easily find the members in the parent structure from the linked list node.

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:    the pointer to the member.
 * @type:   the type of the container struct this is embedded in.
 * @member: the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({          \
    const typeof(((type *)0)->member) * __mptr = (ptr); \
    (type *)((char *)__mptr - offsetof(type, member)); })

The corresponding macro with a linked list:

/**
 * list_entry - get the struct for this entry
 * @ptr:    the &struct list_head pointer.
 * @type:   the type of the struct this is embedded in.
 * @member: the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)

Used to return the related members of the supertype structure containing list_head.

Relying on list_entry(), the kernel provides various routines for creating, operating, and other linked list management.

 

Linked list operation

Linked list node initialization:

#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)
static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}

Linked list node initialization in static initialization of data structure:

static struct ts_ops bm_ops = {
    .name         = "bm",
    .find         = bm_find,
    .init         = bm_init,
    .get_pattern      = bm_get_pattern,
    .get_pattern_len  = bm_get_pattern_len,
    .owner        = THIS_MODULE,
    .list         = LIST_HEAD_INIT(bm_ops.list)
};

Doubly linked lists usually do not have a so-called head, but many times you need to specify:

static LIST_HEAD(cpufreq_governor_list);

Then use:

    list_for_each_entry(t, &cpufreq_governor_list, governor_list)
        if (!strnicmp(str_governor, t->name, CPUFREQ_NAME_LEN))
            return t;

The operations related to the linked list are all in include\linux\list.h. Note that the operation functions are all inline functions, so they are in the header file.

I will not introduce them one by one here.

 

queue

Queue first in last out:

If the code conforms to the producer/consumer pattern, use queues.

 

Queue operation

The Linux kernel general queue implementation is called kfifo, located in kernel\kfifo.c, and declared in include\linux\kfifo.h.

queue:

struct kfifo {
    unsigned char *buffer;  /* the buffer holding the data */
    unsigned int size;  /* the size of the allocated buffer */
    unsigned int in;    /* data is added at offset (in % size) */
    unsigned int out;   /* data is extracted from off. (out % size) */
};

Dynamic queue allocation:

extern void kfifo_init(struct kfifo *fifo, void *buffer,
            unsigned int size);
extern __must_check int kfifo_alloc(struct kfifo *fifo, unsigned int size,
            gfp_t gfp_mask);

Queue static allocation:

#define DECLARE_KFIFO(name, size) \
union { \
    struct kfifo name; \
    unsigned char name##kfifo_buffer[size + sizeof(struct kfifo)]; \
}
#define INIT_KFIFO(name) \
    name = __kfifo_initializer(sizeof(name##kfifo_buffer) - \
                sizeof(struct kfifo), \
                name##kfifo_buffer + sizeof(struct kfifo))

Guess:

extern unsigned int kfifo_in(struct kfifo *fifo,
                const void *from, unsigned int len);

Send out:

extern __must_check unsigned int kfifo_out(struct kfifo *fifo,
                void *to, unsigned int len);

 

Mapping

Mapping is also called associative array.

Mapping refers to the correspondence between key values.

The key is unique.

The mapping implemented in Linux is a mapping from a unique identifier (UID, actually a value of type int) to a pointer.

Linux provides idr data structure for mapping UID (include\linux\idr.h).

struct idr {
    struct idr_layer *top;
    struct idr_layer *id_free;
    int       layers; /* only valid without concurrent changes */
    int       id_free_cnt;
    spinlock_t    lock;
};

If you need to map a UID to an object, use mapping.

 

Mapping operation

Initialize an idr:

void idr_init(struct idr *idp);

Obtaining UID, there are two steps:

int idr_pre_get(struct idr *idp, gfp_t gfp_mask);
int idr_get_new(struct idr *idp, void *ptr, int *id);

The UID is stored in id and is associated with the ptr pointer.

Find UID:

void *idr_find(struct idr *idp, int id);

Delete UID:

void idr_remove(struct idr *idp, int id);

Destroy idr:

void idr_destroy(struct idr *idp);

However, the memory allocated to UID will not be cleared unless it is used:

void idr_remove_all(struct idr *idp);

 

tree

The tree structure is a tree-shaped data structure that can provide a hierarchy.

Each node of a binary tree has at most two outgoing edges.

A binary search tree is a binary tree with ordered nodes:

  • The left branch node value of the root is less than the root node value;
  • The value of the right branch node is greater than the value of the root node;
  • All subtrees are also binary search trees;

The depth difference of all leaf nodes of the self-balancing binary search tree does not exceed 1.

The red-black tree is a self-balancing binary search tree, which has the following specifics:

  • All nodes are either red or black;
  • The leaf nodes are all black;
  • Leaf nodes do not contain data;
  • All non-leaf nodes have two child nodes;
  • If a node is red, its child nodes are all black;
  • In the path from a node to its leaf node, if it always contains the same number of black nodes, the path is the shortest compared to other paths;

If you need to store a large amount of data and retrieve it quickly, use red-black trees.

 

Red-black tree implementation

The main balanced binary tree data structure of Linux is the red-black tree.

The red-black tree implemented by Linux is called rbtree.

The root node is represented by rb_root (include\linux\rbtree.h):

struct rb_root
{
    struct rb_node *rb_node;
};

Other nodes are represented by rb_node:

struct rb_node
{
    unsigned long  rb_parent_color;
#define RB_RED      0
#define RB_BLACK    1
    struct rb_node *rb_right;
    struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));

The rbtree implementation does not provide search and insert routines.

 

Guess you like

Origin blog.csdn.net/jiangwei0512/article/details/106143604