一、实验内容

1. 通过内核的方式使用系统调用

需要使用的命令

rm menu -rf //强制删除当前menu
git clone http://github.com/mengning/menu.git //重新克隆新版本的menu cd menu ls make rootfs //rootfs是事先写好的一个脚本,自动编译自动生成根文件系统,同时自动启动MenuOS

2. 将上周选择的系统调用添加到MenuOS中

  打开menu中的 test.c文件,添加Gitpid和Gitpidasm代码

int Getpid(int argc , char * argv[])
{
int pid;
pid=getpid();
printf("pid=%d\n",pid);
return 0;
}

int Getpidasm(int argc , char *argv[])
{
int pid;
asm volatile(
"mov $0,%%ebx\n\t"
"mov $0x14,%%eax\n\t"
"int $0x80\n\t"
"mov %%eax, %0\n\t"
:"=m"(pid)
);
printf("pid = %d\n",pid);
return 0;
}

  在main函数中添加

MenuConfig(“getpid","Show pid",Getpid);
MenuConfig("getpid-asm","Show pid(asm)",Getpidasm);

  重新make 后。可以看到menuOS有了getpid的命令,功能为返回当前进程的标识。

3.使用gdb跟踪分析这该系统调用内核函数

  系统中已经成功添加了该函数调用功能。然后对该程序进行调试分析,使用gdb跟踪分析这该系统调用内核函数。

  需要使用命令为

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

file linux-3.18.6/vmlinux 加载调试内核符号表。

b 设置断点   n 单步执行  

二、系统调用过程分析 

1. 系统调用在内核代码中的工作机制和初始化

int 0x80——>system call:通过中断向量匹配

system call——>sys_xyz():通过系统调用号匹配

 

 

一旦执行int 0x80后立刻跳转到system_call执行

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

  syscall_call 函数到系统调用服务例程通过系统调用号联系起来:在上面执行软中断 0x80 时,系统调用号会被放入eax寄存器(参数的传递),system_call 函数读取eax寄存器获取参数(当前系统调用的调用号),将其乘以4生成偏移地址。然后以中断向量表(sys_call_table)为基址,以系统调用号所确定的为偏移地址相加得到最后的物理地址:基址+偏移地址 => 系统调用服务例程的地址。其中 sys_call_table 基址在文件 arch/x86/kernel/syscall_table_32.S 中定义,同时表中每一项例程的地址占用4个字节,所以上面乘以4。

  由于系统调用例程在定义时时用 asmlinkage 标记了的,所以编译器仅从堆栈中获取该函数的参数。在进入system_call函数前,用户应用会把参数存放到寄存器中,system_call 函数执行时会首先把这些寄存器压入堆栈。这样对系统调用服务例程可以直接从堆栈照片能够获取参数。

3.从system_call开始到iret结束之间的过程

  从整体过程来看,系统通过 int 0x80 从用户态进入内核态。在这个过程中系统先保存了中断环境,然后执行系统调用函数。system_call() 函数通过系统调用号查找系统调用表 sys_cal_table 来查找到具体的系统调用服务进程。在执行完系统调用后在执行 iret 之前,内核做了一系列检查,用于检查是否有新的中断产生。如果没有新的中断,则通过已保存的系统中断环境返回用户态。这样就完成了一个系统调用过程。系统调用通过 INT 0x80 进入内核,跳转到 system_call() 函数,然后执行相应服务进程。因为代表了用户进程,所以这个过程并不属于中断上下文,而是属于进程上下文。

4.系统调用处理过程的汇编代码分析

.macro INTERRUPT_RETURN  ; 中断返回
    iret
.endm
.macro SAVE_ALL          ; 保护现场
    ...
.macro RESTORE_INT_REGS
    ...
.endm

ENTRY(system_call)
    SAVE_ALL
syscall_call:
    call *sys_call_table(,%eax,4)
    movl %eax, PT_EAX(%esp)  ; store the return value
syscall exit:
    testl $_TIF_ALLWORK_MASK, %ecx # current->work
    jne syscall_exit_work
restore_all:
    RESTORE_INT_REGS
irq_return:
    INTERRUPT_RETURN      
ENDPROC(system_call)

syscall_exit_work:
    testl  $_TIF_WORK_SYSCALL_EXIT, %ecx
    jz work_pending
END(syscall_exit_work)

work_pending:
    testb $_TIF_NEED_RESCHED, %cl
    jz work_notifysig
work_resched:
    call schedule
    jz restore_all
work_notifysig:
    ...                  ; deal with pending signals
END(work_pending)

  无论是中断返回(ret_from_intr) ,还是系统调用返回,都使用了 work_pending 和resume_userspace。对于宏SAVE_ALL来说,这条语句会把将寄存器的值压入堆栈当中,压入堆栈的顺序对应struct pt_regs ,出栈时,这些值传递到struct pt_regs的成员,实现从汇编代码向C程序传递参数。struct pt_regs可以在arch/x86/include/asm/ptrace.h中查看。用户态到内核态需要int 0x80进行中断,只有生成了中断向量后才可以切换状态。中断处理让CPU停止当前工作转为执行系统内核中预设的一些任务,因此必须要对当前CPU执行的任务进行执行现场的保护工作,并对一些其他工作进行检查,完成调用后,再进行检查,才能执行iret返回。系统内部调用涉及CPU架构等内容,不同的CPU对于系统调用的汇编具体代码是不一样的。

三、总结

(1)open 函数通过系统调用号与 sys_open 系统调用服务例程函数联系起来

(2)int 0x80 通过中断向量(0x80)与 system_call 联系起来的

(3)系统调用中断本质上是一个保存当前工作状态,然后处理,最后返回并且恢复进程的过程

刘帅

原创作品转载请注明出处

《Linux内核分析》MOOC课程

 

posted on 2016-03-24 10:47  linux20135104  阅读(412)  评论(0编辑  收藏  举报