Kernel task_struct

Understanding task_struct in the Linux Kernel - Navigating the .children and .sibling Fields

The original release is on Ed.


When delving into the intricacies of the Linux operating system, one of the fundamental structures you’ll encounter is the task_struct. This structure serves as the backbone for representing processes within the kernel, encapsulating all the essential information about a process, such as its state, scheduling information, memory usage, and more. Mastery of task_struct is crucial for anyone looking to contribute to kernel development or deepen their understanding of process management in operating systems.

The Role of task_struct in Process Management

At its core, task_struct is the kernel’s way of keeping track of every process running on the system. Each process, whether it’s a user application or a kernel thread, is represented by its own task_struct instance. This structure contains various fields that store vital data about the process, including its unique process identifier (PID), parent process, state, priority, and pointers to other related processes.

One of the more complex aspects of task_struct is how it manages relationships between processes, particularly the parent-child hierarchy. Understanding the interplay between the .children and .sibling fields is essential for navigating this hierarchy effectively.

Decoding the .children and .sibling Fields

In the Linux kernel, processes are organized in a hierarchical tree structure, where each process can have multiple child processes. This hierarchy is managed using linked lists, with each task_struct maintaining pointers to its children and siblings.

task_struct

The .children Field: Entry Point to Child Processes

The .children field in a task_struct points to the first child of a given process. It’s important to note that this field is not a direct pointer to a child process but rather serves as the sentinel node of the linked list containing all child processes. In other words, .children acts as an anchor point from which all child processes can be traversed.

The .sibling Field: Linking Sibling Processes

Each child process has a .sibling field that links it to the next sibling in the list. This creates a chain of child processes, allowing for iteration over all children of a parent process. And looping back to the sentinel node, the .children field of their parent process, ensures that all children are accounted for.

Quiz: Traversing the Process Tree

After studying the diagram, try answering the following questions to test your understanding of the .children and .sibling fields.

Consider the following process hierarchy currently running on the system, taken from the output of the ps command (with appropriate options):

\_ bash
    \_ A
    |   \_ B
    |   |   \_ C
    |   \_ D
    |   |   \_ E
    |   \_ F
    |       \_ G
    \_ H
        \_ I

The following kernel function, given a task_struct pointer, prints the name of the task’s parent and the task itself.

void print_tasks_0(struct task_struct *proc) {
  pr_info("%s\n", proc->parent->comm);
  pr_info("%s\n", proc->comm);
}

When invoked with the task_struct pointer of process “I”, it generates the following output in the kernel log buffer:

H
I

The followings present some variations of print_tasks_0. For each kernel function, write the output generated by the function if we pass in the task_struct pointer of process “D”.

(1) Write the output when proc points to D’s task_struct.

void print_tasks_1(struct task_struct *proc) {
  struct list_head *cur;
  struct task_struct *p;

  cur = proc->parent->children.next;
  while (1) {
    p = container_of(cur, struct task_struct, sibling);
    pr_info("%s\n", p->comm);
    cur = p->children.next;
    if (cur == &p->children)
      break;
  }
}

(2) Write the output when proc points to D’s task_struct.

void print_tasks_2(struct task_struct *proc) {
  struct list_head *cur;
  struct task_struct *p;

  cur = proc->parent->children.next;
  while (cur != &proc->parent->children) {
    p = container_of(cur, struct task_struct, sibling);
    pr_info("%s\n", p->comm);
    cur = p->sibling.next;
  }
}

(3) Write the output when proc points to D’s task_struct.

void print_tasks_3(struct task_struct *proc) {
  struct list_head *cur;
  struct task_struct *p;

  cur = proc->sibling.next;
  while (cur != &proc->sibling) {
    p = container_of(cur, struct task_struct, sibling);
    pr_info("%s\n", p->comm);
    cur = p->sibling.next;
  }
}

Check your answers to see if you’ve got them right!

(1) print_tasks_1 (2) print_tasks_2 (3) print_tasks_3
B B UNPREDICTABLE
C D
F

For future OS course teaching teams, please contact me if you need an editable version of the diagram or the full-length quiz. And feel free to link to this page if you want to share it.

Alex Jiakai XU
Alex Jiakai XU
Computer Science Student

My research interests include computer systems, programming languages, software designing, and cyberspace security.