分析system_call中断处理过程(基于实验楼平台)
前言
之前我们使用了两种方法进行了系统调用,一种是使用库函数(调用API),第二种是C语言嵌入内嵌汇编。详见博客
给MenuOS增加命令
首先可以删除实验楼自带的系统,然后再git clone一个新的,这样减少了编译过程,一个make搞定
我们进入menu中的test.c,新增我上一次博客中的两个功能的代码(一个API,一个内嵌汇编)-注意要加头文件**#include<unistd.h>**
之前我们编译这个内核需要的命令是
gcc -pthread -o init linktable.c menu.c test.c -m32 -static
mv init ../rootfs
cd ../rootfs
find . | cpio -o -Hnewc | gzip -9 > ../rootfs.img
cd ..
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img
- 1
- 2
- 3
- 4
- 5
- 6
这四行命令意思是将三个C语言一起编译成可执行文件Init,将Init移动到rootfs(没有就自己建)文件夹下,进入rootfs文件夹,第四行是将这个文件夹下的init打包成rootfs.img镜像。最后一行使用qemu虚拟机模拟rootfs.img镜像
但是现在只需要在menu文件夹下执行如下命令即可
make rootfs
- 1
可以使用函数(pid为1-对应的是init)
使用gdb跟踪调用内核函数sys_getpid
gdb调试
首先返回父目录,再启动镜像,但一定要把镜像冻结起来,命令如下
cd ..
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
- 1
- 2
这里-s是在1234端口创建一个gdb-server,-S是将其冻结起来,方便gdb调试
再打开一个窗口,打开gdb,然后输入
file linux-3.18.6/vmlinux
target remote:1234
- 1
- 2
将内核加载进来
然后可以设置两个断点
b start_kernel
b sys_getpid
- 1
- 2
查找sys_getpid和system_call代码
可以看到这个sys_getpid函数在kernel/sys.c里,查找代码如下
/**
* sys_getpid - return the thread group id of the current process
*
* Note, despite the name, this returns the tgid not the pid. The tgid and
* the pid are identical unless CLONE_THREAD was specified on clone() in
* which case the tgid is the same in all threads of the same group.
*
* This is SMP safe as current->tgid does not change.
*/
SYSCALL_DEFINE0(getpid)
{
return task_tgid_vnr(current);
}
/* Thread ID - the internal kernel "pid" */
SYSCALL_DEFINE0(gettid)
{
return task_pid_vnr(current);
}
/*
* Accessing ->real_parent is not SMP-safe, it could
* change from under us. However, we can use a stale
* value of ->real_parent under rcu_read_lock(), see
* release_task()->call_rcu(delayed_put_task_struct).
*/
SYSCALL_DEFINE0(getppid)
{
int pid;
rcu_read_lock();
pid = task_tgid_vnr(rcu_dereference(current->real_parent));
rcu_read_unlock();
return pid;
}
SYSCALL_DEFINE0(getuid)
{
/* Only we change this so SMP safe */
return from_kuid_munged(current_user_ns(), current_uid());
}
SYSCALL_DEFINE0(geteuid)
{
/* Only we change this so SMP safe */
return from_kuid_munged(current_user_ns(), current_euid());
}
SYSCALL_DEFINE0(getgid)
{
/* Only we change this so SMP safe */
return from_kgid_munged(current_user_ns(), current_gid());
}
SYSCALL_DEFINE0(getegid)
{
/* Only we change this so SMP safe */
return from_kgid_munged(current_user_ns(), current_egid());
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
实际的system_call在linux-3.18.6/arch/x86/kernel/entry_32.S里
ENTRY(system_call)
RING0_INT_FRAME # can't unwind into user space anyway
ASM_CLAC
pushl_cfi %%eax # 保存系统调用号
SAVE_ALL #保存现场,将用到的所有CPU寄存器保存到栈中
GET_THREAD_INFO(%%ebp) #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 #检查系统调用号(系统调用号应小于nr_syscalls)
jae syscall_badsys #不合法 跳入异常处理
syscall_call:
call *sys_call_table(,%%eax,4) #通过系统调用号在系统调用表中找到相应的系统调用内核处理函数 -这一行很重要
syscall_after_call:
movl %%eax,PT_EAX(%%esp) # store the return value 保存返回值到栈中
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 # current->work
jne syscall_exit_work #需要,进入syscall_exit_work,这是常见进程调度时机
restore_all:
TRACE_IRQS_IRET #恢复现场
restore_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 << 8) | SEGMENT_RPL_MASK), %%eax
cmpl $((SEGMENT_LDT << 8) | USER_RPL), %%eax
CFI_REMEMBER_STATE
je ldt_ss # returning to user-space with LDT SS
#endif
restore_nocheck:
RESTORE_REGS 4 # skip orig_eax/error_code
irq_return:
INTERRUPT_RETURN #结束的iret
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
画出流程图
至于流程图中的syscall_exit_work,working_pending与work_resched在linux-3.18.6/arch/x86/kernel/entry_32.S里的593到665行。
总结
上次实验是通过API和内嵌汇编来系统调用,这次是具体分析了系统调用的内核处理过程,比较重要的是call *sys_call_table(,%%eax,4),这一段汇编是通过系统调用号,比如我这次实验中的0x14(变为十进制就是20),来查找与之匹配的系统调用函数,20对应的系统调用函数是getpid,其他系统调用原理与之相似,详细对应关系见对应关系,在流程图中SAVE_ALL将所有CPU寄存器保存到栈中,找到syscall_call,sys_call_table与call *sys_call_table(,%%eax,4)调用系统调用函数。
以上有很多是我个人见解,如果有错误,欢迎各位大佬指正。