进程与内存1-内核线程建立

原创 2013年12月03日 11:03:32

以前写的东西,感觉写的过于繁琐,从这以后尽量写的简洁明朗一点,也有助于我自己以后再看。(内核版本linux-3.2.36)

我们要解决的问题。

1.      简单描述内核线程创建过程。

2.      为什么kthread_create()调用后,我们还要调用wake_up_process来唤醒调用我们的线程函数,用户态并不需要。

 

kthread_create(threadfn, data, namefmt,arg...);

这是创建内核进程的主要函数。

它会唤醒kthread线程去建立线程,然后通过一个完成量等待完成,如果创建成功会设置调度方法(默认用nomal)和优先级(默认为0)。

kthread最终会调用kernel_thread,其实你可以用kernel_thread直接创建线程,可以上网看看如何使用,也很简单。

kernel_thread是依赖处理器架构的,但是最终都是调用do_fork(),我看了x86和arm都是一样的调用参数

do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0,&regs, 0, NULL, NULL);

所谓的依赖处理机主要体现在regs,regs在arm中是什么,我们下面再看。先记住它。

 

下面看do_fork,网上有do_fork的分析,但是和我这个版本好像都不一样。

我会省略一些代码。只做说明

long do_fork(unsigned long clone_flags,

              unsigned long stack_start,

              struct pt_regs *regs,

              unsigned long stack_size,

              int __user *parent_tidptr,

              int __user *child_tidptr)

{

       struct task_struct *p;

       int trace = 0;

       long nr;

       在开始分配东西之前,做一些初步的论证和权限检查

 

        确定那个事件报告给tracer。但下面的条件要成立。

       (likely(user_mode(regs)) && !(clone_flags & CLONE_UNTRACED))

       条件的意思是用户模式且没有CLONE_UNTRACED,即不是通过kernel_thread创建。

       在arm上,user_mode为(((regs)->ARM_cpsr & 0xf) ==0) 看看arm的cpsr你就知道了,用户模式判断

 

       p = copy_process(clone_flags, stack_start, regs, stack_size,

                         child_tidptr, NULL,trace);

     

       下面会判断是否有效,如果有效p,向下

       然后分配PID。

根据CLONE_PARENT_SETTID,新创建的子进程的ID号登记到父进程的相关字段中。

根据CLONE_VFORK初始化完成量,这个完成量在mm_release时向父进程发送信号,唤醒父进程。

audit_context初始化,其实和父进程的一样,这是审计上下文,

/*

每个进程的进程结构(或者进程上下文)含有审计上下文结构audit_context指针,审计上下文记录进程上下文的审计信息,当进程从进入系统调用和 退出系统调用时,使用审计上下文结构audit_context记录系统调用进入和退出的各种属性数据,如:时间戳、参数、调用号等。审计上下文还通过辅 助数据结构链表记录进程运行中的各种关键数据结构的审计信息。详细在此http://book.51cto.com/art/200712/62881.htm

*/

为了防止在creation时被追踪,我们设置了PF_STARTING标准,现在清除它。

调用wake_up_new_task():唤醒一个新的task。这个函数主要是将新的进程加入到进程调度队列并设此进程为可被调度的,以后这个进程可以被进程调度模块调度执行。

但是现在执行的不是我们的线程函数,我们的线程还要等到调用wake_up_process()再运行。

下面会解释。

根据tace标志,告知ptracer

根据CLONE_VFORK,让父进程freezer。

}

 

现在我们还有p =copy_process(clone_flags, stack_start, regs, stack_size,

                child_tidptr, NULL, trace);

这个函数也有好多网上的分析,我分析我关心的,其他的自己看看:

http://hi.baidu.com/zengzhaonong/item/df005e13633205a4feded523

我说一些:

调用p = dup_task_struct(current);

这个为新进程创建一个内核栈、thread_info结构和task_struct,这些值与当前进程的值相同。

还有一个copy_mm(clone_flags,p);

这个函数在这我们先记住就可以了,因为设置了CLONE_VM,这个函数只是把父进程的mm和mm_active赋值给子进程。内核线程是没有用户空间的mm。mm为空。即使是用户线程,在这也是空,因为写时拷贝机制。

 

下面还有一句

retval = copy_thread(clone_flags, stack_start,stack_size, p, regs);

这个要对应于kernel_thread。上面说了这个基于平台。我会以arm平台作为讲解。没有兴趣的就不要往下看了

copy_thread是使用参数regs初始化子进程的内核堆栈空间

我们先看kernel_thread

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

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

{

       struct pt_regs regs;

 

       memset(&regs, 0, sizeof(regs));

 

       regs.ARM_r4 = (unsigned long)arg; //这个arg是一个结构体,里面记录我们的线程函数,传入参数,还有创建线程时要等待的完成量

       regs.ARM_r5 = (unsigned long)fn;//这个是kthread,内核提供的,先记住它。

        regs.ARM_r6 = (unsignedlong)kernel_thread_exit;//我的就是do_exit

       regs.ARM_r7 = SVC_MODE | PSR_ENDSTATE | PSR_ISETSTATE;//看下面

       regs.ARM_pc = (unsigned long)kernel_thread_helper;

       regs.ARM_cpsr = regs.ARM_r7 | PSR_I_BIT;//结合上面就是禁止IRQ,系统模式。有个E位,我的arm920t没有这个位。

 

       return do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, &regs, 0, NULL,NULL);

}

kernel_thread_helper。我们看看它干了什么。

 

asm(   ".pushsection .text\n"//这是elf段堆栈操作命令,它将当前段(及子段)推入段堆栈的顶部。

"      .align\n"//对齐

"      .type   kernel_thread_helper,#function\n"//函数符号

"kernel_thread_helper:\n"

#ifdef CONFIG_TRACE_IRQFLAGS//这个不看

"      bl      trace_hardirqs_on\n"

#endif

"      msr     cpsr_c, r7\n"//设置cpsr_c,这个和cpsr区别是屏蔽0~7位。即只保留N Z C V位

"      mov     r0, r4\n"//传入函数的参数

"      mov     lr, r6\n"//退出时的函数。

"      mov     pc, r5\n"//执行r5函数

"      .size   kernel_thread_helper, . -kernel_thread_helper\n"//设置内存大小,为0

"      .popsection");//堆栈顶段出栈

从上面看kernel_thread_helper会执行我们自定义的线程函数。

它是如何调用,这个问题应该是调度的问题。不过还是简单说一下

 

我们看看copy_thread,就看几句

       struct thread_info *thread = task_thread_info(p);

       struct pt_regs *childregs = task_pt_regs(p);

 

       *childregs = *regs;

       childregs->ARM_r0 = 0;

       childregs->ARM_sp = stack_start;//传入的栈地址

从上面看除了栈地址不一样,其他都一样。

       memset(&thread->cpu_context, 0, sizeof(struct cpu_context_save));

       thread->cpu_context.sp = (unsigned long)childregs;//让这个进程的thread_info的sp指向这个regs,这个就是上面kernel_thread赋值的。

       thread->cpu_context.pc = (unsigned long)ret_from_fork;

       …

       if (clone_flags & CLONE_SETTLS)

                thread->tp_value =regs->ARM_r3;

CLONE_SETTLS是子进程创建新的TLS(thread-local storage);

上面的structthread_info:每个任务都有一个thread_info结构,放在内核栈的尾端。

看到上面的cpu_context.pc了吧,它是进程下一次调度时的指令开始地址。所以下一次会执行ret_form_fork,不细看了,它会从堆栈中找到kernel_thread_helper地址并执行。

 

现在让我们弄清楚为什么内核线程要调用wake_up_process后再能运行。为什么上面已经调用wake_up_new_task(),但是没有运行我们的线程函数。在调度时运行的是kthread。

static int kthread(void *_create)

{

       /* Copy data: it's on kthread's stack */

       struct kthread_create_info *create = _create;

       int (*threadfn)(void *data) = create->threadfn;//我们的模块线程函数

       void *data = create->data;//传入参数

       struct kthread self;

       int ret;

 

       self.should_stop = 0;

       self.data = data;

       init_completion(&self.exited);

       current->vfork_done = &self.exited;

 

       /* OK, tell user we're spawned, wait for stop or wakeup */

       __set_current_state(TASK_UNINTERRUPTIBLE);//在这设置为睡眠,所以你要通过调用wake_up_process来唤醒。

       create->result = current;

       complete(&create->done);//这个会通知上面的kthread_create(),没有它就会死机了。

       schedule();//再次调度,当然不会掉当前的函数。因为被设置为TASK_UNINTERRUPTIBLE

 

       ret = -EINTR;

       if (!self.should_stop)

                ret = threadfn(data);//等到什么时候可以运行了,才会到这。

 

       /* we can't just return, we must preserve "self" on stack */

       do_exit(ret);

}

线程与进程在内核中的实现

内核代码为2.6.35.13。 1      概述 进程与其对应的线程之间使用相同的内存空间、文件描述符和一些其他的东西。 2      分析 在内核中,线程与进程都是用结构体task_struct来...
  • styshoo
  • styshoo
  • 2015年07月22日 10:29
  • 786

Linux下的进程类别(内核线程、轻量级进程和用户进程)以及其创建方式--Linux进程的管理与调度(四)

本文声明 日期 内核版本 架构 作者 GitHub CSDN 2016-05-12 Linux-4.5 X86 & arm gatieme LinuxDevi...
  • gatieme
  • gatieme
  • 2016年05月23日 15:42
  • 8185

三种线程——内核线程、轻量级进程、用户线程

三种线程——内核线程、轻量级进程、用户线程 内核线程 内核线程就是内核的分身,一个分身可以处理一件特定事情。这在处理异步事件如异步IO时特别有用。内核线程的使用是廉价的,唯一使用的资源就是...
  • thinkone
  • thinkone
  • 2016年07月14日 21:57
  • 1428

内核线程、轻量级进程、用户线程三种线程概念解惑(线程≠轻量级进程)

转载 关于进程、线程和轻量级进程的一些笔记 [维基百科-轻量级进程]https://en.wikipedia.org/wiki/Light-weight_process#See_a...
  • gatieme
  • gatieme
  • 2016年05月23日 15:29
  • 5518

内核线程、轻量级进程、用户线程的区别和联系

 这是一篇关于Linux内核的线程、轻量级进程和用户线程区别与联系的相当不错的文章,强烈建议阅读...... 内核线程 内核线程只运行在内核态,不受用户态上下文的拖累。 ...
  • zhuzeji
  • zhuzeji
  • 2015年02月28日 10:49
  • 764

内核线程与普通进程的区别

内核线程与普通进程的区别 1. 内核线程没有地址空间,这通过将mm指针设为NULL来实现。也就是说内核线程是没有用户上下文的进程。 (Kernel threads do not have a p...
  • yj4231
  • yj4231
  • 2013年09月18日 10:18
  • 3627

内核线程和普通进程的区别

内核线程的主要作用 1. 周期性的将dirty内存页同步到磁盘设备上。 比如 bpflush线程周期性的把dirty数据写回磁盘 2. 内存页很少的情况下,把内存page 交换到磁盘空间。 比如ks...
  • kickxxx
  • kickxxx
  • 2013年07月01日 11:03
  • 2087

守护进程(内核线程和普通进程)

内核线程(thread)或叫守护进程(daemon), 在操作系统中占据相当大的比例,当Linux操作系统启动以后,尤其是Xwindow也启动以后,你可以用”ps”命令查看系统中的进程,这时会发现很多...
  • hunanchenxingyu
  • hunanchenxingyu
  • 2014年05月05日 23:45
  • 4295

进程与内存2-内核线程应用(简单实例)

这个节是建立内核线程的例子(linux-3.2.36) 就是循环打印,有几个宏我说明一下 KERNEL_THREAD: 用kernel_thread()建立线程 KTHREAD_CREATE: ...
  • xxxxxlllllxl
  • xxxxxlllllxl
  • 2013年12月09日 09:39
  • 1462

关于“内核线程”、“用户线程”概念的理解

今天偶然谈起了进程的相关概念,发现其中有许多不清晰的地方,现就以上的概念做一些研究,所参考的资料全部来自于网络,所以对于其中不正确的地方,欢迎大家给我指正,让我能够对以上概念更加清晰。...
  • u012927281
  • u012927281
  • 2016年06月09日 22:50
  • 6623
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:进程与内存1-内核线程建立
举报原因:
原因补充:

(最多只允许输入30个字)