2018-2019-1 20189221 《Linux内核原理与分析》第六周作业

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
",getpid());
        count++;
    }
    else {
        printf("i am the parent process, my process id is %d
",getpid());
        count++;
    }
    printf("count: %d
",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”指令完成调用返回;

  • 当到达内核态后,操作系统需要严格检查系统调用传递的参数,确保不破坏整个系统的安全性;

  • 执行系统调用可导致进程等待某事件发生,从而可引起进程切换;

原文地址:https://www.cnblogs.com/gdman/p/9955183.html