• Linux进程管理 (篇外)内核线程简要介绍


    关键词:kthread、irq、ksoftirqd、kworker、workqueues

    在使用ps查看线程的时候,会有不少[...]名称的线程,这些有别于其它线程,都是内核线程。

    其中多数内核线程从名称看,就知道其主要功能。

    比如给中断线程化使用的irq内核线程,软中断使用的内核线程ksoftirqd,以及work使用的kworker内核线程。

    本文首先概览一下Linux都有哪些内核线程,然后分析创建内核线程的API。

    在介绍内核线程和普通线程都有哪些区别?

    最后介绍主要内核线程(irq/ksoftirqd/kworker/)的创建过程及其作用。

    1. ps下初步认识Linux内核线程

    在ps -a会显示如下,可以看出内核线程都用[...]标注。

    并且pid=1的init进程是所有用户空间进程的父进程;pid=2的kthreadd内核线程是所有内核线程的父线程。

    内核线程分为几大类:softirq、kworker、irq及其他。

    PID   USER     TIME   COMMAND
        1 0          0:01 {linuxrc} init
        2 0          0:00 [kthreadd]
        3 0          0:00 [ksoftirqd/0]
        4 0          0:00 [kworker/0:0]
        5 0          0:00 [kworker/0:0H]
        6 0          0:00 [kworker/u8:0]
        7 0          0:00 [rcu_sched]
        8 0          0:00 [rcu_bh]
        9 0          0:00 [migration/0]
       10 0          0:00 [migration/1]
       11 0          0:00 [ksoftirqd/1]
       12 0          0:00 [kworker/1:0]
       13 0          0:00 [kworker/1:0H]
       14 0          0:00 [migration/2]
       15 0          0:00 [ksoftirqd/2]
       16 0          0:00 [kworker/2:0]
       17 0          0:00 [kworker/2:0H]
       18 0          0:00 [migration/3]
       19 0          0:00 [ksoftirqd/3]
       20 0          0:00 [kworker/3:0]
       21 0          0:00 [kworker/3:0H]
       22 0          0:00 [khelper]
       23 0          0:00 [kdevtmpfs]
       24 0          0:00 [perf]
       25 0          0:00 [kworker/u8:1]
      279 0          0:00 [khungtaskd]
      280 0          0:00 [writeback]
      281 0          0:00 [kintegrityd]
      282 0          0:00 [kworker/0:1]
      284 0          0:00 [bioset]
      286 0          0:00 [kblockd]
      294 0          0:00 [ata_sff]
      408 0          0:00 [rpciod]
      409 0          0:00 [kworker/2:1]
      410 0          0:00 [kworker/1:1]
      412 0          0:00 [kswapd0]
      416 0          0:00 [fsnotify_mark]
      429 0          0:00 [nfsiod]
      449 0          0:00 [kworker/3:1]
      527 0          0:00 [kpsmoused]
      537 0          0:00 [kworker/1:2]
      613 0          0:00 [deferwq]

     2. kthreadd以及创建内核线程API

     2.1 kthreadd:kthreadd内核线程的创建

    内核其他线程的创立,要基于kthreadd。kthreadd线程是其他线程的父线程。

     start_kernel-->rest_init如下:

    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);--------------------------------创建第一个用户空间线程init
        numa_default_policy();
        pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);---------------创建第一个内核线程kthreadd
        rcu_read_lock();
        kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);--------------------kthreadd_task指向kthreadd的task_strcut结构体
        rcu_read_unlock();
        complete(&kthreadd_done);--------------------------------------------------在init进程kernel_init-->kernel_init_freeable中等待kthreadd_done释放
    
        /*
         * The boot idle thread must execute schedule()
         * at least once to get things moving:
         */
        init_idle_bootup_task(current);
        schedule_preempt_disabled();
        /* Call into cpu_idle with preempt disabled */
        cpu_startup_entry(CPUHP_ONLINE);
    }

    kernel_init在kthreadd之前启动,但是kernel_init的很多任务需要基于kthreadd。所以在kernel_init的开头等待reset_init的kthreadd_done完成量。

    因为kernel_init-->kernel_init_freeable-->do_basic_setup-->do_initcalls中很多初始化需要kthread_create支援。

    kernel_init-->kernel_init_freeable:
    static noinline void __init kernel_init_freeable(void)
    {
        /*
         * Wait until kthreadd is all set-up.
         */
        wait_for_completion(&kthreadd_done);-------------------等待kthreadd_done完成量
    ...

         do_basic_setup();---------------------------------------很多初始化需要kthread_create支持

    ...
    }

    内核中有一个线程kthreadd_task负责创建其他内核线程,这个线程的函数为kthreadd()。

    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();----------------------------------------------如果kthread_create_list为空,让出CPU,进入休眠状态。在kthread_create_on_node()中会将要创建进程节点加入到kthread_create_list中,然后唤醒此进程。
            __set_current_state(TASK_RUNNING);
    
            spin_lock(&kthread_create_lock);
            while (!list_empty(&kthread_create_list)) {------------------只要kthread_create_list不为空,遍历kthread_create_list链表
                struct kthread_create_info *create;
    
                create = list_entry(kthread_create_list.next,
                            struct kthread_create_info, list);
                list_del_init(&create->list);----------------------------从kthread_create_list中摘除当前create
                spin_unlock(&kthread_create_lock);
    
                create_kthread(create);----------------------------------创建线程
    
                spin_lock(&kthread_create_lock);
            }
            spin_unlock(&kthread_create_lock);
        }
    
        return 0;
    }
    
    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);----调用do_fork()创建线程
        if (pid < 0) {
            /* If user was SIGKILLed, I release the structure. */
            struct completion *done = xchg(&create->done, NULL);
    
            if (!done) {
                kfree(create);
                return;
            }
            create->result = ERR_PTR(pid);
            complete(done);--------------------------------------------------------触发complete事件
        }
    }
    
    
    pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
    {
        return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
            (unsigned long)arg, NULL, NULL);
    }

    2.2 创建内核线程接口:kthread_create等

    kthread_create()是最常见的创建内核线程的接口。

    kthread_create_on_cpu()相对于kthread_create多了个cpu,但都基于kthread_create_on_node()。

    kthread_run基于kthreadd_create,所以这些函数都基于kthread_create_on_node。

    #define kthread_create(threadfn, data, namefmt, arg...) 
        kthread_create_on_node(threadfn, data, -1, namefmt, ##arg)
    
    struct task_struct *kthread_create_on_cpu(int (*threadfn)(void *data),
                          void *data,
                          unsigned int cpu,
                          const char *namefmt);
    
    
    /**
     * kthread_run - create and wake a thread.
     * @threadfn: the function to run until signal_pending(current).
     * @data: data ptr for @threadfn.
     * @namefmt: printf-style name for the thread.
     *
     * Description: Convenient wrapper for kthread_create() followed by
     * wake_up_process().  Returns the kthread or ERR_PTR(-ENOMEM).
     */
    #define kthread_run(threadfn, data, namefmt, ...)               
    ({                                       
        struct task_struct *__k                           
            = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); 
        if (!IS_ERR(__k))                           --------------------------如果kthread_create()正确创建了一个进程,调用wake_up_process()唤醒它。
            wake_up_process(__k);                       
        __k;                                   
    })

    kthread_create_on_node()负责创建一个线程,填充一个kthread_create_info结构体;然后将此结构体作为一个节点插入kthread_create_list队尾。

    然后唤醒kthreadd_task进行处理,创建线程。

    struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
                           void *data, int node,
                           const char namefmt[],
                           ...)
    {
        DECLARE_COMPLETION_ONSTACK(done);
        struct task_struct *task;
        struct kthread_create_info *create = kmalloc(sizeof(*create),
                                 GFP_KERNEL);---------------------------------创建插入kthread_create_list的节点。
    
        if (!create)
            return ERR_PTR(-ENOMEM);
        create->threadfn = threadfn;
        create->data = data;
        create->node = node;
        create->done = &done;
    
        spin_lock(&kthread_create_lock);
        list_add_tail(&create->list, &kthread_create_list);-------------------将填充的节点插入kthread_create_list中。
        spin_unlock(&kthread_create_lock);
    
        wake_up_process(kthreadd_task);---------------------------------------唤醒kthread_task处理kthread_create_list链表,创建相应的线程。
        /*
         * Wait for completion in killable state, for I might be chosen by
         * the OOM killer while kthreadd is trying to allocate memory for
         * new kernel thread.
         */
        if (unlikely(wait_for_completion_killable(&done))) {------------------等待complete事件触发,在create_kthread()中触发。
            /*
             * If I was SIGKILLed before kthreadd (or new kernel thread)
             * calls complete(), leave the cleanup of this structure to
             * that thread.
             */
            if (xchg(&create->done, NULL))
                return ERR_PTR(-EINTR);
            /*
             * kthreadd (or new kernel thread) will call complete()
             * shortly.
             */
            wait_for_completion(&done);---------------------------------------等待complete事件触发。
        }
        task = create->result;------------------------------------------------创建的结果为task_struct结构体。
        if (!IS_ERR(task)) {
            static const struct sched_param param = { .sched_priority = 0 };
            va_list args;
    
            va_start(args, namefmt);
            vsnprintf(task->comm, sizeof(task->comm), namefmt, args);---------配置进程名称。
            va_end(args);
            /*
             * root may have changed our (kthreadd's) priority or CPU mask.
             * The kernel thread should not inherit these properties.
             */
            sched_setscheduler_nocheck(task, SCHED_NORMAL, &param);-----------设置进程调度策略为NORMAL,优先级为0。
            set_cpus_allowed_ptr(task, cpu_all_mask);
        }
        kfree(create);--------------------------------------------------------释放kthread_create_info。
        return task;
    }

    3. 内核线程和普通线程的区别

    内核线程没有地址空间,所以task_struct->mm指针为NULL。内核线程没有用户上下文。

    内核线程只工作在内核空间,不会切换至用户空间。但内核线程同样是可调度且可抢占的。

    普通线程即可工作在内核空间,也可工作在用户空间。

    内核线程只能访问3GB以上地址,而普通线程可访问所有4GB地址空间。

    4. irq、softirq、woker内核线程

    irq、softirq、worker都可能创建对应的内核线程,有线程就有优先级。

    下面从优先来来看看它们的重要性。

    可以看出中断内核线程优先级很高,为49,并且使用了实时调度策略。softirq和worker都是普通内核线程。

      prio policy
    irq 49 SCHED_FIFO
    softirq 120 SCHED_NORMAL
    worker 120 SCHED_NORMAL
    init 120 SCHED_NORMAL
    kthreadd 120 SCHED_NORMAL
    cfinteractive 0 SCHED_FIFO

    其它特殊内核线程init优先级为120,kthreadd优先级为120.

    cfinteractive优先级最高,主要处理CPU Frequency负载更新。

    4.1 irq/xx-xx:创建处理线程化中断的线程

     request_threaded_irq-->__setup_irq,可见如果设置了thread_fn,并且不允许中断嵌套,则创建一个类似"irq/中断号-终端名称"的线程。

    线程函数是irq_thread,

    /*
     * Internal function to register an irqaction - typically used to
     * allocate special interrupts that are part of the architecture.
     */
    static int
    __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
    {
    ...
        if (new->thread_fn && !nested) {
            struct task_struct *t;
            static const struct sched_param param = {
                .sched_priority = MAX_USER_RT_PRIO/2,
            };
    
            t = kthread_create(irq_thread, new, "irq/%d-%s", irq,----------------在irq_thread中调用irq_thread_fn,进而调用action->thread_fn,request_threaded_irq参数thread_fn。
                       new->name);
    ...
        }
    ...
    }

    request_irq是对request_threaded_irq的封装,创建中断线程的工作交给__setup_irq()

    static inline int __must_check
    request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
            const char *name, void *dev)
    {
        return request_threaded_irq(irq, handler, NULL, flags, name, dev);
    }

    更详细信息参考:《Linux中断管理 (1)Linux中断管理机制》中关于request_irq()介绍。

    4.2 ksoftirqd/xx:创建处理软中断线程

    软中断线程通过smpboot_register_percpu_thread注册softirq_threads创建。

    static struct smp_hotplug_thread softirq_threads = {
        .store            = &ksoftirqd,
        .thread_should_run    = ksoftirqd_should_run,
        .thread_fn        = run_ksoftirqd,
        .thread_comm        = "ksoftirqd/%u",
    };
    
    static __init int spawn_ksoftirqd(void)
    {
        register_cpu_notifier(&cpu_nfb);
    
        BUG_ON(smpboot_register_percpu_thread(&softirq_threads));
    
        return 0;
    }

    smpboot_register_percpu_thread-->__smpboot_create_thread,最终也还是调用kthread_create_on_cpu,创建了类似"ksoftirqd/xx"的内核线程,xx为cpuid号。

    从ps -a中可以看出创建的结果如下,可以看出每个CPU创建了一个ksoftirqd内核线程。

        3 0          0:03 [ksoftirqd/0]
       11 0          0:03 [ksoftirqd/1]
       15 0          0:00 [ksoftirqd/2]
       19 0          0:00 [ksoftirqd/3]

    更详细信息参考:  《Linux中断管理 (2)软中断和tasklet

    4.3 kworker:创建work的工作线程

    kwoker线程是处理work的工作线程,详细参考《Linux中断管理 (3)workqueue工作队列》。

    每个CPU都会创建自己的workqueue,用以集中处理内核kworker。

    workquuue就是把一些任务(work)推迟到一个或一组内核线程中去执行,那个内核线程被称为worker_thread。

    首先看看创建结果,可以看出在init_workqueues中创建了绑定CPU0的两个kworker,分别是nice=0和nice=-20。

    apply_workqueue_attrs创建unbund worker,即kworker/u8:0。

    然后在每个CPU_UP_PREPARE回调中创建两个不同nice的kworker。所以四个CPU一共9个内核线程。

    PID   USER     TIME   COMMAND
        1 0          0:01 {linuxrc} init
        2 0          0:00 [kthreadd]
        3 0          0:00 [ksoftirqd/0]
        4 0          0:00 [kworker/0:0]
        5 0          0:00 [kworker/0:0H]---------------init_workqueues-->create_worker
        6 0          0:00 [kworker/u8:0]---------------apply_workqueue_attrs-->alloc_unbound_pwq-->create_worker
        7 0          0:00 [rcu_sched]
        8 0          0:00 [rcu_bh]
        9 0          0:00 [migration/0]
       10 0          0:00 [migration/1]
       11 0          0:00 [ksoftirqd/1]
       12 0          0:00 [kworker/1:0]---------------workqueue_cpu_up_callback-->create_worker
       13 0          0:00 [kworker/1:0H]
       14 0          0:00 [migration/2]
       15 0          0:00 [ksoftirqd/2]
       16 0          0:00 [kworker/2:0]
       17 0          0:00 [kworker/2:0H]--------------workqueue_cpu_up_callback-->create_worker
       18 0          0:00 [migration/3]
       19 0          0:00 [ksoftirqd/3]
       20 0          0:00 [kworker/3:0]
       21 0          0:00 [kworker/3:0H]--------------workqueue_cpu_up_callback-->create_worker
       22 0          0:00 [khelper]
       23 0          0:00 [kdevtmpfs]
       24 0          0:00 [perf]
       25 0          0:00 [kworker/u8:1]--------------worker_thread-->create_worker
      279 0          0:00 [khungtaskd]
      280 0          0:00 [writeback]
      281 0          0:00 [kintegrityd]
      282 0          0:00 [kworker/0:1]---------------worker_thread-->create_worker
      284 0          0:00 [bioset]
      286 0          0:00 [kblockd]
      294 0          0:00 [ata_sff]
      408 0          0:00 [rpciod]
      409 0          0:00 [kworker/2:1]---------------worker_thread-->create_worker
      410 0          0:00 [kworker/1:1]---------------worker_thread-->create_worker
      412 0          0:00 [kswapd0]
      416 0          0:00 [fsnotify_mark]
      429 0          0:00 [nfsiod]
      449 0          0:00 [kworker/3:1]---------------worker_thread-->create_worker
      527 0          0:00 [kpsmoused]
      537 0          0:00 [kworker/1:2]---------------worker_thread-->create_worker
      613 0          0:00 [deferwq]

    init_workqueues-->create_worker-->kthread_create_on_node,创建"kworker/xx:xxH"内核线程。

    static int __init init_workqueues(void)
    {
        int std_nice[NR_STD_WORKER_POOLS] = { 0, HIGHPRI_NICE_LEVEL };
        int i, cpu;
    ...
        /* create the initial worker */
        for_each_online_cpu(cpu) {---------------------------------遍历CPU[0~3]
            struct worker_pool *pool;
    
            for_each_cpu_worker_pool(pool, cpu) {------------------NR_STD_WORKER_POOLS=2,所以每个CPU有两个pool
                pool->flags &= ~POOL_DISASSOCIATED;
                BUG_ON(!create_worker(pool));
            }
        }
    ...
        system_wq = alloc_workqueue("events", 0, 0);
        system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI, 0);
        system_long_wq = alloc_workqueue("events_long", 0, 0);
        system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND,
                            WQ_UNBOUND_MAX_ACTIVE);
        system_freezable_wq = alloc_workqueue("events_freezable",
                              WQ_FREEZABLE, 0);
        system_power_efficient_wq = alloc_workqueue("events_power_efficient",
                              WQ_POWER_EFFICIENT, 0);
        system_freezable_power_efficient_wq = alloc_workqueue("events_freezable_power_efficient",
                              WQ_FREEZABLE | WQ_POWER_EFFICIENT,
                              0);
        BUG_ON(!system_wq || !system_highpri_wq || !system_long_wq ||
               !system_unbound_wq || !system_freezable_wq ||
               !system_power_efficient_wq ||
               !system_freezable_power_efficient_wq);
        return 0;
    }

    create_worker()函数创建工作线程。 

    static struct worker *create_worker(struct worker_pool *pool)
    {
    ...
        if (pool->cpu >= 0)
            snprintf(id_buf, sizeof(id_buf), "%d:%d%s", pool->cpu, id,-------------cpuid和id,区分cpu和cpu内kworker。
                 pool->attrs->nice < 0  ? "H" : "");
        else
            snprintf(id_buf, sizeof(id_buf), "u%d:%d", pool->id, id);--------------u表示不指定cpu。
    
        worker->task = kthread_create_on_node(worker_thread, worker, pool->node,
                              "kworker/%s", id_buf);
    ...
    }

    更详细信息参考:《Linux中断管理 (3)workqueue工作队列》、《Linux workqueue工作原理》、《Concurrency Managed Workqueue之(一):workqueue的基本概念

    5. 其他内核线程

    rcu_sched、rcu_bh

    migration

    khelper

    kdevtmpfs

    perf

    writeback

    kintegrityd

    bioset

    kblockd

    ata_sff

    rpciod

    kswapd

    nfsiod

    kpsmpused

    deferwq

  • 相关阅读:
    html实现时间输入框
    使用textarea标签代替input标签可以实现输入框的大小调节,自动换行,滚动条显示
    requests模块的使用
    抓包工具Fiddler使用教程
    git 使用merge 对本地分支进行合并 并进行代码提交的流程
    js常用方法汇总
    js对象排序
    原生javasxript获取浏览器的滚动距离和可视窗口的高度
    深度对象拷贝
    curl扩展代码
  • 原文地址:https://www.cnblogs.com/arnoldlu/p/8336998.html
Copyright © 2020-2023  润新知