linux0.11内核分析之系统调用

linux0.11内核分析之系统调用

概述

在这里插入图片描述

操心系统的核心是内核,拥有访问受保护的内存空间以及底层硬件设备的权限。为了使用户进程不能直接操作内核,操作系统将进程状态划分为高权限的核心态与低权限的用户态,而系统调用接口就是linux0.11系统中用户进程使用核心态功能的门户。

系统调用相关源码分析
1、pause

因为用户pause函数的定义在libc中,所以下面的代码可能会有一点晦涩

// 以下为pause.c内容
#include <signal.h>
#include <unistd.h>
#include <sysdep-cancel.h>

/* Suspend the process until a signal arrives.
   This always returns -1 and sets errno to EINTR.  */

int
__libc_pause (void)
{
  sigset_t set;

  int rc =
    SYSCALL_CANCEL (rt_sigprocmask, SIG_BLOCK, NULL, &set, _NSIG / 8);
  if (rc == 0)
    rc = SYSCALL_CANCEL (rt_sigsuspend, &set, _NSIG / 8);

  return rc;
}

weak_alias (__libc_pause, pause)

//以下为syscall cancel内容
#define SYSCALL_CANCEL(...) \
  ({									     \
    long int sc_ret;							     \
    if (SINGLE_THREAD_P) 						     \
      sc_ret = __SYSCALL_CALL (__VA_ARGS__);   				     \
    else								     \
      {									     \
	int sc_cancel_oldtype = LIBC_CANCEL_ASYNC ();			     \
	sc_ret = __SYSCALL_CALL (__VA_ARGS__);				     \
        LIBC_CANCEL_RESET (sc_cancel_oldtype);				     \
      }									     \
    sc_ret;								     \
  })

看起来有点复杂,但实际上作用几乎只有一个,那就是把sys_pause系统调用号压入eax,随后调用system_call

2、system_call
bad_sys_call:
	movl $-1,%eax
	iret
.align 4
reschedule:
	pushl $ret_from_sys_call
	jmp schedule
.align 4
system_call:
	cmpl $nr_system_calls-1,%eax
	ja bad_sys_call
	push %ds
	push %es
	push %fs
	pushl %edx
	pushl %ecx		# push %ebx,%ecx,%edx as parameters
	pushl %ebx		# to the system call
	movl $0x10,%edx		# set up ds,es to kernel space
	mov %dx,%ds
	mov %dx,%es
	movl $0x17,%edx		# fs points to local data space
	mov %dx,%fs
	call sys_call_table(,%eax,4)
	pushl %eax
	movl current,%eax
	cmpl $0,state(%eax)		# state
	jne reschedule
	cmpl $0,counter(%eax)		# counter
	je reschedule
ret_from_sys_call:
	movl current,%eax		# task[0] cannot have signals
	cmpl task,%eax
	je 3f
	cmpw $0x0f,CS(%esp)		# was old code segment supervisor ?
	jne 3f
	cmpw $0x17,OLDSS(%esp)		# was stack segment = 0x17 ?
	jne 3f
	movl signal(%eax),%ebx
	movl blocked(%eax),%ecx
	notl %ecx
	andl %ebx,%ecx
	bsfl %ecx,%ecx
	je 3f
	btrl %ecx,%ebx
	movl %ebx,signal(%eax)
	incl %ecx
	pushl %ecx
	call do_signal
	popl %eax
3:	popl %eax
	popl %ebx
	popl %ecx
	popl %edx
	pop %fs
	pop %es
	pop %ds
	iret

在内核调用系统函数的时候,会用一个陷入指令“INT 0x80”引发一个内部中断,随后调用system_call函数来进行处理(系统函数的调用),在system_call中,进程的状态从用户态转换为核心态,system_call函数根据保存在esp寄存器中的系统调用号sys_call_table中寻找对应的系统调用函数并执行。

分析system_call函数的行为:

  • 将eax寄存器中的系统调用号与定义的最大系统调用号(nr_system_calls)进行比较,如果eax超出系统调用号的范围,则认为该系统调用无效,跳转至bad_sys_call,随即iret返回用户态

  • 如果系统调用号有效,则将当前用户进程的段寄存器ds、es、fs压栈保存,方便调用完成后恢复用户进程状态

  • 把edx、ecx、ebx寄存器保存的值压入栈中作为系统调用的参数(参数通过栈传递)

  • 设定内核态进程的段寄存器

  • 调用sys_call_table中系统调用号对应的函数指针

  • 调用完成后进行进程调度(进程调度的知识可以看这篇文章的system_call部分

  • 完成进程调度后,在do_signal中侦测内核信息并处理*(比如alarm定时器的时间信号)*

  • 随后恢复用户进程栈并返回到用户态

3、内核态系统调用函数

此处同样以常见的sys_pause函数为例

int sys_pause(void)
{
	current->state = TASK_INTERRUPTIBLE;
	schedule();
	return 0;
}

实际上,系统调用函数在linux0.11内核中的实现并不算复杂,如上面的sys_pause函数,只是将当前进程的状态设为可中断的睡眠态,然后执行一次进程调度

之所以要大费周章地通过多层调用及状态转换来完成这个功能,就是因为系统调用功能中直接对当前进程的运行状态进行了改变,并且进行了进程调度。为了防止用户误操作破坏操作系统的稳定性,系统定义了内核态和用户态并将权限进行了分离 (试想用户误操作把所有进程的运行状态变成了不可中断的睡眠态,导致整个系统瘫痪的美丽场景)

系统调用流程图

千言万语不如一张图,请诸君自取

请添加图片描述

强行总结

其实整个系统调用就是一个权限分离的小把戏,把内核态的核心资源保护起来,并提供一些接口让用户得以在制定的规则下使用内核资源又不破坏操作系统核心的稳定性,理解了这个流程之后,接下来对于外部设备的访问会更加的得心应手

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值