文章目录
  1. 1. 系统调用 getpid
  2. 2. 添加 MenuOS 菜单
  3. 3. system_call 流程分析
  4. 4. 总结
  5. 5. 参考

系统调用 getpid

选用的系统调用为getpid,调用号为20 (/linux-3.18.6/arch/x86/syscalls/syscall_32.tbl):

20    i386    getpid            sys_getpid

添加 MenuOS 菜单

在 menu/test.c 中添加如下两个函数: GetPid()是直接调用库函数, 而GetPidAsm()是用汇编方式实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int GetPid(int argc, char *argv[])
{
pid_t pid = getpid();
printf("pid: %d\n", pid);
return 0;
}
int GetPidAsm(int argc, char *argv[])
{
pid_t pid;
asm volatile(
"mov $0x14, %%eax\n\t"
"int $0x80\n\t"
"mov %%eax, %0\n\t"
: "=m"(pid)
);
printf("pid-asm: %d\n", pid);
return 0;
}

同时在main()中添加新的菜单项。

1
2
3
4
5
6
7
int main()
{
...
MenuConfig("pid", "Show Current PID", GetPid);
MenuConfig("pid-asm", "Show Current PID(asm)", GetPidAsm);
ExecuteMenu();
}

再 gcc 编译, 生成新的 rootfs.img, 启动 qemu。
如下是运行截图:

MenuOS

system_call 流程分析

test.c中定义的GetPidAsm直接使用汇编指令int $0x80来进行系统调用: 先将getpid的系统调用号0x14放入%eax, int $0x80指令之后将返回值从%eax 放回 pid, 最后打印出来。

int $0x80 触发软件中断, 代码进入到中断处理函数 system_call 中, 即为系统调用处理函数。 system_call 的对应的中断在如下文件中设置:

/linux-3.18.6/init/main.c
asmlinkage __visible void __init start_kernel(void)
561    trap_init();

/linux-3.18.6/arch/x86/kernel/traps.c
void __init trap_init(void)
838 #ifdef CONFIG_X86_32
839     set_system_trap_gate(SYSCALL_VECTOR, &system_call);
840     set_bit(SYSCALL_VECTOR, used_vectors);
841 #endif

/linux-3.18.6/arch/x86/include/asm/irq_vectors.h
50 #ifdef CONFIG_X86_32
51 # define SYSCALL_VECTOR            0x80
52 #endif

再来看看 system_call 的定义, 中间删除了两段由宏 #ifdef CONFIG_X86_ESPFIX32 包围的代码:

/linux-3.18.6/arch/x86/kernel/entry_32.S
489    # system call handler stub
490 ENTRY(system_call)
491    RING0_INT_FRAME            # can't unwind into user space anyway
492    ASM_CLAC
493    pushl_cfi %eax            # save orig_eax
494    SAVE_ALL
495    GET_THREAD_INFO(%ebp)
496                    # system call tracing in operation / emulation
497    testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
498    jnz syscall_trace_entry
499    cmpl $(NR_syscalls), %eax
500    jae syscall_badsys
501 syscall_call:
502    call *sys_call_table(,%eax,4)
503 syscall_after_call:
504    movl %eax,PT_EAX(%esp)        # store the return value
505 syscall_exit:
506    LOCKDEP_SYS_EXIT
507    DISABLE_INTERRUPTS(CLBR_ANY)    # make sure we don't miss an interrupt
508                    # setting need_resched or sigpending
509                    # between sampling and the iret
510    TRACE_IRQS_OFF
511    movl TI_flags(%ebp), %ecx
512    testl $_TIF_ALLWORK_MASK, %ecx    # current->work
513    jne syscall_exit_work
514
515 restore_all:
516    TRACE_IRQS_IRET
517 restore_all_notrace:
...
530 restore_nocheck:
531    RESTORE_REGS 4            # skip orig_eax/error_code
532 irq_return:
533    INTERRUPT_RETURN
534 .section .fixup,"ax"
535 ENTRY(iret_exc)
536    pushl $0            # no error code
537    pushl $do_iret_error
538    jmp error_code
539 .previous
540    _ASM_EXTABLE(irq_return,iret_exc)
541
...
587    CFI_ENDPROC
588 ENDPROC(system_call)


/linux-3.18.6/arch/x86/include/asm/irqflags.h
145 #define INTERRUPT_RETURN        iret
  • 493-494 先将 %eax 和其它寄存器入栈 (SAVE_ALL), 495-498 根据是根据当前线程状态来决定是否调用 syscall_trace_entry 吧? (不确定根据哪些状态位)
  • 499-500 如果系统调用号大于最大调用号 NR_syscalls, 则转入 syscall_badsys, 设置异常返回值于 %eax
  • 501-502 调用对应的系统调用函数。 sys_call_table 是起始地址, 由%eax中的调用号来确定调用函数的偏移量
  • 503~ 之后就开始准备返回了,512-513 会对中断信号进行处理
  • 530-531 RESTORE_REGS 4 从栈中恢复寄存器的值
  • 532-533 宏 INTERRUPT_RETURN 定义了返回指令 `iret

总结

系统调用的大致过程如下:

  • 中断 int $0x80 触发系统调用, %eax 传递系统调用过
  • 中断处理函数 system_call 先将寄存器的值入栈,先检查中断号, 再调用由 sys_ 开始的系统函数, 最后从栈恢复寄存器, iret 指令返回
  • 系统调用结果由 %eax 传回

参考

文章目录
  1. 1. 系统调用 getpid
  2. 2. 添加 MenuOS 菜单
  3. 3. system_call 流程分析
  4. 4. 总结
  5. 5. 参考