实例分析系统调用过程

我们由刚开始接触编程的一个程序说起。

上述一段代码,通过gcc编译,生成一个.o文件,运行,就能输出printf函数要输出的文字。那么,对于printf()函数这个调用,操作系统到底发生了什么?

内核态与用户态

为了方便管理,我们将计算机系统资源分为用户态和内核态,对于内核部分,一般是对进程资源进行管理,而这些东西是不允许直接操作的,所有对这些资源的访问都必须有操作系统控制。在Linux中,系统调用(System call)是用户空间访问内核的唯一手段,除了异常和陷入外,它是内核唯一的合法入口。

那么怎么样才能引起系统调用呢?即怎么从用户态来到内核态?一般来说,系统调用都是通过软件中断实现的,在X86系统上的软件中断是由int 0x80指令引起的,而128号异常处理程序就是系统调用处理程序system_call()。

有了以上知识,我们再做以下几点说明。

1、将内核程序和用户程序隔离,即用户态和内核态,这是由硬件来实现的,这个我们在讲内存的时候经常会提到,比如某段内存不能随便访问,某段内存仅仅只让操作系统访问。

2、那么怎么知道当前程序执行在什么态呢?由于CS:IP是指向需要执行的指令的地址的,那么我们用CS的低两位来表示,其中0是内核态,3是用户态。

3、内核态可以访问任何数据,用户态不能访问内核态数据。

4、这里还介绍两个重要的参数,DPL即描述目标内存段的特权级,CPL(CS的低两位)表示当前的特权级,只有当CPL<=DPL时,才能执行当前指令。注意,初始化的时候DPL=0。

printf函数的展开

对于我们文章开始提到的printf()函数的调用过程大致如下所示。下面我们将分析整个调用过程。

对于任何用户程序,它最终都有一段包含int指令的代码;操作系统通过写中断处理,获取想要调用程序的编号;操作系统根据编号执行相应的代码。

对于printf()来说,最终展开成包含int指令的代码,如下。

Linux/lib/write.c
_syscall3(int,write,int,fd,const,char*,buf,off_t,count)  //3表示参数为3个,此次调用是库函数调用,注意第一个是函数名,展开就是int write(int fd,const char * buf,off_t count)

Linux/include/unistd.h
#define _syscall3(type,name,atype,a,btype,b,ctype,c)
 type name(atype a,btype b,ctype c)
{
  long _res;
  _asm_ volitile(“int 0x80”:”=a”(_res):””(_NR_##name),”b”((long)(a)),”c”((long)(b)),”d”((long)(c)));
  if(__res>=0) return (type)__res;
  errno=-__res;
  return -1;
}//此段代码为内嵌汇编,就是将_NR_write(即系统调用号)放在eax中。

而_NR_write的系统调用号定义下面的函数中。

上面说的就是将系统调用号赋值给eax,然后调用int 0x80。那么int 0x80做了什么?

Int 0x80中断的处理

前面说过中断是通过itd表来找处理函数,然后执行,ITD表如下所示。

void sched_init(void)
{set_system_gate(0x80,&system_call);}  //该函数用来设置0x80中断处理

在linux/include/asm/system.h中
#define set_system_gate(n,addr)   //n为中断号,addr为中断地址
_set_gate(&idt[n],15,3,addr);     //idt是中断向量表基址

#define _set_gate(gate_addr,type,dpl,addr)  //DPL=3,当DPL置3之后,CPL就会满足小于等于DPL了
__asm__(“movw %%dx,%%ax\n\t” 
        “movw %0,%%dx\n\t”
         ”movl %%eax,%1\n\t”
         “movl %%edx,%2”::”i”
         ((short)(0x8000+dpl<<13)+type<<8))),
         ”o”(*(4+(char *)(gate_addr))),
        ”d”((char *)(addr),
        ”a”(0c00080000))

当上面DPL=3之后,就能进入80中断了,此时CS=8,IP=system_call。当CS等于8的时候,CPL=0,那么就完全满足CPL<=DPL了,那么就能进入内核了。说白了int 0x80就是让CS:IP进入到内核,可以在内核中执行。

中断处理程序system_call

在linux/kernel/system_call.s中
Nr_system_calls=72
.globl _system_call
_system_call:cmpl $nr_system_calls-1,%eax  //eax存放的是系统调用号
    ja bad_sys_call
    push %ds
    push %es
    push %fs
    pushl %eax
    pushl %ecx
    pushl %ebx
    movl $0x10,%edx
    mov %dx,%ds
    mov %dx,%es
    movl $0x17,%edx
    mov %dx,%fs
    call _sys_call_table(,%eax,4)   //a(,%eax,4)=a+4*eax,
    pushl %eax  //返回值压栈,留给ret_from_sys_call返回时使用
    ......
ret_from_sys_call:
    popl %eax
    ......
    iret 

 _sys_call_table+4*%eax就是相应的系统调用处理函数入口,由于每个系统调用对应的函数占用4个字节,所以*4。其中_sys_call_table就是sys_call_table的基地址,也就是指向那个表的第0个系统调用,而%eax中存放的是4,那么最终就指向sys_write了。所以整个调用过程如下。

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值