操作系统原理、实现与实践(二)

习题

  1. 父子进程虽然都执行“同样”的代码,但是在不同的进程中执行,执行一次fork()则会复制一次当前的程序,两个程序同步运行下去;

  2. 代码执行情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
父进程
- 生成子进程1
- 打印B
- 生成子进程2
- 打印C
子进程1
- 打印A
- 打印B
- 生成子进程3
- 打印C
子进程2
- 打印C
子进程3
- 打印C
  1. scanf("ID:%d",&id)展开成read系统调用是:
1
2
3
4
5
char buf[MAXLEN];
buf = "ID:";
write(1,buf,3);
memset(buf,0,sizeof(buf));
read(1,buf,sizeof(int));
  1. systemcall3(int,write,int,fd,char*,buf,int,count)展开宏为:
1
2
3
4
5
6
7
8
9
10
{
long _res;
__asm__ volatile ("int $0x80"
: "=a" (_res)
: "0" (__NR_##name), "b" ((long)(arg1)), "c" ((long)(arg2)), "d" ((long)(arg3)));
if (_res >= 0)
return (type) _res;
errno = -_res;
return -1;
}

实践

  1. include/unistd.h中增加:
1
2
#define __NR_iam  72
#define __NR_whoami 73
  1. include/linux/sys.h中增加:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extern int sys_iam();
extern int sys_whoami();


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};
  1. 修改kernel/system_call.s中系统调用的数量:
1
nr_system_calls = 74
  1. 增加kernel/who.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <errno.h>
#include <linux/kernel.h>
#include <asm/segment.h>

char kernelname[23];

int sys_iam(const char *name) {
int len = 0;

// 1. 在获取字符串长度时,加入最大长度限制
while (len <= 23) {
if (get_fs_byte(name + len) == '\0') {
break; // 找到终止符
}
len++;
}

// 2. 检查长度是否符合规范
if (len > 23) {
return -EINVAL; // 返回无效参数错误
}

// 3. 将字符串复制到内核空间
int i;
for (i = 0; i < len; i++) {
kernelname[i] = get_fs_byte(name + i);
}

// 4. 添加终止符
kernelname[len] = '\0';

return len; // 返回成功复制的长度
}

int sys_whoami(char *name, unsigned int size) {
int len = 0;

// 1. 获取内核字符串的长度,并使用之前定义的常量
// 这里的循环也应该防止越界访问 kernelname
while (len <= 23 && kernelname[len] != '\0') {
len++;
}

// 2. 检查用户提供的缓冲区大小是否足够
// 需要确保缓冲区可以容纳 len 个字符 + 1 个终止符
if (len >= size) {
// 如果用户缓冲区太小,返回错误码
return -EINVAL;
}

// 3. 复制字符串到用户空间
int i;
for (i = 0; i < len; i++) {
put_fs_byte(kernelname[i], name + i);
}

// 4. 添加终止符
put_fs_byte('\0', name + len);

// 5. 返回实际复制的字符数
return len;
}
  1. kernel/Makefile中增加,并编译内核
1
2
3
4
5
6
7
8
9
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.s who.o: who.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
  1. 启动系统后,在用户空间编写两个程序(whoami.ciam.c),调用该系统调用

whoami.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#define __LIBRARY__
#include <stdio.h>
#include <unistd.h>
#include <errno.h>


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

#define SIZE 23

int main(void)
{
char name[SIZE+1];
int res;
res = whoami(name, SIZE+1);
if (res == -1)
errno = EINVAL;
else
printf("%s\n", name);
return res;
}

iam.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#define __LIBRARY__
#include <unistd.h>
#include <errno.h>


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

#define NAMELEN 100
char name[NAMELEN];

int main(int argc, char *argv[])
{
int res;
int namelen = 0;
if (2 <= argc) {
while ((name[namelen] = argv[1][namelen]) != '\0')
namelen++;
printf("iam.c: %s, %d\n", name, namelen);
res = iam(name);
errno = EINVAL;
return res;
}
}
  1. 编译,这里有个小坑,需要把/usr/include/unistd.h头文件更新一下,与编译时的源代码一致,否则会报ENOENT错误
1
2
gcc -o whoami whoami.c 
gcc -o iam iam.c
  1. 编译完成后,测试该系统调用,测试代码如下,采用的是 https://github.com/Wangzhike/HIT-Linux-0.11/blob/master/2-syscall/test/testlab2.sh.
image-20250821223245366

整个过程为:

  1. 用户态的程序调用系统调用函数API

  2. API函数通过int 0x80的中断,进入到内核态中

  3. system_call根据系统调用号,来找到该系统调用的内核函数,并进行处理

  4. 处理完成后将内核函数的返回值返回给用户态程序