运行模式
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中宏定义转换
- _syscall0中0表示无参数的系统调用
- type是返回值类型,name是函数名称
- volatile 表示编译器不作优化,int $0x80进入软中断
- ‘=’表示__res为输出参数,‘a’表示占用寄存器eax
- 下一行无‘=’表示输入,此时系统调用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); \
}
- _syscall4中4表示有4个参数,每个参数有一个类型
- 参数通过寄存器ebx,ecx等传递
- 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); \
}