• linux 3.10中完成量的使用


    完成量是基于等待队列设计的,所以显然不能在中断上下文使用完成量。

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

    我们来看一个使用完成量的经典例子:

    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;
    };

    在创建内核线程的例子中,我们使用了一个kthread_create_info结构来封装了一个完成量:

    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;---------------要创建的线程的主函数
        create.data = data;
        create.node = node;
        init_completion(&create.done);------------动态初始化完成量
    
        spin_lock(&kthread_create_lock);
        list_add_tail(&create.list, &kthread_create_list);-------------加入链表,相当于把请求挂在一个双向循环链表中
        spin_unlock(&kthread_create_lock);
    
        wake_up_process(kthreadd_task);-----------唤醒处理完成量的内核线程,来处理我们发送的请求
        wait_for_completion(&create.done);--------等待完成,这个在等待完成量的期间,会导致本进程睡眠
    。。。。。。。

    如上代码是提交请求的一侧,那么,处理请求的一侧是怎么完成该任务,并通知到请求方呢?

    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);--------------取出请求
                list_del_init(&create->list);------------将请求从链表隔离
                spin_unlock(&kthread_create_lock);-------解锁,这个锁保证加入请求和解除请求的串行化
    
                create_kthread(create);------------------创建线程
    
                spin_lock(&kthread_create_lock);
            }
            spin_unlock(&kthread_create_lock);
        }
    
        return 0;
    }

    简单地看,没看到怎么通知请求方,代码其实是在create_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);-------------------通知请求方,一般就是唤醒了
        }
    }

    以上就是使用完成量的经典例子,两个互不干扰的执行流,一个通过wait_for_completion来等待请求完成,一个通过complete,还有complete_all等来通知请求方,完成交互。

    除了动态初始化一个完成量,还有一种静态初始化的方式,

    static __initdata DECLARE_COMPLETION(kthreadd_done);
     
    比如我们下面要描述的kthreadd线程,在 rest_init 中调用 的时候:(为啥叫rest_init,个人觉得是,因为包括mm,调度器之类的都已经初始化好了,就剩下这个初始化了,所以叫rest_init
    ,这个函数还有个特点就是,它最终会调用cpu_startup_entry(CPUHP_ONLINE); 死循环,永不退出,一直处于内核态)

    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);------------创建2号进程,也就是kthradd内核线程
        rcu_read_lock();
        kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
        rcu_read_unlock();
        complete(&kthreadd_done);------------唤醒被阻塞的进程

    在kthreadd 创建之前,

    kernel_init-->kernel_init_freeable函数会使用静态初始化的完成量  kthreadd_done 来等待 kthreadd 内核线程创建好。
    static noinline void __init kernel_init_freeable(void)
    {
        /*
         * Wait until kthreadd is all set-up.
         */
        wait_for_completion(&kthreadd_done);

    也就是说,在2号进程,也就是 kthreadd 被创建好之前,1号进程其实是阻塞的。有一个疑问就是,为什么1号进程要等待2号进程呢?因为假设1号进程不等待,那么用户态进程就可能通过

    系统调用来获取资源,而如果这些资源是由2号线程或者2号线程的子线程来维护的话,则必然产生oops。所以这个地方的完成量,起的是一个时序的作用。

    我们可以看到,内核线程的创建接口,是由 kthreadd 内核线程来完成fork的,

     ps -ef |grep -i kthreadd
    root         2     0  0 9月15 ?       00:00:00 [kthreadd]

    这个内核线程的pid是2,其他所有的内核线程都是它fork出来的,因为init进程占据了pid 1,所以它的pid是2。

    我们假设一下,如果pid 为1的init进程,最终不去执行

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

    那么它这个时候纯粹还是内核线程,它全部工作在内核态,没有用户态进程的os有没有用呢?

    我觉得是有的,没有交互罢了,全部在内核态。恩,如果你把一些任务放在内核里面完成,完全可以不要用户态进程嘛。

    总结一下:
    进程1又称为init进程,是所有用户进程的祖先,注意,是用户进程,不是内核进程,内核进程的祖先是kthreadd,这哥们负责fork所有的内核线程。
    由进程0在start_kernel调用rest_init创建
    init进程PID为1,当调度程序选择到init进程时,init进程开始执行kernel_init ()函数
    init是个普通的用户态进程,它是Unix系统内核初始化与用户态初始化的接合点,它是所有用户进程的祖宗。在运行init以前是内核态初始化,该过程(内核初始化)的最后一个动作就是运行/sbin/init可执行文件。
    完成量,既可以完成通信的作用,又可以完成时序控制的作用,既能够动态初始化来完成交互,又可能静态init来完成交互。
    水平有限,如果有错误,请帮忙提醒我。如果您觉得本文对您有帮助,可以点击下面的 推荐 支持一下我。版权所有,需要转发请带上本文源地址,博客一直在更新,欢迎 关注 。
  • 相关阅读:
    NBIbatis 微信框架
    NBIbatis 框架体系说明
    NBIbatis 基础框架
    .NET开发者必备的工具箱
    开源中国上一些有用的开源系统
    TfS+强制删除签出锁定项
    thinkphp支持大小写url地址访问,不产生下划线
    sqlserver 链接 ODBC 访问 MySql
    ibatis + log4net 配置注意事项
    Devexpress GridView内嵌dx:ASPxGridLookup取得控件值乱跳解决方案
  • 原文地址:https://www.cnblogs.com/10087622blog/p/9666929.html
Copyright © 2020-2023  润新知