赞
踩
Linux系统调用本质是
执行宏 _syscall[0~6] (NUMBER, …),(0 ~6是参数个数)
该宏将子功能号传入eax,第一个参数传入ebx,第二个参数传入ecx,第三个参数传入edx,第四个参数传入esi,第五个参数传入edi,然后执行int 0x80中断。
中断向量号就是0x80,对应的中断处理程序中,call [syscall_table + eax * 4]
syscall_table[syscall_nr] 应该提前写好对应的处理函数
比如 syscall_table[SYS_GETPID] =sys_getpid
本次实验下,内联汇编引发的int 80中断的宏,被封装在c函数里,这就和c语言库函数实现系统调用是类似的。
本次实验,用户进程在特权级3下调用getpid(),调用宏_syscall0(SYS_GETPID)内联汇编进入中断
中断处理程序调用syscall_table[SYS_GETPID],即sys_getpid()获得PCB->pid返回。
根据二进制接口abi,c函数返回值会存入eax寄存器,但是sys_getpid()是在内核下完成的,返回值会存入内核态的eax,中断结束,会将0特权级栈保存用户进程的环境全部pop,所以将eax覆盖0特权级栈的eax处,这样恢复环境后,pid就能成功回到用户态的那句内联汇编,eax输入到c变量
mov [esp + 8 * 4], eax
为什么是8 * 4?
宏_syscall0(NUMBER)是无参数的,只有一个子功能号,内联汇编下输入eax
中断处理程序直到pushad才算是把这个eax(内含子功能号)保存下来,然后又入了三个 有add esp, 12,所以显然eax在第8个
#define IDT_DESC_CNT 0x81 // 目前总共支持的中断数 extern uint32_t syscall_handler(void); /*初始化中断描述符表*/ static void idt_desc_init(void) { int i, lastindex = IDT_DESC_CNT - 1; for (i = 0; i < IDT_DESC_CNT; i++) { make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]); } /* 单独处理系统调用,系统调用对应的中断门dpl为3, * 中断处理程序为单独的syscall_handler */ make_idt_desc(&idt[lastindex], IDT_DESC_ATTR_DPL3, syscall_handler); put_str(" idt_desc_init done\n"); }
;;;;;;;;;;;;;;;; 0x80号中断 ;;;;;;;;;;;;;;;; [bits 32] extern syscall_table section .text global syscall_handler syscall_handler: ;1 保存上下文环境 push 0 ; 压入0, 使栈中格式统一 push ds push es push fs push gs pushad ; PUSHAD指令压入32位寄存器,其入栈顺序是: ; EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI push 0x80 ; 此位置压入0x80也是为了保持统一的栈格式 ;2 为系统调用子功能传入参数 push edx ; 系统调用中第3个参数 push ecx ; 系统调用中第2个参数 push ebx ; 系统调用中第1个参数 ;3 调用子功能处理函数 call [syscall_table + eax*4] ; 编译器会在栈中根据C函数声明匹配正确数量的参数 add esp, 12 ; 跨过上面的三个参数 ;4 将call调用后的返回值存入待当前内核栈中eax的位置 mov [esp + 8*4], eax jmp intr_exit ; intr_exit返回,恢复上下文
#include "syscall.h" #define syscall_nr 32 typedef void* syscall; syscall syscall_table[syscall_nr]; /* 返回当前任务的pid */ uint32_t sys_getpid(void) { return running_thread()->pid; } /* 初始化系统调用 */ void syscall_init(void) { put_str("syscall_init start\n"); syscall_table[SYS_GETPID] = sys_getpid; put_str("syscall_init done\n"); }
#ifndef __USERPROG_SYSCALLINIT_H
#define __USERPROG_SYSCALLINIT_H
#include "stdint.h"
void syscall_init(void);
uint32_t sys_getpid(void);
#endif
struct lock pid_lock; /* 分配pid */ static pid_t allocate_pid(void) { static pid_t next_pid = 0; lock_acquire(&pid_lock); next_pid++; lock_release(&pid_lock); return next_pid; } /* 初始化线程基本信息 */ void init_thread(struct task_struct* pthread, char* name, int prio) { memset(pthread, 0, sizeof(*pthread)); pthread->pid = allocate_pid(); strcpy(pthread->name, name); } /* 初始化线程环境 */ void thread_init(void) { put_str("thread_init start\n"); list_init(&thread_ready_list); list_init(&thread_all_list); lock_init(&pid_lock); /* 将当前main函数创建为线程 */ make_main_thread(); }
typedef int16_t pid_t;
struct task_struct{
pid_t pid;
}
#include "syscall.h" /* 无参数的系统调用 */ #define _syscall0(NUMBER) ({ \ int retval; \ asm volatile ( \ "int $0x80" \ : "=a" (retval) \ : "a" (NUMBER) \ : "memory" \ ); \ retval; \ }) /* 一个参数的系统调用 */ #define _syscall1(NUMBER, ARG1) ({ \ int retval; \ asm volatile ( \ "int $0x80" \ : "=a" (retval) \ : "a" (NUMBER), "b" (ARG1) \ : "memory" \ ); \ retval; \ }) /* 两个参数的系统调用 */ #define _syscall2(NUMBER, ARG1, ARG2) ({ \ int retval; \ asm volatile ( \ "int $0x80" \ : "=a" (retval) \ : "a" (NUMBER), "b" (ARG1), "c" (ARG2) \ : "memory" \ ); \ retval; \ }) /* 三个参数的系统调用 */ #define _syscall3(NUMBER, ARG1, ARG2, ARG3) ({ \ int retval; \ asm volatile ( \ "int $0x80" \ : "=a" (retval) \ : "a" (NUMBER), "b" (ARG1), "c" (ARG2), "d" (ARG3) \ : "memory" \ ); \ retval; \ }) /* 返回当前任务pid */ uint32_t getpid() { return _syscall0(SYS_GETPID); }
#ifndef __LIB_USER_SYSCALL_H
#define __LIB_USER_SYSCALL_H
#include "stdint.h"
#include "thread.h"
enum SYSCALL_NR {
SYS_GETPID,
};
uint32_t getpid(void);
#endif
#include "interrupt.h" #include "init.h" #include "thread.h" #include "print.h" #include "process.h" #include "console.h" #include "syscall.h" #include "syscall-init.h" void k_thread_a(void* ); void k_thread_b(void* ); void u_prog_a (void); void u_prog_b (void); int prog_a_pid = 0, prog_b_pid=0; int main(void) { put_str("I am kernel\n"); init_all(); process_execute(u_prog_a, "user_prog_a"); process_execute(u_prog_b, "user_prog_b"); intr_enable(); console_put_str(" main_pid:0x"); console_put_int(sys_getpid()); console_put_char('\n'); thread_start("k_thread_a", 31, k_thread_a, "argA "); thread_start("k_thread_b", 8, k_thread_b, "argB "); while(1); return 0; } void k_thread_a(void* arg){ char* para = arg; console_put_str(" thread_a_pid:0x"); console_put_int(sys_getpid()); console_put_char('\n'); console_put_str(" prog_a_pid:0x"); console_put_int(prog_a_pid); console_put_char('\n'); while(1); } void k_thread_b(void* arg){ char* para = arg; console_put_str(" thread_b_pid:0x"); console_put_int(sys_getpid()); console_put_char('\n'); console_put_str(" prog_b_pid:0x"); console_put_int(prog_b_pid); console_put_char('\n'); while(1); } void u_prog_a(void) { prog_a_pid = getpid(); while(1); } void u_prog_b(void) { prog_b_pid = getpid(); while(1); }
用户进程的getpid()才算是系统调用,调用了宏触发中断,有特权级转换。
线程的sys_getpid()只是调用了内核函数而已,但功能一致。
用户进程能否调用sys_getpid()?答案是可以的,只是没有涉及特权级变化。
目前来看,用户进程涉及特权级变化,要么是时钟中断、键盘中断,要么是本次实验的系统调用getpid()内联汇编触发int 80中断,中断意味着要从tss.esp使用0特权级栈。
或者lock被阻塞(??lock代码忘了,不一定,回头再看)
分析如下:
main函数入口地址位于0xc0001500 ,这处于0xc0000000 ~ 0xc0100000即3GB到3GB+1MB的区域,sys_getpid()这个函数也不会离main有多远,所以,编译器给sys_getpid这个标号的地址也在3GB到3GB+1MB的区域,而3GB到4GB的页目录项都复制到每个进程的页目录表了。
其次用户进程在内核cs、ds被成功赋值成RPL=3的选择子,也指向早已被填充好的好的DPL=3的代码段,数据段,所以均会获得为0的段基址,同时DPL=3的代码段,数据段段界限都允许段内偏移地址在4GB,因此eip的寻址范围是4GB,用户态完全可以执行sys_getpid()。
因为我们的os在DPL=3的段描述符下未限制段界限。所以用户态(CPL=3)有3GB以上页表映射,也不会段越界,可以随意调用内核函数。
正常情况下完整的os,内核函数应该被严格限制在DPL=0的段内,其他段的范围都访问不到,那么即使页表映射支持,也会存在越特权级。只有特权级为0的时候才能访问。

十九、二十这两次实验代码编译好后都出不来结果,报缺页异常。
但是只要随便改一下time.c,可能makefile把他重新编译了一下,就出来了,很莫名其妙搞不懂。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。