以printf() 打印内核中的一段字符串为例
-
printf() 是用户函数无法进入内核,因此需要进行系统调用,进入内核的方式是使用int 0x80 中断 -
printf() 函数想要进入系统内核是通过系统调用write() 实现
#define __LIBRARY__
#include <unistd.h>
_syscall3(int,write,int,fd,const char *,buf,off_t,count)
-
_syscall3 被展开成一段包含int 0x80 中断汇编代码的C代码,见下文代码
...
#define __NR_write 4
...
#define _syscall3(type,name,atype,a,btype,b,ctype,c) \
type name(atype a,btype b,ctype c) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c))); \
if (__res>=0) \
return (type) __res; \
errno=-__res; \
return -1; \
}
因此_syscall3(int,write,int,fd,const char *,buf,off_t,count) 其展开后结果如下所示: -
通过int 0x80 中断,代码将进入系统内核 操作系统的已经做好准备工作如下示: ①main.c 中初始化 ②在函数sched_init() 中设置系统调用中断门,引导int 0x80 中断,可以看到该中断需要靠system_call 函数处理 ③int 0x80 中断的处理,set_system_gate() 其实就是宏_set_gate() ④宏_set_gate() 也是嵌入汇编指令,其中的addr 是&system_call ,是个地址;该宏主要就是初始化好了IDT表 中int 0x80 对应的表项
- 上述的
&idt 即为IDT表 的起始位置,所以&idt[n] (这里n=0x80 )就表示中断0x80 对应的表项在IDT表中位置 - 设置好
CS=8 ,其中CPL=0 - 设置好
IP=addr ⑤如此一来,当int 0x80 中断来的时候,根据CS = 8 也即段选择子为8,就可以去GDT表中找到真正要执行的代码的段基地址,然后结合IP的偏移,找到INT 0x80 的实际处理函数system_call -
下面我们进入system_call 代码,也即真正的中断处理程序linux/kernel/system_call.s 在程序中,跳到一个表里面的一个函数地址 _sys_call_table 是一个函数地址表,其中write对应的处理函数的地址就放在表的%eax 索引处 -
然而sys_write 才是真正的处理函数,可以对内核中的数据进行读取,也就是所谓的系统调用
|