操作系统之——为Linux0.11添加系统调用

1.目标

为Linux0.11添加两个系统调用, 分别为:

  1. int iam(const char* name);
    功能为:将字符串name拷贝到内核并保存,要求 name 的长度不能超过 23 个字符。返回值是拷贝的字符数。如果 name 的字符个数超过了 23,则返回 “-1”,并置 errno 为 EINVAL。
    kernal/who.c 中实现此系统调用。
  2. int whoami(char* name, unsigned int size);
    它将内核中由 iam() 保存的名字拷贝到 name 指向的用户地址空间中,同时确保不会对 name 越界访存(name 的大小由 size 说明)。返回值是拷贝的字符数。如果 size 小于需要的空间,则返回“-1”,并置 errno 为 EINVAL。
    也是在 kernal/who.c 中实现。

同时为两个系统调用添加测试程序,分别为 iam.cwhoami.c

2.分析

2.1 系统调用处理过程

  • 应用程序调用库函数(API);
  • API 将系统调用号存入 EAX,然后通过中断调用使系统进入内核态;
  • 内核中的中断处理函数根据系统调用号,调用对应的内核函数(系统调用);
  • 系统调用完成相应功能,将返回值存入 EAX,返回到中断处理函数;
  • 中断处理函数返回到 API 中;
  • API 将 EAX 返回给应用程序。

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

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

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

1. 把系统调用(__NR_xxx)的编号存入 EAX;
2. 把函数参数存入其它通用寄存器;
3. 触发 0x80 号中断(int 0x80)。

2.2 举例分析

2.2.1 宏定义

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

linux-0.11/lib/close.c为例:

#define __LIBRARY__
#include <unistd.h>

_syscall1(int, close, int, fd)

同理,在增加系统调用的时候也要有相应的宏定义(在编写测试程序时需要):

_syscall1(int, iam, const char*, myname)
_syscall2(int, whoami, char*, myname, unsigned int, size)

2.2.2 宏展开

_syscall1 是一个宏,在 include/unistd.h 中定义,_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; \
}

_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 中定义:
在这里插入图片描述
所以需要在 include/unistd.h 中为iamwhoami增加系统调用号:

#define __NR_whoami 72
#define __NR_iam	73

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

在中断 0x80 发生后,自动调用函数 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

由于增加了两个系统调用,所以需要修改nr_system_calls 的值为74.
最重要的是这一行: call sys_call_table(,%eax,4)
其中 eax 中放的是系统调用号,即 __NR_xxxxxx

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

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

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

2.2.4 实现sys_iam与sys_whoami

主要调用两个函数:

static inline unsigned char get_fs_byte(const char * addr)
{
	unsigned register char _v;

	__asm__ ("movb %%fs:%1,%0":"=r" (_v):"m" (*addr));
	return _v;
}
static inline void put_fs_byte(char val,char *addr)
{
__asm__ ("movb %0,%%fs:%1"::"r" (val),"m" (*addr));
}

3.具体步骤

综合上述分析,需要修改的地方有:

(1)在 include/unistd.h 中为iamwhoami增加系统调用号:

#define __NR_whoami 72
#define __NR_iam	73

(2)修改 kernel/system_call.s 中的系统调用总数:

nr_system_calls = 74

(3)在 include/linux/sys.hsys_call_table中增加sys_iamsys_whoami
在这里插入图片描述
(4)在 kernal/ 中新增who.c实现sys_iamsys_whoami

#include <errno.h>
#include <unistd.h>
#include <asm/segment.h>
#include <linux/kernel.h>

#define MAXLEN 23
char username[MAXLEN+1] = {0};

int sys_iam(const char* myname)
{
	printk("sys_iam is running...\n");
	unsigned int i = 0;
	unsigned int namelen = 0;
	
	while(get_fs_byte(myname+namelen)!='\0')
		++namelen;
	if(namelen > MAXLEN){
		errno = EINVAL;
		return -1;
	}
	printk("namelen: %d\n", namelen);
	while(i < namelen){
		username[i] = get_fs_byte(myname+i);
		++i;
	}
	username[i] = '\0';
	printk("username: %s\n", username);
	return namelen;
}

int sys_whoami(char* myname, unsigned int size)
{
	printk("sys_whoami is running...\n");
	unsigned int i = 0;
	unsigned int namelen = 0;
	while(username[namelen] != '\0')
		++namelen;
	if(size < namelen){
		errno = EINVAL;
		return -1;	
	}
	for(; i<=namelen; i++){
		put_fs_byte(username[i], myname+i);
	}
	printk("namelen: %d\n", namelen);
	return namelen;
}

(4)修改 kernal/ 中的 Makefile
在这里插入图片描述
增加红线上的内容。

(5)在linux-0.11目录下执行make all

(6)在oslab/hdc/usr/root中增加测试程序:

cd /home/shiyanlou/oslab/oslab
sudo ./mount-hdc
cd hdc/usr/root

增加测试程序iam.c与whoami.c。

  • iam.c
#define __LIBRARY__
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

_syscall1(int, iam, const char*, myname)

int main(int argc, char** argv)
{
	int res = iam(argv[1]);
	if(res == -1){
		printf("systemcall failed\n");
	}
	else{
		printf("systemcall success\n");
	}
	return 0;
}

  • whoami.c
#define __LIBRARY__
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

_syscall2(int, whoami, char*, myname, unsigned int, size)
#define SIZE 23

int main(int argc, char** argv)
{
	char myname[SIZE+1] = {0};
	int res = whoami(myname, SIZE+1);
	if(res == -1)
		printf("systemcall failed\n");
	else{
	/*	printf("systemcall success\n");*/
		printf("%s\n", myname);
	}
	return 0;
}

程序中的注释要使用/**/,不要使用//,否则编译出错。。。
(7)将testlab2.sh拷贝到hdc/usr/root运行环境。

sudo umount hdc
cd /home/shiyanlou/oslab/oslab
./run

运行结果:
在这里插入图片描述

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值