创建main函数和链接C文件

  一般C语言使用main()函数作为程序的入口点, 为了符合我们平时的编程习惯, 这里我们也使用main()函数作为C代码的入口点, 并在"start.asm"文件中添加中断服务程序来调用C函数。

  在这一节教程,我们将尝试创建一个"main.c"文件和一个包含常用函数原型的头文件"system.h"。"main.c"中包含mian()函数, 它将作为你C代码的入口。在内核开发中, 我们一般不从main()函数返回。多数操作系统在main中初始化内核和子程序、加载shell, 然后main函数会进入空循环中。在多任务系统中, 当没有其他需要运行的任务时, 将一直执行这个空循环。下面是"main.c"文件的示例,其中包含了最基本的main()函数和一些我们以后会用到的函数体。

main.c

#include <system.h>

/* 你将要自己完成这些代码  */
unsigned char *memcpy(unsigned char *dest, const unsigned char *src, int count)
{
/* 在此处添加代码, 将'src'中count字节的数据复制到'dest'中,
* 最后返回'dest' */
} unsigned char *memset(unsigned char *dest, unsigned char val, int count)
{
/* 在此处添加代码, 将'dest'中的count字节全部设置成值'val',
* 最后返回'dest' */
} unsigned short *memsetw(unsigned short *dest, unsigned short val, int count)
{
/* 在此处添加代码, 将'dest'中的count双字节设置成值'val',
* 最后返回'dest'
* 注意'val'是双字节 16-bit*/
} int strlen(const char *str)
{
/* 返回字符串的长度
* 遇到某个字节的值为0x00结束 */
} /* 我们之后将使用这个函数通过IO端口从设备读取数据, 如键盘等
* 我们使用内联汇编代码(inline assembly)实现该功能 */
unsigned char inportb (unsigned short _port)
{
unsigned char rv;
__asm__ __volatile__ ("inb %1, %0" : "=a" (rv) : "dN" (_port));
return rv;
} /* 我们将使用这个函数通过I/O端口向设备写数据
* 用来修改文本模式和光标位置
* 同样,我们使用内联汇编来实现这个单用C无法实现的功能 */
void outportb (unsigned short _port, unsigned char _data)
{
__asm__ __volatile__ ("outb %1, %0" : : "dN" (_port), "a" (_data));
} /* 这是一个非常简单的main函数, 它内部仅进行死循环 */
void main()
{
/* 你可以在这里添加语句 */ /* 保留此循环
* 不过, 在'start.asm'里也有一个无限循环, 防止你不小心删除了下面这一行*/
for (;;);
}

  在你编译之前, 我们需要在'start.asm'中添加2行代码。我们需要让编译器知道main()在外部文件中, 我们还需要从'start.asm'文件中调用main()函数。打开'start.asm'文件, 在stublet:的下面添加下面2行代码:

	extern _main
call _main

  先等等编译, _main前面的下划线是什么东西, 我们在C语言里声明的是main啊?编译器gcc在编译时会在所有函数和变量名前面加上下划线。因此, 要从汇编中引用C文件中的函数和变量, 我们都需要在前面加上下划线!

  现在我们还缺少一个"system.h"文件。创建一个名为"include"的文件夹, 在该文件加下创建一个名为"system.h"的空白文本文件, 将mencpymemsetmemsetwstrleninportboutportb这些函数的函数原型添加到该头文件中。#ifndef#define#endif用于防止头文件被多次声明。这个头文件用来包含你在内核中使用的所有函数, 你可以随意添加你需要的函数来扩展此库。

include/system.h

#ifndef __SYSTEM_H
#define __SYSTEM_H /* MAIN.C */
extern unsigned char *memcpy(unsigned char *dest, const unsigned char *src, int count);
extern unsigned char *memset(unsigned char *dest, unsigned char val, int count);
extern unsigned short *memsetw(unsigned short *dest, unsigned short val, int count);
extern int strlen(const char *str);
extern unsigned char inportb (unsigned short _port);
extern void outportb (unsigned short _port, unsigned char _data); #endif

  接下来, 我们要来编译这些文件。打开之前的"build.bat"文件, 添加下面一行命令来编译你的"main.c"。这个命令运行了gcc编译器, gcc后面有一堆参数:

  • -Wall用于显示所有的警告
  • -O-fstrength-reduce-fomit-frame-pointer-finline-functions用于编译优化
  • -nostdinc-fno-builtin告诉编译器我们将不适用C标准库函数
  • -I./include告诉编译器我们的头文件在当前文件夹下的"include"文件夹里
  • -c表示当前仅编译, 不链接
  • -o main.o表示输出文件名为main.o
  • main.c是我们要编译的文件
gcc -Wall -O -fstrength-reduce -fomit-frame-pointer -finline-functions -nostdinc -fno-builtin -I./include -c -o main.o main.c

  别忘了, 按照"build.bat"文件中的指示, 把"main.o"添加到链接文件的列表中去。像这样:

ld -T link.ld -o kernel.bin start.o main.o

  当前完整的"build.bat"文件内容如下:

build.bat

echo Now assembling, compiling, and linking your kernel:
nasm -f aout -o start.o start.asm
rem Remember this spot here: We will add 'gcc' commands here to compile C sources
gcc -Wall -O -fstrength-reduce -fomit-frame-pointer -finline-functions -nostdinc -fno-builtin -I./include -c -o main.o main.c rem This links all your files. Remember that as you add *.o files, you need to
rem add them after start.o. If you don't add them at all, they won't be in your kernel!
ld -T link.ld -o kernel.bin start.o main.o
echo Done!
pause

  最后, 如果你不知道该怎么实现那些附加函数, 如memcpy函数, 这里有一份参考代码:

#include <system.h>

unsigned char *memcpy(unsigned char *dest, const unsigned char *src, int count)
{
const unsigned char *sp = (const unsigned char *)src;
unsigned char *dp = dest;
for(; count != 0; count--) *dp++ = *sp++;
return dest;
} unsigned char *memset(unsigned char *dest, unsigned char val, int count)
{
unsigned char *temp = (unsigned char *)dest;
for( ; count != 0; count--) *temp++ = val;
return dest;
} unsigned short *memsetw(unsigned short *dest, unsigned short val, int count)
{
unsigned short *temp = (unsigned short *)dest;
for( ; count != 0; count--) *temp++ = val;
return dest;
} int strlen(const char *str)
{
int retval;
for(retval = 0; *str != '\0'; str++) retval++;
return retval;
} /* 我们之后将使用这个函数通过IO端口从设备读取数据, 如键盘等
* 我们使用内联汇编代码(inline assembly)实现该功能 */
unsigned char inportb (unsigned short _port)
{
unsigned char rv;
__asm__ __volatile__ ("inb %1, %0" : "=a" (rv) : "dN" (_port));
return rv;
} /* 我们将使用这个函数通过I/O端口向设备写数据
* 用来修改文本模式和光标位置
* 同样,我们使用内联汇编来实现这个单用C无法实现的功能 */
void outportb (unsigned short _port, unsigned char _data)
{
__asm__ __volatile__ ("outb %1, %0" : : "dN" (_port), "a" (_data));
} /* 这是一个非常简单的main函数, 它内部仅进行死循环 */
void main()
{
/* 你可以在这里添加语句 */ /* 保留此循环
* 不过, 在'start.asm'里也有一个无限循环, 防止你不小心删除了下面这一行*/
for (;;);
}

PS: 下面是我自己写的

Win10安装gcc编译器

  Win10安装gcc、g++、make: https://www.cnblogs.com/raina/p/10656106.html

本节教程对应的Linux下的编译脚本

echo "Now assembling, compiling, and linking your kernel:"
nasm -f elf64 -o start.o start.asm
# Remember this spot here: We will add 'gcc' commands here to compile C sources
gcc -Wall -O -fstrength-reduce -fomit-frame-pointer -finline-functions -nostdinc -fno-builtin -I./include -c -o main.o main.c # This links all your files. Remember that as you add *.o files, you need to
# add them after start.o. If you don't add them at all, they won't be in your kernel!
ld -T link.ld -o kernel.bin start.o main.o
echo "Done!"
read -p "Press a key to continue..."

_main的问题

  我的gcc版本是7.4.0, 在编译C程序时并没有在函数和变量名前面自己加下划线, 所以按照原教程写的汇编代码, 编译后会报下面的错误:

start.o: In function `stublet':

start.asm:(.text+0x29): undefined reference to `_main'

把"start.asm"里_main的下划线去掉, 再编译就行了。


此文原创禁止转载,转载文章请联系博主并注明来源和出处,谢谢!

作者: Raina_RLN https://www.cnblogs.com/raina/

Bran的内核开发教程(bkerndev)-04 创建main函数和链接C文件的更多相关文章

  1. Bran的内核开发教程(bkerndev)-02 准备工作

    准备工作   内核开发是编写代码以及调试各种系统组件的漫长过程.一开始这似乎是一个让人畏惧的任务,但是并不需要大量的工具集来编写自己的内核.这个内核开发教程主要涉及使用GRUB将内核加载到内存中.GR ...

  2. Bran的内核开发教程(bkerndev)-01 介绍

    介绍   内核开发不是件容易的事,这是对一个程序员编程能力的考验.开发内核其实就是开发一个能够与硬件交互和管理硬件的软件.内核也是一个操作系统的核心,是管理硬件资源的逻辑.   处理器或是CPU是内核 ...

  3. Bran的内核开发教程(bkerndev)-06 全局描述符表(GDT)

    全局描述符表(GDT)   在386平台各种保护措施中最重要的就是全局描述符表(GDT).GDT为内存的某些部分定义了基本的访问权限.我们可以使用GDT中的一个索引来生成段冲突异常, 让内核终止执行异 ...

  4. Bran的内核开发教程(bkerndev)-03 内核初步

    目录 内核初步 内核入口 链接脚本 汇编和链接 PS: 下面是我自己写的 64位Linux下的编译脚本 内核初步   在这节教程, 我们将深入研究一些汇编程序, 学习创建链接脚本的基础知识以及使用它的 ...

  5. Bran的内核开发教程(bkerndev)-08 中断服务程序(ISR)

    中断服务程序(ISR)   中断服务程序(ISR)用于保存当前处理器的状态, 并在调用内核的C级中断处理程序之前正确设置内核模式所需的段寄存器.而工作只需要15到20行汇编代码来处理, 包括调用C中的 ...

  6. Bran的内核开发教程(bkerndev)-05 打印到屏幕

    打印到屏幕   现在, 我们需要尝试打印到屏幕上.为此, 我们需要管理屏幕滚动, 如果能允许使用不同的颜色就更好了.好在VGA视频卡为我们提供了一片内存空间, 允许同时写入属性字节和字符字节对, 可以 ...

  7. Bran的内核开发教程(bkerndev)-07 中断描述符表(IDT)

    中断描述符表(IDT)   中断描述符表(IDT)用于告诉处理器调用哪个中断服务程序(ISR)来处理异常或汇编中的"int"指令.每当设备完成请求并需要服务事, 中断请求也会调用I ...

  8. 【DSP开发】DSP能用VS2010生成的链接库文件吗?

    [DSP开发]DSP能用VS2010生成的链接库文件吗? 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 说明:可能这个问题让行家看上去就会莞尔一笑,但是很多 ...

  9. IntelliJ创建main函数、for循环,System.out.println()等快捷建(转载)

    在编写代码的时候直接输入psv就会看到一个psvm的提示,此时点击tab键一个main方法就写好了. psvm 也就是public static void main的首字母. 依次还有在方法体内键入f ...

随机推荐

  1. 并发之初章Java内存模型

    >>>>>>博客地址<<<<<< >>>>>>首发博客<<<<< ...

  2. 腾讯工作近十年大佬:不是我打击你!你可能真的不会写Java

    文章核心 其实,本不想把标题写的那么恐怖,只是发现很多人干了几年 Java 以后,都自认为是一个不错的 Java 程序员了,可以拿着上万的工资都处宣扬自己了,写这篇文章的目的并不是嘲讽和我一样做 Ja ...

  3. SSM相关面试题(简答)

    1.springmvc的执行    流程: 2.mybstis的执行流程: 3.ioc和DI的理解: 4.对aop的理解: 5.spring中常见的设计模式: 6.spring中声明式事务处理的配置: ...

  4. <%@ include %>导入的文件乱码

    如: <% String ss = (String) session.getAttribute("username"); if (ss == null || ss == &q ...

  5. valueForKey与valueForKeyPath 区别

    1.删除数组中重复的数据 2.valueForKeyPath:可以深层次取到子属性,不管隐藏的多深  valueForKey:无法取到深层次子属性 但是也有其相似的地方: 比如:快速找到字典数组中ke ...

  6. Spring+Mybatis整合的练手小项目(一)项目部署

    声明:教程是网上找的,代码是自己敲的 项目目录大致如下: 1. 首先创建Maven工程,在pom.xml中加入项目所需依赖: <?xml version="1.0" enco ...

  7. 从原理到场景 系统讲解 PHP 缓存技术

    第1章课程介绍 此为PHP相关缓存技术的课堂,有哪些主流的缓存技术可以被使用? 第1章 课程介绍 1-1课程介绍1-2布置缓存的目的1-3合理使用缓存1-4哪些环节适合用缓存 第2章 文件类缓存 2- ...

  8. ReactNative之Redux详解

    用redux有一段时间了,感觉还是有必要把其相关的知识点系统的总结一下的,毕竟好记性不如烂笔头.上篇博客更新了关于<ES6中的迭代器.Generator函数以及Generator函数的异步操作& ...

  9. MIT-Adobe FiveK Dataset 图片自动下载

    MIT-Adobe FiveK Dataset 图片自动下载 MIT-Adobe FiveK是现在很多做图像增强(image enhancement)与图像修饰(image retouching)方面 ...

  10. C++进程间通讯方式

    1.剪切板模式. 在MFC里新建两个文本框和两个按钮,点击发送按钮相当于复制文本框1的内容,点击接收按钮相当于粘贴到文本框2内: 发送和接收按钮处功能实现如下: void CClipboard2Dlg ...