什么是内核支持线程,什么是用户级线程

转载 2011年05月19日 19:28:00

线程的实现可以分为两类:用户级线程(User-Level Thread)和内核线线程(Kernel-Level Thread).后者又称为内核支持的线程或轻量级进程.

用户线程指不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。

 

内核线程:   由操作系统内核创建和撤销。内核维护进程及线程的上下文信息以及线程切换。一个内核线程由于I/O操作而阻塞,不会影响其它线程的运行。Windows   NT和2000/XP支持内核线程  
   
   
  用户线程:由应用进程利用线程库创建和管理,不以来于操作系统核心。不需要用户态/核心态切换,速度快。操作系统内核不知道多线程的存在,因此一个线程阻 塞将使得整个进程(包括它的所有线程)阻塞。由于这里的处理器时间片分配是以进程为基本单位,所以每个线程执行的时间相对减少。

Windows NT OS/2 支持内核线程。 Linux 支持内核级的多线程

-----------------------

关于内核线程(kernel_thread)

 

唐宇 carino@mail.ustc.edu.cn

我们知道Linux内核使用内核线程来将内核分成几个功能模块,
像kswapd,kflushd等,系统中的init进程也是由idle进程调用
kernel_thread()来实现产生的.

我们先来看看内核线程的实现,再来分析内核线程的性质.
int kernel_thread(int(*fn)(void*arg),void *arg,int flags)
{
long retval,d0;

__asm__ __volitate__(
"movl %%esp,%%esi/n/t"
"int $0x80/n/t"
"cmpl %%esp,%%esi/n/t"
"je 1f /n/t"
"movl %4,%%eax/n/t"
"pushl %%eax/n/t"
"call *%5/n/t"
"movl %3,%0/n/t"
"int $0x80/n/t"
"1:/t"
:"=&a"(retval),"=&S"(d0)
:"0"(__NR_clone),"i"(__NR_exit),
"r"(arg),"r"(fn),
"b"(flags | CLONE_VM)
:"memory"
);

return retval;
}

这段代码翻译成直观的ASM码:
{
movl __NR_clone,%0;
movl __NR_exit,%3;
movl arg,%4;
movl fn,%5;
movl flags|CLONE_VM,%ebx;
mov %%esp,%%esi;
int $0x80;

cmpl %%esp,%%esi;
je 1f;
movl %4,%%eax;
pushl %%eax
call *%5;
movl %3,%0;
int $0x80;
1: movl %%eax,retval
movl %%esi,d0
}

它的伪C码为:
int kernel_thread()
{
pid=clone(flags);
if(child)
{
fn(arg);
exit(0);
}
return pid;
}

从上面的代码可以看出,内核线程有以下性质:
1.
内核线程是通过系统调用clone()来实现的,使用CLONE_VM标志(用户还可以
提供其他标志,CLONE_PID,CLONE_FS,CLONE_FILES等),因此内核线程与调用
的进程(current)具有相同的进程空间.

2.
由于调用进程是在内核里调用kernel_thread(),因此当系统调用返回时,子进程也处于
内核态中,而子进程随后调用fn,当fn退出时,子进程调用exit()退出,所以子进程是在
内核态运行的.

3.
由于内核线程是在内核态运行的,因此内核线程可以访问内核中数据,调用内核函数.
运行过程中不能被抢占等等.

请注意在kernel_thread是如何调用系统调用的,我们知道kernel_thread是在内核中
调用,所以他是可以直接调用系统调用的,像sys_open()等,但是在这里kernel_thread
通过系统调用门(int$80)来间接调用clone()函数,就提出以下问题:
1.为什么这样?
2.如果我们直接调用sys_clone()会有什么样的结果呢?

int kernel_thread()
{
int pid;
pid=sys_clone();
if(!pid)
{

exit();
}
return pid;
}


这样,当子进程获取CPU资源时(运行时),从ret_from_fork恢复执行,栈布局对于子进程而言
是不对的,问题在于当子进程运行到RESTORE_ALL的IRET,仔细想一想栈布局的变化.

由sys_clone()的申明可知调用sys_clone需要pt_regs的栈结构,如果我们直接调用sys_clone
是没用办法做到的(如果可以我们也需要精心为它准备栈,//:-(,真是伤神)
同理,其他的类似系统调用,我们也必须通过int$80的系统调用门来实现.
而对于sys_execl,sys_open,sys_close,sys_exit,则可以直接调用.//xixi,我们可以
改动kernel_thread来测试sys_exit是否可以直接调用,同时也可以使用sys_clone的直接调用
来证明我们的分析是否正确.

而如果我们使用系统调用门(int$80)来解决问题,我们使用同样的方法来分析:
A2)
ebx <-- ( esp after save all ,ready for syscalls )
ecx
...
oldeip <-- ( esp before SAVE_ALL which construct stack for syscalls )
oldcs
eflags
d0 <- ( space for local variables )
retval
fn <- ( arguments for kernel_thread )
arg
clone_flags
eip <- ( retore ip for kernel_thread )
..

由于kernel_thread在内核的代码段中,所以没有发生栈切换,所有的压栈/退栈都是在
内核栈中进行的.请注意这样栈中便没有(OLDSS,OLDESP),所以在kernel_thread声明了
两个局部参数(retval,d0),对于retval的意义是明显的,而d0大概是(dummy local
variable
0,...n)的意思吧,:)


B2)子进程运行前:
子进程的TSS,栈布局

ebx <- esp
ecx
...
oldeip
oldcs
eflags
d0 <- (局部变量d0)
retval <- (局部变量retval)


运行到RESTORE_ALL时,将恢复CPU各寄存器,当运行到IRET时,
由于在相同特权等级的转移,所以没有发生特权级切换,所以ESP,SS没有发生变化.

BTW,由上面的分析可知,kernel_thread创建的进程是不能转到用户态运行的.
-------------------------------
 

 

Linux 为内核代码和数据结构预留了几个页框。这些页永远不会 被转出到磁盘上。从 0x0 到 0xc0000000 (PAGE_OFFSET) 的线性地址可由用户代码和内核代码进行引用。从 PAGE_OFFSET 到 0xffffffff 的线性地址只能由内核代码进行访问。

这意味着在 4 GB 的内存空间中,只有 3 GB 可以用于用户应用程序。

 

我已经向您展示了(32 位架构上的) Linux 内核按照 3:1 的比率来划分虚拟内存:3 GB 的虚拟内存用于用户空间,1 GB 的内存用于内核空间。内核代码及其数据结构都必须位于这 1 GB 的地址空间中,但是对于此地址空间而言,更大的消费者是物理地址的虚拟映射。

 

某一个进程只能运行在用户方式(user mode )或内核方式(kernel mode )下。用户程序运行在用户方式下,而系统调用运行在内核方式下。在这两种方式下所用的堆栈不一样:用户方式下用的是一般的堆栈,而内核方式下用的是固定大小的堆栈(一般为一个内存页的大小)

 

所有的进程部分运行与用户态,部分运行于系统态。底层的硬件如何支持这些状态各不相同但是通常有一个安全机制从用户态转入系统态并转回来。用户态比系统态的权限低了很多。每一次进程执行一个系统调用,它都从用户态切换到系统态并继续执行。这时让核心执行这个进程。

 

fork 一个进程的时候,必须建立进程自己的内核页目录项(内核页目录项要
与用户空间的的页目录放在同一个物理地址连续的页面上,所以不能共享,但
所有进程的内核页表与进程0 共享

两个代表 (code 和 data/stack)是内核空间从[0xC000 0000] (3 GB)到[0xFFFF FFFF] (4 GB) //线形地址
两个代表 (code 和 data/stack)是用户空间从[0] (0 GB) 到 [0xBFFF FFFF] (3 GB)         //线性地址

通常情况下,内核是不会调用用户层的代码,要想实现这逆向的转移,一般做法是在用户进程的核心栈(tss->esp0)压入用户态的SS,ESP,EFLAGS,CS,EIP,伪装成用户进程是通过陷阱门进入核心态,之后通过iret返回用户态。

在 32 位线形地址中的 4 GB 虚拟空间中,其中有 1 GB 作为 内核空间,从 3G—4G。 每个进程都有自己的 3 G 用户空间,它们共享1GB 的内核空间。当一个进程从用户空间进入内核空间时,它就不再有自己的进程空间了。

应用程序在虚拟内存中布局,并且有一块很大的栈空间。当然是用来保存函数调用历史及当前函数中的自动变量的。而相反,内核具有非常小的栈,它可以只和一个 4096 字节的页那样大。我们自己的函数(如 LKM)必须和整个内存空间调用链一同共享这个栈。因此,声明大的自动变量并不是个好注意,若我们要大的结构,则应该在调用时动态分配。

对内核线程的虚拟空间总结一下:
1、创建的时候:
父进程是用户进程,则mm和active_mm均共享父进程的,然后内核线程一般调用daemonize适头舖 m
父进程是内核线程,则mm和active_mm均为
NULL
总之,内核线程的mm = NULL;进程调度的时候以此为依据判断是用户进程还是内核线程。

2、进程调度的时候
如果切换进来的是内核线程,则置active_mm为切换出去的进程的active_mm;
如果切换出去的是内核线程,则置active_mm为NULL。

linux在创建用户任务的时候,给每个任务都分配了一个kernel mode stack。一个运行在用户态的任务如果被一个IRQ打断,中断处理要做一次堆栈切换。这时linux好像使用了任务的kernel mode stack,也就是说linux系统中没有一个唯一的系统堆栈,而是每一个任务都有一个系统堆栈,中断处理的栈使用的就是被打断任务的系统堆栈。内核线程也是进程,只不过没有自己的用户空间,但task_struct和内核堆栈还是得有的,要不怎么运行呢?

[1. 内核在主动进行进程调度时,可以自己设置将要投入运行进程 的sp0为TSS段中的sp0,则该用户进程在进入内核后使用的是它自身的系统堆栈,但如果cpu运行在某一用户进程时,而为另一用户进程服务的外部中断 发生了,在进入内核后使用的是当前用户进程的系统堆栈,还是中断服务的另一用户进程的系统堆栈呢 ?
2操作系统映象是否拥有自己的堆栈空间?还是利用用户进程的系统堆栈?

回答:  1。外部中断不是为某个用户进程服务的,是为整个操作系统服务的,它始终用当前进程的核心堆栈。
2。用用户进程的系统堆栈。]

 

. Kernel Thread 与 LWP的关系
首先线程和进程的概念(教科书中)是不同的,但是严格实现的角度来说去实现线程又不太现实。所以从实现线程一开始工程人员就是拿简化的进程(叫轻量进程)来模拟的线程,这涉及到进程所拥有的资源:
内核部分:一个进程应该拥有个PCB块+7k的内核栈(严格来说比7k稍微小点点),还包括信号栈等等资源,都在那个8k的两个页面里面。
核外部分:包含理论上4G的虚拟内存空间。
在实现内核线程的时候,部分书籍上认为没有核外部分资源的“进程”就叫内核线程了。而普通的线程是通过共享核外部分的虚存来做的(实现的时候就是走的不同接口copy指针即可)。
2. Kernel Thread 是怎样实现的 ? 不是简单的说几个函数
部分参考第1个问题的回答。创建内核线程的接口,应该是copy了对应的8k的两个页面,然后做了初始化,而核外的资源并没有获得到。
3. Unix 怎样调度这些 Kernel Thread?
有了上述的概念就比较清楚了,内核在调度的时候认的是PCB控制块,所以调度上是一样的。只是对于专门的kernel thread,实现的时候做了些手脚,让其每隔一小段间隔来运行,做些清理、刷新、统计类工作。如有个回写磁盘的内核线程就是个非常典型的例子。

 

转自:http://blog.sina.com.cn/s/blog_62467c4b0100g55p.html

 

更多解释:http://jsj.qhnu.edu.cn/os/bbs/PrintPost.asp?ThreadID=311

               http://book.51cto.com/art/201006/206946.htm

相关文章推荐

嵌入式常用算法:环形缓冲区算法

嵌入式下环形缓冲区的实现

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

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

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

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

[操作系统原理] 内核级线程与用户级线程异同

比如在有的系统中,特倍是一些数据库管理系统如IBM的infomix系统,所实现的用户级线程(UserLevel Threads ,ULT);而另一些系统如(Mac os的前身Macintosh和OS/...

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

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

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

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

【原创】从内核创建用户态线程

http://bbs.pediy.com/showthread.php?p=1304480#post1304480

linux进程的地址空间,核心栈,用户栈,内核线程

地址空间: 32位linux系统上,进程的地址空间为4G,包括1G的内核地址空间,和3G的用户地址空间。 内核栈: 进程控制块task_struct中保存了2个page大小的信息。 为...

线程实现的两种方式 —— 用户空间和内核中

在用户空间中实现线程特点: 整个线程包放入用户空间中,内核对线程包一无所知。从内核角度考虑,就是按正常的方式管理,即单线程进程 优点: 1、用户级线程包可以在不支持线程的操作系统上实现 2、线...
  • jyxmust
  • jyxmust
  • 2017年06月17日 11:18
  • 445
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:什么是内核支持线程,什么是用户级线程
举报原因:
原因补充:

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