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

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

实验3 系统调用


提醒

这次实验涉及的宏过于复杂,加上本人能力有限,我也没有花大量时间去研究每一段代码,只是理解到每一段代码做了什么这一程度。


实验目的

此次实验的基本内容是:在 Linux 0.11 上添加两个系统调用,并编写两个简单的应用程序测试它们。


1.iam()


第一个系统调用是 iam(),其原型为:

int iam(const char * name);

完成的功能是将字符串参数 name 的内容拷贝到内核中保存下来。要求 name 的长度不能超过 23 个字符。返回值是拷贝的字符数。如果 name 的字符个数超过了 23,则返回 “-1”,并置 errno 为 EINVAL。


2.whoami()


第二个系统调用是 whoami(),其原型为:

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

它将内核中由 iam() 保存的名字拷贝到 name 指向的用户地址空间中,同时确保不会对 name 越界访存(name 的大小由 size 说明)。返回值是拷贝的字符数。如果 size 小于需要的空间,则返回“-1”,并置 errno 为 EINVAL。


应用程序如何调用系统调用

在通常情况下,调用系统调用和调用一个普通的自定义函数在代码上并没有什么区别,但调用后发生的事情有很大不同。


调用自定义函数是通过 call 指令直接跳转到该函数的地址,继续运行。


而调用系统调用,是调用系统库中为该系统调用编写的一个接口函数,叫 API(Application Programming Interface)。API 并不能完成系统调用的真正功能,它要做的是去调用真正的系统调用,过程是:


把系统调用的编号存入 EAX;

把函数参数存入其它通用寄存器;

触发 0x80 号中断(int 0x80)。

linux-0.11 的 lib 目录下有一些已经实现的 API。Linus 编写它们的原因是在内核加载完毕后,会切换到用户模式下,做一些初始化工作,然后启动 shell。而用户模式下的很多工作需要依赖一些系统调用才能完成,因此在内核中实现了这些系统调用的 API。


我们不妨看看 lib/close.c,研究一下 close() 的 API:

#define __LIBRARY__
#include <unistd.h>
_syscall1(int, close, int, fd)


其中 _syscall1 是一个宏,在 include/unistd.h 中定义。

#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; \
}


将 _syscall1(int,close,int,fd) 进行宏展开,可以得到:

int close(int fd)
{
    long __res;
    __asm__ volatile ("int $0x80"
        : "=a" (__res)
        : "0" (__NR_close),"b" ((long)(fd)));
    if (__res >= 0)
        return (int) __res;
    errno = -__res;
    return -1;
}


这就是 API 的定义。它先将宏 __NR_close 存入 EAX,将参数 fd 存入 EBX,然后进行 0x80 中断调用。调用返回后,从 EAX 取出返回值,存入 __res,再通过对 __res 的判断决定传给 API 的调用者什么样的返回值。


其中 __NR_close 就是系统调用的编号,在 include/unistd.h 中定义:

#define __NR_close    6
/*
所以添加系统调用时需要修改include/unistd.h文件,
使其包含__NR_whoami和__NR_iam。
*/
/*
而在应用程序中,要有:
*/
/* 有它,_syscall1 等才有效。详见unistd.h */
#define __LIBRARY__
/* 有它,编译器才能获知自定义的系统调用的编号 */
#include "unistd.h"
/* iam()在用户空间的接口函数 */
_syscall1(int, iam, const char*, name);
/* whoami()在用户空间的接口函数 */
_syscall2(int, whoami,char*,name,unsigned int,size);


在 0.11 环境下编译 C 程序,包含的头文件都在 /usr/include 目录下。


该目录下的 unistd.h 是标准头文件(它和 0.11 源码树中的 unistd.h 并不是同一个文件,虽然内容可能相同),没有 __NR_whoami 和 __NR_iam 两个宏,需要手工加上它们,也可以直接从修改过的 0.11 源码树中拷贝新的 unistd.h 过来。


从“int 0x80”进入内核函数

int 0x80 触发后,接下来就是内核的中断处理了。先了解一下 0.11 处理 0x80 号中断的过程。


在内核初始化时,主函数在 init/main.c 中,调用了 sched_init() 初始化函数:

void main(void)
{
//    ……
    time_init();
    sched_init();
    buffer_init(buffer_memory_end);
//    ……
}


sched_init() 在 kernel/sched.c 中定义为:

void sched_init(void)
{
//    ……
    set_system_gate(0x80,&system_call);
}


set_system_gate 是个宏,在 include/asm/system.h 中定义为:

#define set_system_gate(n,addr) \
    _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))


虽然看起来挺麻烦,但实际上很简单,就是填写 IDT(中断描述符表),将 system_call 函数地址写到 0x80 对应的中断描述符中,也就是在中断 0x80 发生后,自动调用函数 system_call。


接下来看 system_call。该函数纯汇编打造,定义在 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


system_call 用 .globl 修饰为其他函数可见。


call sys_call_table(,%eax,4) 之前是一些压栈保护,修改段选择子为内核段,call sys_call_table(,%eax,4) 之后是看看是否需要重新调度,这些都与本实验没有直接关系,此处只关心 call sys_call_table(,%eax,4) 这一句。


根据汇编寻址方法它实际上是:call sys_call_table + 4 * %eax,其中 eax 中放的是系统调用号,即 __NR_xxxxxx。


显然,sys_call_table 一定是一个函数指针数组的起始地址,它定义在 include/linux/sys.h 中:

fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,...

增加实验要求的系统调用,需要在这个函数表中增加两个函数引用 ——sys_iam 和 sys_whoami。当然该函数在 sys_call_table 数组中的位置必须和 __NR_xxxxxx 的值对应上。


同时还要仿照此文件中前面各个系统调用的写法,加上:

extern int sys_whoami();
extern int sys_iam();

不然,编译会出错的。

目录
相关文章
|
23天前
|
Linux 开发者 iOS开发
Python系统调用实战:如何在不同操作系统间游刃有余🐟
本文介绍了 Python 在跨平台开发中的强大能力,通过实际例子展示了如何使用 `os` 和 `pathlib` 模块处理文件系统操作,`subprocess` 模块执行外部命令,以及 `tkinter` 创建跨平台的图形用户界面。这些工具和模块帮助开发者轻松应对不同操作系统间的差异,专注于业务逻辑。
35 2
|
2月前
|
网络协议 Linux 调度
深入探索Linux操作系统的心脏:内核与系统调用####
本文旨在揭开Linux操作系统中最为核心的部分——内核与系统调用的神秘面纱,通过生动形象的语言和比喻,让读者仿佛踏上了一段奇妙的旅程,从宏观到微观,逐步深入了解这两个关键组件如何协同工作,支撑起整个操作系统的运行。不同于传统的技术解析,本文将以故事化的方式,带领读者领略Linux内核的精妙设计与系统调用的魅力所在,即便是对技术细节不甚了解的读者也能轻松享受这次知识之旅。 ####
|
2月前
|
缓存 算法 安全
深入理解Linux操作系统的心脏:内核与系统调用####
【10月更文挑战第20天】 本文将带你探索Linux操作系统的核心——其强大的内核和高效的系统调用机制。通过深入浅出的解释,我们将揭示这些技术是如何协同工作以支撑起整个系统的运行,同时也会触及一些常见的误解和背后的哲学思想。无论你是开发者、系统管理员还是普通用户,了解这些基础知识都将有助于你更好地利用Linux的强大功能。 ####
41 1
|
2月前
|
Linux 开发者 iOS开发
Python系统调用实战:如何在不同操作系统间游刃有余🐟
【10月更文挑战第2天】Python 以其卓越的跨平台能力和丰富的标准库,成为解决多操作系统环境下编程挑战的理想选择。本文将通过实际例子展示 Python 如何简化跨平台开发。从文件系统操作到系统调用,再到 GUI 应用开发,Python 提供了多种工具和模块,如 `os`、`pathlib` 和 `subprocess`,帮助开发者轻松应对不同平台间的差异,让你成为真正的跨平台高手。
20 5
|
5月前
|
弹性计算 运维
阿里云操作系统智能助手OS Copilot实验测评报告
**OS Copilot 产品体验与功能反馈摘要** 运维人员发现OS Copilot易上手,文档清晰,助其高效排查故障(8/10分)。愿意推荐并参与开源开发。亮点在于知识问答,能快速筛选答案。相较于竞品,优点是新手友好、文档清晰,但功能扩展性待增强。期望增加系统错误排查与解决方案,并集成ECS等,以优化系统安装流程。
阿里云操作系统智能助手OS Copilot实验测评报告
|
5月前
|
弹性计算 运维 自然语言处理
阿里云操作系统智能助手OS Copilot实验测评报告
OS Copilot是针对Linux的智能助手,助力学习、运维及编程。用户界面直观,自然语言交互方便新手。官方文档详尽,但初次配置略复杂,适合学生和开发者。在提高代码编写和调试效率、系统学习上得分高,功能亮点包括代码生成、问答和命令执行。用户期待更多操作系统支持、自动错误分析和系统排查功能。
184 3
|
4月前
|
Linux 开发者 iOS开发
Python系统调用实战:如何在不同操作系统间游刃有余🐟
【8月更文挑战第5天】Python系统调用实战展示了如何轻松应对跨平台挑战。通过`os`与`pathlib`模块处理文件系统操作,如创建目录及获取用户主目录,自动适配不同操作系统的路径格式。利用`subprocess`模块执行外部命令,智能选择`ls`或`dir`等系统特定指令。借助Tkinter创建图形用户界面,实现一次编写到处运行的目标。这些技巧让开发者专注于应用逻辑,简化跨平台开发流程。
50 0
|
5月前
|
弹性计算 运维
阿里云操作系统智能助手OS Copilot的实验测评报告
OS Copilot 产品体验摘要 用户角色与场景:一位计算机学生使用辅助学习和解决问题,特别是通过代码解释功能加深理解。 易用性与文档:初者可能会觉得有些细节不明确。 帮助程度:用户给予极高评价,对学习帮助大,评分10分,快速定位和解决代码问题,提升学习效率。 推荐与参与:用户愿意推荐给他人。 功能体验:用户尝试了所有功能,对知识问答、辅助编程和命令执行特别感兴趣,尤其是命令执行帮助大。 对比其他产品:OS Copilot优点是便捷、准确。 期望功能:用户希望增加自动报错分析和系统错误排查。 联动体验:用户期待,以实现更全面的工具集。 总结:整体体验积极,用户看好其潜力,期待改进和未来联动。
|
5月前
|
弹性计算 运维 Python
阿里云操作系统智能助手OS Copilot实验测评报告
**OS Copilot 产品测评摘要** - 学生使用,用于学习和编码,发现上手难度较高,指引文档不清晰,特别是Access ID设置和代码复制流程。 - 功能上,评分9分,辅助编程和知识问答功能显著提升了学习效率,减少了错误。 - 愿意推荐,并有兴趣参与开源开发以提升自我。 - 希望增强错误排查,提供具体错误原因和位置。 - 联动ACK智能助手可增强学习效果。 [链接]: https://developer.aliyun.com/topic/instructions-for-os-copilot
|
22天前
|
安全 Linux 数据安全/隐私保护
Vanilla OS:下一代安全 Linux 发行版
【10月更文挑战第30天】
42 0
Vanilla OS:下一代安全 Linux 发行版
下一篇
无影云桌面