深入了解Linux内核-系统调用如何实现?

Rand_cs

系统调用就是调用操作系统提供的一系列内核功能函数,因为内核总是对用户程序持不信任的态度,一些核心功能不能直接交由用户程序来实现执行。用户程序只能发出请求,然后内核调用相应的内核函数来帮着处理,将结果返回给应用程序。如此才能保证系统的稳定和安全。本文采用

的实例来讲解系统调用具体是如何实现的。

系统调用是给用户态下的程序使用的,但是用户程序并不直接使用系统调用,而是系统调用在用户态下的接口。这个用户接口就是操作系统提供的系统调用 APIAPIAPI,一般遵循 POSIX标准

这个具体的内核功能函数咱们就不讨论了,内核中的表现形式就是一个个不同的函数,咱们这儿只讨论两件事:

xv6 实例

先来看张总图把握一下整体流程:

int fork(void);
int write(int, const void*, int);
#include "syscall.h"
#include "traps.h"

#define SYSCALL(name) \
  .globl name; \
  name: \
    movl $SYS_ ## name, %eax; \
    int $T_SYSCALL; \
    ret

SYSCALL(fork)
SYSCALL(write)
SYSCALL(getpid)

这是用汇编来写的,而且使用了宏定义,我们来仔细阅读一下这段代码

.global name 声明了一个全局可见的名字,可以是变量也可以是函数名,这里就与用户接口的函数名。函数名就相当于一个地址,name: 后面的代码就是这个函数具体要做的事,就像 c 语言编写函数时的函数体,只不过这里是用汇编写的而已。

所以这个函数做了什么事?应该一目了然啊,就三条指令:

#define SYS_fork       1
#define SYS_getpid     11
#define SYS_write      16

这个号就是自定义的,能够将每个系统调用唯一区分开就好。

上面的宏定义中还涉及了 # 的用法,# 一般有两种用法:

#define STR(x)       #x
#define CAT(x, y)  x##y
void trap(struct trapframe *tf)
{
  if(tf->trapno == T_SYSCALL){    //如果向量号表示的是系统调用
    if(myproc()->killed)   
      exit();
    myproc()->tf = tf;   //当前进程的中断栈帧
    syscall();     //执行系统调用入口程序
    if(myproc()->killed)
      exit();
    return;
  }
  /*******略略略略********/
}

可以看到,如果中断栈帧中的向量号表示的是系统调用号的话,就会去执行系统调用入口程序。

这个系统调用入口程序定义在 syscall.c 里面:

void syscall(void)
{
  int num;
  struct proc *curproc = myproc();  //获取当前进程的PCB

  num = curproc->tf->eax;       //获取系统调用号
  if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
    curproc->tf->eax = syscalls[num]();      //调用相应的系统调用处理函数,返回值赋给eax
  } else {
    cprintf("%d %s: unknown sys call %d\n",
            curproc->pid, curproc->name, num);
    curproc->tf->eax = -1;
  }
}
extern int sys_fork(void);
extern int sys_getpid(void);
extern int sys_write(void);

static int (*syscalls[])(void) = {
[SYS_fork]    sys_fork,
/**********************/
[SYS_getpid]  sys_getpid,
/**********************/
[SYS_write]   sys_write,
/***********************/
}

内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料

学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

push size
push buf
push fd
call  write
int argint(int n, int *ip)  //获取系统调用的第n个int型的参数,存到ip这个位置
{
  return fetchint((myproc()->tf->esp) + 4 + 4*n, ip);    //原栈中获取n个int型参数,加4是跳过
}

int fetchint(uint addr, int *ip)
{
  struct proc *curproc = myproc();

  if(addr >= curproc->sz || addr+4 > curproc->sz)
    return -1;
  *ip = *(int*)(addr);
  return 0;
}
int argptr(int n, char **pp, int size)
{
  int i;
  struct proc *curproc = myproc();
 
  if(argint(n, &i) < 0)
    return -1;
  if(size < 0 || (uint)i >= curproc->sz || (uint)i+size > curproc->sz)
    return -1;
  *pp = (char*)i;
  return 0;
}
int argptr(int n, char *pp, int size)  //pp类型变为char*
{
  int i;
 /*********************/
  if(argint(n, &i) < 0)
    return -1;
/*********************/
  pp = (char*)i;  //这里变为直接给pp赋值
  return 0;
}
char *p;
argptr(1, &p, n);

/**如果用一级指针**/
argptr(1, p, n);

还有个获取字符串的函数,跟获取指针差不了太多,只是多了一个算字符串长度的步骤,这里就不赘述了。

本文关于系统调用就这么多,最后再看张图来捋一捋:

这是以 write 系统调用为例的系统调用过程图,图是丑了点,不过这条线应该捋得还是挺清晰的,好啦,本文就到这里,有什么错误还请批评指正,也欢迎大家来同我讨论交流学习进步。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值