`
yiyanwan77
  • 浏览: 182930 次
  • 性别: Icon_minigender_1
  • 来自: 威海
社区版块
存档分类
最新评论

线程-内核态与用户态

 
阅读更多

线程的实现可以分为两类:用户级线程(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。用用户进程的系统堆栈。]


分享到:
评论

相关推荐

    linux使用eventfd进行用户态与内核态通信

    完整的linux使用eventfd进行用户态与内核态通信代码,里面还涉及linux用户态线程亲核,以及对应的内核态线程亲核问题。初学者,写了好几天,亲测,可用

    netlink内核态和用户态编程

    netlink编程,内核态和用户态编程。netlink编程,内核态和用户态编程。

    Linux 用户态与内核态的交互――netlink 篇

    netlink 套接字实现的,例如iprote2网络管理工具,它与内核的交互就全部使用了netlink,著名的内核包过滤框架Netfilter在与用户空间的通读,也在最新版本中改变为netlink,无疑,它将是Linux用户态与内核态交流的...

    Java与线程.pdf

    用户线程的优势在于不需要系统内核支持,但劣势在于没有系统内核的支持,所有的线程操作都需要在用户态和内核态之间切换。 内核线程是在内核态中实现的,需要内核的帮助。它的任务映射到各个处理器上,各种线程操作...

    驱动开发教程

    6.用户态HOOK与UNHOOK |-RING3注射DLL到系统进程 |-RING3的INLINE HOOK和UNHOOK |-RING3的EAT HOOK和IAT HOOK ------------------------------ 7.反回调 |-枚举与删除创建进线程回调 |-枚举与删除加载映像回调 |-...

    WIN64驱动编程基础教程

    6.用户态HOOK与UNHOOK |-RING3注射DLL到系统进程 |-RING3的INLINE HOOK和UNHOOK |-RING3的EAT HOOK和IAT HOOK ------------------------------ 7.反回调 |-枚举与删除创建进线程回调 |-枚举与删除加载映像回调 |-...

    [14本经典Android开发教程]-8-Linux内核阅读心得体会

    2 内核态与用户态的区别 55 读核感悟 同步问题 内核态自旋锁的实现 56 1自旋锁的总述 56 2非抢占式的自旋锁 56 3 锁的释放 57 4 与用户态的自旋锁的比较 57 5 总结 58 读核感悟 内存管理 free命令详解 58 读核感悟 ...

    哈工大操作系统实验8——内核级线程

    哈工大操作系统实验8——内核级线程。本次的实验仅完成了用户态的实现。内核级要实现实在困难,耗费巨大精力也不见得能有好的成效,而且重要的是内核级仅占一个。

    寒江独钓-Windows内核安全编程(高清完整版).part6

    11.6 在内核态完成功能的实现 363 11.6.1 请求的分发与实现 363 11.6.2 等待设备绑定完成与指定设备名 364 11.6.3 指派设备的完成 365 11.6.4 处理读请求 368 11.6.5 处理写请求 370 11.7 协议驱动的接收问题 374 ...

    Linux内核阅读

    2 内核态与用户态的区别...................................................55 读核感悟-同步问题-内核态自旋锁的实现.......................................56 1自旋锁的总述....................................

    寒江独钓-Windows内核安全编程(高清完整版).part1

    11.6 在内核态完成功能的实现 363 11.6.1 请求的分发与实现 363 11.6.2 等待设备绑定完成与指定设备名 364 11.6.3 指派设备的完成 365 11.6.4 处理读请求 368 11.6.5 处理写请求 370 11.7 协议驱动的接收问题 374 ...

    寒江独钓-Windows内核安全编程(高清完整版).part3

    11.6 在内核态完成功能的实现 363 11.6.1 请求的分发与实现 363 11.6.2 等待设备绑定完成与指定设备名 364 11.6.3 指派设备的完成 365 11.6.4 处理读请求 368 11.6.5 处理写请求 370 11.7 协议驱动的接收问题 374 ...

    哈工大 操作系统实验8 linux0.01内核级线程代码及相关提交

    最难得一个,本人只完成了用户态线程,内核态由于时间原因未完成留给聪明的你吧。这个是07级哈尔滨工业大学操作系统实验的辛勤劳动,下面的压缩包中包含源代码,及实验报告,最好自己搭建平台,本人在电脑中又装了 ...

    寒江独钓-Windows内核安全编程(高清完整版).part4

    11.6 在内核态完成功能的实现 363 11.6.1 请求的分发与实现 363 11.6.2 等待设备绑定完成与指定设备名 364 11.6.3 指派设备的完成 365 11.6.4 处理读请求 368 11.6.5 处理写请求 370 11.7 协议驱动的接收问题 374 ...

    寒江独钓-Windows内核安全编程(高清完整版).part7

    11.6 在内核态完成功能的实现 363 11.6.1 请求的分发与实现 363 11.6.2 等待设备绑定完成与指定设备名 364 11.6.3 指派设备的完成 365 11.6.4 处理读请求 368 11.6.5 处理写请求 370 11.7 协议驱动的接收问题 374 ...

    寒江独钓-Windows内核安全编程(高清完整版).part5

    11.6 在内核态完成功能的实现 363 11.6.1 请求的分发与实现 363 11.6.2 等待设备绑定完成与指定设备名 364 11.6.3 指派设备的完成 365 11.6.4 处理读请求 368 11.6.5 处理写请求 370 11.7 协议驱动的接收问题 374 ...

    Windows内核安全与驱动开发光盘源码

    13.3.2 在用户态通过DLL使用通信端口的范例 311 13.4 Minifilter的安装与加载 314 13.4.1 安装Minifilter的INF文件 314 13.4.2 启动安装完成的Minifilter 316 第14章 网络传输层过滤 317 14.1 TDI概要 317 ...

    寒江独钓-Windows内核安全编程(高清完整版).part2

    11.6 在内核态完成功能的实现 363 11.6.1 请求的分发与实现 363 11.6.2 等待设备绑定完成与指定设备名 364 11.6.3 指派设备的完成 365 11.6.4 处理读请求 368 11.6.5 处理写请求 370 11.7 协议驱动的接收问题 374 ...

    疯狂内核之——Linux虚拟内存

    3.1 用户态内存分配 117 3.1.1 mm_struct数据结构 118 3.1.2 内核线程的内存描述符 122 3.2 线性区的数据结构 123 3.2.1 线性区数据结构 123 3.2.2 红-黑树算法 126 3.2.3 线性区访问权限 128 3.3 线性区的底层处理 ...

    内核线程中获取接收到的信号

    在测试开发的内核模块时,发现了一个BUG:在模块没有卸载时使用reboot命令重启系统的话,系统重启不了,查看日志发现在...  在用户态获取阻塞的信号,调用的是sigpending(),因此首先尝试调用sys_sigpending()来获取。

Global site tag (gtag.js) - Google Analytics