2018-2019-1 20189221 《Linux内核原理与分析》第六周作业
实验五
实验过程
将Fork函数移植到Linux的MenuOS
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程。在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。通过fork返回的值来判断当前进程是子进程还是父进程。
启动MenuOS:
在test.c中添加代码如下:
int Fork(int argc, char *argv[])
{
pid_t fpid;
int count = 0;
fpid = fork();
if (fpid < 0)
printf("error in fork!");
else if (fpid == 0) {
printf("i am the child process, my process id is %d\n",getpid());
count++;
}
else {
printf("i am the parent process, my process id is %d\n",getpid());
count++;
}
printf("count: %d\n",count);
return 0;
}
int main()
{
PrintMenuOS();
SetPrompt("MenuOS>>");
MenuConfig("version","MenuOS V1.0(Based on Linux 3.18.6)",NULL);
MenuConfig("quit","Quit from MenuOS",Quit);
MenuConfig("fork","fork a child process",Fork);
ExecuteMenu();
}
运行结果:
sys_call过程分析
gdb调试:
由于实验楼多次卡顿,十分浪费时间
调试代码:
$ gdb
(gdb) file linux-3.18.6/vmlinux
(gdb) target remote:1234
(gdb) continue
在sys_fork设置断点,在qemu中输入fork-asm命令,可以看到停在了sys_fork()函数中。
然后s单步执行,finish返回do_fork函数,返回值$2=866,即分配了pid=866的子进程。继续单步,到了schedule()中,此时发生了进程调度。finish后schedule返回。
再次单步执行,出现Cannot find bounds of current function。此时gdb已无法跟踪。
gdb中输入c继续执行,看到qemu中输出fid = 866,即子进程的pid为866。
system_call的代码:
ENTRY(system_call)
RING0_INT_FRAME
ASM_CLAC
pushl_cfi %eax //保存系统调用号;
SAVE_ALL //可以用到的所有CPU寄存器保存到栈中
GET_THREAD_INFO(%ebp) //ebp用于存放当前进程thread_info结构的地址
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) //合法,对照系统调用号在系统调用表中寻找相应服务例程
movl %eax,PT_EAX(%esp) //保存返回值到栈中
syscall_exit:
testl $_TIF_ALLWORK_MASK, %ecx //检查是否需要处理信号
jne syscall_exit_work //需要,进入 syscall_exit_work
restore_all:
TRACE_IRQS_IRET //不需要,执行restore_all恢复,返回用户态
irq_return:
INTERRUPT_RETURN //相当于iret
system_call流程:
实验总结
系统调用是特殊的中断函数,是多种中断处理过程的集合
系统调用的过程其实是另一种上下文切换的实现:
首先SAVE ALL保存上下文
根据IDT调用内核函数
执行RESTORE_ALL并返回用户模式
系统调用与中断的共同之处
-
保存现场
在系统调用时,用SAVE_ALL来保存系统调用时的上下文。
中断处理的第一步也是要保存中断程序现场。
中断处理完之后,可以返回到原来被中断的地方,在原有的运行环境下继续正确的执行下去。 -
确定中断信息
在系统调用中,需要将系统调用号通过eax传入,通过sys_call_table查询到调用的系统调用,然后跳转到相应的程序进行处理。
中断处理时系统也需要有一个中断号,通过检索中断向量表,了解中断的类型和设备。 -
处理中断
跳转到相应的中断处理程序后,对中断进行处理。
-
返回
系统调用时最后要restore_all恢复系统调用时的现场,并用iret返回用户态。
同样,执行完中断处理程序,内核也要执行特定指令序列,恢复中断时现场,并使得进程回到用户态。
系统调用与一般函数的不同之处
不是通过“CALL”指令而是通过“INT”指令发起调用;
不是通过“RET”指令,而是通过“IRET”指令完成调用返回;
当到达内核态后,操作系统需要严格检查系统调用传递的参数,确保不破坏整个系统的安全性;
执行系统调用可导致进程等待某事件发生,从而可引起进程切换;