超详细!操作系统实验三 系统调用(哈工大李治军)(二)

简介: 超详细!操作系统实验三 系统调用(哈工大李治军)(二)

实现 sys_iam() 和 sys_whoami()

添加系统调用的最后一步,是在内核中实现函数 sys_iam() 和 sys_whoami()。


每个系统调用都有一个 sys_xxxxxx() 与之对应,它们都是我们学习和模仿的好对象。


比如在 fs/open.c 中的 sys_close(int fd):

int sys_close(unsigned int fd)
{
//    ……
    return (0);
}


它没有什么特别的,都是实实在在地做 close() 该做的事情。


所以只要自己创建一个文件:kernel/who.c,然后实现两个函数就万事大吉了。


按照上述逻辑修改相应文件

通过上文描述,我们已经理清楚了要修改的地方在哪里


1.添加iam和whoami系统调用编号的宏定义(_NR_xxxxxx),文件:include/unistd.h



2.修改系统调用总数, 文件:kernel/system_call.s



3.为新增的系统调用添加系统调用名并维护系统调用表,文件:include/linux/sys.h



4.为新增的系统调用编写代码实现,在linux-0.11/kernel目录下,创建一个文件 who.c

#include <asm/segment.h>
#include <errno.h>
#include <string.h>
char _myname[24];
int sys_iam(const char *name)
{
    char str[25];
    int i = 0;
    do
    {
        // get char from user input
        str[i] = get_fs_byte(name + i);
    } while (i <= 25 && str[i++] != '\0');
    if (i > 24)
    {
        errno = EINVAL;
        i = -1;
    }
    else
    {
        // copy from user mode to kernel mode
        strcpy(_myname, str);
    }
    return i;
}
int sys_whoami(char *name, unsigned int size)
{
    int length = strlen(_myname);
    printk("%s\n", _myname);
    if (size < length)
    {
        errno = EINVAL;
        length = -1;
    }
    else
    {
        int i = 0;
        for (i = 0; i < length; i++)
        {
            // copy from kernel mode to user mode
            put_fs_byte(_myname[i], name + i);
        }
    }
    return length;
}


修改 Makefile

要想让我们添加的 kernel/who.c 可以和其它 Linux 代码编译链接到一起,必须要修改 Makefile 文件。


Makefile 里记录的是所有源程序文件的编译、链接规则,《注释》3.6 节有简略介绍。我们之所以简单地运行 make 就可以编译整个代码树,是因为 make 完全按照 Makefile 里的指示工作。


Makefile 在代码树中有很多,分别负责不同模块的编译工作。我们要修改的是 kernel/Makefile。需要修改两处。


(1)第一处

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.o。



(2)第二处

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



Makefile 修改后,和往常一样 make all 就能自动把 who.c 加入到内核中了。


编写测试程序

到此为止,内核中需要修改的部分已经完成,接下来需要编写测试程序来验证新增的系统调用是否已经被编译到linux-0.11内核可供调用。首先在oslab目录下编写iam.c,whoami.c

/* iam.c */
#define __LIBRARY__
#include <unistd.h> 
#include <errno.h>
#include <asm/segment.h> 
#include <linux/kernel.h>
_syscall1(int, iam, const char*, name);
int main(int argc, char *argv[])
{
    /*调用系统调用iam()*/
    iam(argv[1]);
    return 0;
}
/* whoami.c */
#define __LIBRARY__
#include <unistd.h> 
#include <errno.h>
#include <asm/segment.h> 
#include <linux/kernel.h>
#include <stdio.h>
_syscall2(int, whoami,char *,name,unsigned int,size);
int main(int argc, char *argv[])
{
    char username[64] = {0};
    /*调用系统调用whoami()*/
    whoami(username, 24);
    printf("%s\n", username);
    return 0;
}


以上两个文件需要放到启动后的linux-0.11操作系统上运行,验证新增的系统调用是否有效,那如何才能将这两个文件从宿主机转到稍后虚拟机中启动的linux-0.11操作系统上呢?这里我们采用挂载方式实现宿主机与虚拟机操作系统的文件共享,在 oslab 目录下执行以下命令挂载hdc目录到虚拟机操作系统上。

sudo ./mount-hdc 

再通过以下命令将上述两个文件拷贝到虚拟机linux-0.11操作系统/usr/root/目录下,命令在oslab/目录下执行:

cp iam.c whoami.c hdc/usr/root

如果目标目录下存在对应的两个文件则可启动虚拟机进行测试了。


编译

[/usr/root]# gcc -o iam iam.c
[/usr/root]# gcc -o whoami whoami.c


运行测试

[/usr/root]# ./iam wcf
[/usr/root]# ./whoami


命令执行后,很可能会报以下错误:



这代表虚拟机操作系统中/usr/include/unistd.h文件中没有新增的系统调用调用号


为新增系统调用设置调用号

#define __NR_whoami     72
#define __NR_iam      73


再次执行:



实验成功


为什么这里会打印2次?


因为在系统内核中执行了 printk() 函数,在用户模式下又执行了一次 printf() 函数。


要知道到,printf() 是一个只能在用户模式下执行的函数,而系统调用是在内核模式中运行,所以 printf() 不可用,要用 printk()。


printk() 和 printf() 的接口和功能基本相同,只是代码上有一点点不同。printk() 需要特别处理一下 fs 寄存器,它是专用于用户模式的段寄存器。




天道酬勤

实验三总共花费7小时,看的不是特别仔细,没有特别深入的学习宏展开和内联汇编。但基本理解了系统调用的目的和方式,Linus永远的神!

目录
相关文章
|
4月前
|
Linux Shell 调度
操作系统实验一:进程和线程(1)
实验内容 一、进程的创建 编写一段源程序,使用系统调用fork()创建子进程,当此程序运行时,在系统中有父进程和子进程在并发执行。观察屏幕上的显示结果,并分析原因(源代码:forkpid.c)。
87 0
|
2月前
|
Linux C语言
操作系统 | 添加系统调用
操作系统 | 添加系统调用
31 0
|
5月前
|
存储 算法 调度
操作系统实验五:存储管理设计
操作系统实验五:存储管理设计
155 0
|
2月前
|
资源调度 调度 UED
CPU执行系统调用时发生中断,操作系统还能切回中断前的系统调用继续执行吗?
系统调用服务例程在执行过程中,通常不会被中断。系统调用服务例程的执行是一个原子操作,即在执行期间不会被中断。这是为了确保在系统调用服务例程执行期间对内核数据结构的一致性和完整性。
|
4月前
|
调度 数据库
操作系统实验一:进程和线程(2)
七、共享资源的互斥访问 创建两个线程来实现对一个数的递加 pthread_example.c 1、运行
39 0
|
5月前
|
存储 缓存 前端开发
操作系统期末实验:多用户二级文件系统
操作系统期末实验:多用户二级文件系统
227 0
|
5月前
|
存储 算法 搜索推荐
操作系统实验四:进程调度
操作系统实验四:进程调度
81 0
|
14天前
|
监控 Unix Linux
Linux操作系统调优相关工具(四)查看Network运行状态 和系统整体运行状态
Linux操作系统调优相关工具(四)查看Network运行状态 和系统整体运行状态
29 0
|
16天前
|
Linux 编译器 开发者
Linux设备树解析:桥接硬件与操作系统的关键架构
在探索Linux的庞大和复杂世界时🌌,我们经常会遇到许多关键概念和工具🛠️,它们使得Linux成为了一个强大和灵活的操作系统💪。其中,"设备树"(Device Tree)是一个不可或缺的部分🌲,尤其是在嵌入式系统🖥️和多平台硬件支持方面🔌。让我们深入了解Linux设备树是什么,它的起源,以及为什么Linux需要它🌳。
Linux设备树解析:桥接硬件与操作系统的关键架构
|
1月前
|
Linux 数据安全/隐私保护 虚拟化
Linux技术基础(1)——操作系统的安装
本文是龙蜥操作系统(Anolis OS) 8.4 的安装指南,用户可以从[龙蜥社区下载页面](https://openanolis.cn/download)获取ISO镜像。安装方法包括物理机的光驱和USB闪存方式,以及虚拟机中的VMware Workstation Pro设置。安装过程涉及选择语言、配置安装目标、选择软件集合和内核,设置Root密码及创建新用户。安装完成后,可通过文本模式或图形化界面验证系统版本,如Anolis OS 8.4,标志着安装成功。