Rand_cs
系统调用就是调用操作系统提供的一系列内核功能函数,因为内核总是对用户程序持不信任的态度,一些核心功能不能直接交由用户程序来实现执行。用户程序只能发出请求,然后内核调用相应的内核函数来帮着处理,将结果返回给应用程序。如此才能保证系统的稳定和安全。本文采用
![](https://img-blog.csdnimg.cn/img_convert/7aa9ea00f7cad2ddf956c1f5a7dd0509.png)
的实例来讲解系统调用具体是如何实现的。
系统调用是给用户态下的程序使用的,但是用户程序并不直接使用系统调用,而是系统调用在用户态下的接口。这个用户接口就是操作系统提供的系统调用 APIAPIAPI,一般遵循 POSIX标准 。
![](https://img-blog.csdnimg.cn/img_convert/c3ccefc8c6d49ce9b6f3285136e4d21d.webp?x-oss-process=image/format,png)
![](https://img-blog.csdnimg.cn/img_convert/f6f3828b79dd59182750f202af96ea10.webp?x-oss-process=image/format,png)
![](https://img-blog.csdnimg.cn/img_convert/e9dd8e89cff51a9d61d2fe9de4445b32.webp?x-oss-process=image/format,png)
这个具体的内核功能函数咱们就不讨论了,内核中的表现形式就是一个个不同的函数,咱们这儿只讨论两件事:
![](https://img-blog.csdnimg.cn/img_convert/133d53ef3689a40bfa923e93c07ce8bf.webp?x-oss-process=image/format,png)
xv6 实例
先来看张总图把握一下整体流程:
![](https://img-blog.csdnimg.cn/img_convert/3cbe5206208c99776c145f41c989dbe0.webp?x-oss-process=image/format,png)
![](https://img-blog.csdnimg.cn/img_convert/bf80f26f929d2ff18ae4a18564637491.png)
int fork(void);
int write(int, const void*, int);
![](https://img-blog.csdnimg.cn/img_convert/fb0ab632d2067cc70cf2a66ff3dd18a9.png)
#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 语言编写函数时的函数体,只不过这里是用汇编写的而已。
所以这个函数做了什么事?应该一目了然啊,就三条指令:
![](https://img-blog.csdnimg.cn/img_convert/7c31ea7df2a59b1e1f57790094863e70.png)
![](https://img-blog.csdnimg.cn/img_convert/b67ba29cb31a4f544e894a2c98af01b6.png)
#define SYS_fork 1
#define SYS_getpid 11
#define SYS_write 16
这个号就是自定义的,能够将每个系统调用唯一区分开就好。
上面的宏定义中还涉及了 # 的用法,# 一般有两种用法:
#define STR(x) #x
#define CAT(x, y) x##y
![](https://img-blog.csdnimg.cn/img_convert/038d36ad73829cd1f2aa9a7ce821810f.webp?x-oss-process=image/format,png)
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;
}
}
![](https://img-blog.csdnimg.cn/img_convert/462236f09a4c38f7daba7150dafff830.webp?x-oss-process=image/format,png)
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,
/***********************/
}
![](https://img-blog.csdnimg.cn/img_convert/cf006935f0f975d8067db3813812a753.webp?x-oss-process=image/format,png)
![](https://img-blog.csdnimg.cn/img_convert/f123d872ecd7b3050eac5072410f0b4a.jpeg)
内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料
学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈
push size
push buf
push fd
call write
![](https://img-blog.csdnimg.cn/img_convert/e557a7086028e7a4e92261bad14d9d04.webp?x-oss-process=image/format,png)
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;
}
![](https://img-blog.csdnimg.cn/img_convert/ef10d11f98fa6eca43ac6a2badabe5ef.webp?x-oss-process=image/format,png)
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;
}
![](https://img-blog.csdnimg.cn/img_convert/73afa547e433e072013092594b6ce916.jpeg)
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;
}
![](https://img-blog.csdnimg.cn/img_convert/0f21cee41153e189be53615e8c11845d.png)
char *p;
argptr(1, &p, n);
/**如果用一级指针**/
argptr(1, p, n);
![](https://img-blog.csdnimg.cn/img_convert/6521e61c149dc351ce7c6087318e199a.webp?x-oss-process=image/format,png)
还有个获取字符串的函数,跟获取指针差不了太多,只是多了一个算字符串长度的步骤,这里就不赘述了。
本文关于系统调用就这么多,最后再看张图来捋一捋:
![](https://img-blog.csdnimg.cn/img_convert/fb222d174076d49dbacf1b3b5eac7d20.webp?x-oss-process=image/format,png)
这是以 write 系统调用为例的系统调用过程图,图是丑了点,不过这条线应该捋得还是挺清晰的,好啦,本文就到这里,有什么错误还请批评指正,也欢迎大家来同我讨论交流学习进步。