linux下的系统调用为什么不能使用linux 用户态 内核态堆栈

http://blog.chinaunix.net/uid-1829236-id-3182279.html
究竟什么是用户态,什么是内核态,这两个基本概念以前一直理解得不是很清楚,根本原因个人觉得是在于因为大部分时候我们在写程序时关注的重点和着眼的角度放在了实现的功能和代码的逻辑性上,先看一个例子:
1.&&&&&void&testfork(){&&
2.&&&&&if(0&=&=&fork()){&&
3.&&&&&printf(&create&new&process&success!\n&);&&
4.&&&&&}&&
5.&&&&&printf(&testfork&ok\n&);&&
6.&&&&&}&&
这段代码很简单,从功能的角度来看,就是实际执行了一个fork(),生成一个新的进程,从逻辑的角度看,就是判断了如果fork()返回的是则打印相关语句,然后函数最后再打印一句表示执行完整个testfork()函数。代码的执行逻辑和功能上看就是如此简单,一共四行代码,从上到下一句一句执行而已,完全看不出来哪里有体现出用户态和进程态的概念。
如果说前面两种是静态观察的角度看的话,我们还可以从动态的角度来看这段代码,即它被转换成CPU执行的指令后加载执行的过程,这时这段程序就是一个动态执行的指令序列。而究竟加载了哪些代码,如何加载就是和操作系统密切相关了。
熟悉Unix/Linux系统的人都知道,fork的工作实际上是以系统调用的方式完成相应功能的,具体的工作是由sys_fork负责实施。其实无论是不是Unix或者Linux,对于任何操作系统来说,创建一个新的进程都是属于核心功能,因为它要做很多底层细致地工作,消耗系统的物理资源,比如分配物理内存,从父进程拷贝相关信息,拷贝设置页目录页表等等,这些显然不能随便让哪个程序就能去做,于是就自然引出特权级别的概念,显然,最关键性的权力必须由高特权级的程序来执行,这样才可以做到集中管理,减少有限资源的访问和使用冲突。
特权级显然是非常有效的管理和控制程序执行的手段,因此在硬件上对特权级做了很多支持,就Intel x86架构的CPU来说一共有0~3四个特权级,0级最高,3级最低,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查,相关的概念有&CPL、DPL和RPL,这里不再过多阐述。硬件已经提供了一套特权级使用的相关机制,软件自然就是好好利用的问题,这属于操作系统要做的事情,对于&Unix/Linux来说,只使用了0级特权级和3级特权级。也就是说在Unix/Linux系统中,一条工作在级特权级的指令具有了CPU能提供的最高权力,而一条工作在3级特权级的指令具有CPU提供的最低或者说最基本权力。
3)用户态和内核态
现在我们从特权级的调度来理解用户态和内核态就比较好理解了,当程序运行在3级特权级上时,就可以称之为运行在用户态,因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;反之,当程序运行在级特权级上时,就可以称之为运行在内核态。
虽然用户态下和内核态下工作的程序有很多差别,但最重要的差别就在于特权级的不同,即权力的不同。运行在用户态下的程序不能直接访问操作系统内核数据结构和程序,比如上面例子中的testfork()就不能直接调用&sys_fork(),因为前者是工作在用户态,属于用户态程序,而sys_fork()是工作在内核态,属于内核态程序。
当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态,比如testfork()最初运行在用户态进程下,当它调用fork()最终触发&sys_fork()的执行时,就切换到了内核态。
2.&用户态和内核态的转换
1)用户态切换到内核态的3种方式
a.&系统调用
这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如前例中fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
b.&异常&&&&&&&&&&&&&
当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
c.&外围设备的中断
当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。
2)具体的切换操作
从触发方式上看,可以认为存在前述3种不同的类型,但是从最终实际完成由用户态到内核态的切换操作上来说,涉及的关键步骤是完全一致的,没有任何区别,都相当于执行了一个中断响应的过程,因为系统调用实际上最终是中断机制实现的,而异常和中断的处理机制基本上也是一致的,关于它们的具体区别这里不再赘述。关于中断处理机制的细节和步骤这里也不做过多分析,涉及到由用户态切换到内核态的步骤主要包括:
[1]&从当前进程的描述符中提取其内核栈的ss0及esp0信息。
[2]&使用ss0和esp0指向的内核栈将当前进程的cs,eip,eflags,ss,esp信息保存起来,这个
过程也完成了由用户栈到内核栈的切换过程,同时保存了被暂停执行的程序的下一
[3]&将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器,开始
执行中断处理程序,这时就转到了内核态的程序执行了。
原文地址:
http://blog.csdn.net/pizi0475/article/details/6284274
MS-DOS等操作系统在单一的CPU模式下运行,但是一些类Unix的操作系统则使用了双模式,可以有效地实现时间共享。在Linux机器上,CPU要么处于受信任的内核模式,要么处于受限制的用户模式。除了内核本身处于内核模式以外,所有的用户进程都运行在用户模式之中。
内核模式的代码可以无限制地访问所有处理器指令集以及全部内存和I/O空间。如果用户模式的进程要享有此特权,它必须通过系统调用向设备驱动程序或其他内核模式的代码发出请求。另外,用户模式的代码允许发生缺页,而内核模式的代码则不允许。
在2.4和更早的内核中,仅仅用户模式的进程可以被上下文切换出局,由其他进程抢占。除非发生以下两种情况,否则内核模式代码可以一直独占CPU:
(1) 它自愿放弃CPU;
(2) 发生中断或异常。
2.6内核引入了内核抢占,大多数内核模式的代码也可以被抢占。
下面是从网上贴过来的,可能说的更明白一些。
一、内核空间和用户空间&&&Linux简化了分段机制,使得虚拟地址与线性地址总是一致,因此,Linux的虚拟地址空间也为0~ 4G。Linux内核将这4G字节的空间分为两部分。将最高的1G字节(从虚拟地址0xCxFFFFFFFF),供内核使用,称为&内核空间&。而将较低的3G字节(从虚拟地址0xxBFFFFFFF),供各个进程使用,称为&用户空间&)。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。
二、内核态和用户态&&&当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。这与处于内核态的进程的状态有些类似。
三、进程上下文和中断上下文&&&处理器总处于以下状态中的一种:
1、内核态,运行于进程上下文,内核代表进程运行于内核空间;
2、内核态,运行于中断上下文,内核代表硬件运行于内核空间;
3、用户态,运行于用户空间。
用户空间的应用程序,通过系统调用,进入内核空间。这个时候用户空间的进程要传递很多变量、参数的值给内核,内核态运行的时候也要保存用户进程的一些寄存器值、变量等。所谓的&进程上下文&,可以看作是用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境等。
硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。所谓的&中断上下文&,其实也可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被打断执行的进程环境)。
http://blog.chinaunix.net/uid--id-4081104.html
&& linux下的系统调用如何从用户态进入内核态?这个问题一直以来都是模模糊糊,最近阅读了《程序员的自我修养》第十二章:系统调用与API,终于对此有了一个算是比较清醒和深刻的认识:总结了一下,画了一个图大致如下(注:没有内核态返回内核态流程):&&&&&&&&&&&&&&&&其中x86有两种方式从用户态陷入内核态:int 0x80系统调用以及新型的sysenter指令。本文暂且基于经典的int 0x80方式。对于用户态,地球人都知道我们写的C程序要调用到glibc库,与我们所理解的库调用并没有多大差异。但是对于glibc库如何陷入内核态,就需要详细的了解一下。&&&&& & 首先,glibc对系统调用做了一系列的封装,典型如fork()函数,比如我们所调用的fork()函数,在glibc中实际上定义的是如下一个宏:&&&&& & _syscall0(pid_t, fork);&&&&& & 这里有一系列的__syscallN宏定义,其中的N范围从0-6,这是因为x86在linux下支持的最多的参数个数为6个。每个宏对应着相应个数的参数。fork()没有参数,所以是_syscall0。这个宏的参数pid_t是返回值的类型,fork是函数的名字,用于下一步的扩展。其中i386版本的_syscall0宏定义如下:&&&&&&&&#define _syscall0(type,name)& &&&\{& &long __ & & & & & & & & & & & & &\&&&&__asm__ volatile("int 0x80" & &\&&&&& & : "=a" (__res) & & & & & & & & &\&&&&& & : "0" & (__NR_##name)); &\&&&&__syscall_return(type, __res);\}& &&&&&&&&&&& & 这就是采用gcc内联汇编的形式编写的_syscall0宏,通过这个宏,可以很明显的看出来是通过int 0x80方式触发中断,进入内核态中断处理。并指明使用eax来传递参数和返回值。而此处的参数就是系统调用号,所以由此可知,触发中断进入内核时,所有的系统调用号都是通过eax传递,这样在内核态,想知道是什么系统调用,直接查询eax寄存器就可以得到系统调用号了。&&&&& & 进入中断,查询中断号,根据安装的中断处理程序就可以得到这是一个系统调用,于是查询系统调用表,根据eax中传递的系统调用号就可以得到对应的系统调用。&&&&& & 内核处理完成之后,最后通过__syscall_return从用户态返回内核态。当然,返回值还是通过eax传递返回的。&&&&& & 至于用户态和内核态的堆栈的切换,记住其中最重要的一个宏SAVE_ALL,就可以查询到其中的切换了,此处也就不讲述。
阅读(...) 评论()君,已阅读到文档的结尾了呢~~
系统调用 系统调用失败 linux操作系统 linux操作系统下载 linux系统装win7 linux系统 linux系统下载 linux文件系统 linux 系统日志 linux 查看系统版本
扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
2012 第5章 Linux系统调用
举报该文档为侵权文档。
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
将文档分享至:
分享完整地址
文档地址:
粘贴到BBS或博客
flash地址:
支持嵌入FLASH地址的网站使用
html代码:
&embed src='/DocinViewer-4.swf' width='100%' height='600' type=application/x-shockwave-flash ALLOWFULLSCREEN='true' ALLOWSCRIPTACCESS='always'&&/embed&
450px*300px480px*400px650px*490px
支持嵌入HTML代码的网站使用
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口linux系统中一次用户态进程死循环案例的分析过程以及解决办法
作者:佚名
字体:[ ] 来源:互联网 时间:10-09 09:32:00
这篇文章主要为大家介绍了linux系统中用户态进程死循环的分析过程,业务进程(用户态多线程程序)挂死,操作系统反应迟钝,系统日志没有任何异常。从进程的内核态堆栈看,看似所有线程都卡在了内核态的堆栈流程,其实不是,那么如何分析造成死循环的原因?请看下文
1、问题现象
业务进程(用户态多线程程序)挂死,操作系统反应迟钝,系统日志没有任何异常。从进程的内核态堆栈看,看似所有线程都卡在了内核态的如下堆栈流程中:
[root@vmc116 ~]# cat /proc/27007/task/11825/stack
[&ffffffff8100baf6&] retint_careful+0x14/0x32
[&ffffffffffffffff&] 0xffffffffffffffff
2、问题分析
1)内核堆栈分析
从内核堆栈看,所有进程都阻塞在 retint_careful上,这个是中断返回过程中的流程,代码(汇编)如下:
entry_64.S
ret_from_intr:
DISABLE_INTERRUPTS(CLBR_NONE)
TRACE_IRQS_OFF
decl PER_CPU_VAR(irq_count)
/* Restore saved previous stack */
CFI_DEF_CFA rsi,SS+8-RBP
/* reg/off reset after def_cfa_expr */
leaq ARGOFFSET-RBP(%rsi), %rsp
CFI_DEF_CFA_REGISTER
CFI_ADJUST_CFA_OFFSET
RBP-ARGOFFSET
retint_careful:
CFI_RESTORE_STATE
$TIF_NEED_RESCHED,%edx
retint_signal
TRACE_IRQS_ON
ENABLE_INTERRUPTS(CLBR_NONE)
pushq_cfi %rdi
SCHEDULE_USER
popq_cfi %rdi
GET_THREAD_INFO(%rcx)
DISABLE_INTERRUPTS(CLBR_NONE)
TRACE_IRQS_OFF
jmp retint_check
这其实是用户态进程在用户态被中断打断后,从中断返回的流程,结合retint_careful+0x14/0x32,进行反汇编,可以确认阻塞的点其实就在SCHEDULE_USER这其实就是调用schedule()进行调度,也就是说当进程走到中断返回的流程中时,发现需要调度(设置了TIF_NEED_RESCHED),于是在这里发生了调度。有一个疑问:为什么在堆栈中看不到schedule()这一级的栈帧呢?因为这里是汇编直接调用的,没有进行相关栈帧压栈和上下文保存操作。
2)进行状态信息分析从top命令结果看,相关线程实际一直处于R状态,CPU几乎完全耗尽,而且绝大部分都消耗在用户态:[root@vmc116 ~]# toptop - 09:42:23 up 16 days,& 2:21, 23 users,& load average: 84.08, 84.30, 83.62Tasks: 1037 total,& 85 running, 952 sleeping,&& 0 stopped,&& 0 zombieCpu(s): 97.6%us,& 2.2%sy,& 0.2%ni,& 0.0%id,& 0.0%wa,& 0.0%hi,& 0.0%si,& 0.0%stMem:& k total, k used,&& 563388k free,&& 374152k buffersSwap: k total,&&& 38644k used, k free, k cached
& PID USER&&&&& PR& NI& VIRT& RES& SHR S %CPU %MEM&&& TIME+& COMMAND&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 27074 root&&&&& 20&& 0 m& 14m R 10.2& 0.5 321:06.17 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 27084 root&&&&& 20&& 0 m& 14m R 10.2& 0.5 296:23.37 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 27085 root&&&&& 20&& 0 m& 14m R 10.2& 0.5 337:57.26 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 27095 root&&&&& 20&& 0 m& 14m R 10.2& 0.5 327:31.93 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 27102 root&&&&& 20&& 0 m& 14m R 10.2& 0.5 306:49.44 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 27113 root&&&&& 20&& 0 m& 14m R 10.2& 0.5 310:47.41 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 25730 root&&&&& 20&& 0 m& 14m R 10.2& 0.5 283:03.37 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 30069 root&&&&& 20&& 0 m& 14m R 10.2& 0.5 283:49.67 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 13938 root&&&&& 20&& 0 m& 14m R 10.2& 0.5 261:24.46 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 16326 root&&&&& 20&& 0 m& 14m R 10.2& 0.5 150:24.53 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &6795 root&&&&& 20&& 0 m& 14m R 10.2& 0.5 100:26.77 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 27063 root&&&&& 20&& 0 m& 14m R& 9.9& 0.5 337:18.77 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 27065 root&&&&& 20&& 0 m& 14m R& 9.9& 0.5 314:24.17 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 27068 root&&&&& 20&& 0 m& 14m R& 9.9& 0.5 336:32.78 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 27069 root&&&&& 20&& 0 m& 14m R& 9.9& 0.5 338:55.08 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 27072 root&&&&& 20&& 0 m& 14m R& 9.9& 0.5 306:46.08 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 27075 root&&&&& 20&& 0 m& 14m R& 9.9& 0.5 316:49.51 z_itask_templat&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& ...
3)进程调度信息从相关线程的调度信息看:[root@vmc116 ~]# cat /proc/27007/task/11825/schedstat 68 612 3557465[root@vmc116 ~]# cat /proc/27007/task/11825/schedstat 13 241 3557509[root@vmc116 ~]# cat /proc/27007/task/11825/schedstat 31 315 3557686[root@vmc116 ~]# cat /proc/27007/task/11825/schedstat 17 861 3557793[root@vmc116 ~]# cat /proc/27007/task/11825/schedstat 21 726 3557875发现相关线程的调度统计一直在增加,说明相关线程一直是在被调度运行的,结合其状态也一直是R,推测很可能在用户态发生了死循环(或者非睡眠死锁)。
这里又有问题:为什么从top看每个线程的CPU占用率只有10%左右,而不是通常看到的死循环进程导致的100%的占用率?因为线程数很多,而且优先级都一样,根据CFS调度算法,会平均分配时间片,不会让其中一个线程独占CPU。结果为多个线程间轮流调度,消耗掉了所有的cpu。。另一个问题:为什么这种情况下,内核没有检测到softlockup?因为业务进程的优先级不高,不会影响watchdog内核线程(最高优先级的实时线程)的调度,所以不会产生softlockup的情况。再一个问题:为什么每次查看线程堆栈时,总是阻塞在retint_careful,而不是其它地方?因为这里(中断返回的时候)正是调度的时机点,在其它时间点不能发生调度(不考虑其它情况~),而我们查看线程堆栈的行为,也必须依赖于进程调度,所以我们每次查看堆栈时,正是查看堆栈的进程(cat命令)得到调度的时候,这时正是中断返回的时候,所以正好看到的阻塞点为retint_careful。
4)用户态分析从上面的分析看,推测应该是用户态发生了死锁。
用户态确认方法:部署debug信息,然后gdb attach相关进程,确认堆栈,并结合代码逻辑分析。最终确认该问题确为用户态进程中产生了死循环。
以上就是linux系统中一次用户态进程死循环案例的分析过程,谢谢阅读,希望能帮到大家,请继续关注脚本之家,我们会努力分享更多优秀的文章。
大家感兴趣的内容
12345678910
最近更新的内容2433人阅读
ARM嵌入式相关(279)
使用的 glibc : glibc-2.17
使用的 linux kernel :linux-3.2.07
系统调用是内核向用户进程提供服务的唯一方法,应用程序调用操作系统提供的功能模块(函数)。
用户程序通过系统调用从用户态(user mode)切换到核心态(kernel mode ),从而可以访问
相应的资源。这样做的好处是:
为用户空间提供了一种硬件的抽象接口,使编程更加容易。
有利于系统安全。
有利于每个进程度运行在虚拟系统中,接口统一有利于移植。
&&&&&&&&&&&& 运行模式、地址空间、上下文:
运行模式(mode)
Linux 使用了其中的两个:特权级0和特权级3&,即内核模式(kernel
mode) 和用户模式(user mode )
地址空间(space )
a)每个进程的虚拟地址空间可以划分为两个部分:用户空间和内核空间
b)在用户态下只能访问用户空间;而在核心态下,既可以访问用户空间,又可以访问内核空间。&
c)内核空间在每个进程的虚拟地址空间中都是固定的(虚拟地址为3G~4G的地址空间)。
上下文(context )
一个进程的上下文可以分为三个部分:用户级上下文、寄存器上下文以及系统级上下文。
a)用户级上下文:正文、数据、用户栈以及共享存储区;
b)寄存器上下文:通用寄存器、程序寄存器(IP )、处理机状态寄存器(EFLAGS)、栈指针(ESP);
c)系统级上下文:进程控制块task_struct 、内存管理信息(mm_struct 、vm_area_struct、pgd 、pmd、
&& pte 等)、核心栈等。
&&&&&&&&&&&& 系统调用、API和C 库
a)Linux 的应用编程接口(API)遵循POSIX标准
b)Linux 的系统调用作为c库的一部分提供。c库中实现了Linux
的主要API,
&& 包括标准c库函数和系统调用。
c)应用编程接口(API)其实是一组函数定义,这些函数说明了如何获得一个给定的服务;
&& 而系统调用是通过软中断向内核发出一个明确的请求,每个系统调用对应一个封装例程
& (wrapper routine,唯一目的就是发布系统调用)。一些API应用了封装例程。
&&&&& @a@ API还包含各种编程接口,如:C库函数、OpenGL 编程接口等
d)系统调用的实现是在内核完成的,而用户态的函数是在函数库中实现的
&&&&&&&&&& 系统调用与操作系统命令
a)操作系统命令相对应用编程接口更高一层,每个操作系统命令都是一个可执行程序,
&& 比如ls 、hostname 等,
b)操作系统命令的实现调用了系统调用
c)通过&strace&命令可以查看操作系统命令所调用的系统调用,如:
&& strace ls
&& strace hostname
&&&&&&&&&&& 系统调用与内核函数
a)内核函数在形式上与普通函数一样,但它是在内核实现的,需要满足一些内核编程的要求
b)系统调用是用户进程进入内核的接口层,它本身并非内核函数,但它是由内核函数实现的
c)进入内核后,不同的系统调用会找到各自对应的内核函数,
&& 这些内核函数被称为系统调用的“服务例程 ”
&&&&&&&&&&系统调用处理程序及服务例程
a)当用户态的进程调用一个系统调用时,CPU切换
到内核态并开始执行一个内核函数
b)系统调用处理程序执行下列操作:
&& @a@ 在内核栈保存大多数寄存器的内容
&& @b@ 调用名为系统调用服务例程(system
call service routine)的相应的C函数来
&&&&&处理系统调用
&& @c@ 通过ret_from_sys_call(& ) 函数从系统调用返回
&&&&&&&&&&& 系统调用流程
&&&&&&&&& 系统调用中参数传递
a)每个系统调用至少有一个参数,即通过&eax
寄存器传递来的系统调用号
b)用寄存器传递参数必须满足两个条件:&
&& @a@&每个参数的长度不能超过寄存器的长度
&& @b@&参数的个数不能超过6
个(包括eax 中传递的系统调用号),否则,
&&&&&&&用一个单独的寄存器指向进程地址空间中这些参数值所在的一个内存区
c)在少数情况下,系统调用不使用任何参数
d)服务例程的返回值必须写到eax
很多系统调用需要不止一个参数
普通C函数的参数传递是通过把参数值写入堆栈(用户态堆栈或内核态堆栈)来实现的。
但因为系统调用是一种特殊函数,它由用户态进入了内核态,所以既不能使用用户态的堆栈
也不能直接使用内核态堆栈
在int $0x80汇编指令之前,系统调用的参数被写入CPU的寄存器。然后,在进入内核态调用系统调用服务例程之前,内核再把存放在CPU寄存器中的参数拷贝到内核态堆栈中。因为毕竟服务例程是C函数,它还是要到堆栈中去寻找参数的
&&&&&&&&&&&&&&&&&&&&&&&系统调用小结
程序执行系统调用大致可归结为以下几个步骤:
1、程序调用libc 库的封装函数。
2、调用软中断int 0x80& 进入内核。
3、在内核中首先执行system_call 函数(首先将系统调用号(eax)和可以
&&&用到的所有CPU寄存器保存到相应的堆栈中(由SAVE_ALL完成)),
&& 接着根据系统调用号在系统调用表中查找到对应的系统调用服务例程。
4、执行该服务例程。
5、执行完毕后,转入ret_from_sys_call 例程,从系统调用返回
在深入讨论内核和用户空间库如何实现系统调用的技术细节之前,
简要看一下内核以系统调用形式实际提供的各个函数是很有用处的。
每个系统调用都通过一个符号常数标识,符号常数的定义是平台相关的,
在内核源码&&include/asm_xx/unistd.h&&中指定,
XX表示平台相关,有些是&asm_arch,有的是&asm_generic&等
用于实现系统调用的处理程序函数,在形式上有如下几个共同的特性:
1,每个函数的名称前缀都是
sys_&,将该函数唯一地标识为一个系统调用,
&& 更精确的说,标识为一个系统调用的处理程序函数。
2,所有的处理程序函数都最多接收
5 个参数。否则,
&&&用一个单独的寄存器指向进程&
地址空间中这些参数值所在的一个内存区即可
3,所有的系统调用都在内核态执行。
系统调用由内核分配的一个编号唯一标识。
所有的系统调用都由一处中枢代码处理,根据调用编号和一个静态表,将调用分派到具体的函数。
传递的参数也是由中枢代码处理,这样参数的传递独立于实际的系统调用。
从用户态到内核态,以及调用分派和参数传递,都是由汇编语言代码实现的。
为容许用户态和内核态之间的切换,用户进程必须通过一条专用的机器指令,引起处理器/内核
对该进程的关注,这需要 C 标准库的协助。内核也必须提供一个例程,来满足切换请求并执行
相关操作。该例程不能在用户空间中实现,因为其中需要执行普通应用程序不允许执行的命令。
系统调用表 &linux/arch/arm/kernel/calls.S& (armV7)
以系统调用 open() 函数为例:
1,X86 平台:
1,用户空间
&& 1,函数 open() 的声明
&&&&& @1@ 在使用 open()函数时,要 include &fcntl.h&
2,内核空间
(1)系统启动时,对INT 0x80进行一定的初始化。
使用汇编子程序setup_idt(linux/arch/i386/kernel/head.S)初始化idt表(中断描述符表),这时所有的入口函数偏移地址都被设为ignore_int ,如下图所示。
2)用户程序需要系统提供服务的时候,会通过系统调用产生一个int 0x80的软中断,就会进入到系统调用的入口函数,入口函数存放在以下文件当中
MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal0
接下来,会进入到系统调用表查找到系统调用服务程序的入口函数的地址,再进行跳转,
整个过程如下图所示:
MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal0
1,系统调用号:
MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal02,系统调用原型:
MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal0
其中这里使用了一个宏asmlinkage&,我们再看一下它在系统里的定义:
MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal0
后面的&表示的是不通过寄存器来传递参数,通过栈来传递
&&& 所以系统调用的入口函数里面参数的传递:
&&& 定义 SAVE_ALL 是将参数压到堆栈中,然后通过堆栈来进行参数的传递
3,获取系统调用入口函数:
sys_call_table 每一项占用4个字节。system_call函数可以读取 eax 寄存器,获取当前系统调用的
系统调用号,将其乘以 4 生成偏移地址,然后以 sys_call_table 为基址,基址加上偏移地址
所指向的内容,既是应该调用的服务程序的地址。
&& 在本例中,sys_open 是系统调用服务程序的入口地址
4,调用系统调用函数:(在新的内核中,函数的实现并不是直接通过 sys_xxx 函数,
&&&&&&&&&&&&&&&&&&&&&& 而是通过一个宏的封装)
sys_open -& do_sys_open -& do_filp_open -&do_last-& nameidata_to_filp -& __dentry_open&
其中宏 SYSCALL_DEFINE3 定义如下:
在本例中,结合 sys_open 的定义:
可以知道,SYSCALL_DEFINE3 中的数字 3 表示这个函数需要传递 3 个参数。
其中 “##”表示宏中字符直接,即:
SYSCALL_DEFINEx(3,_open,__VA_ARGS__)
其中 SYSCALL_DEFINEx 定义如下:
其中 __SYSCALL_DEFINEx 定义如下:
在本例中如下:
MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal0
当我们自己定义一个不需要传递参数的系统调用的时候,可以这样定义我们的函数:
SYSCALL_DEFINE0(mycall)
printk(&This&is&my_sys_call\n&);
5,对调用的函数进行解析
@a1@& open的核心处理在函数 do_sys_open() 中
do_filp_open()解析:
path_openat():
link_path_walk():
walk_component():
最后根据条件打开设备:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:290105次
积分:3844
积分:3844
排名:第6580名
转载:565篇
(5)(11)(31)(1)(13)(14)(1)(27)(16)(14)(3)(46)(29)(35)(23)(58)(9)(7)(34)(2)(8)(6)(1)(2)(2)(28)(33)(71)(39)(2)

我要回帖

更多关于 linux 用户态驱动 的文章

 

随机推荐