Linux学习笔记:从进程到线程

Linux学习笔记从进程到线程

 

不管是内核态线程还是用户态进程,他们的创建流程中都调用了do_fork()这个系统调用,但是为什么我们说内核态只有线程没有进程?现在探讨下进程和线程的联系和差别。

 

一.各自创建的函数调用

用户进程的创建函数调用流程:

用户态:Fork() -> clone() -> 内核态:sys_clone()-> do_fork()

内核线程的创建流程:

内核态入口1:

  kthread_create()

  |

    &kthread_create_list

  |

start_kernel()->rest_init()->kernel_thread()->kthreadd()->create_kthread()->kernel_thread()->do_fork()->kthread()->usr_entry()

 

内核态入口2:kernel_thread()->do_fork()->usr_entry()

可以看到:

a. Linux通过一个统一的入口do_fork()把内核线程和用户进程的实现进行了归一。实际上通过传不同的参数给do_fork()决定了这次创建的是进程还是线程。

b.与用户态不同,内核态提供给了用户两种创建线程的选择:入口分别为kthread_createkernel_thread

 

二.那么用户进程和内核线程的差别具体体现在哪里呢?

1. 是否共享地址空间

我们知道,是否共享地址空间,决定了这次do_fork出来的究竟是线程还是进程,内核态的所有进程”,其实都是线程,因为他们都共享同一个内核地址空间。这是由内核线程创建函数kernel_thread()决定的:

/*

 * Create a kernel thread.

 */

pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)

{

     struct pt_regs regs;

 

     memset(®s, 0, sizeof(regs));

 

     regs.ARM_r1 = (unsigned long)arg;

     regs.ARM_r2 = (unsigned long)fn;

     regs.ARM_r3 = (unsigned long)do_exit;

     regs.ARM_pc = (unsigned long)kernel_thread_helper;

     regs.ARM_cpsr = SVC_MODE;

 

     return do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);

}

  不管内核以何种方式创建新进程,最终都会走到上面的kernel_thread()->do_fork()流程

do_fork()调用传递的参数CLONE_VM,决定了内核的所有新建进程的地址空间都是共享的。

#define CLONE_VM  0x00000100    /* set if VM shared between processes */

因此,内核只存在线程。

 

2. 入口的差别:

   进程:可以看到,用户态进程对do_fork的系统调用对用户来说直接创建的,使用方式比较简单,就是说任何一个进程都可以创建一个子进程,同时这个子进程的父进程就是调用fork()的那个进程。

 

    线程:内核提供了两个常用创建线程的接口kernel_thread()kthread_create(),这两种创建线程的方式,他们存在着明显的差别:

1kernel_thread()比较简单,直接调用do_fork(),创建的新线程继承了kernel_thread()的信号量等上下文,并且调用kernel_thread()的线程成为了新建线程的父进程,比如:

若线程A调用kernel_thread()创建了一个新的线程B,则AB的父进程,并且B的入口函数继承了A的上下文环境。这样一来,内核中由kernel_thread()创建的线程,他们的父进程不一定相同。

   kernel_thread()创建的线程会立即执行。

 

2kthread_create()的实现相对比较复杂一些,不会直接创建新线程,而是把新线程的信息放入一个链表,然后唤醒守护任务:“kthreadd“,由这个守护任务来调用kernel_thread()创建新线程:

kthread_create()

{

    …
     list_add_tail(&create.list, &kthread_create_list);  /*放入待创建线程链表,并没有真正创建线程*/

   …

    wake_up_process(kthreadd_task); /*唤醒kthreadd线程,让他来实现真正的创建 */
   ....
    wait_for_completion(&create.done); /*等待create_kthread中的完成*/

}
 

看看Kthreadd做了些什么:

Kthreadd()

{

    /* Setup a clean context for our children to inherit. */

    /*作为所有新建线程的父亲,这里保证了所有新建线程有一个”纯种的基因”*/

    set_task_comm(tsk, "kthreadd");

    ignore_signals(tsk);

    set_user_nice(tsk, KTHREAD_NICE_LEVEL);

    set_cpus_allowed_ptr(tsk, CPU_MASK_ALL_PTR);

 

    current->flags |= PF_NOFREEZE | PF_FREEZER_NOSIG;//设置当前进程状态为可中断的状态,这种睡眠状态可响应信号处理等

  ….

           while (!list_empty(&kthread_create_list)) {    /*轮询待创建线程信息*/

….

              create_kthread(create);        /*进行一次线程创建*/  

       ….
}

看看create_kthread做了些什么:

create_kthread(create);

{

    int pid;

    /* We want our own signal handler (we take no signals by default). */

    /*新建在共享内存空间的基础上,进一步共享了文件系统空间和打开文件句柄*/

    pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);

  ….
   {
   /*使用完成量completion机制等待完成*/

   wait_for_completion(&create->started);


     * root may have changed our (kthreadd's) priority or CPU mask.

     * The kernel thread should not inherit these properties.

     */

    /*这些本来可以继承Kthreadd的入口,但是再设置防止更改*/

      sched_setscheduler(create->result, SCHED_NORMAL, ¶m);

      set_user_nice(create->result, KTHREAD_NICE_LEVEL);

      set_cpus_allowed_ptr(create->result, CPU_MASK_ALL_PTR);
    }
      complete(&create->done);  /*通知kthread_create() 线程创建完成*/
}
kthread()
{
 /* OK, tell user we're spawned, wait for stop or wakeup */
   /*这里已经是被创建子线程的运行空间了, 而这里把任务设置为了TASK_UNINTERRUPTIBLE 并调用schedule()
   使得用户的入口函数threadfn(data)不能马上被执行  */
   __set_current_state(TASK_UNINTERRUPTIBLE);
   complete(&create->started);
   schedule(); /*进入睡眠,等待唤醒*/


    /*走到这里,说明已经被(wake_up_process()?)唤醒*/
   if (!kthread_should_stop())
      ret = threadfn(data);   /*用户子进程真正参数和入口函数在这里被传递和执行。*/
}
总结一下:
a.如果线程A调用kthread_create()创建一个新线程B,那么B既不是A的子线程,也不会继承A的上下文。”kthreadd”这个守护线程的子线程,也继承”kthreadd”的上下文。

  换句话说:所有被kthread_create()创建新线程,都是”kthreadd”这个守护线程的子线程,也只继承”kthreadd”的上下文。

b. kthread_create()创建新线程不会立即执行用户的入口函数,而是等待用户去唤醒他。

 

3. 内核线程与用户进程相关的其他差别:

1> 内核线程始终运行在内核态,永远不会切换到用户态。而用户态进程可能通过系统调用切换到内核态,系统调用结束后还原为用户态。

2> 用户态进行如果不共享地址空间,会使用到写时拷贝技术。内核线程不共享空间就用不到。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值