我可以在编程操作系统时包含C库(因为它们是用C语言编写的)

时间:2014-10-24 05:14:17

标签: c operating-system raspberry-pi libraries c-libraries

我正在尝试为Raspberry Pi创建一个操作系统(没有什么大的,只是为了好玩),尽管我可以在Assembly中编写所有内容,但这比在C中编写它要困难得多。我想知道是否(为什么不能,如果我不能)我可以将C库(文件)包含在操作系统中,这样我就不必重写它们了。它不会起作用,因为库本身是用C语言编写的吗?

1 个答案:

答案 0 :(得分:7)

不,您必须将C库移植到您的操作系统,因为该库具有"存根"挂钩到操作系统细节。 C标准要求某些标题以独立模式存在,您始终可以使用它。但像printf这样的库函数必须自己实现或通过填充存根来移植。看看newlib,看看你必须做的工作。它至少需要一个具有syscall接口的工作内核(用于读取,写入等)。这些将取决于操作系统中可用的功能(例如,文件系统。)取自FAQ:

  
      
  1. 将newlib移植到新平台需要执行哪些步骤?
  2.         
        

    基本端口需要更改许多文件并添加一些文件     目录。

             
          
    1. 将一个子目录添加到您平台的newlib / libc / machine目录

               

      在此目录中,您需要具有setjmp / longjmp实现。这是必需的,因为setjmp / longjmp通常是     汇编。查看libc / machine / fr30目录并复制/修改     那里的文件。

    2.     
    3. 编辑newlib / libc / include / machine / ieeefp.h

               

      这定义了您平台的ieee字节顺序。编译器应该定义标识您的机器的东西。在一些     在这种情况下,字节顺序可能是编译器选项,因此您可能需要这样做     除了您的平台标识符之外,请检查另一个定义看到     文件中的示例。

    4.     
    5. 编辑newlib / libc / include / machine / setjmp.h

               

      您需要指定setjmp缓冲区特性以与setjmp / longjmp实现相匹配。这只是大小     该     setjmp缓冲区。有关示例,请参阅文件。

    6.     
    7. 编辑newlib / libc / include / sys / config.h

               

      根据需要,这有各种各样的定义。大多数情况下,它定义了一些最大值。有些默认值可能适用于您的平台     如果你不需要做任何事情。

    8.     
    9. 编辑configure.host

               

      您需要添加配置,以便newlib可以识别它。您应该通过以下方式为您的平台指定新的计算机目录     该     machine_dir变量。如果需要,可以添加特殊的newlib编译     标志。 sys_dir用于OS的东西,所以你不需要改变它。     较旧的平台使用sys_dir来实现系统调用,但事实并非如此     正确而且是历史上的麻烦。 syscall_dir是一个选择,但是     我建议默认指定syscall_dir = syscalls。阅读     newlib / libc / include / reent.h中的注释,用于解释选择。

    10.     
    11. 将一个平台子目录添加到libgloss

               

      您需要为您的平台添加bsp。这是newlib和所需的任何链接描述文件所需的最小系统调用。这个     从板到板(它也可以是模拟器)各不相同。见     例如mn10300或fr30。您需要编辑configure.in和     重新生成配置,以便它将构建您的新文件。默认情况下你     获取libnosys,它提供了一组默认的系统调用存根。该     大多数存根只是返回失败。你仍然需要提供一个     __exit例程。这可以像生成停止程序的异常一样简单。

    12.     
    13. 可能会覆盖标题文件

               

      如果需要覆盖任何默认机器头文件,可以将机器目录添加到newlib / libc / machine / Header文件中     那     子目录将覆盖中找到的默认值     newlib / libc的/包括/机器。您可能不需要这样做。

    14.               

      这假设您已经处理过将新配置添加到     顶级目录文件。

               

      现在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库绝对不是编写内核时的第一步。