平台:smart210
CPU:S5PV210
目标:在smart210裸板上移植stdio(标准输入输出)的两个核心函数,printf()与scanf()。
知识储备:
1.这里我们直接从主目录下的Makefile分析移植所需要的一系列操作
CC = arm-linux-gcc
LD = arm-linux-ld
AR = arm-linux-ar
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
INCLUDEDIR := $(shell pwd)/include
CFLAGS := -Wall -O2 -fno-builtin
CPPFLAGS := -nostdinc -I$(INCLUDEDIR)
export CC AR LD OBJCOPY OBJDUMP INCLUDEDIR CFLAGS CPPFLAGS
objs := start.o main.o uart.o clock.o lib/libc.a
stdio.bin: $(objs)
${LD} -Tstdio.lds -o stdio.elf $^
${OBJCOPY} -O binary -S stdio.elf $@
${OBJDUMP} -D stdio.elf > stdio.dis
.PHONY : lib/libc.a
lib/libc.a:
cd lib; make; cd ..
%.o:%.c
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
%.o:%.S
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
clean:
make clean -C lib
rm -f *.bin *.elf *.dis *.o
关键点在于stdio.bin,是我们最终生成的二进制文件,它依赖于start.o,main.o,uart.o,clock.o与lib/libc.a这些文件,其中.o的来源是经过arm-linux-gcc -c -o 命令生成的,libc.a是通过进入lib目录下静态编译生成的库文件,为什么要使用静态编译,主要是因为我们所需要的printf()和scanf()移植依赖于linux内核提供的内核函数,而我们现在是属于裸板无操作系统的状态下,即使采用动态编译成功生成了.bin文件,也无法在裸板开发板上运行。所以,我们需要手动建一个库,把linux内核支持的printk()函数的相关.c .h文件经过修改后都移植过来,于是我们采用了静态编译的方法生成libc.a库(可以注意到CPPFLAGS带了-nostdinc选项的意思就是不使用标准库,即不动态链接标准库,而是采用当前主目录下的include文件夹里面的.h)。在main.c程序里面包含的“stdio.h"文件里,我们能发现如下代码
extern int vsnprintf(char *buf, size_t size, const char *fmt, va_list args);
extern int snprintf(char * buf, size_t size, const char *fmt, ...);
extern int vsprintf(char *buf, const char *fmt, va_list args);
extern int sprintf(char * buf, const char *fmt, ...);
extern int vsscanf(const char * buf, const char * fmt, va_list args);
extern int sscanf(const char * buf, const char * fmt, ...);
extern void putc(unsigned char c);
extern unsigned char getc(void);
int printf(const char *fmt, ...);
int scanf(const char * fmt, ...);
这说明了一系列与printf()和scanf()相关的子函数都是外部函数,在编译器在链接的时候,就能把libc.a里面的各.o文件内的相关函数链接过来。强大的链接器帮我们把各个独立的.o文件根据函数名(地址)链接起来,生成链接文件elf,该文件又可以进一步改造成适用于开发板使用.bin文件!
2.从/lib/Makefile分析lib/libc.a库文件的生成
objs := div64.o lib1funcs.o ctype.o muldi3.o printf.o string.o vsprintf.o
libc.a: $(objs)
${AR} -r -o $@ $^
%.o:%.c
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
%.o:%.S
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
clean:
rm -f libc.a *.o
可知,libc.a文件依赖于div64.o lib1funcs.o ctype.o muldi3.o printf.o string.o vsprintf.o ,由于采用了主目录下的Makefile所输出的CPPFLAGS与CFLAGS配置,届时arm-linux-cpp在编译.c文件的时候,就会自动跑去主目录的include库下去找需要的函数了。这些.o文件中,最核心的是printf.o,对应的.c文件实现的最关键的两个函数如下:
int printf(const char *fmt, ...)
{
int i;
int len;
va_list args;
va_start(args, fmt);
len = vsprintf(g_pcOutBuf,fmt,args);
va_end(args);
for (i = 0; i < strlen(g_pcOutBuf); i++)
{
putc(g_pcOutBuf[i]);
}
return len;
}
int scanf(const char * fmt, ...)
{
int i = 0;
unsigned char c;
va_list args;
while(1)
{
c = getc();
putc(c);
if((c == 0x0d) || (c == 0x0a))
{
g_pcInBuf[i] = '\0';
break;
}
else
{
g_pcInBuf[i++] = c;
}
}
va_start(args,fmt);
i = vsscanf(g_pcInBuf,fmt,args);
va_end(args);
return i;
}
拿printf()做分析,我们能发现程序内容先对变参数进行处理,把我们输入给printf的每一个参数都做字符串分析,比如%d和数字之类的,然后拷贝对应参数变量的值结合第一个字符串参数里的指令输出到g_pcOutBuf这个字符缓冲区里面,最后循环调用我们的串口单字符打印函数putc()来输出。最终我们就达到了printf()的使用效果了!而scanf()也是类似的!