2016-03-25

张超的《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

我的虚拟环境和代码在https://www.shiyanlou.com/courses/reports/1028332

我们这次主要分为两部分:

1.系统调用system_call的处理过程

2.给MenuOS增加time和time-asm命令

1.系统调用system_call的处理过程

490ENTRY(system_call)
RING0_INT_FRAME # can't unwind into user space anyway
492 ASM_CLAC
493 pushl_cfi %eax # save orig_eax
494 SAVE_ALL
495 GET_THREAD_INFO(%ebp)
496 # system call tracing in operation / emulation
497 testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
498 jnz syscall_trace_entry
499 cmpl $(NR_syscalls), %eax
500 jae syscall_badsys
501syscall_call:
502 call *sys_call_table(,%eax,4)
503syscall_after_call:
504 movl %eax,PT_EAX(%esp) # store the return value
505syscall_exit:
506 LOCKDEP_SYS_EXIT
507 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 # current->work
jne syscall_exit_work
restore_all:
TRACE_IRQS_IRET
517restore_all_notrace:
#ifdef CONFIG_X86_ESPFIX32
movl PT_EFLAGS(%esp), %eax # mix EFLAGS, SS and CS
# Warning: PT_OLDSS(%esp) contains the wrong/random values if we
# are returning to the kernel.
# See comments in process.c:copy_thread() for details.
movb PT_OLDSS(%esp), %ah
movb PT_CS(%esp), %al
andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << ) | SEGMENT_RPL_MASK), %eax
cmpl $((SEGMENT_LDT << ) | USER_RPL), %eax
CFI_REMEMBER_STATE
je ldt_ss # returning to user-space with LDT SS
#endif
530restore_nocheck:
RESTORE_REGS # skip orig_eax/error_code
532irq_return:
INTERRUPT_RETURN
.section .fixup,"ax"
535ENTRY(iret_exc)
pushl $ # no error code
pushl $do_iret_error
jmp error_code
.previous
_ASM_EXTABLE(irq_return,iret_exc)

system_call

我们的system_call代码如上所述。

有实际开发经验的人都知道,在操作系统上运行的某个应用程序,如果想完成一些实际有用的功能,必然会用到操作系统提供的接口,这些接口被称为系统调用(System Call)。

由操作系统提供的功能,通常应用程序本身是无法实现的。例如对文件进行操作,应用程序必需通过系统调用才能做到,因为只有操作系统才具有直接管理外围设备的权限。又如进

程或线程间的同步互斥操作,也必需经由操作系统对内核变量进行维护才能完成。

应用程序的进程通常在user模式下运行,当它调用一个系统调用时,进程进入kernel模式,执行的是kernel内部的代码,从而具有执行特权指令的权限,完成特定的功能。换句话说,

系统调用是应用程序主动进入操作系统内核的入口。

由程序员的代码主动发起的中断。有两种用法:(1)用来实现系统调用;(2)通知调试器某个特殊事件。

至此,我们发现了中断与系统调用的关系:系统调用是一种特殊的中断类型。

系统调用的处理例程在IDT表中占有一项。这一项是在trap_init函数中被初始化的,如下:

set_system_gate(SYSCALL_VECTOR,&system_call);

当系统调用发生时,通过中断机制,系统调用例程system_call被调用。system_call由汇编语言和C的代码构成,它的执行过程大概分为4个步骤(注意参数的传入和返回值的传出过

程):

从寄存器中取出系统调用号(system call number)和输入参数,然后将这些寄存器的值压入kernel栈中。这一部分的代码用汇编写成。

根据系统调用号(system call number)查找系统调用分派表(system call dispatch table),找到系统调用服务例程(system call service routine )。汇编语言。

调用查到的系统调用服务例程。这一部分用C语言写成,因为已经将输入参数保存在kernel栈中,所以在C函数的参数表中能够拿到输入参数,使得系统调用服务例程在表面上

看与一个普通的C函数没有区别。

将系统调用服务例程的返回值出栈,重新保存在寄存器中。汇编语言。

上面描述的系统调用例程system_call在kernel空间中执行。在执行前,系统调用号和输入参数已经存入了寄存器,这个存入过程由user空间的代码完成。实际上,每个真正的系统调

用基本上都有一个封装它的库函数,一般是在这个库函数中完成系统调用号和输入参数的保存动作。当系统调用例程system_call执行完毕后,返回值通过寄存器再传回user空间的库

函数。

下面详细地介绍上面所讲的4个步骤。

在第1步之前,user空间的封装函数已经将对应的系统调用号保存在eax寄存器中,将输入参数保存在ebx, ecx, edx, esi,以及edi寄存器中(因此最多传6个参数,包括系统调用号)。

第1步中将输入参数寄存器的值压入kernel栈的操作由汇编代码__SAVE_ALL宏完成。如下:

#define __SAVE_ALL \

cld; \

pushl %es; \

pushl %ds; \

pushl %eax; \

pushl %ebp; \

pushl %edi; \

pushl %esi; \

pushl %edx; \

pushl %ecx; \

pushl %ebx; \

movl $(__USER_DS), %edx; \

movl %edx, %ds; \

movl %edx, %es;

第2步中的系统分派表在kernel代码中以变量sys_call_table表示。查找系统调用服务例程的动作就是从sys_call_table里找系统调用号(存在eax寄存器中)指向的那一项,如下:

syscall_call:

call *sys_call_table(,%eax,4)

sys_call_table中的项在sys_call_table.c文件中定义:

syscall_handler_t *sys_call_table[] = {

......

[ __NR_exit ] (syscall_handler_t *) sys_exit,

[ __NR_fork ] (syscall_handler_t *) sys_fork,

[ __NR_read ] = (syscall_handler_t *) sys_read,

[ __NR_write ] = (syscall_handler_t *) sys_write,

......

[ __NR_socketcall ] (syscall_handler_t *) sys_socketcall,

......

};

在这里我们注意到一些常用的系统调用号,如,exit系统调用号为__NR_exit = 1,fork系统调用号为__NR_fork =2,read系统调用号为__NR_read = 3,write系统调用号为

__NR_write =4,所有socket相关的API的系统调用号都是__NR_socketcall= 102。

第3步,执行C函数实现的系统调用例程。该例程最多接受6个参数(包括系统调用号),返回值是一个整型。返回值为非负,表示执行成功;返回值为负,表示执行出错,该错误码的

绝对值会最后存在user空间的errno全局变量中。

第4步,调用syscall_exit_work退出系统调用,并从kernel模式回到user模式。第3步的C函数执行return err的时候,编译后的代码已经将返回值存在了eax寄存器中。

最后,回到user模式的封装函数中,对返回值eax进行检查。如果eax小于0,则将eax的相反数(即绝对值)存到errno全局变量中,同时将eax值置为-1,这时封装函数返回-1;如果

eax大于等于0,则封装函数返回eax的值。

具体分析:

大致过程:SYSterm_call---运行到SAVE—ALL(保护现场)继续运行----table表找对应程序----iret结束返回

分析call对应函数功能(简化)

注意)通过SAVE_ALL宏完成把所有相关寄存器的内容都保存在堆栈中

以下是各项简化函数的表示意思,依据视频依次是:

GET_THREAD_INFO(%ebp):将当前信息保存在ebp

testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%esp)  jnz syscall_trace_entry:判断是否 trace调用

cmpl $(NR_syscalls), %eax jae syscall_badsys:判断系统调用号是否超出最大值

call *sys_call_table(,%eax,4):系统调用的数字实际上是一个序列号,表示其在系统的一个数组sys_call_table[]中的位置。

movl %eax,PT_EAX(%esp):保存系统调用的返回值

DISABLE_INTERRUPTS(CLBR_ANY):屏蔽其他系统调用

movl TI_flags(%esp), %eax:寄存器ecx是通用寄存器,在保护模式中,可以作为内存偏移指针(此时,DS作为 寄存器或段选择器),此时为返回到系统调用之前做准备

testl $_TIF_ALLWORK_MASK, %eax     jne syscall_exit_work :退出系统调用之前,检查是否需要处理信号

RESTORE_REGS 4​ :x86架构恢复寄存器代码

INTERRUPT_RETURN即iret: 系统调用是通过软中断指令

INT 0x80 实现的,而这条INT 0x80指令就被封装在C库的函数中。(软中断和我们常说的硬中断不同之处在于,软中断是由指令触发的,而不是由硬件外设引起的。)

INT 0x80 这条指令的执行会让系统跳转到一个预设的内核空间地址,它指向系统调用处理程序,即system_call函数。

实验过程如下:

1.先切换到我们的虚拟机的LinuxKernel目录下

2.qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

3.右键->新建窗口(水平分割)-> gdb

4.file linux-3.18.6/vmlinux

5.target remote:1234

6.b start_kernel

7.c

2.给MenuOS增加time和time-asm命令

0)更新menu代码到最新版

1)在main函数中增加MenuConfig

2)增加对应的Time函数和TimeASM函数

3)make rootfs

具体如下:

Linux系统调用system_call的更多相关文章

  1. [Linux]系统调用理解(1)

    本文是Linux系统调用专栏系列文章的第一篇,对Linux系统调用的定义.基本原理.使用方法和注意事项大概作了一个介绍,以便读者对Linux系统调用建立一个大致的印象. 什么是系统调用? Linux内 ...

  2. 关于Linux系统调用,内核函数【转】

    转自:http://blog.csdn.net/ubuntulover/article/details/5988220 早上听人说到某个程序的一部分是内核态,另一部分是用户态,需要怎么怎么.当时突然想 ...

  3. Linux系统调用(转载)

    目录: 1. Linux系统调用原理 2. 系统调用的实现 3. Linux系统调用分类及列表 4.系统调用.用户编程接口(API).系统命令和内核函数的关系 5. Linux系统调用实例 6. Li ...

  4. 剖析Linux系统调用的执行路径

    在什么是操作系统这篇文章中,介绍过操作系统像是一个代理一样,为我们去管理计算机的众多硬件,我们需要计算机的一些计算服务.数据管理的服务,都由操作系统提供接口来完成.这样做的好处是让一般的计算机使用者不 ...

  5. linux内核剖析(六)Linux系统调用详解(实现机制分析)

    本文介绍了系统调用的一些实现细节.首先分析了系统调用的意义,它们与库函数和应用程序接口(API)有怎样的关系.然后,我们考察了Linux内核如何实现系统调用,以及执行系统调用的连锁反应:陷入内核,传递 ...

  6. 使用 Linux 系统调用的内核命令【转】

    转自:http://www.ibm.com/developerworks/cn/linux/l-system-calls/ 探究 SCI 并添加自己的调用 Linux® 系统调用 —— 我们每天都在使 ...

  7. Linux系统调用过程分析

    參考: <Linux内核设计与实现> 0 摘要 linux的系统调用过程: 层次例如以下: 用户程序------>C库(即API):INT 0x80 ----->system_ ...

  8. [Linux]Linux系统调用列表

    本文列出了大部分常见的Linux系统调用,并附有简要中文说明. 以下是Linux系统调用的一个列表,包含了大部分常用系统调用和由系统调用派生出的的函数.这可能是你在互联网上所能看到的唯一一篇中文注释的 ...

  9. 别出心裁的Linux系统调用学习法

    别出心裁的Linux系统调用学习法 操作系统与系统调用 操作系统(Operating System,简称OS)是计算机中最重要的系统软件,是这样的一组系统程序的集成:这些系统程序在用户对计算机的使用中 ...

随机推荐

  1. 几种常用的Java数据源解决方案

    http://blog.163.com/qqabc20082006@126/blog/static/22928525201041944847653/

  2. InstallShield 覆盖安装

    “吾乐吧软件站”提供了很全面详细的InstallShield制作安装包教程(http://www.wuleba.com/23892.html),但是按上面的方法再次制作的升级安装包,安装后会在系统中同 ...

  3. UVA 305 Joseph (约瑟夫环 打表)

     Joseph  The Joseph's problem is notoriously known. For those who are not familiar with the original ...

  4. view ondraw

    窗口发生重绘时会被应用程序的窗口框架给调用 要使输出的东西始终能在窗口中看到 就可以使用该函数  窗口从到有的时候就会产生WM_PAINT消息,让窗口发生重绘 这是程序就会执行到ONDRAW函数处 所 ...

  5. js获取对象位置的方法

    scrollHeight: 获取对象的滚动高度. scrollLeft:设置或获取位于对象左边界和窗口中目前可见内容的最左端之间的距离 scrollTop:设置或获取位于对象最顶端和窗口中可见内容的最 ...

  6. IE6浏览器兼容问题及部分解决方案(网上整理)

    作为一个初涉前端技术的IT菜鸟,IE浏览器的兼容问题是一个不得不跨越的坎.为了能够在不同浏览器达到同样的显示效果,就不得不花心思想出办法实现兼容.由于各大主流浏览器内核不同,各自的实现标准有所差异,因 ...

  7. bayes

    from numpy import * import time starttime = time.time() def loadDataSet(): postingList = [['my', 'do ...

  8. IOS文件操作的两种方式:NSFileManager操作和流操作

    1.常见的NSFileManager文件方法 -(NSData *)contentsAtPath:path //从一个文件读取数据 -(BOOL)createFileAtPath: path cont ...

  9. 如何打一手好Log(转)

    如果项目上过线的话,那你一定知道Log是多么重要. 为什么说Log重要呢?因为上线项目不允许你调试,你只能通过Log来分析问题.这时打一手好Log的重要性绝不亚于写一手好代码.项目出问题时,你要能拿出 ...

  10. Java正则表达式的语法与示例

    Java正则表达式的语法与示例 java 正则表达式 正则表达式语法 java正则表达式语法 java正则表达式 概要: Java正则表达式的语法与示例 | |目录 1匹配验证-验证Email是否正确 ...