Linux内核分析(五):系统调用深入分析
2017-03-26 22:12
393 查看
何天杨+ 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
这周的实验在上周实验四的基础上,进一步的操作:
1.将系统调用函数getppid命令加入menuos中
2.通过gdb跟踪sys_getppid系统调用执行的完整过程
步骤:给MenuOS增加getpid和getpid-asm命令
0)更新menu代码到最新版
1)在main函数中增加MenuConfig
2)增加对应的getpid函数和getpid-asm函数
3)make rootfs
一、实验内容
通过内核的方式使用系统调用
需要使用的命令
2.打开menu中的 test.c文件,添加Gitpid和Gitpidasm代码
在main函数中添加
代码添加完成后make rootfs重新编译,此时系统会自动启动。如下图:
3.使用gdb跟踪分析这该系统调用内核函数
系统中已经成功添加了该函数调用功能,然后对该程序进行调试分析,使用gdb跟踪分析这该系统调用内核函数。
需要的命令为
b 设置断点 n 单步执行
二、系统调用过程分析
系统调用在内核代码中的工作机制和初始化
int 0x80——>system call:通过中断向量匹配
system call——>sys_xyz():通过系统调用号匹配
中断相关的初始化代码是通过linux-3.18.6/init/main.c文件中的start_kernel函数里的trap_init()初始化的。执行int $0x80指令后内核开始执行system_call入口处开始的代码,位于entry_32.S汇编文件中。
下面是system_call汇编代码
2 以下是syscall_exit_work相关代码:
3.下面是work_pending的相关代码,在注释中解释相关内容
4.restore_all
5.irq_return
无论是中断返回(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.执行int 0x80指令后系统从用户态进入内核态,跳到system_call()函数处执行相应服务进程。在此过程中内核先保存中断环境,然后执行系统调用函数。
2.system_call()函数通过系统调用号查找系统调用表sys_cal_table来查找具体系统调用服务进程。
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.执行完系统调用后,iret之前,内核会检查是否有新的中断产生、是否需要进程切换、是否学要处理其它进程发送过来的信号等。如果没有新的中断,则通过已保存的系统中断环境返回用户态。这样就完成了一个系统调用过程。系统调用通过 INT 0x80 进入内核,跳转到 system_call() 函数,然后执行相应服务进程。因为代表了用户进程,所以这个过程并不属于中断上下文,而是属于进程上下文。
4.内核是处理各种系统调用的中断集合,通过中断机制实现进程上下文的切换,通过系统调用管理整个计算机软硬件资源。
5.如没有新的中断,restore保存的中断环境并返回用户态完成一个系统调用过程。
这周的实验在上周实验四的基础上,进一步的操作:
1.将系统调用函数getppid命令加入menuos中
2.通过gdb跟踪sys_getppid系统调用执行的完整过程
步骤:给MenuOS增加getpid和getpid-asm命令
0)更新menu代码到最新版
1)在main函数中增加MenuConfig
2)增加对应的getpid函数和getpid-asm函数
3)make rootfs
一、实验内容
通过内核的方式使用系统调用
需要使用的命令
rm menu -rf //强制删除当前menu git clone http://github.com/mengning/menu.git //重新克隆新版本的menu cd menu ls make rootfs //rootfs是事先写好的一个脚本,自动编译自动生成根文件系统,同时自动启动MenuOS
2.打开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 rootfs重新编译,此时系统会自动启动。如下图:
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 单步执行
二、系统调用过程分析
系统调用在内核代码中的工作机制和初始化
int 0x80——>system call:通过中断向量匹配
system call——>sys_xyz():通过系统调用号匹配
中断相关的初始化代码是通过linux-3.18.6/init/main.c文件中的start_kernel函数里的trap_init()初始化的。执行int $0x80指令后内核开始执行system_call入口处开始的代码,位于entry_32.S汇编文件中。
下面是system_call汇编代码
1.SAVE ALL // 保存调用前寄存器相关的信息 2.call *sys_call_table(,%eax,4) // 执行系统调用对应的处理函数,eax存放系统调用号。 //通过linux-3.18.6/arch/x86/syscalls/syscall_32.tbl找到系统调用号对应处理函数 3.movl %eax,PT_EAX(%esp) // 保存系统调用处理函数返回值到exa 4.testl $_TIF_ALLWORK_MASK, %ecx # current->work 5.jne syscall_exit_work // 这两句检查调用退出前是否有其他工作要处理,如有则跳到syscall_exit_work处继续处理,以下是syscall_exit_work相关
2 以下是syscall_exit_work相关代码:
syscall_exit_work: testl $_TIF_WORK_SYSCALL_EXIT, %ecx # 测试是否退出前还有工作要处理,如有则跳到work_pending 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 END(syscall_exit_work)
3.下面是work_pending的相关代码,在注释中解释相关内容
work_pending: testb $_TIF_NEED_RESCHED, %cl #是否有要继续调度的相关信号 jz work_notifysig #跳转到处理信号相关的代码处 work_resched: call schedule #时间调度,进程调度的时机在这里处理 LOCKDEP_SYS_EXIT DISABLE_INTERRUPTS(CLBR_ANY) #确认没有忽略中断 # setting need_resched or sigpending # between sampling and the iret TRACE_IRQS_OFF movl TI_flags(%ebp), %ecx andl $_TIF_WORK_MASK, %ecx # is there any work to be done other 是否有其他工作要处理 # than syscall tracing? jz restore_all #如果没有则恢复中断上下文,即恢复进入之前保存的寄存器内容 testb $_TIF_NEED_RESCHED, %cl jnz work_resched work_notifysig: # deal with pending signals and 处理相关信号 # notify-resume requests #ifdef CONFIG_VM86 testl $X86_EFLAGS_VM, PT_EFLAGS(%esp) movl %esp, %eax jne work_notifysig_v86 # returning to kernel-space or vm86-space 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 # %eax contains pt_regs pointer popl_cfi %ecx movl %eax, %esp jmp 1b #endif END(work_pending)
4.restore_all
restore_all: RESTORE_INT_REGS // 中断返回之前恢复相关寄存器的内容
5.irq_return
irq_return: INTERRUPT_RETURN # 这两行代码主要是返回到用户态
无论是中断返回(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.执行int 0x80指令后系统从用户态进入内核态,跳到system_call()函数处执行相应服务进程。在此过程中内核先保存中断环境,然后执行系统调用函数。
2.system_call()函数通过系统调用号查找系统调用表sys_cal_table来查找具体系统调用服务进程。
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.执行完系统调用后,iret之前,内核会检查是否有新的中断产生、是否需要进程切换、是否学要处理其它进程发送过来的信号等。如果没有新的中断,则通过已保存的系统中断环境返回用户态。这样就完成了一个系统调用过程。系统调用通过 INT 0x80 进入内核,跳转到 system_call() 函数,然后执行相应服务进程。因为代表了用户进程,所以这个过程并不属于中断上下文,而是属于进程上下文。
4.内核是处理各种系统调用的中断集合,通过中断机制实现进程上下文的切换,通过系统调用管理整个计算机软硬件资源。
5.如没有新的中断,restore保存的中断环境并返回用户态完成一个系统调用过程。
相关文章推荐
- linux内核文件IO的系统调用实现分析
- Linux内核学习之四--进程、进程调度、系统调用、proc文件系统和内核异常分析
- linux内核mount系统调用源码分析 http://blog.csdn.net/wugj03/article/details/41958029
- linux内核分析---系统调用实现代码分析
- Linux内核分析第四周学习总结:扒开系统调用的三层皮(上)
- linux内核分析笔记----系统调用
- Linux内核分析(五)系统调用过程解析
- Linux系统调用详解(实现机制分析)--linux内核剖析(六)
- Linux内核分析课程7_execve()函数对应的系统调用处理过程
- Linux内核分析:实验四--使用嵌入汇编系统调用
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】Linux系统调用的实现机制分析
- Linux内核分析(六)----字符设备控制方法实现|揭秘系统调用本质
- linux内核中断、异常、系统调用的分析以及实践
- Linux内核分析 笔记四 系统调用的三个层次 ——by王玥
- linux内核分析---系统调用实现代码分析
- linux内核mount系统调用源码分析
- Linux内核分析(四)系统调用,用户态及内核态
- Linux内核分析(六)----字符设备控制方法实现|揭秘系统调用本质
- linux内核分析第四周-使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
- Linux内核分析第四周学习总结——系统调用的工作机制