(2023-2024-1)20232830《Linux内核原理分析与设计》第六周作业
1. 《庖丁解牛Linux分析》第6章
可执行程序工作原理
2. 实验五:分析 system_call 中断处理过程
2.1 gdb 跟踪分析系统调用内核函数
将上周实验函数 sysinfo 写入test.c文件中;
编译内核,看到添加的命令;
返回父目录,启动镜像,并冻结;
重新打开一个终端,打开gdb,并输入:
file linux-3.18.6/vmlinux
target remote:1234
接着我们设置两个断点,如下截图所示:
打开相应文件在相应的行找到相应的代码;
system_call:
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
sys_getpid:
/**
* 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());
}
2.2 system_call开始至结束流程图
3. 总结
用户态进程调用 int 0x80 或 system_call 时,实际上触发了一个中断,这个中断使得 CPU 暂停当前进程的执行,保存当前执行环境(现场),并切换到内核态执行系统内核中预设的一些任务。这个过程确保了用户态进程在执行系统调用时能够以受控制的方式进入内核态,而系统调用执行结束后,再次保存当前执行环境,回到内核态,最终通过 iret 指令返回到用户态。
在这个过程中,中断处理程序会对调用的任务进行各种检查,包括参数的合法性、权限的验证等。然后,系统可能会进行进程调度,选择下一个要执行的任务。在系统调用执行完成后,还会进行进一步的检查,确保任务的正确执行。最后,通过 iret 指令返回到用户态,让用户态进程继续执行。这样的设计保证了系统调用的安全性和正确性。