五、系统调用(1)

来源:互联网 发布:php如何解决ajax跨域 编辑:程序博客网 时间:2024/06/05 00:58

运行模式
    Inter系列处理器有实模式和保护模式。刚启动处于实模式只能使用实地址访问内存。保护模式下可以使用段页机制,虚地址寻址等,保护模式下还提供4个特权级,linux只使用特权级0(内核模式)和特权级3(用户模式)。

地址空间
    Linux虚存管理机制下,进程使用虚拟地址,进程都有自己的虚拟空间,通过地址转换机制转换成对物理地址的引用。每个进程的虚拟地址空间可以划分为两个部分:用户空间和系统空间。用户模式下只能访问用户空间,核心模式下可以访问这两个空间。系统空间中每个进程的虚拟地址都是3G-4G,所有进程的系统空间都会映射到单一内核地址空间。使得内核可以访问任何进程的地址空间。
    进程执行系统调用时,通过特殊指令使系统陷入内核,并将控制权交给内核,由内核代替进程完成操作。完成后内核执行一组特征指令返回到用户态。

上下文

  • 用户级上下文:正文、数据、用户栈、共享存储区等
  • 寄存器上下文:通用寄存器、程序寄存器、状态寄存器、栈等
  • 系统级上下文:进程控制块、内存管理信息、核心栈等
        发生进程调度时,操作系统必须对上面全部上下文进行切换。系统调用进行的是模式切换(只进行寄存器上下文切换)比进程切换容易得多。

地址装换

这里写图片描述
Linux更偏向使用页机制,但是分段是Intel硬件机制所规定的所有使用两级地址转换。硬件除了使用自己的寄存器外还使用驻留在内存的描述符表、页表等数据结构来进行地址转换。

逻辑地址转线性地址
这里写图片描述
这里写图片描述

  • 段选择符:索引部分+TI部分+RPL部分(当前特权级)。RPL决定当前进程访问相应段的权限;TI部分选择局部描述符表(LDT)还是全局描述符表(GDT)。描述表中每个描述符描述一个段的段基址,加偏移地址就唯一确定一个线性地址。

    线性地址转物理地址
    这里写图片描述
    Linux分页机制采用两级分页,段机制描述可大可小的存储块,分页机制管理的是固定大小的页(一般为4K)。整个物理空间和线性空间都看成是由一个一个页面组成。通过分页机制可以把线性空间的一个页面映射到物理空间的一个页。

    arch/i386/kernel/entry.S

    进程执行int指令陷入到内核,系统对堆栈进行切换,首先获取核心堆栈信息,再把用户堆栈信息保存到核心栈,这几步由硬件完成。

//寄存器入栈和出栈#define SAVE_ALL \    cld; \    pushl %es; \    pushl %ds; \    pushl %eax; \    pushl %ebp; \    pushl %edi; \    pushl %esi; \    pushl %edx; \    pushl %ecx; \    pushl %ebx; \//__KERNEL_DS在include/asm-i386/segment.h中定义为//#define __KERNEL_DS   0x18,即使用内核堆栈    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;  \//iret返回指令,如果同级别,则弹出eip,cs,EFLAGS;//如果是不同级别,则还需弹出堆栈指针esp,ss3:  iret;       \

系统调用表依次保存着所有系统调用的函数指针

.dataENTRY(sys_call_table)    .long SYMBOL_NAME(sys_ni_syscall)   /* 0  -  old "setup()" system call*/    .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_ni_syscall)   /* reserved for fremovexattr *///'.'表示当前地址,sys_call_table为数组首地址//(.-sys_call_table)/4表示已用系统调用数//NR_syscalls-(.-sys_call_table)/4为没有定义的系统调用数    .rept NR_syscalls-(.-sys_call_table)/4//往剩余空间填充sys_ni_syscall        .long SYMBOL_NAME(sys_ni_syscall)    .endr

这里写图片描述

//linux核心栈的位置在task_struct之后的两个页面(8192处),//通过-8192与上栈指针得到task_struct结构指针#define GET_CURRENT(reg)    movl $-8192, reg    andl %esp, reg - 陷入进内核机器会自动保存和转化堆栈ENTRY(system_call)    pushl %eax          #保存系统调用号    SAVE_ALL    GET_CURRENT(%ebx)//得到当前进程结构控制块的指针    testb $0x02,tsk_ptrace(%ebx)   # 若是受监视进程则跳转    jne tracesys    cmpl $(NR_syscalls),%eax//判断是否是可用系统调用    jae badsys    //根据系统调用号查询系统调用表,调用对应函数    call *SYMBOL_NAME(sys_call_table)(,%eax,4)    movl %eax,EAX(%esp)     # save the return value//从系统调用返回,判断是否需要重新调度//是否有进程向其发送信号,执行处理函数//中断也从这里返回ENTRY(ret_from_sys_call)    cli             # need_resched and signals atomic test    cmpl $0,need_resched(%ebx)    jne reschedule    cmpl $0,sigpending(%ebx)    jne signal_returnrestore_all:    RESTORE_ALL

系统调用实现过程

  • arch/i386/kernel/traps.c中trap_init函数初始化中断描述符表。
  • include/asm-i386/hw_irq.h中定义#define SYSCALL_VECTOR 0x80
  • 即在中断描述表的第0x80项填入陷阱门描述符,使控制安全转移到system_call
  • 这就保证每次执行int 0x80时,系统把控制权转移到system_call
void __init trap_init(void){    .........    set_system_gate(SYSCALL_VECTOR,&system_call);    .........    cpu_init();}
  • include/asm-i386/unistd.h定义了NR开头系统调用对应的数字
  • 后面会通过宏把用户调用的系统转换成以NR开头,再转换成对应数字通过eax寄存器作为syscall_table的索引
#define __NR_exit         1#define __NR_fork         2#define __NR_read         3#define __NR_write        4#define __NR_open         5#define __NR_close        6

include/asm-i386/unistd.h中宏定义转换

  1. _syscall0中0表示无参数的系统调用
  2. type是返回值类型,name是函数名称
  3. volatile 表示编译器不作优化,int $0x80进入软中断
  4. ‘=’表示__res为输出参数,‘a’表示占用寄存器eax
  5. 下一行无‘=’表示输入,此时系统调用name变为__NR_name
#define _syscall0(type,name) \type name(void) \{ \long __res; \__asm__ volatile ("int $0x80" \    : "=a" (__res) \    : "0" (__NR_##name)); \__syscall_return(type,__res); \}int pause(void){    long __res; \    __asm__ volatile ("int $0x80" \        : "=a" (__res) \        : "0" (__NR_pause)); \    __syscall_return(int,__res); \}
  1. _syscall4中4表示有4个参数,每个参数有一个类型
  2. 参数通过寄存器ebx,ecx等传递
  3. SAVE_ALL那里把寄存器值压栈,一方面保护环境,另外把系统调用的参数也入栈,则系统调用处理函数可以从栈中得到参数
#define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \{ \long __res; \__asm__ volatile ("int $0x80" \    : "=a" (__res) \    : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \      "d" ((long)(arg3)),"S" ((long)(arg4))); \__syscall_return(type,__res); \} 
阅读全文
0 0
原创粉丝点击