内核线程

转载 2012年03月26日 23:03:26

原文链接 http://blog.csdn.net/yunsongice/article/details/5522012


按照传统UNIX规定的一些操作系统标准,一些重要的任务需要由进程来周期性地执行。这些任务包括刷新磁盘高速缓存,交换出不用的页框,维护网络连接等等。那么,由于这些系统进程只运行在内核态,所以Linux将他改造了,跟一般的进程不一样了,给它取个名称叫内核线程(kernel thread)。这个内核线程跟普通进程最大的区别就是,只运行在内核态,不受不必要的用户态上下文的拖累。在Linux中,内核线程在以下几个方面不同于普通进程:


1、内核线程只运行在内核态,而普通进程既可以运行在内核态,也可以运行在用户态。
2、因为内核线程只运行在内核态,它们只使用大于PAGE_OFFSET的线性地址空间。另一方面,不管在用户态还是在内核态,普通进程可以用4GB的线性地址空间。

创建一个内核线程:
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{
    struct pt_regs regs;
    int err;

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

    regs.ebx = (unsigned long) fn;
    regs.edx = (unsigned long) arg;

    regs.xds = __USER_DS;
    regs.xes = __USER_DS;
    regs.orig_eax = -1;
    regs.eip = (unsigned long) kernel_thread_helper;
    regs.xcs = __KERNEL_CS;
    regs.eflags = X86_EFLAGS_IF | X86_EFLAGS_SF | X86_EFLAGS_PF | 0x2;

    /* Ok, create the new process.. */
    err = do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);
    if (err == 0) /* terminate kernel stack */
        task_pt_regs(current)->eip = 0;
    return err;
}

kernel_thread函数创建一个新的内核线程,它接受的参数有:所要执行内核函数的地址(fn)、要传递给函数的参数(arg)、一直clone标志(flags)。该函数本质上以下面的方式调用do_fork:
do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, &regs, 0, NULL, NULL)

CLONE_VM标志避免复制调用进程的页表:由于新内核线程无论如何都不会访问用户态地址空间,所以这种复制无疑会造成时间和空间的浪费。

CLONE_UNTRACED标志保证不会有任何进程跟踪内核线程,因为任何跟踪都无意义。

传递给do_fork的参数regs表示该内核线程(本质上是个内核态进程)的各寄存器的值。kernel_thread函数把ebx和edx分别设置为参数fn和arg的值,以使内核栈栈顶成为该函数的入口。

把regs.xds和regs.xes设置成__USER_DS,为什么这么放,《执行进程间切换 》博文中写得很清楚,这里不再话下。

把eip寄存器的值设置为kernel_thread_helper(同样是位于/arch/i386/kernel/entry.S文件中,记住,跟进程相关的汇编语言都在该文件中)汇编语言代码段的地址:
movl %ebx, %eax
pushl %edx
call *%ebx
pushl %eax
call do_exit

因此,新的内核线程开始执行fn(arg)函数,如果该函数结束,内核线程执行系统调用_exit(),并把fn(arg)函数的返回值传递给它。下面,我们来看看几个重要的内核线程:

(1)0号进程

系统引导时,从无到有创建一个内核线程。这个祖先进程使用下列静态分配的数据结构:
- 存放在init_task变量中的进程描述符,由INIT_TASK(tsk)宏完成对它的初始化。
- 存放在init_thread_union变量中的thread_info描述符和内核堆栈。由INIT_THREAD_INFO宏完成对它们的初始化。
- 由进程描述符指向下列表:
    init_mm
    init_fs
    init_files
    init_signals
    init_sighand
- 这些表分别由下列宏进行初始化:
    INIT_MM
    INIT_FS
    INIT_FILES
    INIT_SIGNALS
    INIT_SIGHAND
- 主内核页全局目录存放在swapper_pg_dir中。

start_kernel()函数初始化内核需要的所有数据结构,激活中断,创建1号进程(内核线程),称为init进程:
kernel_thread(init, NULL, CLONE_FS|CLONE_SIGHAND);

新创建的内核线程有PID了,其值为1,并与0号进程共享每进程所有的内核数据结构。此外,当调度程序选择到它时,init进程开始执行init()函数。

创建init进程后,进程0执行cpu_idle()函数,该函数本质上是在开中断情况下重复执行hlt汇编指令。只有当没有其他进程处于TASK_RUNNING状态时,调度程序才选择进程0。

(2)1号进程

由进程0创建的内核线程执行init()函数,init()依次完成内核初始化。init()调用execve()系统调用装入可执行程序init。结果,init内核线程成为一个普通进程,且拥有自己的每进程内核数据结构。在系统关闭之前,init进程一直存活,因为它创建和监控在操作系统外层执行的所有进程的活动。

(3)keventd进程

执行keventd_wq工作队列中的函数。

(4)kapmd

处理与高级电源管理相关的事件。

(5)kswapd

执行内存周期回收。

(6)pdflush

刷新“脏”缓冲区的内容到磁盘以回收内存。

(7)kblockd

执行kblockd_workqueue工作队列中的函数。实质上,它周期性地激活块设备驱动程序。

(8)ksoftirqd

运行tasklet;系统中每个CPU都有这样一个内核线程。

相关文章推荐

用户级线程和内核级线程的区别

1 .内核级线程:切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户态;可以很好的利用smp,即利用多核cpu。windows线程就是这样的。  2. 用户级线程...

内核线程、轻量级进程、用户线程

转载:http://www.cnitblog.com/tarius.wu/articles/2277.html 转载:http://www.fansoo.com/blog/2011/kernel-t...

内核级线程与用户级线程异同

这几天在和同学讨论的时候,对于内核线程和用户线程概念上出现了问题,在网上搜索了不少文章,感觉还是不是我想要的,下来查看了以前学习的操作系统才弄 明白,在这里将我的理解写出来让大家看看,和大家分享分享...

易语言查看内核线程

linux内核线程对信号的处理过程

原文:http://blog.csdn.net/dog250/article/details/5303238 linux中的线程分为用户线程和内核线程,用户线程是标准的线程,完全的自主性...

XIP与内核线程文档

  • 2011-03-21 22:47
  • 290KB
  • 下载

等待队列和内核线程 在触摸屏代码中的应用

相关的头文件 #include "tpd.h" #include #include #include #include #include #includ...

ucore操作系统实验lab4 -- 内核线程管理

练习一:分配并初始化一个进程控制块(需要编码)   alloc_proc函数(位于kern/process/proc.c中)负责分配并返回一个新的struct proc_struct结构,用于存储新建...

linux内核线程创建销毁机制

这个话题乍一听貌似比较大,其实线程创建本身就是一件很平常的事情。 下面将要介绍的是,新版linux中创建内核线程的机制做了一些变化(其实本质没变,最终还是调用do_fork()来实现),和控制线程的...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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