哈工大操作系统实验(二)操作系统调用——极其通俗易懂

前言

本文采用自顶向下的方式完成实验,也即从自己编写的用户应用程序开始一步一步完成最终的功能,这样可以更好地把握主线。

零、总览框图

在这里插入图片描述

一、编写应用程序

该应用程序iam.c和whoami.c将会调用iam()和whoami()这两个系统调用。
虽然实验讲义中提供了testlab2.sh用于测试我们的系统调用能否成功运行,但是编写自己的应用程序查看效果会更直观。以下仅以iam.c为例。

iam.c
建议放在oslab/hdc/usr/root目录下

这个hdc在哪?
首先在oslab’下执行sudo ./mount就会出现了
注意1:在取消挂载前记得执行sync来将数据写入硬盘
取消挂载:在oslab下,执行sudo umount hdc
注意2:一定要取消挂载后才可以打开linux-0.11

#define __LIBRARY__
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
_syscall1(int,iam,const char*,name)

int main(int argc,char* argv[])
{
    iam(argv[1]);	/*这里使用了iam()这个系统调用*/
    return 0;
}

可以看到main函数上有一个接口
_syscall1(int,iam,const char*,name)
这个接口就是用来定位iam()这个系统调用的。这个syscall1在include/unistd.h下面。

补充:whoami.c

#define __LIBRARY__
#include <unistd.h>
#include <errno.h>
#include <stdio.h>

_syscall2(int, whoami, char*, name, unsigned int, size);

int main(int argc, char ** argv)
{
    char t[30];
    whoami(t, 30);
    printf("%s\n", t);
    return 0;
}

二、追溯include/unistd.h的_syscall1(type,name,atype,a)

我们先来看看_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; \
}

1、先看第7行,这里是输入参数行,意思是将__NR_##name送到%eax,将a送到%ebx。

由于我们刚刚是这样调用的_syscall1(int,iam,const char*,name),所以输入参数行的意思就是将__NR_iam送到%eax,将argv[1]送到%ebx

2、再看第5行,这里是开始执行的第一行,调用了$0x80中断。

由上面可知,我们的系统里面肯定是没有__NR_iam这个宏的,所以我们需要在unistd.h添加__NR_iam这个宏。如下:

#define __NR_sigaction    67
#define __NR_sgetmask    68
#define __NR_ssetmask    69
#define __NR_setreuid    70
#define __NR_setregid    71   /*Linux system_call total 72*/

#define __NR_iam    72     /*new system_call 72 and 73*/
#define __NR_whoami    73

注意!!!
修改unistd.h需要修改oslab/hdc/usr/include/unistd.h才会生效。

最后两行是我们添加的。(不仅添加了__NR_iam,还添加了__NR_whoami)

好,现在的下一步就是去研究0x80中断了。

三、研究0x80中断

这部分在实验讲义6.2里面描述的非常详细,下面仅给出有关框图总结。

内核初始化时,

Created with Raphaël 2.2.0 内核初始化时 ①init/main.c/main() ②sched_init() ③宏set_system_gate(0x80,&system_call) ④宏_set_gate(&idt[n],15,3,addr)

重点就在_set_gate这个宏上面

#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
    "movw %0,%%dx\n\t" \
    "movl %%eax,%1\n\t" \
    "movl %%edx,%2" \
    : \
    : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
    "o" (*((char *) (gate_addr))), \
    "o" (*(4+(char *) (gate_addr))), \
    "d" ((char *) (addr)),"a" (0x00080000))

这段代码就是设置中断描述符表项
在这里插入图片描述

  • 这里面的gate_addr就是段选择符。
  • addr就是段描述符在段描述符表的偏移地址,也就时图中的过程入口点地址。

由上,对于set_system_gate(0x80,&system_call) ------->_set_gate(&idt[n],15,3,addr)这个调用来说,就是设置了0x80最终会找到一个叫system_call的函数(过程)

好的,我们接下来看看system_all这个函数(过程)

四、追溯system_call

该system_call过程在oslab/linux-0.11/kernel/system_call.s下

!……
! # 这是系统调用总数。如果增删了系统调用,必须做相应修改
nr_system_calls = 72
!……

.globl system_call
.align 2
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,是传递给系统调用的参数
    pushl %ebx

! # 让ds, es指向GDT,内核地址空间
    movl $0x10,%edx
    mov %dx,%ds
    mov %dx,%es
    movl $0x17,%edx
! # 让fs指向LDT,用户地址空间
    mov %dx,%fs
    call sys_call_table(,%eax,4)
    pushl %eax
    movl current,%eax
    cmpl $0,state(%eax)
    jne reschedule
    cmpl $0,counter(%eax)
    je reschedule
  • 第3行,由于我们需要添加两个系统调用(iam和whoami),所以要+2,即改为nr_system_calls = 74
  • 第29行,是重点,call sys_call_table(,%eax,4)

对于call sys_call_table(,%eax,4),从汇编寻址方面来考虑,它是call sys_call_table + 4 * %eax,即sys_call_table是数组基址,实际上是找到sys_call_table数组的第%eax个元素(是个函数),然后执行这个函数。

这个时候的%eax是什么呢?可以回头看看上面的syscall1(),它里面设置了%eax等于__NR_iam的值啊!

接下来,我们追溯 sys_call_table这个数组。

五、追溯sys_call_table数组

该数组在os/linux-0.11/include/linux/sys.h

fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid,sys_setregid, sys_iam, sys_whoami };//在中断向量表的最后填上系统调用

在最后加上我们的系统调用,分别叫sys_iam和sys_whoami
当然,还要像其它系统调用一样注册为extern。

extern int sys_sigaction();
extern int sys_sgetmask();
extern int sys_ssetmask();
extern int sys_setreuid();
extern int sys_setregid();

extern int sys_iam();//需要新增的地方
extern int sys_whoami();

六、编写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  = 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

就是添加了一个who.c
第二处

### 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

就是添加了一行who.s who.o: who.c …/include/linux/kernel.h …/include/unistd.h

大功告成!!
取消挂载后,就可以开机了!

八、开机

开机后,在/usr/root下执行一下程序来编译

gcc -o iam iam.c 
gcc -o whoami whoami.c 
sync

编译应用程序成功后,可以尝试执行他们了
在这里插入图片描述
成功了!!

如果想测试讲义的testlab2.sh,可以按一下步骤进行

1、关闭linux0.11机子
2、chmod +x testlab2.sh
3、挂载hdc
4、复制testlab2.sh到/usr/root
5、取消挂载
6、打开linux-0.11机子
7、运行./testlab2.sh

  • 11
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值