Use of completion quantity in linux 3.10

Keywords: Linux data structure kernel

The completion quantity is designed based on the waiting queue, so it is obviously impossible to use the completion quantity in the interrupt context.

struct completion {
    unsigned int done;
    wait_queue_head_t wait;
};

Let's take a classic example of using completion quantity:

struct kthread_create_info
{
    /* Information passed to kthread() from kthreadd. */
    int (*threadfn)(void *data);
    void *data;
    int node;

    /* Result passed back to kthread_create() from kthreadd. */
    struct task_struct *result;
    struct completion done;

    struct list_head list;
};

In the example of creating a kernel thread, we used a kthread_create_info structure to encapsulate a completion quantity:

struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
                       void *data, int node,
                       const char namefmt[],
                       ...)
{
    struct kthread_create_info create;

    create.threadfn = threadfn;---------------The main function of the thread to create
    create.data = data;
    create.node = node;
    init_completion(&create.done);------------Dynamic initialization completion

    spin_lock(&kthread_create_lock);
    list_add_tail(&create.list, &kthread_create_list);-------------Adding a linked list is equivalent to hanging the request in a two-way circular linked list
    spin_unlock(&kthread_create_lock);

    wake_up_process(kthreadd_task);-----------Wake up the kernel thread that handles the completed volume to process the requests we send
    wait_for_completion(&create.done);--------Wait for completion, which will cause the process to sleep during the period of waiting for completion
. . . . . . . 

The above code is the side that submits the request, so how does the side that processes the request complete the task and notify the requester?

int kthreadd(void *unused)
{
    struct task_struct *tsk = current;

    /* Setup a clean context for our children to inherit. */
    set_task_comm(tsk, "kthreadd");
    ignore_signals(tsk);
    set_cpus_allowed_ptr(tsk, cpu_all_mask);
    set_mems_allowed(node_states[N_MEMORY]);

    current->flags |= PF_NOFREEZE;

    for (;;) {
        set_current_state(TASK_INTERRUPTIBLE);
        if (list_empty(&kthread_create_list))
            schedule();
        __set_current_state(TASK_RUNNING);

        spin_lock(&kthread_create_lock);
        while (!list_empty(&kthread_create_list)) {
            struct kthread_create_info *create;

            create = list_entry(kthread_create_list.next,
                        struct kthread_create_info, list);--------------Fetch request
            list_del_init(&create->list);------------Isolate requests from linked lists
            spin_unlock(&kthread_create_lock);-------Unlock, this lock ensures the serialization of join and release requests

            create_kthread(create);------------------Create thread

            spin_lock(&kthread_create_lock);
        }
        spin_unlock(&kthread_create_lock);
    }

    return 0;
}

Simply put, I don't see how to notify the requester. The code is actually creating_ Implemented in kthread:

static void create_kthread(struct kthread_create_info *create)
{
    int pid;

#ifdef CONFIG_NUMA
    current->pref_node_fork = create->node;
#endif
    /* We want our own signal handler (we take no signals by default). */
    pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
    if (pid < 0) {
        create->result = ERR_PTR(pid);
        complete(&create->done);-------------------Notifying the requester is generally wake-up
    }
}

The above is a classic example of using completion quantity. Two execution streams do not interfere with each other, and one passes through wait_for_completion to wait for the request to be completed, one through complete and one through complete_all and so on to notify the requester and complete the interaction.

In addition to dynamically initializing a completion quantity, there is also a static initialization method,

static __initdata DECLARE_COMPLETION(kthreadd_done);

For example, the kthreadd thread we will describe below is   rest_ When calling in init: (why is it called rest_init, the individual thinks it is, because mm, scheduler and so on have been initialized, and this initialization is left, so it is called rest_init).

, another feature of this function is that it will eventually call cpu_startup_entry(CPUHP_ONLINE); it is an endless loop, never exits, and always in the kernel state)

static noinline void __init_refok rest_init(void)
{
    int pid;

    rcu_scheduler_starting();
    /*
     * We need to spawn init first so that it obtains pid 1, however
     * the init task will end up wanting to create kthreads, which, if
     * we schedule it before we create kthreadd, will OOPS.
     */
    kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
    numa_default_policy();
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);------------Create process 2, that is kthradd Kernel thread
    rcu_read_lock();
    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
    rcu_read_unlock();
    complete(&kthreadd_done);------------Wake up blocked processes

Before kthreadd is created,

kernel_ init-->kernel_ init_ The freeable function uses the completion of static initialization    kthreadd_ Don, wait   Kthreadd kernel thread is created.

static noinline void __init kernel_init_freeable(void)
{
    /*
     * Wait until kthreadd is all set-up.
     */
    wait_for_completion(&kthreadd_done);

That is, in process 2, that is   Before kthreadd is created, process 1 is actually blocked. One question is, why should process 1 wait for process 2? Because assuming that process 1 does not wait, the user state process may pass

The system call is used to obtain resources. If these resources are maintained by thread 2 or a child thread of thread 2, oops is bound to occur. Therefore, the completion of this place plays a sequential role.

We can see that the creation interface of kernel thread is composed of   kthreadd kernel thread to complete fork,

 PS - EF | grep - I kthreadd root September 15, 2000? 00:00:00 [kthreadd]

The pid of this kernel thread is 2, and all other kernel threads are fork ed out by it. Because the init process occupies pid 1, its pid is 2.

Let's assume that if the init process with pid 1 does not execute in the end

if (!run_init_process("/sbin/init") ||
        !run_init_process("/etc/init") ||
        !run_init_process("/bin/init") ||
        !run_init_process("/bin/sh"))

So at this time, it is purely a kernel thread. It all works in the kernel state. Is it useful for an os without user state processes?

I think there is. There is no interaction. It's all in the kernel state. Well, if you put some tasks in the kernel, you can completely avoid user mode processes.

To sum up:

  • Process 1, also known as init process, is the ancestor of all user processes. Note that it is a user process, not a kernel process. The ancestor of the kernel process is kthreadd. This brother is responsible for fork ing all kernel threads.
  • Started by process 0_ Kernel calls rest_init create
  • The PID of the init process is 1. When the scheduler selects the init process, the init process starts to execute the kernel_init() function
  • Init is an ordinary user state process. It is the junction of Unix system kernel initialization and user state initialization. It is the ancestor of all user processes. Before running init, it is kernel initialization. The last action of this process (kernel initialization) is to run the / sbin/init executable.
  • The completion quantity can not only complete the role of communication, but also complete the role of timing control. It can not only complete the interaction by dynamic initialization, but also complete the interaction by static init.

Posted by jasraj on Sat, 04 Dec 2021 20:18:44 -0800