内核代码阅读(19) - 系统调用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)
上一篇:内核代码阅读(18) - 时钟中断的上半部do_timer和下半部timer_bh


下一篇:内核代码阅读(25) - 强制调度