系统调用
概述
- 概述:在通常情况下,调用系统调用和调用一个普通的自定义函数在代码上并没有什么区别,但调用后发生的事情有很大不同。调用自定义函数是通过call指令直接跳转到该函数的地址,继续运行。而调用系统调用,是调用系统库中为该系统调用编写的一个接口函数,叫API(Application Programming Interface)。API并不能完成系统调用的真正功能,它要做的是去调用真正的系统调用
- 任务:在 Linux 0.11上添加两个系统调用,并编写两个简单的应用程序测试它们
- 修改的文件:
/usr/root/iam.c # 编写应用程序1
/user/root/whoami.c # 编写应用程序2
/usr/root/include/unistd.h # 添加系统调用号以及接口
linux-0.11/kernel/system_call.s # 修改系统调用数
linux-0.11/include/linux/sys.h # 修改table
linux-0.11/kernel/who.c # 编写系统调用的实现
linux-0.11/kernel/Makefile # 重新编译内核
- 调用过程
1. 应用程序调用库函数(API);
2. API将系统调用号存入EAX,然后通过中断调用使系统进入内核态;
3. 内核中的中断处理函数根据系统调用号,调用对应的内核函数(系统调用);
4. 系统调用完成相应功能,将返回值存入EAX,返回到中断处理函数;
5. 中断处理函数返回到API中;
6. API将EAX返回给应用程序。
详细调用过程
应用程序iam.c/whoami.c
- 首先取消hdc的挂载
/oslab下执行 sudo ./mount
# 在取消挂载前记得在bochs中执行sync来将数据写入linux0.11挂载的文件系统
# 一定要取消挂载后才可以打开linux0.11
- 编写/usr/root/iam.c
#include __LIBRARY__
#include <unistd.h>
int main(int argc, char* argv[]) {
iam(argv[1]);
return 0;
}
- 编写usr/root/whoami.c
#include __LIBRARY__
#include <unistd.h>
int main(int argc, char* argv[]) {
char tmp[30];
whoami(tmp, 30);
printf("%s\n", tmp);
return 0;
}
注:上面两段代码的#include __LIBRARY__
一定要加上,不然unistd.c中#define定义的系统调用号以及sys_call不会起作用,原因见unistd.c的源代码
应用程序通过unistd.c展开系统调用
注:此处的unistd.c是/usr/root/include/unistd.h;因为此刻的调用还在用户态,没有进入内核态
-
添加系统调用号__NR_##name
-
添加系统调用接口_syscall1
_syscall1和_syscall2是宏函数,负责按规则展开iam和whoami,_syscall1的内容如下:
#define _syscall1(type,name,atype,a) \
type name(atype a) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(a))); \
if (__res >= 0) \
return (type) __res; \
errno = -__res; \
return -1; \
}
系统调用接口负责将系统调用号__NR_##name送到%eax,其他参数送到其他寄存器
注:系统调用接口一般不放在unistd.h这,而是专门放在一个.c文件里,参照close.c
执行int 0x80中断
关于内核的中断处理过程详见实验讲义
当set_system_gate把0x80传给_set_gate(&idt[n],15,3,addr)这个调用后,_set_gate会在idt表里找到0x80对应执行的函数——system_call
int 0x80在idt表中对应着system_call函数
该system_call在oslab/linux-0.11/kernel/system_call.s下,由汇编代码编写
- 修改system_call.s
- nr_system_calls表示系统调用的总数,因为新添加了2个系统调用,所以要将这里的原本的72改成74
- system_call.s的第29行_sys_call_table+4*%eax是相应系统调用处理函数的入口,_sys_call_table是一个系统调用处理函数表,里面存放着所有的系统调用处理函数;4表示每个系统调用地址占4*16=64个字节,%eax即是由_syscall1存放的系统调用号
- 修改_sys_call_table
此table在oslab/linux-0.11/include/linux/sys.h
编写系统调用的实现代码who.c
who.c里面包含系统调用函数sys_iam()和sys_whoami()
把该文件放在oslab/linux-0.01/kernel下
#include <string.h>
#include <errno.h>
#include <asm/segment.h>
char msg[24];
/*sys_iam就是把iam.c输入的一个命令行参数从用户空间放到内核空间中*/
int sys_iam(const char * name)
{
char tep[26];
int i = 0;
for(; i < 26; i++)
{
tep[i] = get_fs_byte(name+i);
if(tep[i] == '\0') break;
}
if (i > 23) return -(EINVAL);
strcpy(msg, tep);
return i;
}
/*sys_whoami则是将此参数从内核空间取到用户空间,再由whoami.c打印出来*/
int sys_whoami(char * name, unsigned int size)
{
int len = 0;
for (;msg[len] != '\0'; len++);
if (len > size)
{
return -(EINVAL);
}
int i = 0;
for(i = 0; i < size; i++)
{
put_fs_byte(msg[i], name+i);
if(msg[i] == '\0') break;
}
return i;
}
关于如何在用户态和内核态传递消息,讲义已经叙述得非常清楚,此处不再赘述。
这结束了吗?还没有,我们仅仅是编写了who.c放在kernel/下。一个.c文件是对于系统来说是没有任何用的,我们需要编译链接它,让它成为可执行文件。这时候就要改makefile了,为什么?因为整个linux都是都是由它来编译的。
修改makefile并重新编译内核
Makefile在linux-0.11/kernel/下
- 修改OBJS
OBJS = sched.o system_call.o traps.o asm.o fork.o \
panic.o printk.o vsprintf.o sys.o exit.o \
signal.o mktime.o
改为:
OBJS = sched.o system_call.o traps.o asm.o fork.o \
panic.o printk.o vsprintf.o sys.o exit.o \
signal.o mktime.o who.o
- 修改Dependencies
### Dependencies:
exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \
../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \
../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \
../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \
../include/asm/segment.h
改为:
### Dependencies:
who.s who.o: who.c ../include/linux/kernel.h ../include/unistd.h
exit.s exit.o: exit.c ../include/errno.h ../include/signal.h \
../include/sys/types.h ../include/sys/wait.h ../include/linux/sched.h \
../include/linux/head.h ../include/linux/fs.h ../include/linux/mm.h \
../include/linux/kernel.h ../include/linux/tty.h ../include/termios.h \
../include/asm/segment.h
- 在oslab/liunx-0.11/下执行
make all
重新编译内核,再在oslab/下运行./run
即可
开机编译应用程序
- 编译
gcc -o iam iam.c
gcc -o whoami whoami.c
sync
- 执行
参考文献
https://blog.csdn.net/lyj1597374034/article/details/110778041
https://ehye.github.io/2020/04/01/hit-oslab2/
https://hoverwinter.gitbooks.io/hit-oslab-manual/content/sy2_syscall.html