Linux Kernel base tree Application Analysis
LInux kernel base tree Application Analysis-- Lvyilong316
The base tree can be seen as a trie tree with binary strings as keywords. It is a multi-tree structure similar to a multi-layer index table, each intermediate node contains an array of pointers pointing to multiple nodes, and the leaf node contains pointers pointing to the actual object (because the object does not have a tree node structure, the parent node is considered as a leaf node ).
Figure 1 shows a base tree sample where the tree has a branch of 4 (2 ^ 2) and a tree height of 4. Each leaf node of the tree is used to quickly locate the offset in the 8-bit file, you can find the 4x4x4x4 = 256 (number of leaf nodes) page. For example, the path composition values of the two leaf nodes corresponding to the dotted line in the figure are 0x00000010 and 0x11111010, point to the cache page corresponding to the corresponding offset in the file.
Figure 1
In the Linux kernel, the base tree is used to map the Handle id or page index of an object to a pointer to an object (specifically, it is converted to a path composed of pointers in some columns ), this is achieved by using the id segment as the index of the pointer array of each layer node (the items in the pointer array are called slot below. Segments are typically obtained using a bitmask that shifts the id right to a specified number of digits and the specified length, such as (id> n) & IDR_MASK. For example, a 32-bit id value can be converted into eight single-bit strings (each containing four digits) in a 4-bit segmentation method. Each string can be regarded as 1-bit from high to low ~ The slot index of the layer-8 node obtains the pointer pointing to the next node through the slot index of the previous node, so that until the last layer, the index points to the final object. As shown in figure 2, the id is 8 bits. The method of dividing by 4 bits can form a two-layer base tree. The lowest layer has a total of (2 ^ 4) * (2 ^ 4) = 2 ^ 8 = 256 leaf nodes, so the function stores 256 objects, and the maximum id of the object is 256-1 = 255 (id starts from 0 ).
Figure 2
From this point of view, the retrieval of objects in the base tree is a little slower than that of a fixed array, but it uses the idea of changing time to space. it is very suitable for scenarios where the number of nodes dynamically changes, and its time complexity is acceptable, reaching O (log2nN), where 2n is the number of pointer slots for each node, while n corresponds to the bit length of the segment mask.
1. Application of the base tree in the Linux Kernel 1.1 File Cache Page ManagementIn earlier versions of the kernel (for example, 2.4.0), the file page cache is organized through the common hash page_hash_table (hash Based on the index corresponding to the cache page ), the specified page of the specified file can be quickly searched through the hash, and there is not much additional memory consumption, but its disadvantages are also obvious, because all access files are cached on the same hash page and pagecache_lock is used for query, the concurrent access performance of multiple processes is reduced, in a particular situation, it is intolerable. Therefore, you can use the file address space in the 2.6 kernel to manage the cache pages by yourself, so that the page search of each file does not affect each other and improves the concurrency performance. "The file pages of the Linux2.6 kernel are managed by the base tree, and the page index determines its location in the tree. The data structure of the object in the file address space is as follows:
- struct address_space{
- struct inode *host; /*owner:inode,bIock_device*/
- struct radix_tree_root pagetree;
- )
The page_tree points to the root of the base tree, And the Pointer Points to the radix_tree_root structure.
- Struct radix_tree_root {
- Unsigned int height; // The height of the tree
- Gfp_t gfp_mask;
- Struct radix _ tree _ node * rnode; // point to the root node of the base tree
- };
Rnode points to the root node of the base tree. The root node is a radix_tree_node structure.
- struct radix_tree_node{
- unsigned int heigh; /*Height from the bottom*/
- unsigned int count;
- struct rcu_head rcu_head;
- void *slots[RADlX_TREE_MAP_SIZE];
- unsigned Iong tags[RADIX_TREE_MAX_TAGS][RADIX_TREE_TAG_LONGS];
- };
Height indicates the height of the node. count indicates the number of child nodes (that is, the number of non-empty slots), and slots indicates the child node pointer array (for leaf nodes, it points to the corresponding page structure ), the tags array uses bitmap to indicate whether each subtree contains a page with the corresponding flag. The two marks are dirty and written back respectively.
# DefinePAGECACHETAGDIRTY0
# Definepagecache?agvvrlteback1
1.2 process-related communication ipc Object Management
The ipc object (such as the shared memory object shm) in the earlier kernel (such as 2.6.11) is managed using a Fixed Array (the Object id is the subscript of the array), which has a defect, that is, when the number of objects increases dramatically and the number of original array objects is insufficient, it is necessary to re-allocate the new array through grow_ary (), and then copy the content between the new and old arrays, when the number of objects changes greatly, it is necessary to face frequent array allocation and release, which is detrimental to the performance. Therefore, in 2.6.24, the ipc object is managed by using the base tree idr, although the positioning of objects through idr is not as direct as the Array (the time complexity is the height of the tree), but in exchange for good dynamic performance, it will not face large-scale memory allocation when adding objects, you only need to create one or several (extension tree) Tree nodes, and the performance of getting idle IDS is better than that of arrays, which directly affects the speed of inserting new objects. The idr structure is used to manage the base Tree Containing ipc objects, indexed by the Object id:
- struct idr{
- struct idr_Iayer *top;
- struct idr_Iayer *id_free;
- int Iayers;
- int id_free_ cnt;
- spinlock_t lock;
- };
Idr_layer is the tree node structure, top points to the root node, and layers is the tree height. id_free maintains a temporary idle node linked list, And id_free_cnt indicates the number of nodes in the idle linked list.
2. Code analytics 2.1 Linux \ lib \ radix-tree.c
(1) tree height and maximum index conversion:
- Static _ init unsigned long _ maxindex (unsigned int height)
- {
- Unsigned int width = height * RADIX_TREE_MAP_SHlFT; // when the RADIX_TREE_MAP_SHlFT value is 6, it indicates that each node has 2 ^ 6 = 64 slots and the value is 4, 2 ^ 4 = 16 Slots
- Int shift = RADlX_TREE_INDEX_BlTS-width:
- If (shift <0)
- Return ~ 0UL;
- If (shift> = BITS_PER_LONG)
- Return 0UL;
- Return ~ 0UL> shift;
- }
First, the width of the total index is obtained from the height (number of layers of the tree) and the index bit width of each node (RADIX_TREE_MAP_SHlFT uses several digits for indexing, then convert the bit width to the maximum index (that is, the number of leaves ), for example, the maximum value of a 32-bit index is 2 ^ 32-bits, and the maximum index is 2 ^ (2*4)-1. (4-digit index, each node can have 2 ^ 4 = 16 branches (slot), so the second layer has 2 ^ (2*4) nodes, start from the leftmost end of the second layer, and the maximum index is 2 ^ (2*4)-1 ). You can call this function cyclically to obtain the maximum index values of trees of various heights stored in a static array height_to_maxindex. This is implemented by calling radix_tree_init ()-> radix_tree_init_maxindex () during initialization.
(2) Insert an object
The root parameter points to the root node, and the index indicates the page index, item:
- Int radix_tree_insert (struct radix_tree_root * root, unsigned long index, void * item)
- {
- Struct radix_tree_node * node = NULL, * sIot;
- Unsigned int height, shift;
- Int offset;
- Int error;
- BUG_0N (radix_tree_is_indirect_ptr (item ));
- // If the current index exceeds the maximum index of the tree, you must call radix_tree_extend () to extend the height of the tree until the maximum index can accommodate the index value in the parameter.
- If (index> radix_tree_maxindex (root-> height )){
- Error = radix_tree_extend (root, index );
- If (error)
- Return error;
- )
- SIot = radix_tree_indirect_to_ptr (root-> rnode );
- // Take the height of the current tree he-ght and the initial right shift of the page index
- Height = root-> height;
- Shift = (height-1) * RADIX_TREE_MAP_SHIFT;
- Offset = 0;/* uninitiaIised var warning */
- // Search from the height layer in a loop based on the index until the 1st layer node (subtree nodes are allocated as needed in the middle)
- WhiIe (height> 0 ){
- // If sIot is a null pointer, an intermediate node needs to be allocated.
- If (sIot = NULL ){
- /* Have to add a chiId node .*/
- If (! (Slot = radjx_tree_node_alloc (root) // call the SIab distributor to allocate a new node.
- Return-ENOMEM;
- Slot-> height = height; // you can specify the node height.
- // If the node is not empty, the new node is allocated as its subnode; otherwise, the new node is allocated as the root node.
- If (node ){
- // Add the newly allocated node pointer to the offset slot in the pointer array of the node
- Rcu_assign_pointer (node-> sIots [offset], slot );
- Node-> count ++; the number of child nodes of worker node increases by 1
- } Else
- Rcu_assign_pointer (root-> rnode, radix_tree_ptr_to_indirect (sIot ));
- }
- // Adjust the index. The node and sIot go down (the sIot points to the subnode of the node), adjust the shift count, and the height is reduced by 1.
- Offset = (index> shift) & RADIX_TREE_MAP_MASK; // calculate the slot of the data items in the current layer based on the data item index. For example, if the index is 32 bits, the key is set to 4 bits, the slot location of the data item in the top layer is the slot location corresponding to the first four bits, and the slot location corresponding to the second layer (from top to bottom) is the slot location corresponding to the next four bits.
- Node = sIot;
- SIot = node-> sIots [offset];
- Shift-= RADIX_TREE_MAP_SHIFT;
- Height --;
- )
- /* Bit indexes of node at Layer 2. Point the slot (array item) corresponding to the set to the object indicated by item to complete object insertion */
- If (node ){
- Node-> count ++;
- Rcu_assign_pointer (node-> sIots [offset], item );
- ...
- }
(3) Delete an object:
- Void * radi × _ tree_deIete (struct radix_tree_root * root, unsignedIong index)
- {
- /* Use the path array to store node pointers and indexes along the search path. The length of the array is the maximum path length (the maximum height of the number) + 1, an extra NULL pointer (used as a sentry )*/
- Struct radix_tree_path path [RADIX_TREE_MAX_PATH + 1], * pathp = path;
- Struct radix_tree_node * slot = NULL;
- Struct radix_tree_node * to_free;
- Unsjgned int height, shift;
- Int tag;
- Int offset;
- // Height is initialized to the height of the tree
- Height = root-> height:
- // Check whether the index of the object to be deleted is beyond the tree range
- If (index> radix_tree_maxindex (height ))
- Goto out;
- // The sIot class points to the root node. During the following process, the slot always points to an intermediate node.
- SIot = root-> rnode;
- // Return directly for an empty tree with a height of 0
- If (height = 0 ){
- Root_tag_clear_all (root );
- Root-> rnode = NULL:
- Goto out;
- }
- Slot = radix_tree_indirect_to_ptr (sIot );
- // Save the number of digits of the index to be shifted in shift
- Shift = (height-1) * RADIX_TREE_MAP_SHIFT;
- // Set the node of the first entry in the path array to null to indicate the whistle
- Pathp-> node = NULL;
- // This loop traverses the object corresponding to the id from the root node, and nodes and slots along the path are stored in the array pointed to by pathp.
- Do {
- If (sIot = NULL) // if a NULL pointer is encountered on the way (the specified object certainly does not exist), return directly
- Goto out;
- Pathp ++; // The path array pointer increments pathp-> node stores the groove index of the current node, and pathp-> node stores the current node
- Offset = (index> shift) & RADIX_TREE_MAP_MASK;
- Pathp-> offset = offset;
- Pathp-> node = sIot;
- // Obtain the pointer of the next node based on the index and adjust the shift count
- SIot = slot-> sIots [offset];
- Shift-= RADIX_TREE_MAP_SHIFT;
- Height --;
- } WhiIe (height> 0 );
- If (sIot = NULL)
- Goto out;
- ...
- To_free = NULL;
- /* This loop uses the pathp array record to traverse from the parent node of the object to the root node, the corresponding slot pointer is null (the underlying node slot pointer is null, that is, the object is deleted from the tree), the number of subnodes decreases, and all nodes with empty slots are released. In either case, the cycle ends: (1) the root node has been reached and processed; (2) the node with a number of subnodes not 0 */
- While (pathp-> node ){
- Pathp-> node-> sIots [pathp-> offset] = NULL;
- Pathp-> node-> count --;
- /* Queue the node for deferred freeina after the last reference to it disappears (set NULL, above )*/
- If (to_free)
- Radix_tree_node_free (to_free );
- // Encounter the number of subnodes not 0 node, if it is the root node, call the radix-tree_shrink () to try to contract the tree, and then exit the loop
- If (pathp-> node-> count ){
- If (pathp-> node = radix_tree_indirect_to_ptr (root-> rnode ))
- Radix_tree_shrink (root );
- Goto out;
- }
- // Node with zero slots in use so free it
- To_free = pathp-> node;
- Pathp --;
- }
- /* Running here indicates that the tree does not contain objects and becomes an empty tree. The root node of to_free is released. The tree height is set to 0, and the root pointer is set to null */
- Root_tag_clear_aIl (root );
- Root-> height = 0;
- Root-> rnode = NULL;
- If (to_free)
- Radix_tree_node_free (to_free );
- Out:
- Return sIot;
- }
(4) tree extension:
- StatIc int radix_tree_extend (struct radix_tree_root * root, unsigned Iong index)
- {
- Struct radix_tree_node * node;
- Unsigned int height;
- Int tag;
- // Increase the height by 1
- Height = root-> height + 1;
- The histogram loop compares the maximum index value and index of the tree. by increasing the height, the tree can accommodate the objects of the specified index.
- While (index> radix_tree_maxindex (height ))
- Height ++;
- // For an empty tree, leave it for future node allocation. Here, only adjust the height of the tree.
- If (root-> rnode = NULL ){
- Root-> height = height;
- Goto out;
- }
- Add a single subtree above the original root node. so that the tree height reaches the specified value, the root node is replaced by the root of the new subtree, and the leaf node of the new subtree points to the original root/node, the newly added node has the features that all slots except the slot 0 pointer pointing to the subnode are null pointers, that is, the leftmost single tree. this adjustment is equivalent to adding a 0 string to the high position of the original id string, so that the original object id value and its position in the new expansion tree still maintain a correct ing relationship.
- Do {
- Unsigned int newheight;
- If (! (Node = radix_tree_node_aIIoc (root )))
- Return-ENOMEM;
- /* Lncrease the height .*/
- Node-> slots [0] = radix_tree_indirect_to_ptr (root-> rnode );
- /* Propagate the aggregated tag info into the new root */
- For (tag = 0; tag
- If (root_tag_get (root, tag ))
- Tag_set (node, tag, 0 );
- }
- Newheight = root-> height + 1;
- Node-> height = newheight;
- Node-> count = 1;
- Node = radix_tree_ptr_to_indirect (node );
- Rcu_assign_pointer (root-> rnode, node );
- Root-> height = newheight;
- } WhiIe (height> root-> height ):
- Out:
- Return 0;
- }
(5) shrinkage of the tree.
Start from the root node and check the nodes that meet the condition that all the other slots pointers except 0th slots are null until the node at Layer n does not meet this condition ~ The n-1 layer of a single tree shrinks, and node f is released along the way to return to the slab distributor), and then the n-layer node is used as the new root node:
- Static inIine void radix_tree_shrink (struct radix_tree_root * root)
- {
- /* Try to shrink tree height */
- WhiIe (root-> height> 0 ){
- Struct radix_tree _ node * to _ free = root-> rnode;
- Void * newptr;
- BUG_0N (! Radix_free_is_indirect_ptr (to_free ));
- To_free = radix_tree_indirect_to_ptr (to_free );
- // Exit the loop if the number of subnodes of the current node is not equal to 1
- If (to_free-> count! = 1)
- Break;
- // The sub-node does not point to the 0th slot or exit the loop.
- If (! To_free-> slots [0])
- Break;
- // Newptr stores the to_free unique sub-node pointer
- Newptr = to_free-> slots [0];
- If (root-> height> 1)
- Newptr = radix_free_ptr_to_indirect (newptr );
- // Subnode as the New Root Node
- Root-> rnode = newptr;
- Root-> height --; // The height of the tree decreases
- /* Must only free zeroed nodes into the sIab */
- Tag_clear (to_free, 0, 0 );
- Tag_cIear (to_free, 1, 0 );
- Release to_free Node
- To_free-> sIots [0] = NULL;
- To_free-> count = 0;
- Radix_tree_node_free (to_free );
- }
- }
(6) query objects by page index:
- Void * radix_tree_lookup (struct radix_tree_root * root, unsigned long index)
- {
- Unsigned int height, shift;
- Struct radix_tree_node * node, ** slot;
- Node = rcu_dereference (root-> rnode );
- ...
- Height = node-> height;
- If (index> radix_tree_maxindex (height ))
- Return NULL;
- // Set the number of digits of the initial shift
- Shlft = (height-1) * RADIX_TREE_MAP_SHlFT;
- /* Perform layer-by-layer search from the top-down loop. The index, shift, and bit mask are used to obtain the slot index, and then the current node and the slot index obtain the slot sIot. then, the node points to the lower-layer node indicated by the node pointer slot, and finally adjusts the shift and current height until it reaches layer 0. At this time, the node points to the object */
- Do {
- SIot = (struct radix_tree_node **) (node-> sIots + (index> shift) & RADIX_TREE_MAP_MASK ));
- Node = rcu_dereference (* sIot );
- If (node = NULL)
- Return NULL;
- Shift-= RADIX_TREE_MAP_SHlFT;
- Height --;
- } WhiIe (height> 0 );
- Return node;
- }
2.2Linux \ Iib \ idr. c
The idr mechanism is basically a set of methods of the base tree, but it has more functions to find idle IDs, so it cannot completely copy the above mechanism.
Code analysis is omitted.
3. File page caching of peripheral functions 3.1
The add_to_page_cache () function calls radix_tree_insert () to insert a specified page to the specified location of the specified file page cache base tree. find_get_page () searches for the page with the specified index in the base tree of the address space. Both functions are included in Linux \ mm \ filemap. c. Their operations on the base tree are write and read respectively. Because the base tree function in radix_tree.c does not have the synchronization method, their peripheral functions must be called to include the synchronization measures. the two peripheral functions use the Read and Write locks of the address space. add_to_page_cache () calls write_lock_irq (& mapping-> tree_lock) before calling radix_tree_insert, find_get_page () calls read_lock_irq (& mapping-> tree_lock); to lock the read.
3.2 idr mechanism of ipc
Ipc_findkey () calls idr_find () to traverse the base tree from 0 until the object with the specified key value is found. ipc_addid () calls idr_get_new () add the object to the idr tree and return the id corresponding to the location. ipc_rmid () calls idr_remove () to delete the object with the specified id from the idr tree. These functions include Linux \ ipc \ unic. c. Their synchronization problems are guaranteed by the Read and Write semaphores used by the outermost ipc function. For example, the call path of ipc_rmid () is shm_close ()-> shm_destroy ()-> shm_rmid () -> Use down_write (& shm_ids (ns) in ipc_rmid () and shm_close ). rw_mutex); the shared memory ids are locked, which sacrifices a certain degree of concurrency, but ensures data consistency. in future versions, it is estimated that more fine-grained locks or better concurrency mechanisms will be used. Similarly, the call path of ipc_addid () is sys_shmget ()-> ipcget ()-> ipcget_new ()-> newseg ()-> shm_addid ()-> ipc_addid (), in ipcget_new (), down_write (& ids-> rw_mutex) is also used; write locks the entire ids.
5. Conclusion
For the data structure of the object located by id, the fixed array is the most direct and the fastest. And shift with logical operations
The combination of operations is followed by the hash list of hash functions. However, arrays are suitable for scenarios where the number of objects does not change much or the maximum number of objects is not many. arrays are not suitable for scenarios where the object distribution is sparse. Otherwise, memory waste is serious; however, when querying, inserting, or deleting a hash table, the entire table must be locked. Frequent sharing may lead to poor concurrent performance. In addition, the uniqueness of the location and id ing is missing, this method is not applicable to scenarios where IDs need to be automatically generated. The base tree learns from each other. Its search performance is within the acceptable range, and its memory consumption is not large. It is also dynamic and can be scaled down or expanded as needed. More importantly, it has a unique ing relationship between location and id like an array, so that it is easy to generate an id value when a new object is added, which is not in the hash list. in addition, many such trees can be created in the system, which improves the concurrency performance.