给裸机移植printf功能
一.可变参数函数的原型声明
typeVAFunction(type arg1, type arg2, … );
参数可以分为两部分:个数确定的固定参数和个数可变的可选参数。函数至少需
要一个固定参数,固定参数的声明和普通函数一样;可选参数由于个数不确定,
声明时用"..."表示。固定参数和可选参数公同构成一个函数的参数列表。
二.具体分析
下面是分析c库中的printf函数,但完全适用与内核printk的分析
三个关键宏:
void va_start ( va_list arg_ptr, prev_param ); /* ANSI version */
type va_arg ( va_list arg_ptr, type );
void va_end ( va_list arg_ptr );
在这些宏中,va就是variableargument(可变参数)的意思;
arg_ptr是指向可变参数表的指针;
prev_param指可变参数表的前一个固定参数;
type为可变参数的类型。
va_list也是一个宏,其定义为typedefchar * va_list,实质上是一char型指针。
char型指针的特点操作对其作用的结果是增1和减1(因为sizeof(char)为1)。
<1>va_start宏
(1)定义:
#defineva_start ( ap, v ) ( ap = (va_list)&v + _INTSIZEOF(v) )_INTSIZEOF宏定义为:#define_INTSIZEOF(n) ((sizeof ( n ) + sizeof ( int ) - 1 ) & ~( sizeof(int ) -1 ) )
(2)作用:
根据v取得可变参数表的首指针并赋值给ap,方法:最后一个固定参数v的地址+第一个变参对v的偏移地址,然后赋值给ap,这样ap就是可变参数表的首地址。
(3)举例:
如果有一 va函数的声明是voidva_test(char a, char b,char c, …),则它的固定参数依次是a,b,c,最后一个固定参数argN为c,因此就是va_start(arg_ptr,c)。
<2>va_arg宏
(1)定义:
#defineva_arg(list, mode) ((mode *)(list = (char*) ((((int)list + (__builtin_alignof(mode)<=4?3:7)) & (__builtin_alignof(mode)<=4?-4:-8))+sizeof(mode))))[-1]
(2)作用:
指取出当前 arg_ptr所指的可变参数并将ap指针指向下一可变参数
<3>va_end宏
(1)定义为:
#defineva_end ( list )
(2)作用:
结束可变参数的获取。va_end( list )实际上被定义为空,没有任何真实对应的代码,用于代码对称,与va_start对应。
三.实践
<1>怎样得到可变参数个数?归纳起来有三种办法:
(1)函数的第一个参数,指定后续的参数个数,如func(intnum,...)
(2)根据隐含参数,判断参数个数,如printf系列的,通过字符串中%的个数判断
(3)特殊情况下(如参数都是不大于0xFFFF的int),
可以一直向低处访问堆栈,直到
返回地址。
<2>举例说明三种情况:
(1)情况1
#include<stdio.h>
#include<stdarg.h>
void VariableFunc(int prev_param, ...)
{
va_list
arg_ptr;
//可变参数表的首指针
va_start(arg_ptr,
prev_param); //取得可变参数表的首地址并赋给arg_ptr
for(int
i=0;i<prev_param;i++)
{
int
ParamValue;
ParamValue=va_arg(arg_ptr,
int);//取出当前arg_ptr所指的可变参数并将ap指针指向下一可变参数
printf("这是第%d个可变参数,值:%d,类型:int\n",i+1,ParamValue);
}
va_end(arg_ptr);//执行清理工作
}
(2)情况2
#include<stdio.h>
#include<stdarg.h> //包含些头文件
#include<string>
using namespace std;
//模仿printf函数,写一个printk函数
void printk(char* prev_param, ...)
{
int
j = 0;
va_list
arg_ptr; //可变参数表的首指针
va_start(arg_ptr,prev_param);
//取得可变参数表的首地址
string
FormatStr(prev_param); //保存格式化的字符串
int
InsertPos;
//当在固定参数中找到%符号时:
while(-1!=(InsertPos=FormatStr.find("%")))
{
//根据%后面的字符分别进行处理
if(FormatStr[InsertPos+1]=='d')
//%号后是'd'就转为字符再插入
FormatStr
{
char
buf[15];
int
IntValud=va_arg(arg_ptr,int); //从可变参数列表中获得数据
itoa(IntValud,buf,10);
//Int 转string并保存在buf
FormatStr.erase(InsertPos,2);//擦除两个字符%d
FormatStr.insert(InsertPos,buf);
//插入Int值到FormatStr
}
else
if(FormatStr[InsertPos+1]=='s') ////%号后是's'就直接将字符串插入FormatStr
{
FormatStr.erase(InsertPos,2);
FormatStr.insert(InsertPos,va_arg(arg_ptr,char*));
}
}
printf("%s\n",FormatStr.c_str());
//打印出处理后的FormatStr
va_end(arg_ptr);//执行清理工作
}
void main()
{
printk("show
you how %s %s work %d","printf","function",88);
}
(3)情况3
#include <stdio.h>
#include <stdarg.h>
struct T_Progs{
int x;
int y;
};
void func(T_Progs *tProgs,...)
{
int total = 0;
va_list ap;
T_Progs *p;
va_start(ap,
tProgs);
p = tProgs;
printf("x[%d]=%d\n",total,p->x);
printf("y[%d]=%d\n",total,p->y);
total++;
while
(p = (va_arg(ap,T_Progs*)))
{
printf("x[%d]=%d\n",total,p->x);
printf("y[%d]=%d\n",total,p->y);
total ++;
}
va_end(ap);
printf("参数个数:%d\n",total);
}
void main(void)
{
T_Progs
test1,test2;
test1.x = 1;test2.x = 3;
test1.y = 2;test2.y = 4;
func(&test1,&test2,NULL);
}
四.最简单的移植步骤
<1>我们许多选择:
(1)移植linux的printf,版本越新越难移植,但是功能也越强大
(2)移植uboot的printf,实际uboot也是移植到内核的
(3)完全自己编写,但是功能比较弱
在保证整个裸机其他代码部分没有任何问题,且编译器也没有任何问题的情况下,上述三种方法都是可行的。
下面我们只是直接采用韦东山老师移植好的printf相关的库文件,他的办法是移植2.4内核版本的printf功能
<2>拷贝附件里相关库文件到裸机代码根目录
<3>修改makefile如附件所示,必须严格按照makefile里的相关设置
<4>make 编译并测试
测试代码如下:
void test_printf(void)
{
char *p="this is %s test";
char c='H';
int d=-256;
int k=0;
printf("testing printf\n");
printf("test string ::: %s\ntest char ::: %c\ntest digit ::: %d\ntest X ::: %x\ntest unsigned ::: %u\ntest zero ::: %d\n",p,c,d,d,d,k);
}
[ 此帖被wuweidong在2012-05-21 14:36重新编辑 ]