内核代码阅读(19) - 系统调用trap

本文涉及的产品
公网NAT网关,每月750个小时 15CU
简介: 系统调用trap

系统调用机制(sethostname)

用户空间

sethostname库函数

sethostname是glibc封装的库函数:
int sethostname(const char *name, size_t len);
查看sethostname的代码:
objdump -d /usr/lib/libc.a
    mov %ebx, %edx
    mov 0x8(%esp,1), %ecx
    mov 0x4(%esp,1), %ebx
    mov $0x4a, %eax
    int $0x80
    mov %edx, %ebx
    cmp $0xfffff001, %eax
    jae 1a<sethostname+0x1a>
    ret
1) 系统调用参数是通过寄存器传递的,因为系用调用trap到内核中会发生栈切换,不能通过栈来传递参数。
   系统调用的参数个数最多6个。
2) mov 0x8(%esp,1), %ecx
   参数len到%ecx
3) mov 0x4(%esp,1), %ebx
   参数name到%ebx
4) mov $0x4a, %eax
   sethostname的系统调用号
5) int $0x80
   系统调用中断
6) cmp $0xfffff001, %eax
   jae 1a<sethostname+0x1a>
   检查返回值如果在-4095到-1之间,说明有错误发生。
   其中,sethostname+0x1a是__syscall_error,在链接过程中由连接器填入。

sethostname库函数返回出错的处理

__syscall_error的汇编代码:
neg %eax
     push %eax
     call 4<__syscall_error_1+0x2>
     pop %ecx
     mov %ecx, (%eax)
     mov $0xffffffff, %eax
     ret
1) neg %eax
   把返回码取反,并且压入栈中。
2) call 4<__syscall_error_1+0x2>
   把全局变量errono地址加载到%eax中。
3) mov %ecx, (%eax)
   把错误码写入errono
4) mov $0xffffffff, %eax
   将%eax的内容填入-1,系统调用出错的通用的返回码是-1.errono中有具体的错误码。

内核空间

系统调用对应的陷阱门

系统调用通过INT指令穿过陷阱门后到达了内核空间,并且发生了栈的切换。
陷阱门和中断门的差别就是:陷阱门的n全下级别是DPL为3.
穿过陷阱门进入内核状态并不关闭中断,所以系统调用是可以被中断的。

系统调用入口0x80的服务程序 system_call

ENTRY(system_call)
        pushl %eax                        # save orig_eax
        SAVE_ALL
        GET_CURRENT(%ebx)
        cmpl $(NR_syscalls),%eax
        jae badsys
        testb $0x02,tsk_ptrace(%ebx)        # PT_TRACESYS
        jne tracesys
        call *SYMBOL_NAME(sys_call_table)(,%eax,4)
        movl %eax,EAX(%esp)                # save the return value
    ENTRY(ret_from_sys_call)
1) pushl %eax
   保存系统调用号
2) GET_CURRENT(%ebx)
   当前进程的task_struct指针到%ebx
3) testb $0x02,tsk_ptrace(%ebx)
   jne tracesys
   检查进程是否启用了PT_TRACESYS,跟踪子进程的系统调用(strace工具)。
4) call *SYMBOL_NAME(sys_call_table)(,%eax,4)
   SYMBOL_NAME(sys_call_table)(,%eax,4) => sys_call_table + %eax*4
   sys_call_table[]是一个函数指针的数组

内核代码 sys_sethostname

asmlinkage long sys_sethostname(char *name, int len)
    {
        int errno;
        if (!capable(CAP_SYS_ADMIN))
                return -EPERM;
        if (len < 0 || len > __NEW_UTS_LEN)
                return -EINVAL;
        down_write(&uts_sem);
        errno = -EFAULT;
        if (!copy_from_user(system_utsname.nodename, name, len)) {
                system_utsname.nodename[len] = 0;
                errno = 0;
        }
        up_write(&uts_sem);
        return errno;
    }
1) if (!capable(CAP_SYS_ADMIN))
   检查权限
2) down_write(&uts_sem);
   获取锁
3) if (!copy_from_user(system_utsname.nodename, name, len))
   从用户空间拷贝数据
copy_from_user
copy_from_user汇编代码
copy_from_user 最终调用到宏__copy_user_zeroing中。
#define copy_from_user(to,from,n)                        \
        (__builtin_constant_p(n) ?                        \
         __constant_copy_from_user((to),(from),(n)) :        \
         __generic_copy_from_user((to),(from),(n)))
static inline unsigned long
    __generic_copy_from_user_nocheck(void *to, const void *from, unsigned long n)
    {
        __copy_user_zeroing(to,from,n);
        return n;
    }
#define __copy_user_zeroing(to,from,size)                                \
    do {                                                                        \
        int __d0, __d1;                                                        \
        __asm__ __volatile__(                                                \
                "0:        rep; movsl\n"                                        \
                "        movl %3,%0\n"                                        \
                "1:        rep; movsb\n"                                        \
                "2:\n"                                                        \
                ".section .fixup,\"ax\"\n"                                \
                "3:        lea 0(%3,%0,4),%0\n"                                \
                "4:        pushl %0\n"                                        \
                "        pushl %%eax\n"                                        \
                "        xorl %%eax,%%eax\n"                                \
                "        rep; stosb\n"                                        \
                "        popl %%eax\n"                                        \
                "        popl %0\n"                                        \
                "        jmp 2b\n"                                        \
                ".previous\n"                                                \
                ".section __ex_table,\"a\"\n"                                \
                "        .align 4\n"                                        \
                "        .long 0b,3b\n"                                        \
                "        .long 1b,4b\n"                                        \
                ".previous"                                                \
                : "=&c"(size), "=&D" (__d0), "=&S" (__d1)                \
                : "r"(size & 3), "0"(size / 4), "1"(to), "2"(from)        \
                : "memory");                                                \
    } while (0)
1) 参数输入部:
   : "=&c"(size), "=&D" (__d0), "=&S" (__d1)
   %0 -> size 和%ecx绑定
   %1 -> __d0 和%edi绑定
   %2 -> __d1 和%esi绑定
2) 参数输出部
   : "r"(size & 3), "0"(size / 4), "1"(to), "2"(from)
   %3 -> size&3 和一个寄存器绑定
   %4 -> size/4 和%0也就是%ecx绑定
   %5 -> to 和%1也就是%edx绑定
   %6 -> from 和%2也就是%edi绑定
3) "0:        rep; movsl\n"
   rep指令执行串操作,从%esi 到 %edi循环执行%ecx次数。
   拷贝4字节。
4) "        movl %3,%0\n"
   "rep; movsb\n"
   拷贝剩下的一个字节。
5) fixup和__ex_table
   错误处理: 如果用户空间传递的from指针压根就是非法地址(没有建立缺页映射)怎么办?
   当然可以在执行拷贝之前调用verify_area,通过task_struct中的mm检查from的合法性。
   但是这样一方面做性能低,另一方面绝大时候的系统调用的参数都是合法的,做了无用功。
   在最新的内核中是通过“事后补救”的方式:如果参数非法了,执行不下去了,恢复现场。而不是对所有的系统调用全部都进行参数检查。
fixup和__ex_table
对于可能出错的地方并不事先做sanity test,而采取“事后补救”。C++的异常借鉴了这种设计。
   gcc编译器把代码编译后生成了text和data段,还支持fixup段和__ex_table段。
   fixup:用于异常发生后的修复。
   __ex_table:用户异常指令地址表。
   .section .fixup 和 .section __ex_table 告诉编译器把相应的代码放在fixup和__ex_table段中。
".section __ex_table,\"a\"\n"
       "        .align 4\n"
       "        .long 0b,3b\n"
       "        .long 1b,4b\n"
       struct exception_table_entry
       {
           unsigned long insn, fixup;
       };
指示标号0处指令的异常由标号3处的代码处理,标号1处指令的异常由4号处理。
   exception_table_entry结构体就存储了异常指令和对应的修复地址。
   同时,gcc把所有的 exception_table_entry结构体按照异常的指令地址排序。
fixup和__ex_table的使用
"0:        rep; movsl\n"       
   标号0处的指令发生异常的情况是esi,edi对应的内存地址是非法地址。
asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
       {
           if (in_interrupt() || !mm)
               goto no_context;
           vma = find_vma(mm, address);
           if (!vma)
               goto bad_area;
       no_context:
           if ((fixup = search_exception_table(regs->eip)) != 0) {
               regs->eip = fixup;
               return;
           }
       }
1) (fixup = search_exception_table(regs->eip)) != 0)
      在__ex_table段中二份查找出错的指令对应的修复地址
   2) regs->eip = fixup;
      把找到的修复地址添入eip中。下一条要执行的指令就是fixup了。
   下面两个函数是在__ex_table表中查找修复地址。
unsigned long
       search_exception_table(unsigned long addr)
       {
           unsigned long ret;
           ret = search_one_table(__start___ex_table, __stop___ex_table-1, addr);
           if (ret) return ret;
           return 0;
       }
static inline unsigned long
       search_one_table(const struct exception_table_entry *first,
                 const struct exception_table_entry *last,
                 unsigned long value)
       {
           while (first <= last) {
              const struct exception_table_entry *mid;
              long diff;
                mid = (last - first) / 2 + first;
                diff = mid->insn - value;
                if (diff == 0)
                        return mid->fixup;
                else if (diff < 0)
                        first = mid+1;
                else
                        last = mid-1;
           }
           return 0;
       }

系统调用的返回 ret_from_sys_call

系统调用的返回和中断处理的返回逻辑一致都要检查是否有软中断要处理
ENTRY(ret_from_sys_call)
        movl SYMBOL_NAME(irq_stat),%ecx                # softirq_active
        testl SYMBOL_NAME(irq_stat)+4,%ecx        # softirq_mask
        jne   handle_softirq
     ret_with_reschedule:
        cmpl $0,need_resched(%ebx)
        jne reschedule
        cmpl $0,sigpending(%ebx)
        jne signal_return
     restore_all:
        RESTORE_ALL
1) movl SYMBOL_NAME(irq_stat),%ecx
    加载irq_stat中的active
 2) testl SYMBOL_NAME(irq_stat)+4,%ecx
    测试 softirq_active & softirq_mask
 3) jne   handle_softirq
    如果非零在从系统调用返回前处理软中断。
 4) RESTORE_ALL
    通过iret返回到用户空间
RESTORE_ALL
#define SAVE_ALL \
        cld; \
        pushl %es; \
        pushl %ds; \
        pushl %eax; \
        pushl %ebp; \
        pushl %edi; \
        pushl %esi; \
        pushl %edx; \
        pushl %ecx; \
        pushl %ebx; \
        movl $(__KERNEL_DS),%edx; \
        movl %edx,%ds; \
        movl %edx,%es;
#define RESTORE_ALL        \
        popl %ebx;        \
        popl %ecx;        \
        popl %edx;        \
        popl %esi;        \
        popl %edi;        \
        popl %ebp;        \
        popl %eax;        \
1:        popl %ds;        \
2:        popl %es;        \
        addl $4,%esp;        \
3:        iret;                \
.section .fixup,"ax";        \
4:        movl $0,(%esp);        \
        jmp 1b;                \
5:        movl $0,(%esp);        \
        jmp 2b;                \
6:        pushl %ss;        \
        popl %ds;        \
        pushl %ss;        \
        popl %es;        \
        pushl $11;        \
        call do_exit;        \
.previous;                \
.section __ex_table,"a";\
        .align 4;        \
        .long 1b,4b;        \
        .long 2b,5b;        \
        .long 3b,6b;        \
.previous

系统调用号

系统调用编号

在include/asm-i386/unistd.h文件中定义了系统调用的编号
#define __NR_exit                  1
    #define __NR_fork                  2
    #define __NR_read                  3
    #define __NR_write                  4

系统调用表sys_call_table的初始化

ENTRY(sys_call_table)
        .long SYMBOL_NAME(sys_ni_syscall)
        .long SYMBOL_NAME(sys_exit)
        .long SYMBOL_NAME(sys_fork)
        .long SYMBOL_NAME(sys_read)
        .long SYMBOL_NAME(sys_write)
        .long SYMBOL_NAME(sys_open)                /* 5 */
        .long SYMBOL_NAME(sys_close)
        .long SYMBOL_NAME(sys_waitpid)
        .long SYMBOL_NAME(sys_creat)
相关实践学习
高可用应用架构
欢迎来到“高可用应用架构”课程,本课程是“弹性计算Clouder系列认证“中的阶段四课程。本课程重点向您阐述了云服务器ECS的高可用部署方案,包含了弹性公网IP和负载均衡的概念及操作,通过本课程的学习您将了解在平时工作中,如何利用负载均衡和多台云服务器组建高可用应用架构,并通过弹性公网IP的方式对外提供稳定的互联网接入,使得您的网站更加稳定的同时可以接受更多人访问,掌握在阿里云上构建企业级大流量网站场景的方法。 学习完本课程后,您将能够: 理解高可用架构的含义并掌握基本实现方法 理解弹性公网IP的概念、功能以及应用场景 理解负载均衡的概念、功能以及应用场景 掌握网站高并发时如何处理的基本思路 完成多台Web服务器的负载均衡,从而实现高可用、高并发流量架构
相关文章
|
30天前
|
缓存 Linux 编译器
C/C++ 函数调用以及Linux中系统调用 开销介绍:介绍C/C函数调用以及Linux中系统调用的开销情况
C/C++ 函数调用以及Linux中系统调用 开销介绍:介绍C/C函数调用以及Linux中系统调用的开销情况
17 0
|
5月前
|
API 开发工具
【Pintos】实现自定义 UserProg 系统调用 | 添加 syscall-nr 系统调用号 | 编写新的参数调用宏
【Pintos】实现自定义 UserProg 系统调用 | 添加 syscall-nr 系统调用号 | 编写新的参数调用宏
69 0
|
3月前
|
安全 Java 程序员
“系统调用”究竟是不是个函数?
- **系统调用**和普通**函数**有何区别? - 什么是**内核态** 和 **用户态**? - 操作系统如何让CPU切换状态? - 内中断、外中断、软中断、硬中断是什么意思? - 库函数和系统调
其他系统调用
其他系统调用
60 0
|
Linux Shell 网络安全
使用Systemtap跟踪系统调用 (一)
SystemTap是一个诊断Linux系统性能或功能问题的开源软件。它使得对运行时的Linux系统进行诊断调式变得更容易、更简单。有了它,开发者或调试人员不再需要重编译、安装新内核、重启动等烦人的步骤。
508 0
使用Systemtap跟踪系统调用 (一)
|
存储 安全 Unix
什么是系统调用?
当谈到系统调用(system call)时,我们首先映入脑海的差不多就是软中断、内核态、用户态。开宗明义第一章,我想让大家先要重新认识一下『系统调用』这个词。
1027 0
什么是系统调用?
|
机器学习/深度学习 C语言