线程的实现可以分为两类:用户级线程(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 Mode)是操作系统内核运行的环境,它拥有...
"系统线程(内核线程)和用户线程区别" 系统线程(内核线程)和用户线程是两种不同的线程模式,它们在实现和应用方面有很大的区别。 系统线程(内核线程)是由操作系统内核创建和撤销的线程,内核维护进程及线程的...
本文将深入探讨如何使用`eventfd`进行用户态与内核态之间的通信,同时也会提及到线程亲核(CPU affinity)的概念。 `eventfd`是Linux 2.6.29引入的一个系统调用,它创建了一个事件文件描述符,可以通过读写这个描述...
- **2.2 内核线程A到内核线程B**:在内核态下,通过更新线程控制块(TCB)中的信息,保存当前线程的状态,并加载新线程的TCB,从而切换到另一个内核线程的内核栈。 - **2.3 内核线程B到用户程序**:当内核线程B...
在用户态编程中,也应考虑线程安全问题。 6. **消息大小限制**:由于Netlink消息是通过网络协议栈传递的,它们受到最大传输单元(MTU)的限制。因此,大消息可能需要分割成多个小消息进行发送。 总之,Netlink是...
例如,进程和线程内核对象默认都是“未通知”状态,当进程或线程结束时,它们的状态会变为“已通知”。开发者可以利用这个特性来检测进程或线程是否还在运行。通过检查内核对象的状态,线程可以决定是否继续执行或...
在Linux 2.4及后续版本中,Netlink套接字成为了用户态和内核态交互的重要机制,尤其在处理中断过程与用户进程间的通信时表现突出。例如,iproute2这样的网络管理工具以及Netfilter包过滤框架都广泛使用了Netlink进行...
用户线程的一个显著特点是其创建和切换速度快,因为不需要涉及用户态和核心态之间的上下文切换。然而,这也带来了几个限制: 1. 当一个用户线程执行I/O操作或遇到页错误而被阻塞时,整个进程都会被阻塞,因为内核...
5. **性能优化**:内核态的优势在于减少了用户态和内核态之间的切换,因此需要充分利用这一点进行性能优化,例如,使用零拷贝技术减少数据复制,或者使用内核BPF(Berkeley Packet Filter)框架进行过滤和预处理。...
- **基于动态二进制转换(DBT)的内核态重构**:利用动态二进制转换技术,重构内核态代码,优化内核与用户态的交互。通过在编译或运行时修改二进制代码,DBT可以减少内核态的代码大小、减少系统调用次数、优化内存访问...
5. **低开销切换**:由于内核线程的调度在内核态进行,线程切换时开销较小,适合于高并发场景。 易语言查看内核线程的功能可能涉及到以下几个方面: 1. **线程枚举**:程序能够遍历并显示系统中的所有内核线程,...
而普通进程则具有独立的用户空间和内核空间,可以运行在用户态也可以切换到内核态执行内核空间的代码。 内核线程在创建时,通常由一个现有的内核线程通过特定的系统调用(如clone())来创建新的线程。系统调用时会...
在用户上下文环境中,可以通过以下方式实现内核态与用户态之间的通信: - **使用系统调用**:内核提供了一些系统调用接口,如`write()`和`read()`,允许用户态程序直接与内核交互。此外,还可以利用`copy_from_user...
- **劣势**:没有系统内核的支持,所有线程操作都需要在用户态和内核态之间切换,增加了复杂性和开销。 ##### 2.2 内核线程 - **定义**:内核线程是在内核态中实现的线程,需要内核的帮助。 - **特点**: - 各种...
6.用户态HOOK与UNHOOK |-RING3注射DLL到系统进程 |-RING3的INLINE HOOK和UNHOOK |-RING3的EAT HOOK和IAT HOOK ------------------------------ 7.反回调 |-枚举与删除创建进线程回调 |-枚举与删除加载映像回调 |-...
2. 睡眠及用户空间的同步,在内核执行的线程可能睡眠,会唤醒调度程序,从而导致调度一个新的用户进程执行。3. Linux 内核支持多处理器系统。所以,如果没有适当的保护,在两个或两个以上的处理器上运行的代码很可能...
6.用户态HOOK与UNHOOK |-RING3注射DLL到系统进程 |-RING3的INLINE HOOK和UNHOOK |-RING3的EAT HOOK和IAT HOOK ------------------------------ 7.反回调 |-枚举与删除创建进线程回调 |-枚举与删除加载映像回调 |-...
然而,当一个线程需要进行I/O操作或调用其他内核服务时,会导致整个进程进入内核态,此时如果其他用户线程也需要执行,它们将被阻塞,直到该线程完成内核操作返回。 - 内核级线程,也称为系统线程,由操作系统内核...