[smart210] 裸板移植printf()与scanf()的步骤

平台: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()也是类似的!
posted @ 2013-07-23 14:30  轻羽  阅读(636)  评论(0编辑  收藏  举报