字符读入与应用名称读取
1. sys_read的实现
在之前借助Opensbi
可以向串口输出一个字符,这里同理,我们可以借助Opensbi
读入从串口输入来的数据,在sbi.c
中新增一个获取读入串口字符的函数:
/** |
返回的字符存储在 ret.error
中,读入字符的调用号是SBI_EXT_0_1_CONSOLE_GETCHAR
。
借助此函数我们就能来实现sys_read
了,应用程序在用户态调用sys_read
来获取一个字符,内核在接收到这来自用户态的系统调用时会进行分发,然后去调用sbi_console_getchar
函数将输出的字符返回给用户态,用户态的sys_read
函数定义如下,__NR_read
系统调用号的值为:63
int sys_read(size_t fd ,const char* buf , size_t len) |
此函数定义在app.c
中,和sys_write
系统调用类似,buf
用来存储从串口输入的字符,对此函数封装一下,定义一个每次从串口获取一个字符的函数:
/* 获取一个字符 */ |
当内核的发现来自用户态的__NR_read
系统调用时需要进行分发:
会去调用__sys_read
函数进行处理:
void __sys_read(size_t fd, const char* data, size_t len) |
此函数会循环读取串口的数据,直到读到一个字符,然后这里会去调用translated_byte_buffer
找到从内核传进来的buf
对应的实际物理地址,然后将串口读到的字符写入此物理地址。这里和__sys_write
同理,应用地址空间和内核地址空间被隔离了,要进行数据传递需要找到实际的物理地址,因此translated_byte_buffer
也做了一点小小的修改:
可以看见根据传入的用户态的地址转换后返回实际对应的物理地址。
来测试一下,首先修改一下应用程序,让time
应用程序不打印东西,在write
应用程序中来读取字符:
编译内核和执行:
可以看见我从键盘输出的字符都成功打印了出来。
2. 内核栈的修改
在内核和用户程序的映射逻辑 | TimerのBlog (yanglianoo.github.io)我们对每个应用程序的内核栈进行了映射,当时映射后的内存分布长这样子:
这样不太好,我当时脑子抽了,实际上trampoline
和app0 kstack
之间应该存在一页guard page
才对,修改后的内存分布长这样子:
映射内核栈的代码也要随之修改一下:
/* 为每个应用程序映射内核栈,内核空间以及进行了映射 */ |
3. 读取应用程序的名称
在后续的开发中我们会使用进程的名字来对应用程序加载和执行,因此需要内核能得到应用的名称,在我们之前的build.c
中加入几行代码:
加入这几行代码后,link_app.S
也会随之改变,原因在于我们按照顺序将各个应用的名字通过 .string
伪指令放到数据段中,注意链接器会自动在每个字符串的结尾加入分隔符 \0
,它们的位置则由全局符号 _app_names
指出。
在loader.c
中新建一个函数来读取应用的名字:
extern char _app_names[]; |
由于链接器会自动在每个字符串的结尾加入分隔符 \0
,因此根据\0
将每个应用程序的名称存储到了app_names
这个数组中。在main函数中调用此函数测试一下:
成功解析到程序的名称。