通过分析system_call中断处理过程来深入理解系统调用
通过分析system_call中断处理过程来深入理解系统调用
前言说明
本篇为网易云课堂Linux内核分析课程的第五周作业,上一次作业中我以2个系统调用(getpid
, open
)作为分析实例来分析系统调用的过程,本篇中我将深入到system_call(汇编级别代码)中来分析其执行过程.
关键词:system_call
, 系统调用
运行环境:
- Ubuntu 14.04 LTS x64
- gcc 4.9.2
- gdb 7.8
- vim 7.4 with vundle
分析过程
从上一次课后,我们对于系统调用在执行过程中的一些基本情况有了一个比较抽象的认识,一个系统调用的基本过程是用户态程序通过int 0x80
中断向量指令实现从用户态进入内核态,系统调用过程中,eax
寄存器负责传递系统调用号,ebx
,ecx
等其他寄存器负责传递其他参数,但是对于执行完了int 0x80
之后,在内核态时:
- 操作系统到底干了哪些具体的工作?
- 系统调用在内核态这一阶段的过程是什么?
基本概念的总结
中断
中断分为2种":- 可屏蔽中断: I/O设备发出的所有的中断请求(IRQ)都产生可屏蔽中断。可屏蔽中断产生两种状态:屏蔽的(masked)或非屏蔽的(unmasked);当中断被屏蔽,则CPU控制单元就忽略它。
- 非可屏蔽中断:总是由CPU辨认。只有几个危急事件引起非屏蔽中断。
进程上下文
一般来说,CPU在任何时刻都处于以下三种情况之一:- 运行于用户空间,执行用户进程;
- 运行于内核空间,处于进程上下文;
- 运行于内核空间,处于中断上下文。
应用程序通过系统调用陷入内核,此时处于进程上下文。现代几乎所有的CPU体系结构都支持中断。当外部设备产生中断,向CPU发送一个异步信号,CPU调用相应的中断处理程序来处理该中断,此时CPU处于中断上下文。
在进程上下文中,可以通过current关联相应的任务。进程以进程上下文的形式运行在内核空间,可以发生睡眠,所以在进程上下文中,可以使作信号量(semaphore)。实际上,内核经常在进程上下文中使用信号量来完成任务之间的同步,当然也可以使用锁。
中断上下文不属于任何进程,它与current没有任何关系(尽管此时current指向被中断的进程)。由于没有进程背景,在中断上下文中不能发生睡眠,否则又如何对它进行调度。所以在中断上下文中只能使用锁进行同步,正是因为这个原因,中断上下文也叫做原子上下文(atomic context)(关于同步以后再详细讨论)。在中断处理程序中,通常会禁止同一中断,甚至会禁止整个本地中断,所以中断处理程序应该尽可能迅速,所以又把中断处理分成上部和下部。
相对于进程而言,就是进程执行时的环境。具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存信息等。一个进程的上下文可以分为三个部分:用户级上下文、寄存器上下文以及系统级上下文- 用户级上下文: 正文、数据、用户堆栈以及共享存储区;
- 寄存器上下文: 通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)、栈指针(ESP);
- 系统级上下文: 进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈。
中断上下文与进程上下文
硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的 一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。所谓的“ 中断上下文”,其实也可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被打断执行的进程环境)。中断时,内核不代表任何进程运行,它一般只访问系统空间,而不会访问进程空间,内核在中断上下文中执行时一般不会阻塞.
System_Call
# system call handler stub
ENTRY(system_call)
RING0_INT_FRAME # can't unwind into user space anyway
ASM_CLAC
pushl_cfi %eax # save orig_eax
SAVE_ALL // 保存系统寄存器信息
GET_THREAD_INFO(%ebp) // 获取thread_info结构的信息
# system call tracing in operation / emulation
testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) // 测试是否有系统跟踪
jnz syscall_trace_entry // 如果有系统跟踪,先执行,然后再回来
cmpl $(NR_syscalls), %eax // 比较eax中的系统调用号和最大syscall,超过则无效
jae syscall_badsys // 无效的系统调用 直接返回
syscall_call:
call *sys_call_table(,%eax,4) // 调用实际的系统调用程序
syscall_after_call:
movl %eax,PT_EAX(%esp) // 将系统调用的返回值eax存储在栈中
syscall_exit:
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
TRACE_IRQS_OFF
movl TI_flags(%ebp), %ecx
testl $_TIF_ALLWORK_MASK, %ecx //检测是否所有工作已完成
jne syscall_exit_work //工作已经完成,则去进行系统调用推出工作
restore_all:
TRACE_IRQS_IRET // iret 从系统调用返回
System_Call的基本处理流程为:
- 首先保存中断上下文(SAVE_ALL,也就是CPU状态,包括各个寄存器),判断请求的系统调用是否有效
- 然后
call *sys_call_table(,%eax,4)
通过系统查询系统调用查到相应的系统调用程序地址,执行相应的系统调用 - 系统调用完后,返回系统调用的返回值
- 关闭中断响应,检测系统调用的所有工作是否已经完成,如果完成则进行
syscall_exit_work
(完成系统调用退出工作) - 最后restore_all(恢复中断请求响应),返回用户态
接下来对于中间比较关键的片段代码进行重点分析
System_Call中的关键部分
syscall_exit_work
syscall_exit_work:
testl $_TIF_WORK_SYSCALL_EXIT, %ecx //测试syscall的工作完成
jz work_pending
TRACE_IRQS_ON //切换中断请求响应追踪可用
ENABLE_INTERRUPTS(CLBR_ANY) # could let syscall_trace_leave() call
//schedule() instead
movl %esp, %eax
call syscall_trace_leave //停止追踪系统调用
jmp resume_userspace //返回用户空间,只需要检查need_resched
END(syscall_exit_work)
该过程为系统调用完成后如何退出调用的过程,其中比较重要的是work_pending
,详见如下:
work_pending:
testb $_TIF_NEED_RESCHED, %cl // 判断是否需要调度
jz work_notifysig // 不需要则跳转到work_notifysig
work_resched:
call schedule // 调度进程
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
TRACE_IRQS_OFF
movl TI_flags(%ebp), %ecx
andl $_TIF_WORK_MASK, %ecx // 是否所有工作都已经做完
jz restore_all // 是则退出
testb $_TIF_NEED_RESCHED, %cl // 测试是否需要调度
jnz work_resched // 重新执行调度代码
work_notifysig: // 处理未决信号集
#ifdef CONFIG_VM86
testl $X86_EFLAGS_VM, PT_EFLAGS(%esp) // 判断是否在虚拟8086模式下
movl %esp, %eax
jne work_notifysig_v86 // 返回到内核空间
1:
#else
movl %esp, %eax
#endif
TRACE_IRQS_ON // 启动跟踪中断请求响应
ENABLE_INTERRUPTS(CLBR_NONE)
movb PT_CS(%esp), %bl
andb $SEGMENT_RPL_MASK, %bl
cmpb $USER_RPL, %bl
jb resume_kernel // 恢复内核空间
xorl %edx, %edx
call do_notify_resume // 将信号投递到进程
jmp resume_userspace // 恢复用户空间
#ifdef CONFIG_VM86
ALIGN
work_notifysig_v86:
pushl_cfi %ecx # save ti_flags for do_notify_resume
call save_v86_state // 保存VM86模式下的CPU信息
popl_cfi %ecx
movl %eax, %esp
jmp 1b
#endif
END(work_pending)
首先是work_pending
这段汇编逻辑:
- 检查是否需要进行调度
- 如果需要,进行进程调度,然后再次进行判断
- 如果无需调度,那就去执行
work_notifying
,处理信号
然后是work_notifysig
的这段汇编逻辑:
- 先检查是否是虚拟8086模式,即8086保护模式
- 如果是,那么需要先保存虚模式下的状态信息
- 然后跳转到之前的代码继续执行
- 将信号投递到进程
- 恢复用户空间
最后返回系统调用
流程图
实验截图
我的总结
系统调用中断本质上是一个保存当前工作状态,然后处理,最后返回并且恢复进程的过程.
参考资料
- Understanding The Linux Kernel, the 3rd edtion
- Linux内核设计与实现,第三版,Robert Love, 机械工业出版社
署名信息
吴欣伟 原创作品转载请注明出处:《Linux内核分析》MOOC课程:http://mooc.study.163.com/course/USTC-1000029000
通过分析system_call中断处理过程来深入理解系统调用的更多相关文章
- 作业五:分析system_call中断处理过程
分析system_call中断处理过程 一.MesuSO增加getpid和getpid-asm 二.使用GDB跟踪系统调用内核函数sys_getpid 分析system_call中断处理过程 使用gd ...
- 《Linux内核分析》第五周:分析system_call中断处理过程
实验 分析system_call中断处理过程 使用gdb跟踪分析一个系统调用内核函数(您上周选择那一个系统调用),系统调用列表参见http://codelab.shiyanlou.com/xref/l ...
- Linux内核分析-分析system_call中断处理过程
姓名:江军 ID:fuchen1994 分析system_call中断处理过程 使用gdb跟踪分析一个系统调用内核函数(您上周选择那一个系统调用),系统调用列表参见http://codelab.shi ...
- Linux内核分析第五周学习总结——分析system_call中断处理过程
Linux内核分析第五周学习总结--分析system_call中断处理过程 zl + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/U ...
- 分析system_call中断处理过程
分析system_call中断处理过程 上周我们使用gcc内嵌汇编调用系统调用,这次我们具体分析下过程. 将getpid嵌入menuos 代码从github下载,步骤如下: 1. 增加一个函数,get ...
- 20135202闫佳歆--week5 分析system_call中断处理过程--实验及总结
week 5 实验:分析system_call中断处理过程 一.使用gdb跟踪分析一个系统调用内核函数(上周选择那一个系统调用)--getpid 复习视频: 如何实现? - 更新menu代码到最新版 ...
- 实验五:分析system_call中断处理过程
原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 如果我写的不好或者有误的地方请留言 ...
- 实验五分析system_call中断处理过程
一.实验要求: 1.使用gdb跟踪分析一个系统调用内核函数 2.根据本周所学知识分析系统调用的过程,从system_call开始到iret结束之间的整个过程,并画出简要准确的流程图 二.实验步骤: 1 ...
- linux内核分析第五周-分析system_call中断处理过程
本实验目的:通过以一个简单的menu小程序,跟踪系统调用的过程,分析与总结系统调用的机制和三层进入的过程. 实验原理:系统调用处理过程与中断处理的机制 系统调用是通过软中断指令 INT 0x80 实现 ...
随机推荐
- currentStyle getComputedStyle兼容
function getStyle(obj,attr){ if(obj.currentStyle) {return obj.currentStyle[attr]} else{ return getCo ...
- ubuntu下nginx+PHP-FPM安装配置
安装nginx apt-get install nginx 配置nginx 位置: /etc/nginx/nginx.conf ,其中包含了 include /etc/nginx/conf.d/*. ...
- Docker下redis的主从配置
1.拉取redis镜像docker pull redis2.启动3个redis容器服务,分别使用到6379.6380.6381端口docker run --name redis-6379 -p 637 ...
- (一) Docker in Docker
一. 背景介绍 工作中,要实现在docker中运行docker,实现镜像的拉取,创建,修改,上传等操作. 尝试过在docker中,安装docker.行不通,服务起不来. 而且直接在 docker 容 ...
- hasChildNodes()方法,nodeName、nodeValue、nodeType介绍
Document对象的使用:hasChildNodes()方法,nodeName.nodeValue.nodeType的简单介绍 一.hasChildNodes() 说明: (1) 该方法 ...
- 组管理命令--groupadd.groupmod.groupdel.gpasswd
添加用户组 格式 groupadd [参数] 组名 参数选项 -g GID:指定新组的GID,默认值是已有的最大的GID加1.-r:建立一个系统专用组,与-g不同时使用时,则分配一个1-499的GID ...
- 提高CPU使用率
某些特殊时候,需要提升下cpu的利用率,此时……………………需要一个极其简单的脚本来完成! #!/bin/bash while (true);do { for i in $(seq 100000 10 ...
- java 生成二维码工具
二维码生成 Gitee:https://gitee.com/search?utf8=%E2%9C%93&search=qrext4j&group_id=&project_id= ...
- (11) openssl req(生成请求证书、私钥和自建CA)
伪命令req大致有3个功能:生成证书请求文件.验证证书请求文件和创建根CA. 由于openssl req命令选项较多,所以先各举几个例子,再集中给出openssl req的选项说明.若已熟悉opens ...
- 201621123079《java程序设计》第六周作业总结
作业06-接口.内部类 1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图或相关笔记,对面向对象思想进行一个总结. 2. 书面作业 1. c ...