主题 : 【分享】给裸机移植printf功能 复制链接 | 浏览器收藏 | 打印
畅游在知识的海洋...
级别: 论坛版主
UID: 33629
精华: 4
发帖: 554
金钱: 3075 两
威望: 615 点
贡献值: 5 点
综合积分: 1188 分
注册时间: 2010-12-03
最后登录: 2015-09-22
楼主  发表于: 2012-05-21 11:52

 【分享】给裸机移植printf功能

给裸机移植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,最后一个固定参数argNc,因此就是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)特殊情况下(如参数都是不大于0xFFFFint),
可以一直向低处访问堆栈,直到
返回地址。

<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重新编辑 ]
附件: 变参函数printf的实现.tar.gz (30 K) 下载次数:86
好好学习,天天鲁管
级别: 侠客
UID: 40508
精华: 1
发帖: 44
金钱: 270 两
威望: 54 点
贡献值: 1 点
综合积分: 108 分
注册时间: 2011-03-19
最后登录: 2018-04-21
1楼  发表于: 2013-04-11 16:41
多谢分享。
级别: 新手上路
UID: 112292
精华: 0
发帖: 48
金钱: 240 两
威望: 48 点
贡献值: 0 点
综合积分: 96 分
注册时间: 2015-01-22
最后登录: 2021-07-30
2楼  发表于: 2018-04-06 22:07
多谢分享