我正在尝试为Raspberry Pi创建一个操作系统(没有什么大的,只是为了好玩),尽管我可以在Assembly中编写所有内容,但这比在C中编写它要困难得多。我想知道是否(为什么不能,如果我不能)我可以将C库(文件)包含在操作系统中,这样我就不必重写它们了。它不会起作用,因为库本身是用C语言编写的吗?
答案 0 :(得分:7)
不,您必须将C库移植到您的操作系统,因为该库具有"存根"挂钩到操作系统细节。 C标准要求某些标题以独立模式存在,您始终可以使用它。但像printf这样的库函数必须自己实现或通过填充存根来移植。看看newlib,看看你必须做的工作。它至少需要一个具有syscall接口的工作内核(用于读取,写入等)。这些将取决于操作系统中可用的功能(例如,文件系统。)取自FAQ:
- 将newlib移植到新平台需要执行哪些步骤?
醇>基本端口需要更改许多文件并添加一些文件 目录。
将一个子目录添加到您平台的newlib / libc / machine目录
在此目录中,您需要具有setjmp / longjmp实现。这是必需的,因为setjmp / longjmp通常是 汇编。查看libc / machine / fr30目录并复制/修改 那里的文件。
编辑newlib / libc / include / machine / ieeefp.h
这定义了您平台的ieee字节顺序。编译器应该定义标识您的机器的东西。在一些 在这种情况下,字节顺序可能是编译器选项,因此您可能需要这样做 除了您的平台标识符之外,请检查另一个定义看到 文件中的示例。
编辑newlib / libc / include / machine / setjmp.h
您需要指定setjmp缓冲区特性以与setjmp / longjmp实现相匹配。这只是大小 该 setjmp缓冲区。有关示例,请参阅文件。
编辑newlib / libc / include / sys / config.h
根据需要,这有各种各样的定义。大多数情况下,它定义了一些最大值。有些默认值可能适用于您的平台 如果你不需要做任何事情。
编辑configure.host
您需要添加配置,以便newlib可以识别它。您应该通过以下方式为您的平台指定新的计算机目录 该 machine_dir变量。如果需要,可以添加特殊的newlib编译 标志。 sys_dir用于OS的东西,所以你不需要改变它。 较旧的平台使用sys_dir来实现系统调用,但事实并非如此 正确而且是历史上的麻烦。 syscall_dir是一个选择,但是 我建议默认指定syscall_dir = syscalls。阅读 newlib / libc / include / reent.h中的注释,用于解释选择。
将一个平台子目录添加到libgloss
您需要为您的平台添加bsp。这是newlib和所需的任何链接描述文件所需的最小系统调用。这个 从板到板(它也可以是模拟器)各不相同。见 例如mn10300或fr30。您需要编辑configure.in和 重新生成配置,以便它将构建您的新文件。默认情况下你 获取libnosys,它提供了一组默认的系统调用存根。该 大多数存根只是返回失败。你仍然需要提供一个 __exit例程。这可以像生成停止程序的异常一样简单。
- 醇>
可能会覆盖标题文件
如果需要覆盖任何默认机器头文件,可以将机器目录添加到newlib / libc / machine / Header文件中 那 子目录将覆盖中找到的默认值 newlib / libc的/包括/机器。您可能不需要这样做。
这假设您已经处理过将新配置添加到 顶级目录文件。
现在linux是一种不同的动物。它是一个具有广泛设置的操作系统 系统调用。如果查看newlib / libc / sys / linux目录,就可以了 会在那里找到一些系统调用(例如,参见io.c)。有一套 为特定平台定义的基本系统调用宏。 对于x86,您将找到定义的这些宏 newlib / libc / sys / linux / machine / i386 / syscall.h文件。在这一刻, linux支持仅适用于x86。要添加另一个平台,请使用syscall.h 必须为新平台和其他平台提供文件 特定于平台的文件也需要移植。
对于newlib,请查看syscall documentation page,其中列出了您需要实现的内容以及最小实现的内容。如果您还没有实现内存管理,那么您很快就会意识到像sbrk
这样的东西会变得毫无意义。在您移植C库时,您可能最终编写了大部分内核。
_exit
退出程序而不清理文件。如果您的系统没有提供此功能,最好避免与子程序链接 需要它(退出,系统)。
close
关闭文件。最少的实施:
int close(int file) { return -1; }
environ
指向环境变量及其值的列表的指针。对于最小的环境,这个空列表就足够了:
char *__env[1] = { 0 }; char **environ = __env;
execve
将控制权转移到新流程。最小的实现(对于没有进程的系统):
#include <errno.h> #undef errno extern int errno; int execve(char *name, char **argv, char **env) { errno = ENOMEM; return -1; }
fork
创建一个新流程。最小的实现(对于没有进程的系统):
#include <errno.h> #undef errno extern int errno; int fork(void) { errno = EAGAIN; return -1; }
fstat
打开文件的状态。为了与这些示例中的其他最小实现保持一致,所有文件都被视为字符 特殊装置。所需的sys / stat.h头文件分发在 这个C库的include子目录。
#include <sys/stat.h> int fstat(int file, struct stat *st) { st->st_mode = S_IFCHR; return 0; }
getpid
过程-ID;这有时用于生成不太可能与其他进程冲突的字符串。系统的最小实现 没有进程:
int getpid(void) { return 1; }
isatty
查询输出流是否为终端。为了与其他仅支持输出的最小实现保持一致 stdout,建议这个最小的实现:
int isatty(int file) { return 1; }
kill
发送信号。最少的实施:
#include <errno.h> #undef errno extern int errno; int kill(int pid, int sig) { errno = EINVAL; return -1; }
link
为现有文件建立新名称。最少的实施:
#include <errno.h> #undef errno extern int errno; int link(char *old, char *new) { errno = EMLINK; return -1; }
lseek
在文件中设置位置。最少的实施:
int lseek(int file, int ptr, int dir) { return 0; }
open
打开一个文件。最少的实施:
int open(const char *name, int flags, int mode) { return -1; }
read
从文件中读取。最少的实施:
int read(int file, char *ptr, int len) { return 0; }
sbrk
增加程序数据空间。由于malloc和相关函数依赖于此,因此有一个有效的实现是很有用的。该 以下是独立系统的必要条件;它利用了这个符号 _end由GNU链接器自动定义。
caddr_t sbrk(int incr) { extern char _end; /* Defined by the linker */ static char *heap_end; char *prev_heap_end; if (heap_end == 0) { heap_end = &_end; } prev_heap_end = heap_end; if (heap_end + incr > stack_ptr) { write (1, "Heap and stack collision\n", 25); abort (); } heap_end += incr; return (caddr_t) prev_heap_end; }
stat
文件的状态(按名称)。最少的实施:
int stat(char *file, struct stat *st) { st->st_mode = S_IFCHR; return 0; }
times
当前流程的计时信息。最少的实施:
int times(struct tms *buf) { return -1; }
unlink
删除文件的目录条目。最少的实施:
#include <errno.h> #undef errno extern int errno; int unlink(char *name) { errno = ENOENT; return -1; }
wait
等待子进程。最少的实施:
#include <errno.h> #undef errno extern int errno; int wait(int *status) { errno = ECHILD; return -1; }
write
写入文件。 libc子例程将使用此系统例程 输出到所有文件,包括stdout-so,如果你需要生成任何文件 输出,例如到一个串口进行调试,你应该做 你的最小写能力。以下极少 实施是一个不完整的例子;它依赖于外围 子程序(未显示;通常,您必须在汇编程序中编写它 从您的硬件制造商提供的示例)到实际 执行输出。
int write(int file, char *ptr, int len) { int todo; for (todo = 0; todo < len; todo++) { outbyte (*ptr++); } return len; }
有关端口newlib所需步骤的更全面概述,请参阅osdev.org。虽然我建议首先阅读网站上与编写内核有关的其他教程,因为移植C库绝对不是编写内核时的第一步。