zoukankan      html  css  js  c++  java
  • Linux系统调用过程

    一. 概述

             系统调用是应用程序与内核交互的一种方式。系统调用作为一种接口,通过系统调用,应用程序能够进入操作系统内核,从而使用内核提供的各种资源,比如操作硬件,开关中断,改变特权模式等等。首先,系统调用是一个软中断,既然是中断那么一般就具有中断号和中断处理程序两个属性,Linux使用0x80号中断作为系统调用的入口,而中断处理程序的地址放在中断向量表里。

    二. 过程

             基于linux-2.6.38,以read()系统调用函数为例进行说明。    

           在用户空间,read()函数的声明位于#include<unistd.h>,原型为:ssize_t read(int fd, void *buf, size_t count)。下面是read()函数在用户空间的定义的伪代码:

     1 ssize_t read(int fd, void *buf, size_t count)
     2 {
     3        long res;
     4        %eax = __NR_read
     5        %ebx = fd 
     6        %ecx = (long)buf
     7        %edx= count
     8        int $0x80
     9        res = %eax
    10        return res;
    11 }

             第4行,用eax寄存器保存read()的系统调用号,在/arch/x86/include/asm/unistd_32.h里定义(#define __NR_read  3);第5~7行,分别将三个参数放入三个寄存器(通过寄存器来传递参数);第8行,执行系统调用,进入内核;第9行,获取eax寄存器所保存的函数返回值。

           执行第8行后已经进入了系统内核,由于这是一个中断,因此程序进入到中断向量表中记录0x80号的中断处理程序,中断向量表的初始化在/arch/x86/kernel/traps.c中定义:

     1 void __init trap_init(void)
     2 {
     3 ...................
     4 
     5    #ifdef CONFIG_X86_32
     6        set_system_trap_gate(SYSCALL_VECTOR, &system_call);
     7        set_bit(SYSCALL_VECTOR, used_vectors);
     8    #endif
     9 ...................
    10 }

    如第6行所示。SYSCALL_VECTOR是系统调用的中断号,在/arch/x86/include/asm/irq_vectors.h中定义:

    1 #ifdef CONFIG_X86_32
    2 # define SYSCALL_VECTOR            0x80
    3 #endif

    正好是0x80。而system_call是系统调用的中断处理函数指针,用户执行int $0x80后会执行到这个函数,它在/arch/x86/kernel/entry_32.S中定义:

     1 ENTRY(system_call)
     2     RING0_INT_FRAME            # can't unwind into user space anyway
     3     pushl_cfi %eax            # save orig_eax
     4     SAVE_ALL
     5     GET_THREAD_INFO(%ebp)
     6                     # system call tracing in operation / emulation
     7     testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)
     8     jnz syscall_trace_entry
     9     cmpl $(nr_syscalls), %eax
    10     jae syscall_badsys
    11 syscall_call:
    12     call *sys_call_table(,%eax,4)
    13     movl %eax,PT_EAX(%esp)        # store the return value
    ...........

    第4行,SAVE_ALL是一个宏,也在这个文件里定义:

     1 .macro SAVE_ALL
     2     cld
     3     PUSH_GS
     4     pushl_cfi %fs
     5     /*CFI_REL_OFFSET fs, 0;*/
     6     pushl_cfi %es
     7     /*CFI_REL_OFFSET es, 0;*/
     8     pushl_cfi %ds
     9     /*CFI_REL_OFFSET ds, 0;*/
    10     pushl_cfi %eax
    11     CFI_REL_OFFSET eax, 0
    12     pushl_cfi %ebp
    13     CFI_REL_OFFSET ebp, 0
    14     pushl_cfi %edi
    15     CFI_REL_OFFSET edi, 0
    16     pushl_cfi %esi
    17     CFI_REL_OFFSET esi, 0
    18     pushl_cfi %edx
    19     CFI_REL_OFFSET edx, 0
    20     pushl_cfi %ecx
    21     CFI_REL_OFFSET ecx, 0
    22     pushl_cfi %ebx
    23     CFI_REL_OFFSET ebx, 0
    24     movl $(__USER_DS), %edx
    25     movl %edx, %ds
    26     movl %edx, %es
    27     movl $(__KERNEL_PERCPU), %edx
    28     movl %edx, %fs
    29     SET_KERNEL_GS %edx
    30 .endm

    主要作用就是将各个寄存器压入栈中。

    第9行,比较eax的值是否大于等于nr_syscalls,nr_syscalls是比最大有效系统调用号大1的值,在/arch/x86/kernel/entry_32.S中定义:

    1 #define nr_syscalls ((syscall_table_size)/4)

    其中syscall_table_size就是系统调用表的大小(单位:字节),syscall_table_size其实是一个数组,数组里存放的是各个系统调用函数的地址,元素类型是long型,除以4刚好是系统调用函数的个数。

    如果从eax寄存器传进来的系统调用号有效,那么就执行第12行,在系统调用表里找到相应的系统调用服务程序,sys_call_table在/arch/x86/kernel/syscall_table_32.S中定义:

    1 ENTRY(sys_call_table)
    2     .long sys_restart_syscall    /* 0 - old "setup()" system call, used for restarting */
    3     .long sys_exit
    4     .long ptregs_fork
    5     .long sys_read
    6     .long sys_write
    7     .long sys_open        /* 5 */
    8     .long sys_close
    9 .................
    *sys_call_table(,%eax,4)指的是sys_call_table里偏移量为%eax*4上的那个值指向的函数,这里%eax=3,那么第5行的sys_read()函数就会被调用。sys_read()在/fs/read_write.c中定义:
     1 SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
     2 {       
     3         struct file *file;      
     4         ssize_t ret = -EBADF;
     5         int fput_needed;
     6         
     7         file = fget_light(fd, &fput_needed);
     8         if (file) {
     9                 loff_t pos = file_pos_read(file);
    10                 ret = vfs_read(file, buf, count, &pos);
    11                 file_pos_write(file, pos);
    12                 fput_light(file, fput_needed);
    13         }
    14         
    15         return ret;
    16 } 

    可见,参数的形式和用户空间的一样。SYSCALL_DEFINE3是一个宏,在/include/linux/syscalls.h中定义:

    1 #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

    SYSCALL_DEFINEx也是一个宏,也在此文件中定义:

    1 #define SYSCALL_DEFINEx(x, sname, ...)                \
    2     __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
    3 .....
    4 #define __SYSCALL_DEFINEx(x, name, ...)                    \
    5     asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))
    6 ......

    宏展开后,就是声明了这么一个函数:

    asmlinkage long sys_read(unsigned int fd, char __user *buf, size_t count);

    asmlingage是一个宏,定义为:__attribute__((regparm(0))),作用是让这个函数只从栈上获取参数(因为之前的SAVE_ALL将参数压到了栈里面)。

          当执行完中断处理程序后,后面会调用RESTORE_REGS来恢复各个寄存器:

    1 ..............
    2     CFI_REMEMBER_STATE
    3     je ldt_ss            # returning to user-space with LDT SS
    4 restore_nocheck:
    5     RESTORE_REGS 4            # skip orig_eax/error_code
    6 ...............

    第5行,RESTORE_REGS的定义:

     1 .macro RESTORE_REGS pop=0
     2     RESTORE_INT_REGS
     3 1:    popl_cfi %ds
     4     /*CFI_RESTORE ds;*/
     5 2:    popl_cfi %es
     6     /*CFI_RESTORE es;*/
     7 3:    popl_cfi %fs
     8     /*CFI_RESTORE fs;*/
     9     POP_GS \pop
    10 .................

    第2行,RESTORE_INT_REGS的定义:

     1 .macro RESTORE_INT_REGS
     2     popl_cfi %ebx
     3     CFI_RESTORE ebx
     4     popl_cfi %ecx
     5     CFI_RESTORE ecx
     6     popl_cfi %edx
     7     CFI_RESTORE edx
     8     popl_cfi %esi
     9     CFI_RESTORE esi
    10     popl_cfi %edi
    11     CFI_RESTORE edi
    12     popl_cfi %ebp
    13     CFI_RESTORE ebp
    14     popl_cfi %eax
    15     CFI_RESTORE eax
    16 .endm

           到这里差不多了,再对read()跟踪的话就会涉及文件系统方面的内容,以后会说的。

  • 相关阅读:
    mysql外键(FOREIGNKEY)使用介绍
    MYSQL数据库-约束
    mysql探究之null与not null
    爬虫
    http://blog.csdn.net/w_e_i_/article/details/70766035
    Python 3.5安装 pymysql 模块
    Python 3.5 连接Mysql数据库(pymysql 方式)
    hdu Bone Collector
    hdu City Game
    hdu Largest Rectangle in a Histogram
  • 原文地址:https://www.cnblogs.com/lknlfy/p/2591366.html
Copyright © 2011-2022 走看看