这里的所谓的裸机编程指的是为“无OS支持的硬件系统编程”,而实际的编程工作肯定需要一个环境,通常这样的情况中,编程和编译的环境叫做“宿主机”,最终的程序在“目标机”上运行(交叉编译)。而OS环境编程指的是最终运行的程序是在有操作系统支持的环境中运行,而编程和编译的环境,可能是运行程序的机器(本地编译),也可能不是(交叉编译)。

裸机编程现在主要是正对低端的嵌入式系统,如SCM(single chip machine)、各式MCU、DSP等。当然,编写PC的bootloader肯定也属于裸机编程。

裸机编程的最原始办法是用汇编语言(一种机器指令的一一对应的记法,和加上一些简单的汇编伪指令),只能使用很有限的指令集,每行代码只能做微小的事情。因此现在裸机编程也普遍使用更高级的语言(通常是C语言),那么从C语言转换到汇编语言这个过程就叫做编译。编译器根据不同的机器,将通用的C代码转化为特定的机器代码,只有十分少量的机器代码仍然需要用到汇编,这其实是一种混合编程的模式。那么,编译器实在是一种十分重要的工具,编译的理论和实践知识也会十分丰富。

裸机编程与OS环境编程的有关思考_编译器

在有OS支持的环境中编程则更加便利了。首先OS管理并扩展了整个机器资源,提供了一个通用的API系统调用接口,程序员通过这个接口与硬件资源打交道,因此在OS上编程更加不需要考虑机器的特性,换句话说就是移植性最佳。作为资源的扩展,OS提供了大量的机制(包括进程、内存管理、设备操作等等)和库文件(这些库文件属于可重用的代码),让编写实用程序更加便利。

其次,编译器与OS之间的关系非常紧密,OS环境编程很少有人用汇编代码,而是可以使用各种层次和类型的高级语言。很容易看出来OS环境编程使用的编译器,其功能要比裸机编程的编译器广泛的多,尽管它们是有紧密的联系。举例而言,gcc编译器能够为多种的硬软件平台编译C/C++程序:可以用gcc编译本地程序,也可以用xxx-xxx-gcc在宿主机上交叉编译目标机器的程序;可以用gcc编译裸机程序,也可以编译OS环境下的程序。总的来说,gcc编译出来的OS环境可执行文件,是裸机环境可执行文件的“超集”。看一下下面这段最简单的makefile代码:



裸机编程与OS环境编程的有关思考_linux_02

key_led.bin : crt0.S  key_led.c

arm-linux-gcc -g -c -o crt0.o crt0.S

arm-linux-gcc -g -c -o key_led.o key_led.c

arm-linux-ld -Ttext 0x0000000 -g crt0.o key_led.o -o key_led_elf

arm-linux-objcopy -O binary -S key_led_elf key_led.bin

clean:

rm -f key_led.dis key_led.bin key_led_elf *.o


裸机编程与OS环境编程的有关思考_linux_02



这段代码编写了一个ARM目标机器上用按键控制LED的程序。gcc的编译首先将其编译链接成elf格式的文件,然后用objcopy工具转换成裸机代码bin文件。因为elf的可执行文件格式是linux系统的标准支持格式,这种文件中不仅含有二进制机器码,而且(如果有需要的话)会含有大量的符号与控制信息(往往是文本格式的),这些符号与控制信息能让这个程序与OS交互,并得到OS的支持。在裸机上执行程序时,仅仅需要机器直接能识别的二进制机器码bin文件,这是一种纯净的二进制机器码文件。从这个过程可以看出来,编译器与OS的关系的确非常紧密。


 ===================================================================

下面考察实际的linux/UNIX系统中的程序描述(based on linux programing 4th edition)。

概略

linux应用程序表现为两种类型的文件:可执行文件和脚本文件。可执行文件能够直接运行,它们包含二进制的机器代码和一些OS必须了解的控制信息。脚本文件被解释器一句句的执行,事先并没有编译成最底层的机器码。脚本文件有很多种,如python、shell等,java虚拟机类似一种解释器,但是它解释java中间码,性能比一般的解释器好。

shell是linux的人机交互界面,本质上来说它是个解释器,解释用户输入的每条指令,当然也可以解释用户编写的脚本。linux标准的程序执行搜索路径有:

/bin   : /usr/bin   : /usr/local/bin   : /sbin   : /usr/sbin

可选的操作系统组件和第三方应用程序可能被安装在/opt目录下,用户通过PATH环境变量来添加默认搜索的目录。从shell执行的程序会继承shell的环境变量,但程序的修改不会反向影响到父程序shell。

linux中的文本编辑器可以选择vim、emacs或者更可视化的eclipse等工具。

开发环境:

1、应用程序。/bin: /usr/bi: /sbin: /usr/sbin 这几个目录一般存放最常用的系统程序,而后来添加的程序往往存放在/opt:/usr/local中,它们分离的系统原本的程序和后续添加的程序。对于个人的程序或开发程序,最好在/home目录中存放它。(usr的意思不是user啊骚年!!是Unix Software Resource !!!)

gcc的驱动程序通常存放在/usr/bin 或者/usr/local/bin中,它调用gcc编译器的其它应用程序,可能存放在/usr/local/bin中,或者其它gcc知道的位置。

至于哪些程序是系统程序,这很难界定,linux kernel本身不带有任何程序,仅仅提供一个API接口。一个最小的简化版系统程序配置如busybox包含常用的如shell及其命令等,仅仅至于数百K的体积。

2、头文件。用C语言或者其它语言进行设计时,需要头文件的目的一般是需要常量的定义,或者需要对系统函数及库函数调用的声明(这些定义和声明可以是来源于用户,也可以是来源于标准库、扩展库)。linux中C语言的头文件总是位于/usr/include,依赖特定版本的头文件通常位于/usr/include/sys或者/usr/include/linux 。

3、库文件。库,是一组预先编译好的函数的集合,这些函数按照可重用的原则编写。标准系统库文件存放于/lib和/usr/lib目录中,其它扩展库可能存放在其它lib目录中。库的名字必须是以lib开头,随后部分指明库的功能,.a代表静态库,.so代表动态库,可能有 .la文件(*注)。虽然库文件和头文件一般存放在标准位置,但可以也编译时用”-L”搜索一个特殊的位置。在程序中如果引用动态库,在编译时要说明动态库的位置,程序运行态也需要库在指定的位置存在。

对于linux来说,负责装载动态库并解析客户程序所引用的函数的程序(动态装载器)是ld.so,或者是ld-linux.so.2/  ld-lsb.so.2/  ld-lsb.so.3。程序运行中搜索动态库的额外位置,可以再文件/etc/ld.so.etc中配置。也可以用ldd程序来查看一个程序运行时需要的动态库。

 

 ​

总结一下就是:

1、可以隐藏具体操作系统的共享库实现的不同,比如Linux是so,Windows是dll。有了.la,统一连接.la文件就可以了

2、连接静态库的时候可以从它来获取静态库的依赖关系。动态库(ELF based .so file)里本身保存了这个信息,不需要.la来获取这个信息。

.la文件理论上是可以不要的,而且如果要实现真正的multilib支持,是一定不能要.la文件。

如果不用.la文件,连接静态库的时候理论上说可以用pkg-config –static来获得依赖关系(库)。

但是现在,不是所有的库都提供相应的.pc文件

现在有些Gentoo开发者和用户正在尝试移除系统里的.la文件,并修复随之带来的问题,以求为就将来portage完美的multilib支持打下坚持的现实基础。