【哈工大操作系统】三、系统调用

概述

  • 概述:在通常情况下,调用系统调用和调用一个普通的自定义函数在代码上并没有什么区别,但调用后发生的事情有很大不同。调用自定义函数是通过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

  1. 首先取消hdc的挂载
/oslab下执行 sudo ./mount
# 在取消挂载前记得在bochs中执行sync来将数据写入linux0.11挂载的文件系统
# 一定要取消挂载后才可以打开linux0.11
  1. 编写/usr/root/iam.c
#include __LIBRARY__
#include <unistd.h>

int main(int argc, char* argv[]) {
    iam(argv[1]);
    return 0;
}
  1. 编写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;因为此刻的调用还在用户态,没有进入内核态
在这里插入图片描述

  1. 添加系统调用号__NR_##name
    在这里插入图片描述

  2. 添加系统调用接口_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下,由汇编代码编写

  1. 修改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存放的系统调用号
  1. 修改_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/下

  1. 修改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

  1. 修改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

  1. 在oslab/liunx-0.11/下执行make all重新编译内核,再在oslab/下运行./run即可

开机编译应用程序

  1. 编译
gcc -o iam iam.c 
gcc -o whoami whoami.c 
sync
  1. 执行
    在这里插入图片描述

参考文献

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
fork是一个系统调用,用于创建一个新的进程。在哈尔滨工业大学的操作系统课程中,学生学习到了fork系统调用的使用和原理。 在操作系统中,每个进程都具有一个唯一的进程ID(PID)和一组资源。当调用fork时,当前进程会被复制,创建一个新的子进程。子进程和父进程具有相同的代码、数据和环境变量等。但是,子进程有自己的独立的内存空间。 fork调用返回两次,一次在父进程中返回子进程的PID,一次在子进程中返回0。这样,父进程可以根据返回的PID来判断fork是否成功,并根据需要进行相应的处理。 fork系统调用被广泛应用于多进程编程中。通过利用fork,可以实现并发执行,提高系统的资源利用率和效率。在操作系统课程中,学生通常学习如何使用fork创建子进程,并使用进程间通信机制进行进程间的数据交换和同步。 通过学习fork系统调用,学生可以了解进程的创建和管理,理解进程的概念和特点,并掌握进程间通信和同步的方法。此外,fork也是其他一些高级系统调用(如exec)的基础,对于学生进一步研究和学习操作系统提供了良好的基础。 总之,哈尔滨工业大学操作系统课程中的fork系统调用是学生学习并发编程和进程管理的重要内容,通过掌握这个系统调用,可以为学生提供丰富的编程经验和操作系统理论基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值