内核线程

按照传统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都有这样一个内核线程。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值