五、系统调用(1)

运行模式
    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,ss
3:  iret;       \

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

.data
ENTRY(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_return
restore_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); \
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值