系统调用

添加系统调用iam()

系统调用iam()的原型为:

int iam(const char * name);

系统调用 iam() 将字符串参数 name 的内容拷贝到内核中保存并返回拷贝的字符数。要求 name 的长度不超过 23 个字符,如果 name 的长度超过 23 则返回 -1,并置 errno 为 EINVAL。
在 who.c 中定义保存 name 的全局静态变量 myname:

static char myname[24];

在内核中实现函数 sys_iam():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int sys_iam(const char * name)
{
char buf[24];
int n;
/* copy into buffer */
for (n = 0; n < 24; n++) {
buf[n] = get_fs_byte(name + n);
if (buf[n] == '\0') break;
}
/* length of name more than 23 */
if (n == 24) {
return -EINVAL;
}
/* less than 23, copy */
strcpy(myname, buf);
return n;
}

函数首先定义长度为 24 的字符串 buf 充当缓冲区,定义 n 用于记录 name 长度。由于指针参数 name 传递地址为应用程序所在地址空间的逻辑地址,在内核中直接访问该地址访问到的是内核空间中的数据而不是用户地址空间中数据。系统调用函数 system_call 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
system_call:
cmpl $nr_system_calls-1,%eax # 检查系统调用编号是否在合法范围内
ja bad_sys_call
push %ds
push %es
push %fs
pushl %edx
pushl %ecx
pushl %ebx # 压栈保存参数
movl $0x10,%edx # ds, es 指向 GDT
mov %dx,%ds
mov %dx,%es
movl $0x17,%edx # fs 指向 LDT
mov %dx,%fs
call sys_call_table(,%eax,4)

system_call 使用寄存器 eax 存放系统调用编号,ebx、ecx、edx 作为函数参数,若参数多于三个或参数不能存放至寄存器则将其封装成结构体,将指针作为参数传递。注意到通过系统调用陷入内核态时,通过寄存器 fs 索引 LDT 中段描述符以访问对应进程用户态中数据。文件 segment.h 中定义了函数 get_fs_byte() 用于获得一个字节的用户空间中数据,函数 put_fs_byte() 用于获得一个字节的用户空间中数据。

1
2
3
4
5
6
7
8
9
10
11
extern inline unsigned char get_fs_byte(const char * addr)
{
unsigned register char _v;
__asm__ ("movb %%fs:%1,%0":"=r" (_v):"m" (*addr));
return _v;
}

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

故系统调用函数 sys_iam() 循环使用函数 get_fs_byte() 由用户地址空间逐字节读取 name 所指向字符串中字符至 buf 中并记录长度,直至读取 24 个字符或读取到字符串末尾 ‘\0’。若循环后 n 等于 24 则说明 name 指向字符串长度超过限长 23,返回 -1 并置 errno 为 EINVAL (定义在 errno.h 中);反之则说明字符串长度小于 23,使用函数 strcpy() 将 buf 拷贝至 myname 并返回长度 n。函数 strcpy() 定义在 string.h 中:

extern inline char * strcpy(char * dest,const char *src);

添加系统调用whoami()

系统调用whoami()的原型为:

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

系统调用 whoami() 将内核中由系统调用 iam() 保存至内核中的字符串 myname 拷贝至 name 指向的用户地址空间中。若拷贝字节数 size 小于 myname 的长度,则返回 -1 并置 errno 为 EINVAL,反之返回拷贝字节数即 myname 长度。

在内核中实现函数 sys_whoami():

1
2
3
4
5
6
7
8
9
10
11
12
13
int sys_whoami(char* name, unsigned int size)
{
int n;
/* get length of myname */
for (n = 0; n < 24; n++) {
if (myname[n] == '\0') break;
}
if (n > size) return -EINVAL;
for (int i = 0; i < n; i++) {
put_fs_byte(myname[i], name + i);
}
return n;
}

函数首先计算 myname 的长度 n,若 n 大于限长 size 则返回 -EINVAL,反之使用函数 put_fs_byte() 将字符串 myname 逐字符拷贝至 name 所指向用户地址空间中并返回拷贝字符串长度。

测试程序

测试程序 iam.c

1
2
3
4
5
6
7
8
9
10
11
12
13
#define __LIBRARY__
#include <unistd.h>
#include <stdio.h>
_syscall1(int, iam, const char*, name);

int main(int argc, char ** argv)
{
if (argc != 2) {
printf("Expect: iam(const char * name)\n");
}
iam(argv[1]);
return 0;
}

测试程序 whoami.c

1
2
3
4
5
6
7
8
9
10
11
12
13
#define __LIBRARY__
#include <unistd.h>
#include <stdio.h>
_syscall2(int, whoami,char*,name,unsigned int,size);
#define MAX_LENGTH 1024

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

测试结果如图 1 所示:

图1

测试程序 testlab2.c 及测试脚本 testlab2.sh 运行结果如图 2 所示:

图2

相关问题

从 Linux 0.11 现在的机制看,它的系统调用最多能传递几个参数?

1
2
3
pushl %edx
pushl %ecx
pushl %ebx # 压栈保存参数

system_call 通过压栈保存系统调用参数,ebx、ecx、edx 三个寄存器中存放着至多三个系统调用参数。

你能想出办法来扩大这个限制吗?

若参数多于三个或有参数过大,不能存放至寄存器中,可将其封装为结构体并传递结构体指针作为系统调用参数,在系统调用函数执行阶段将其拆分。

用文字简要描述向 Linux 0.11 添加一个系统调用 foo() 的步骤

首先在 kernel 文件夹中创建系统调用 foo() 实现文件 foo.c 并编写系统调用函数 sys_foo(),随后在 include/unistd.h 中添加 foo() 对应系统调用编号宏定义 #define __NR_foo X 并在 kernel/system_call.s 中更新 nr_system_calls 为此时系统调用数。紧接着,在 include/linux/sys.h 中添加 extern int sys_foo() 并在 sys_call_table[] 中添加元素 sys_foo 使其位置与 __NR_foo 的值对应。最后,在编译后文件系统的 /usr/include/unistd.h 中添加 __NR_foo 的宏定义。